I would like to create an object, put the object into a vector, and still be able to modify the same object by accessing only the vector. However, I understand that when an object is push_back()
to a vector, the object is actually copied into the vector. As a result, accessing the object in the vector will merely access a similar, but different object.
I'm almost certain that this is not what you want or "should" want. Forgive me that direct opening of my answer, but unless you have a very good reason to do this, you probably don't want to do it.
For that - a vector with references - to work you must guarantee that the referenced objects won't get moved nor destructed while you hold references to them. If you have them in a vector, make sure that vector isn't resized. If you have them on the stack like in your example, then don't let the vector of references or a copy of it leave that stack frame. If you want to store them in some container, use a std::list
(it's iterators - pointers basically - don't get invalidated when inserting or removing elements).
You already noticed that you cannot have a vector of "real" references. The reason therefore is that references aren't assignable. Consider following code:
int a = 42;
int b = 21;
int & x = a; // initialisation only way to bind to something
int & y = b;
x = y;
b = 0;
After that, the value you obtain from x
will be 21, because the assignment didn't change the reference (to be bound to b
) but the referenced object, a
. But a std::vector
explicitly requires this.
You could now set out and write an wrapper around a pointer like ...
template<typename T>
struct my_ref {
T * target;
// don't let one construct a my_ref without valid object to reference to
my_ref(T & t) : target(&t) {}
// implicit conversion into an real reference
operator T &(void) {
return *target;
}
// default assignment works as expected with pointers
my_ref & operator=(my_ref const &) = default;
// a moved from reference doesn't make sense, it would be invalid
my_ref & operator=(my_ref &&) = delete;
my_ref(my_ref &&) = delete;
// ...
};
... but this is pretty pointless since std::reference_wrapper
already provides exactly that:
int main (int, char**) {
int object = 21; // half of the answer
vector<reference_wrapper<int>> v;
v.push_back(object);
v[0].get() = 42; // assignment needs explicit conversion of lhs to a real reference
cout << "the answer is " << object << endl;
return 0;
}
(Example live here)
Now one could argue why using a wrapper around a pointer like std::reference_wrapper
when one could also directly use a pointer. IMO a pointer, having the ability to be nullptr
, changes the semantics of the code: When you have a raw pointer, it could be invalid. Sure, you can just assume that it's not, or put it somewhere in comments, but in the end you then rely on something that's not guaranteed by the code (and this behaviour normally leads to bugs).
If an element of your vector could "reference" an object or be invalid, then still raw pointers aren't the first choice (for me): When you use an element from your vector which is valid, then the object referenced by it is actually referenced from multiple places on your code; it's shared. The "main" reference to the object then should be a std::shared_ptr
and the elements of your vector std::weak_ptr
s. You can then (thread safe) acquire a valid "reference" (a shared pointer) when you need to and drop it when done:
auto object = make_shared<int>(42);
vector<weak_ptr<int>> v;
v.push_back (object);
// ... somewhere later, potentially on a different thread
if (auto ref = v[0].lock()) {
// noone "steals" the object now while it's used here
}
// let them do what they want with the object, we're done with it ...
Finally, please take my answer with a grain of salt, much of it is based on my opinion (and experience) and might not count as "standard practice".