Является ли std :: vector копированием объектов с помощью push_back?

169

После многих исследований с использованием valgrind я пришел к выводу, что std :: vector делает копию объекта, который вы хотите push_back.

Это действительно так ? Вектор не может хранить ссылку или указатель на объект без копии ?!

Спасибо

benlaug
источник
20
Это основной принцип C ++. Объекты являются ценностями. Назначение делает копию. Две переменные, ссылающиеся на один и тот же объект, невозможны, если вы не измените тип с помощью *или не &создадите указатель или ссылку.
Даниэль Уорвикер
8
@DanielEarwicker push_back на самом деле берет ссылку. Из подписи не ясно, что она сделает копию.
Брайан Гордон
3
@BrianGordon - Не говорю, что это так! Отсюда необходимость руководящего принципа. Тем не менее, мы можем вывести что-то из подписи push_back: это требует const&. Либо он выбрасывает значение (бесполезно), либо есть метод поиска. Итак, мы смотрим на сигнатуру back, и она возвращает обычное значение &, поэтому либо было скопировано исходное значение, либо constоно было тихо отброшено (очень плохо: потенциально неопределенное поведение). Таким образом, предполагая, что дизайнеры vectorбыли рациональными ( vector<bool>не выдерживающими), мы заключаем, что он делает копии.
Даниэль Эрвикер

Ответы:

183

Да, std::vector<T>::push_back()создает копию аргумента и сохраняет ее в векторе. Если вы хотите хранить указатели на объекты в вашем векторе, создайте std::vector<whatever*>вместо std::vector<whatever>.

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

Александр Гесслер
источник
Я также хотел бы отметить, что, если вы используете сырые указатели, теперь вы несете ответственность за их очистку. Нет веских причин для этого (я не могу придумать), вы всегда должны использовать умный указатель.
Эд С.
1
Тем не менее, вы не должны использовать std :: auto_ptr в контейнерах stl, для получения дополнительной информации: почему это неправильно использовать stdauto-ptr-with-standard-container
OriginalCliche
24
Начиная с C ++ 11, push_backбудет выполнять перемещение вместо копирования, если аргумент является ссылкой на значение. (Объекты могут быть преобразованы в rvalue ссылки с std::move().)
Emlai
2
@tuple_cat ваш комментарий должен сказать "если аргумент является rvalue". (Если аргумент - это имя сущности, объявленной как ссылка на rvalue, то аргумент на самом деле является lvalue и не будет перемещен из) - проверьте мою правку на ответ «Карла Николла», который изначально допустил эту ошибку
MM
Существует ответ ниже , но чтобы сделать это ясно: Так как C ++ 11 также использовать , emplace_backчтобы избежать копирования или перемещения (объект конструктов на месте предоставленный контейнер).
Wormer
34

Да, std::vectorхранит копии. Как vectorузнать, какова ожидаемая продолжительность жизни ваших объектов?

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

Георг Фрицше
источник
3
научитесь использовать shared_ptr - они делают именно то, что вы хотите. Моя любимая идиома это typedef boost :: shared_ptr <Foo> FooPtr; Затем сделайте контейнеры из FooPtrs
pm100
3
@ pm100 - Ты знаешь boost::ptr_vector?
Мануэль
2
Я также хотел бы использовать, class Foo { typedef boost::shared_ptr<Foo> ptr; };чтобы просто написать Foo::ptr.
Руперт Джонс
2
@ pm100 - shared_ptrэто точно не огонь и забудь. См. Stackoverflow.com/questions/327573 и stackoverflow.com/questions/701456
Даниэль Эрвикер,
2
shared_ptr хорош, если у вас есть совместное владение, но обычно он чрезмерно используется. unique_ptr или boost scoped_ptr имеют гораздо больше смысла, когда право собственности ясно.
Неманя Трифунович
28

Начиная с C ++ 11 все стандартные контейнеры ( std::vectorи std::mapт. Д.) Поддерживают семантику перемещения, что означает, что теперь вы можете передавать значения в стандартные контейнеры и избегать копирования:

// Example object class.
class object
{
private:
    int             m_val1;
    std::string     m_val2;

public:
    // Constructor for object class.
    object(int val1, std::string &&val2) :
        m_val1(val1),
        m_val2(std::move(val2))
    {

    }
};

std::vector<object> myList;

// #1 Copy into the vector.
object foo1(1, "foo");
myList.push_back(foo1);

// #2 Move into the vector (no copy).
object foo2(1024, "bar");
myList.push_back(std::move(foo2));

