Is std::vector copying the objects with a push_back?
Asked Answered
D

8

233

After a lot of investigations with valgrind, I've made the conclusion that std::vector makes a copy of an object you want to push_back.

Is that really true? A vector cannot keep a reference or a pointer of an object without a copy?

Dorris answered 16/2, 2010 at 17:54 Comment(3)
This is a basic principle of C++. Objects are values. Assignment makes a copy. Two variables referring to the same object is not possible unless you modify the type with * or & to make a pointer or reference.Weikert
@DanielEarwicker push_back actually does take a reference. It's not clear from the signature alone that it will make a copy.Pasto
@BrianGordon - Not saying it is! Hence the need for the guiding principle. Even so, we can deduce something from the signature of push_back: it takes a const&. Either it throws the value away (useless), or there's a retrieval method. So we look at the signature of back, and it returns plain &, so either the original value was copied or the const has been silently cast away (very bad: potentially undefined behaviour). So assuming the designers of vector were rational (vector<bool> not withstanding) we conclude it makes copies.Weikert
C
241

Yes, std::vector<T>::push_back() creates a copy of the argument and stores it in the vector. If you want to store pointers to objects in your vector, create a std::vector<whatever*> instead of std::vector<whatever>.

However, you need to make sure that the objects referenced by the pointers remain valid while the vector holds a reference to them (smart pointers utilizing the RAII idiom solve the problem).

Corriecorriedale answered 16/2, 2010 at 17:57 Comment(7)
I would also note that, if you use raw pointers, you are now responsible for cleaning up after them. There's no good reason to do this (not one I can think of anyway), you should always use a smart pointer.Rurik
that said, you should not use std::auto_ptr in stl containers, for more info : why-is-it-wrong-to-use-stdauto-ptr-with-standard-containersBallet
Since C++11, push_back will perform a move instead of a copy if the argument is an rvalue reference. (Objects can be converted to rvalue references with std::move().)Bottle
@tuple_cat your comment should say "if the argument is an rvalue". (If the argument is the name of an entity declared as rvalue reference, then the argument is actually an lvalue and will not be moved from) - check my edit to the answer of "Karl Nicoll" which made that mistake initiallyAncy
There is an answer below but to make it clear: Since C++11 also use emplace_back to avoid any copy or move (construct object in place provided by the container).Retardment
@emlai c++ newbie, when I check the doc for vector::push_back, I saw the signature takes a reference void push_back( const T& value ) but actually it is still taking a copy? This is really confusingUpcoming
@Upcoming Yeah that signature has to make a copy because std::vector has to store the value inside the vector, not just a reference to it. The copy is made inside the function. The new overload that takes an rvalue reference, void push_back( T&& value ), can avoid making the copy (by moving the parameter).Bottle
A
66

From C++11 onwards, all the standard containers (std::vector, std::map, etc) support move semantics, meaning that you can now pass rvalues to standard containers and avoid a copy:

// 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");

Alternatively you can use various smart pointers to get mostly the same effect:

std::unique_ptr example

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 example

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.
Aleksandrovsk answered 1/11, 2016 at 10:45 Comment(11)
Note that std::make_unique is (annoyingly) available only in C++14 and above. Make sure you tell your compiler to set its standard conformance accordingly if you want to compile these examples.Predicable
In 5a you can use auto pFoo = to avoid repetition; and all of the std::string casts can be removed (there is implicit conversion from string literals to std::string)Ancy
@user465139 make_unique can easily be implemented in C++11, so it's only a slight annoyance for someone stuck with a C++11 compilerAncy
@Ancy : Indeed. Here is textbook implementation: template<typename T, typename... Args> unique_ptr<T> make_unique(Args&&... args) { return unique_ptr<T>{new T{args...}}; }Predicable
@Ancy - Good points for std::string and auto usage. I've updated my answer.Aleksandrovsk
If I emplace_back a std::shared_ptr into a std::vector, do the raw pointer before and after the emplace point to the same memory location?Orestes
@Orestes - Yes they should do, but only if you copy. If you use std::move() with std::shared_ptr, the original shared pointer might have it's pointer changed since ownership was passed to the vector. See here: coliru.stacked-crooked.com/a/99d4f04f05e5c7f3Aleksandrovsk
Another question if you can help me. Consider I create std::shared_ptrof several Derived classes and emplace_back them into a std::vector that holds std::shared_ptrs of the Base; do I have same underlying memory? Or does it change due to casting?Orestes
@Orestes - Yep, you can do that. You can pass the std::shared_ptr<derived> to the vector holding std::shared_ptr<base> and the pointer would remain the same :).Aleksandrovsk
I tried to demonstrate the omitting copy constructor based on move semantic - but it does not work as described - copy Ctor still called, demo is here: onlinegdb.com/b_julh3KnNortheasterly
vector stores objects in heap. if you create an object in stack and puch_back, the object should be copied to heap. So, in example 2 It probably still will be expensive mem copy operation under the hood.Seagoing
A
38

