Когда использовать объект в векторе, а когда указатель на объект в векторе?

Когда вы создаете объекты и сохраняете их в векторе. Каковы плюсы и минусы между этими тремя и в каком случае их следует использовать?

Объект:

std::vector<obj> collection;
collection.push_back(obj{});

Указатель на объект:

std::vector<obj*> collection;
collection.push_back(new obj{});

Умный указатель:

std::vector<std::unique_ptr<obj>> collection;
collection.push_back(std::unique_ptr<obj>(new obj{}));

5 ответов

Если тип объекта поддерживает копирование и присваивание, вы должны отказаться от указателей и поместить объект в вектор (и везде - у вас, вероятно, не должно быть никаких указателей на такой объект).

Если тип объекта имеет идентичность и не поддерживает копирование и присваивание (или поддерживает только копирование для реализации clone функция), тогда вам нужно будет держать указатели в векторе. Будь то умные указатели или необработанные указатели, зависит от того, для чего используется вектор и какова политика в отношении времени жизни объекта. По моему опыту, большую часть времени векторы используются для навигации, и в этом случае вы должны использовать необработанные указатели.

  • Первый подход - классический, объекты будут уничтожены в конце срока службы контейнера. Это может иметь недостаток таких операций, как std::sort где много перемещения происходит по сравнению с простым указателем (если это вообще проблема в случае конкретного объекта). Обратите внимание, что это может быть предпочтительным подходом для объектов, поддерживающих копирование / назначение.

  • Второй подход может иметь дополнительную скорость (держать огромные объекты с плохой производительностью копирования / перемещения в контейнере - это всегда плохая идея), но имеет недостаток, заключающийся в том, что приходится самостоятельно освобождать память, если вы не хотите, чтобы эти объекты оставались после контейнера. разрушение.

  • Третий подход является более или менее быстрым, чем второй, для операций сортировки, плюс он имеет преимущество использования интеллектуального указателя, который обеспечит автоматическое освобождение памяти, когда контейнер выходит из области видимости.

Это действительно сводится к предполагаемому сроку службы ваших объектов и тому, насколько эффективно ваши объекты могут быть скопированы. Проведите профилирование для своего предполагаемого тестового примера и не решайте "априори", какой из них следует использовать (как указывает Джеймс, это может быть случай преждевременной оптимизации).

Я бы лично использовал первый вариант в максимально возможной степени, если только нет веской причины использовать другой вариант (два других в любом случае почти эквивалентны, так что здесь нет большой разницы).

Основная причина в том, что это приводит к более чистому коду и почти наверняка лучше для RAII ( http://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization), потому что компилятор генерирует код, который не имеет никаких утечек. Некоторые люди здесь указывают на проблемы производительности, которые являются действительными, но они встречаются реже, чем кажется большинству людей.

Итак, суть (ИМХО) в следующем: переходите к самому простому и чистому варианту, если нет веских причин поступить иначе. Не добавляйте ненужных сложностей в ваше приложение, если вам это абсолютно не нужно. Я видел слишком много ошибок в коде из-за преждевременной оптимизации, которая даже не нужна большую часть времени.

Если ваш объект - простые данные, тогда первый вариант всегда лучший. Новая память выделяется и управляется внутренне векторным объектом, поэтому вам не нужно об этом беспокоиться.

  1. Вектор объектов. Полезно для элементов, которые обычно передаются по значению, легко копировать.

  2. Вектор сырых указателей. Для более сложных объектов (тех, которые вы не хотите копировать), эффективно копировать, но где-то вам нужно управлять временем жизни этих указателей. Даже если вы удаляете, когда удаляете элемент из вектора, вы должны знать, что нет никаких висящих. Это может быть лучшим подходом в тех случаях, когда время жизни объектов управляется в другом месте, и вы знаете, что эти указатели останутся действительными, и вы просто хотите накачать их в векторе для некоторого использования, например, API, который возвращает подмножество большей коллекции может сделать это как вектор указателей на эти объекты.

  3. Чаще shared_ptr был использован для этого, потому что вектор владеет одной копией, а тот, кто извлекает ее для чтения / использования, получает другую копию, поэтому проблема времени жизни не возникает. С unique_ptr это в значительной степени политика, которой он владеет вектор, и главная причина этого более (2) не в том, чтобы вручную удалять его, когда он удаляется из вектора. Вектор должен быть обернут в класс, управляющий им. Использование shared_ptr в течение многих лет это была общепринятая конструкция, когда вы сами не храните объекты для общей безопасности пожизненных гарантий.

Другие вопросы по тегам