7

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 have a beginner's knowledge in C, so I know that I can create a pointer to the object, and make a vector of pointers. e.g. vector<Object *>. However, it seems as if pointers are discouraged in C++, and references are preferred. Yet, I cannot make a vector of references.

I wish to use only the standard libraries, so boost is off limits to me.

I heard of smart pointers. However, it appears as if there are multiple types of smart pointers. Would it not be overkill for this purpose? If smart pointers are indeed the answer, then how do I determine which one to use?

So my question is: What is the standard practice for creating a vector of references/pointers to objects?

In other words, would like the below (pseudo-)code to work.

#include <iostream>
#include <cstdlib>
#include <vector>

using namespace std;

class Object 
{
public:
    int field;
};

vector<Object> addToVector(Object &o)
{
    vector<Object> v;
    v.push_back(o);
    v[0].field = 3; // I want this to set o.field to 3.

    return v;
}

int main()
{
    Object one;
    one.field = 1;
    cout << one.field << endl; // 1 as expected

    Object &refone = one;
    refone.field = 2;
    cout << one.field << endl; // 2 as expected

    vector<Object> v = addToVector(one);

    cout << v[0].field << endl; // 3 as expected

    cout << one.field << endl; // I want to get 3 here as well, as opposed to 2.


    return 0;
}
Community
  • 1
  • 1
Zsw
  • 3,920
  • 4
  • 29
  • 43
  • There is no problem with a vector of pointers as long as the vector doesn't need to worry about the lifetime of the objects they point to. – juanchopanza Aug 19 '15 at 20:31
  • Use a vector of smart pointers instead. I'm pretty sure this question is a duplicate. – πάντα ῥεῖ Aug 19 '15 at 20:31
  • Sorry if it is a duplicate. The only question I found similar to mine had suggested using boost, which I wish to avoid. If this is indeed a duplicate please let me know. – Zsw Aug 19 '15 at 20:34
  • 6
    Just use a vector of pointers. There is no need for smart pointers here. At a push, you can use `std::reference_wrapper`. – juanchopanza Aug 19 '15 at 20:36
  • @Zsw You are aware, that `v.push_back(o);` takes a copy of your reference, that will be unrelated to it when further operations are applied? – πάντα ῥεῖ Aug 19 '15 at 20:40
  • @juanchopanza is right. Though I think usage of smart pointers makes the semantics clearer. If you don't own the instances pointed to in the vector, they should be `std::weak_ptr`s probably. – πάντα ῥεῖ Aug 19 '15 at 20:43
  • 1
    A solution to your original problem is to use `vector::emplace_back`, which has been introduced in C++11. It *creates* an object within the vector from an argument list; for example, `v.emplace_back()` (with an empty argument list) creates a new object in the vector using the default constructor (or value-initialization for non-class types). That object is not copied nor moved into the vector, it is created in-place. – dyp Aug 19 '15 at 21:04
  • @πάνταῥεῖ Yes. Essentially, my question is asking what is the best practices for creating a vector in a way such that when further operations are applied, it will affect the original object as well. – Zsw Aug 20 '15 at 00:23
  • Would someone like to put their comment as an answer? Otherwise I can't accept. – Zsw Aug 20 '15 at 01:48

1 Answers1

2

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_ptrs. 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".

Daniel Jour
  • 15,896
  • 2
  • 36
  • 63
  • 1
    `std::vector` since C++11 doesn't require CopyAssignable. I think the underlying reason why you can't have a vector of references is that you can't have a pointer to a reference. Hence, placement-new of references is impossible, and iterators would require weird workarounds. – dyp Aug 20 '15 at 10:47