Я постепенно переношу и рефакторю код C на C++.
В исходном коде C у меня есть такие конструкции:
Object* obj = &objects[index];
где objects
— глобальный Object[]
.
Я провел рефакторинг кода, чтобы массив распределялся динамически и его содержал другой объект:
struct Info {
Object* objects;
};
В результате постепенного рефакторинга я написал следующую бесплатную функцию:
Object* GetObjects(Info* i) {
return i->objects;
}
Теперь мой вопрос:
Следующий код ведет себя так, как я ожидал (получение адреса объекта в массиве)?:
Object* obj = &GetObjects(info)[index];
Получаю ли я здесь адрес объекта в массиве (то, что я хочу) или адрес указателя или какое-то rvalue и вызываю UB?
[]
имеет более высокий приоритет, чем &
, но учитывается ли это и для вызовов функций?
Я не думаю, что здесь есть УБ. Также, независимо от приоритета, я согласен, что добавление круглых скобок может прояснить ситуацию: Object* obj = &(GetObjects(info)[index]);
С точки зрения валидности код выглядит нормально, но, возможно, GetObjects(info) + index
немного понятнее.
Как насчет чего-то вроде этого? template <typename Object> struct Info { Object* objects; Object& operator[](int index) { return objects[index]; } Info(Object* objects) : objects{ objects } {} };
Да, и, пожалуйста, не используйте указатели и собственную обработку памяти для массивов. Вместо этого используйте std::vector
.
Эта строка:
Object* obj = &GetObjects(info)[index];
Действительно и не должно вызывать UB само по себе.
GetObjects(info)
возвращает Object*
(а не значение r, о котором вы беспокоились), затем к нему можно получить доступ с помощью operator[]
.
Однако код полагается на массив Object*
, чтобы оставаться в живых, пока используется obj
. Если info
является его владельцем, ему также потребуется info
, чтобы остаться в живых (при условии, что он несет ответственность за его жизнь).
Примечания:
Object* obj = &(GetObjects(info)[index]);
struct info
следующий метод:
struct info
{
Object * GetObjectAtIndex(int index)
{
// TODO: check that the index is valid and return nullptr if not
return &(objects[index]);
}
};
struct info
{
Object & GetObjectAtIndex(int index)
{
// TODO: check that the index is valid, decide what to return if not
return objects[index];
}
};
std::vector
может потребоваться перераспределение при увеличении, и при этом итераторы, ссылки и указатели на него будут признаны недействительными.«Однако код полагается на экземпляр info
и массив objects
внутри него, чтобы оставаться в живых до тех пор, пока используется obj». член — это просто указатель, а не массив. Уничтожение экземпляра info
не влияет на реальный массив (по крайней мере, в показанном коде).
Я предположил, что Info
является владельцем массива и, следовательно, уничтожит его при его уничтожении. Я добавлю уточнение.
Да, это моя точка зрения. В показанном коде Info
не владеет массивом. У него есть только член, который указывает на Object
@ 463035818_is_not_an_ai может быть. Я уточнил это.
Кроме того, независимо от того, является ли это самодельным массивом, std::vector
или другой реализацией массива, большинство массивов аннулируют такие указатели, когда вы добавляете элементы, превышающие их текущую емкость. Поэтому будьте осторожны, сохраняя эти указатели, чтобы в это время не добавлялось новых элементов.
@ChristianStieber, это верная точка зрения. Я добавил об этом заметку.
@wohlstad да, извини, комментарий удален. Сначала я пропустил, что вы предлагаете заменить массив на std::vector
, а затем стоит упомянуть примечание.
Если вы не уверены, всегда добавляйте круглые скобки. Или для начала не используйте указатели, используйте ссылки. Чем меньше у вас указателей, тем меньше вероятность возникновения проблем, связанных с указателями.