// #3 Move temporary into vector (no copy).
myList.push_back(object(453, "baz"));

// #4 Create instance of object directly inside the vector (no copy, no move).
myList.emplace_back(453, "qux");

В качестве альтернативы вы можете использовать различные умные указатели, чтобы получить в основном один и тот же эффект:

std::unique_ptr пример

std::vector<std::unique_ptr<object>> myPtrList;

// #5a unique_ptr can only ever be moved.
auto pFoo = std::make_unique<object>(1, "foo");
myPtrList.push_back(std::move(pFoo));

// #5b unique_ptr can only ever be moved.
myPtrList.push_back(std::make_unique<object>(1, "foo"));

std::shared_ptr пример

std::vector<std::shared_ptr<object>> objectPtrList2;

// #6 shared_ptr can be used to retain a copy of the pointer and update both the vector
// value and the local copy simultaneously.
auto pFooShared = std::make_shared<object>(1, "foo");
objectPtrList2.push_back(pFooShared);
// Pointer to object stored in the vector, but pFooShared is still valid.
Карл Николл
источник
2
Обратите внимание, что std::make_unique(досадно) доступно только в C ++ 14 и выше. Если вы хотите скомпилировать эти примеры, убедитесь, что вы указали компилятору установить его стандартное соответствие соответствующим образом.
Ларикс Децидуа
В 5а вы можете использовать, auto pFoo =чтобы избежать повторения; и все std::stringприведения могут быть удалены (есть неявное преобразование из строковых литералов в std::string)
MM
2
@ user465139 make_uniqueможет быть легко реализован в C ++ 11, так что это лишь небольшое раздражение для кого-то, кто застрял с компилятором C ++ 11
ММ,
1
@ ММ: Действительно. Вот реализация учебника:template<typename T, typename... Args> unique_ptr<T> make_unique(Args&&... args) { return unique_ptr<T>{new T{args...}}; }
Ларикс Децидуа
1
@ Анакин - Да, они должны делать, но только если вы копируете. Если вы используете std::move()с std::shared_ptr, исходный общий указатель может изменить свой указатель, так как владение было передано вектору. Смотрите здесь: coliru.stacked-crooked.com/a/99d4f04f05e5c7f3
Карл Николл
15

std :: vector всегда делает копию того, что хранится в векторе.

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

Рид Копси
источник
3
это не зависит от типа. Он всегда делает копию. Если его указатель делает копию указателя
pm100
Вы оба правы. Технически, да, он всегда делает копию. Практически, если вы передаете ему указатель на объект, он копирует указатель, а не объект. Безопасно, вы должны использовать соответствующий умный указатель.
Стивен Судит
1
Да, это всегда копирование - однако, «объект», на который ссылается OP, скорее всего, является классом или структурой, поэтому я имел в виду, зависит ли это от копирования «Объекта», от определения. Хотя плохо сформулировано.
Рид Копси,
3

Мало того, что std :: vector делает копию того, что вы отодвигаете назад, но определение коллекции гласит, что это будет сделано, и что вы не можете использовать объекты без правильной семантики копирования в векторе. Так, например, вы не используете auto_ptr в векторе.

Лиз Альбин
источник
2

В C ++ 11 релевантно emplaceсемейство функций-членов, которые позволяют передавать права собственности на объекты, перемещая их в контейнеры.

Форма использования будет выглядеть

std::vector<Object> objs;

Object l_value_obj { /* initialize */ };
// use object here...

objs.emplace_back(std::move(l_value_obj));

Перемещение для объекта lvalue важно, так как в противном случае оно будет переадресовано как ссылка или константная ссылка, и конструктор перемещения не будет вызван.

LemonPi
источник
0

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

rahmivolkan
источник
1
Примечание о векторах указателей: vector <shared_ptr <obj>> намного безопаснее, чем vector <obj *>, а shared_ptr является частью стандарта с прошлого года.
rich.e
-1

Почему потребовалось много исследований Valgrind, чтобы выяснить это! Просто докажите это себе с помощью простого кода, например

std::vector<std::string> vec;

{
      std::string obj("hello world");
      vec.push_pack(obj);
}

std::cout << vec[0] << std::endl;  

Если напечатан «hello world», объект должен быть скопирован

NexusSquared
источник
4
Это не является доказательством. Если объект не был скопирован, ваш последний оператор будет неопределенным поведением и может напечатать привет.
Мат
4
правильный тест будет модифицировать один из двух после вставки. Если бы они были одним и тем же объектом (если вектор содержал ссылку), оба они были бы изменены.
Франческо Донди