Yes, std::vector stores copies. How should vector know what the expected life-times of your objects are?

If you want to transfer or share ownership of the objects use pointers, possibly smart pointers like shared_ptr (found in Boost or TR1) to ease resource management.

Absorbed answered 16/2, 2010 at 17:56 Comment(12)
learn to use shared_ptr - they do exactly what you want. My favorite idiom is typedef boost::shared_ptr<Foo> FooPtr; Then make containers of FooPtrsSwamper
@Swamper - Do you know boost::ptr_vector ?Granite
According to boost.org/doc/libs/1_42_0/libs/ptr_container/doc/…, it's a raw pointer, not a smart one. Dangerous.Tieratierce
I also like to use class Foo { typedef boost::shared_ptr<Foo> ptr; }; to just write Foo::ptr.Yacano
@manuel - no i do not - i will look. @rupert nice style but it has one weakness, I must own the classSwamper
@Steven. Not dangerous at all. ptr_vector holds the ownership of the object.Chiquitachirico
ptr_vector seems to have some corner case advantages but is non shared - too dangerous compared to the 'fire and forget' behavior of shared_ptrsSwamper
@Rupert Jones, pm100 - I've been using ptr_vector for quite a while and I've never felt the need for shared ownershipGranite
@Nemanja: You're right. The description claims it's a raw pointer, but the header clearly shows auto_ptr is being used. Bad documentation!Tieratierce
@Swamper - shared_ptr is not exactly fire and forget. See stackoverflow.com/questions/327573 and stackoverflow.com/questions/701456Weikert
shared_ptr is good if you have shared ownership, but it is generally overused. unique_ptr or boost scoped_ptr make much more sense when the ownership is clear.Chiquitachirico
"scoped_ptr cannot be used in C++ Standard Library containers. Use shared_ptr if you need a smart pointer that can." boost.org/doc/libs/1_41_0/libs/smart_ptr/scoped_ptr.htmTieratierce
S
19

std::vector always makes a copy of whatever is being stored in the vector.

If you are keeping a vector of pointers, then it will make a copy of the pointer, but not the instance being to which the pointer is pointing. If you are dealing with large objects, you can (and probably should) always use a vector of pointers. Often, using a vector of smart pointers of an appropriate type is good for safety purposes, since handling object lifetime and memory management can be tricky otherwise.

Sharl answered 16/2, 2010 at 17:57 Comment(3)
it doesnt depend on the type. It always makes a copy. If its a pointer is makes a copy of the pointerSwamper
You're both right. Technically, yes, it always makes a copy. Practically, if you pass it a pointer to the object, it copies the pointer, not the object. Safely, you should use an appropriate smart pointer.Tieratierce
Yes, it's always copying - However, the "object" the OP is referring to is most likely a class or struct, so I was referring to whether it's copying the "Object" depends on the definition. Poorly worded, though.Sharl
C
3

Not only does std::vector make a copy of whatever you're pushing back, but the definition of the collection states that it will do so, and that you may not use objects without the correct copy semantics within a vector. So, for example, you do not use auto_ptr in a vector.

Chronoscope answered 16/2, 2010 at 18:1 Comment(0)
B
1

if you want not the copies; then the best way is to use a pointer vector(or another structure that serves for the same goal). if you want the copies; use directly push_back(). you dont have any other choice.

Blakely answered 16/2, 2010 at 18:2 Comment(1)
A note about pointer vectors: vector<shared_ptr<obj> > is alot safer than vector<obj *> and shared_ptr is part of the standard as of last year.Banian
S
0

Relevant in C++11 is the emplace family of member functions, which allow you to transfer ownership of objects by moving them into containers.

The idiom of usage would look like

std::vector<Object> objs;

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

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

The move for the lvalue object is important as otherwise it would be forwarded as a reference or const reference and the move constructor would not be called.

Swagman answered 18/7, 2015 at 16:15 Comment(1)
emplace_back shouldn't be used with an existing object, but to construct a new one in placePalatable
F
-2

Why did it take a lot of valgrind investigation to find this out! Just prove it to yourself with some simple code e.g.

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

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

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

If "hello world" is printed, the object must have been copied

Fritter answered 7/2, 2015 at 11:45 Comment(3)
This doesn't constitute a proof. If the object wasn't copied, your last statement would be undefined behavior and could print hello.Nearsighted
the correct test would be modifying one of the two after the insertion. If they were the same object (if the vector stored a reference), both would be modified.Marge
Showing a different pointer constitutes proof enough. In your example &vec[0] != &obj. Thereof obj was copied over vec[0].Wingfield

© 2022 - 2024 — McMap. All rights reserved.