Должен ли я хранить целые объекты или указатели на объекты в контейнерах?
Разработка новой системы с нуля. Я буду использовать STL для хранения списков и карт некоторых долгоживущих объектов.
Вопрос: Должен ли я гарантировать, что мои объекты имеют конструкторы копирования и хранят копии объектов в моих контейнерах STL, или обычно лучше управлять жизнью и областью видимости и просто хранить указатели на те объекты в моих контейнерах STL?
Я понимаю, что это немного коротко для деталей, но я ищу "теоретический" лучший ответ, если он существует, поскольку я знаю, что оба этих решения возможны.
Два очень очевидных недостатка в игре с указателями:
1) Я должен управлять распределением/освобождением этих объектов самостоятельно в области, выходящей за пределы STL.
2) Я не могу создать временный объект в стеке и добавить его в свои контейнеры.
Есть ли что-нибудь еще, что мне не хватает?
Ответы
Ответ 1
Так как люди работают с эффективностью использования указателей.
Если вы рассматриваете возможность использования std::vector, и если обновлений мало, и вы часто повторяете свою коллекцию, и объект "копий" для хранения не полиморфного типа будет более эффективным, так как вы получите лучшую локальность ссылок.
Otoh, если обновления являются обычными, указатели сохранения сохраняют затраты на копирование/перемещение.
Ответ 2
Это действительно зависит от вашей ситуации.
Если ваши объекты невелики, а копирование объекта является легким, то хранение данных внутри контейнера stl является простым и простым в управлении, на мой взгляд, потому что вам не нужно беспокоиться о пожизненном управлении.
Если объекты большие, а конструктор по умолчанию не имеет смысла, или копии объектов стоят дорого, то, возможно, сохранение с указателями возможно.
Если вы решили использовать указатели на объекты, посмотрите Boost Pointer Container Library. Эта библиотека ускорения обертывает все контейнеры STL для использования с динамически выделенными объектами.
Каждый контейнер-указатель (например, ptr_vector) получает собственность на объект, когда он добавляется в контейнер, и управляет временем жизни этих объектов для вас. Вы также получаете доступ ко всем элементам в контейнере ptr_ по ссылке. Это позволяет вам делать что-то вроде
class BigExpensive { ... }
// create a pointer vector
ptr_vector<BigExpensive> bigVector;
bigVector.push_back( new BigExpensive( "Lexus", 57700 ) );
bigVector.push_back( new BigExpensive( "House", 15000000 );
// get a reference to the first element
MyClass& expensiveItem = bigList[0];
expensiveItem.sell();
Эти классы переносят контейнеры STL и работают со всеми алгоритмами STL, что очень удобно.
Существуют также средства для передачи права собственности на указатель в контейнере вызывающему (через функцию выпуска в большинстве контейнеров).
Ответ 3
Если вы сохраняете полиморфные объекты, вам всегда нужно использовать набор указателей базового класса.
То есть, если вы планируете хранить различные производные типы в своей коллекции, вы должны хранить указатели или получать едят декамон нарезки.
Ответ 4
Извините, что прыгнул через 3 года после события, но предупредительная записка здесь...
В моем последнем крупном проекте моя центральная структура данных была набором довольно простых объектов. Примерно через год в проект, по мере развития требований, я понял, что объект действительно должен быть полиморфным. Прошло несколько недель сложной и неприятной хирургии головного мозга, чтобы зафиксировать структуру данных как набор указателей базового класса и обработать весь побочный ущерб при хранении объектов, литье и т.д. Мне потребовалось пару месяцев, чтобы убедить себя, что новый код работает. Кстати, это заставило меня задуматься о том, насколько хорошо разработана объектная модель С++.
В моем текущем крупном проекте моя центральная структура данных представляет собой набор довольно простых объектов. Примерно через год в проект (который, случается, сегодня) я понял, что объект на самом деле должен быть полиморфным. Вернитесь в сеть, найдите эту цепочку и найдите ссылку Ник в библиотеку контейнеров-указателей Boost. Это именно то, что я должен был написать в прошлый раз, чтобы исправить все, поэтому на этот раз я отдам его.
Мораль, для меня, во всяком случае: если ваша спецификация не на 100% бросается в камень, идите за указателями, и вы можете сэкономить себе много работы позже.
Ответ 5
Почему бы не получить лучшее из обоих миров: сделать контейнер с интеллектуальными указателями (например, boost::shared_ptr
или std::shared_ptr
). Вам не нужно управлять памятью, и вам не нужно иметь дело с большими операциями копирования.
Ответ 6
Обычно хранение объектов непосредственно в контейнере STL является лучшим, поскольку оно является самым простым, наиболее эффективным и проще всего использовать объект.
Если ваш объект сам имеет неповторяемый синтаксис или является абстрактным базовым типом, вам нужно будет хранить указатели (проще всего использовать shared_ptr)
Ответ 7
Вы, кажется, хорошо понимаете разницу. Если объекты маленькие и их легко скопировать, то, вообщем то, сохраните их.
Если нет, я бы подумал о том, чтобы хранить интеллектуальные указатели (а не auto_ptr, рефлексивный счетный указатель) на те, которые вы выделяете в куче. Очевидно, что если вы выберете интеллектуальные указатели, вы не сможете сохранить выделенные объекты в стеке temp (как вы сказали).
@Torbjörn дает хорошее представление о разрезе.
Ответ 8
Использование указателей будет более эффективным, так как контейнеры будут только копировать указатели вместо полных объектов.
Здесь есть какая-то полезная информация о контейнерах STL и интеллектуальных указателях:
Почему неправильно использовать std:: auto_ptr < > со стандартными контейнерами?
Ответ 9
Если объекты должны упоминаться в другом месте кода, сохраните в векторе boost:: shared_ptr. Это гарантирует, что указатели на объект останутся действительными, если вы измените размер вектора.
Т.е:
std::vector<boost::shared_ptr<protocol> > protocols;
...
connection c(protocols[0].get()); // pointer to protocol stays valid even if resized
Если никто не хранит указатели на объекты, или список не растет и не уменьшается, просто сохраняйте его как обычные объекты:
std::vector<protocol> protocols;
connection c(protocols[0]); // value-semantics, takes a copy of the protocol
Ответ 10
Этот вопрос прослушивал меня какое-то время.
Я склоняюсь к хранению указателей, но у меня есть некоторые дополнительные требования (обертки SWIG lua), которые могут не относиться к вам.
Самый важный момент в этом сообщении - проверить его самостоятельно, используя ваши объекты
Я сделал это сегодня, чтобы проверить скорость вызова функции-члена в коллекции из 10 миллионов объектов, 500 раз.
Функция обновляет x и y на основе xdir и ydir (все переменные-члены float).
Я использовал std:: list для хранения обоих типов объектов, и обнаружил, что сохранение объекта в списке немного быстрее, чем использование указателя. С другой стороны, производительность была очень близкой, поэтому все зависит от того, как они будут использоваться в вашем приложении.
Для справки: с -O3 на моем оборудовании указатели заняли 41 секунду, а исходные объекты заняли 30 секунд.