ChatGPT解决这个技术问题 Extra ChatGPT

Is std::vector copying the objects with a push_back?

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 ?!

Thanks

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.
@DanielEarwicker push_back actually does take a reference. It's not clear from the signature alone that it will make a copy.
@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.

A
Alexander Gessler

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).


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.
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-containers
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().)
@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 initially
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).
K
Karl Nicoll

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.

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.
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)
@user465139 make_unique can easily be implemented in C++11, so it's only a slight annoyance for someone stuck with a C++11 compiler
@M.M : Indeed. Here is textbook implementation: template<typename T, typename... Args> unique_ptr<T> make_unique(Args&&... args) { return unique_ptr<T>{new T{args...}}; }
@Anakin - 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/99d4f04f05e5c7f3
G
Georg Fritzsche

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.


learn to use shared_ptr - they do exactly what you want. My favorite idiom is typedef boost::shared_ptr FooPtr; Then make containers of FooPtrs
@pm100 - Do you know boost::ptr_vector ?
I also like to use class Foo { typedef boost::shared_ptr<Foo> ptr; }; to just write Foo::ptr.
@pm100 - shared_ptr is not exactly fire and forget. See stackoverflow.com/questions/327573 and stackoverflow.com/questions/701456
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.
R
Reed Copsey

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.


it doesnt depend on the type. It always makes a copy. If its a pointer is makes a copy of the pointer
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.
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.
L
Liz Albin

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.


r
rahmivolkan

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.


A note about pointer vectors: vector > is alot safer than vector and shared_ptr is part of the standard as of last year.
L
LemonPi

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.


emplace_back shouldn't be used with an existing object, but to construct a new one in place
N
NexusSquared

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


This doesn't constitute a proof. If the object wasn't copied, your last statement would be undefined behavior and could print hello.
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.
Showing a different pointer constitutes proof enough. In your example &vec[0] != &obj. Thereof obj was copied over vec[0].