Say I have the following code
class Car {
public:
string color;
string name;
Car(string c, string n): color(c), name(n){}
}
int main() {
vector<Car> collection1;
vector<Car> collection2;
collection1.emplace_back("black", "Ford");
collection1.emplace_back("white", "BMW");
collection1.emplace_back("yellow", "Audi");
//Question comes here
collection2.push_back(collection1[0]);
}
Now I believe this does a deep copy of collection1[0]
.
I have tried using collection2.emplace_back(move(collection1[0]))
, but then the data fields of collection1[0]
would be gone. I just want this "black Ford" to exist in both vectors, and changes made to this particular object, through either vector, would be reflected on both vectors.
I am guessing that for a vector of real objects, the elements of this vector take actual memory. So an element of collection1
must be independent on any element of collection2
. I think the easiest way is to let collection1
and collection2
to be vectors of pointers and point to the same vector of Car
. But is there any possible means to make the above code work, without using vector of pointers. I ultimately want to return both of these collections back to the previous function, so making vectors of pointers is meaningless.
In short, I want to simulate the List.append()
method in python.
collection1 = [Car("black", "Ford"),Car("white", "BMW"),Car("yellow", "Audi")]
collection2 = []
collection2.append(collection1[0])
collection2[0].color = "blue" // This affects collection1 as well
In C++ languages, standard collections actually contain the object while in other languages like Python or Java, they actually contain references (or pointers) to objects that are stored elsewhere. But as C++ does not include garbage collection, the lifetime of the object must be explicitly managed elsewhere.
The consequence of that design is that, to allow the same object to be used in two different collections, you must use collections of pointers or references (beware, C++ does not directly allow collections of references; however, std::ref
was created for that).
Depending on your use case, you could either use raw pointers (if the lifetime of the actual objects is already managed), or use smart pointers (here, std::shared_ptr
) that internally manage a reference count to ensure that the object is automatically destroyed when the last shared_ptr
is destroyed. This is not far from Python's references to objects, provided you are aware that the destruction of the last shared_ptr
will actually destroy the object(*). Said differently, do not keep any other pointers or references to it if you do not want it to become dangling.
Alternatively, if the collections are not symmetric—that is, if one will actually contain all the objects, while the other will only contain references to objects from the former one—references will be your best bet, and the second collection could be a std::vector<std::reference_wrapper<Car>>
.
Addition per MvG comment.
There is a possibly annoying difference between Python objects and C++ shared_ptr. Python has a full garbage collector that is clever enough to detect circular references and destroys the cycle as soon as there are no external references. Example:
>>> b = ['x']
>>> a = ['y']
>>> b.append(a)
>>> a.append(b)
>>> a
['y', ['x', [...]]]
>>> b
['x', ['y', [...]]]
a contains a ref to b which contains a ref to a...
If a is deleted (or goes out of scope) b will still contain the full chain
>>> del a
>>> b
['x', ['y', [...]]]
but if both a and b are delete (or go out of scope), the gc will detect that there is no more external ref and will destroy everything.
Unfortunately, if you manage to build a cycle of C++ objects using std::shared_ptr
as it only uses local ref counting, each object will have a ref to the other and they will never be deleted even when they will go out of scope which will lead to a memory leak. An example of that:
struct Node {
int val;
std::shared_ptr<Node> next;
};
a = make_shared<Node>(); // ref count 1
b = make_shared<Node>();
a.next = std::shared_ptr<Node>(b);
b.next = std::shared_ptr<Node>(a); // ref count 2!
Hell has come here: even when a and b will both go out of scope, the ref count will still be one and the shared pointers will never delete their objects, what should have normally happend without the circular reference. The programmer must explicitely deals with that and break the cycle (and forbid it to happen). For example b.next = make_shared<Node>();
before b goes out of scope would be enough.
Since you mentioned you don't like pointers, you can use references, but vectors can't store references (because they aren't copyable and assignable). However std::reference_wrapper
wraps a reference in copyable and assignable object.
std::reference_wrapper
is a class template that wraps a reference in a copyable, assignable object. It is frequently used as a mechanism to store references inside standard containers (likestd::vector
) which cannot normally hold references.source: http://en.cppreference.com/w/cpp/utility/functional/reference_wrapper
vector<Car> collection1;
collection1.emplace_back("black", "Ford");
collection1.emplace_back("white", "BMW");
collection1.emplace_back("yellow", "Audi");
vector<std::reference_wrapper<Car>> collection2{collection1.begin(),
collection1.end()};
Using this way, collection2
refers to the same objects as collection1
does. For example:
collection1[0].name = "frogatto!";
std::cout << collection2[0].get().name;
// prints 'frogatto!'
Important:
Note that using this way is discouraged since you must have another entity that manages insertion and removal to/from collection1
and takes appropriate actions on collection2
. @Serge Ballesta's answer is better than mine. Use std::shared_ptr
. Try to love and embrace pointers :)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With