2

I have an existing container filled with automatically allocated struct objects like so:

std::vector<Object> objectList {obj1, obj2, obj3};

I'm trying to create a new container useObjectList that consists of pointers/references that point to those existing objects so that I am able to access and modify the object member values from either list.

I've tried to achieve this by creating a container of shared pointers

std::vector<std::shared_ptr<Object>> useObjectList {std::make_shared<Object>(objectList[1])};

but it hasn't worked out at all (given my limited knowledge, I assume std::make_shared constructs and/or allocates a new object entirely?).

I'd appreciate any advice I could get.

spaL
  • 604
  • 7
  • 21
  • 1
    Are they meant to be non-owning pointers? If so, `std::vector` would do the trick (you'd need to make sure the Objects in the original vector remain valid to avoid dangling-pointer issues, of course; in particular you probably wouldn't want to modify the original vector in any way that might resize it larger or smaller) – Jeremy Friesner Jan 22 '22 at 06:50
  • Use raw pointers `std::vector useObjectList {&obj1, &obj2, &obj3};`. – n. m. could be an AI Jan 22 '22 at 06:50
  • `make_shared` means your `shared_ptr`(s) point at a *copy* of the *objects*. If you want a vector of pointers to the *same* objects, do `std::vector useObjectList{&obj1, &obj2, &obj3}` or (to point at elements of `objectList`, which are also copies of `obj1` .. `obj3` not references/pointers to them) do `std::vector useObjectList{&objectList[0], &objjectList[1], &objList[2]}` – Peter Jan 22 '22 at 06:54
  • Is it okay to use raw pointers in this scenario? From what people tell me and the tutorials I go through, they always recommend avoiding using raw pointers. – spaL Jan 22 '22 at 06:55
  • Depends on what you're going to do with the pointers/references/objects. Raw pointers have their uses, just like "smart" pointers do. If you (as you have) state that you need a vector of pointers/references, without any mention of *why* you need it, then a raw pointer is as good a choice as any. – Peter Jan 22 '22 at 06:59
  • If you don't change the order of elements in the original container, then raw pointers are okay. If not, consider use ```std::reference_wrapper```. – frozenca Jan 22 '22 at 07:52
  • In what way does `reference_wrapper` act differently than a pointer when changing the order? – Sebastian Jan 22 '22 at 08:49
  • Just for the record, `reference_wrapper` doesn't act differently at all; it's equivalent to a pointer. Explained [here](https://stackoverflow.com/q/26766939/509868). – anatolyg Jan 22 '22 at 09:21

2 Answers2

2

To use a "C++ Container of Pointers/References to Existing Objects", or pretty much anything else, you should understand the concept of ownership. Summary: ownership is responsibility for cleanup. You should think which entity should delete your objects. Often, you should rethink this whenever you change your code or add features to it.

std::vector<Object> objectList {obj1, obj2, obj3}; - here, objectList contains copies of objects obj1, obj2 and obj3, and is responsible for deleting them (i.e. owns them). Whichever entity contained the original obj1, obj2 and obj3 retains ownership of these (i.e. will delete them).

A wider context:

void do_stuff()
{
    Object obj1;
    Object obj2;
    Object obj3;
    std::vector<Object> objectList {obj1, obj2, obj3};
}

Here, the scope (or "stack frame") of function do_stuff owns the three objects: they will be deleted when control goes out of the scope. This is a situation where something other than an object owns objects.

std::vector<Object*> objectList {&obj1, &obj2, &obj3}; - here, objectList contains pointers to objects obj1, obj2 and obj3. The ownership of objects themselves is not changed - whatever owned them continues owning them. Some comment mentions "always recommend avoiding using raw pointers"; "always" is nonsense, but this is a good example for illustrating that recommendation.

A wider context:

std::vector<Object*> someObjectList;
void do_stuff()
{
    Object obj1;
    Object obj2;
    Object obj3;
    std::vector<Object*> objectList {&obj1, &obj2, &obj3};
    someObjectList = objectList;
}

Here, the stack frame of function do_stuff owns the three objects. If you have pointers to these objects, it's easy to "send" them to some other entity (someObjectList), which will store the pointers even after the objects have been cleaned up. This is the reason why people "recommend avoiding using raw pointers"; but the real problem here is more fundamental - it's having a "Container of Pointers/References to Existing Objects". This is what people should recommend not doing.

If you really want to reference existing objects, you should make sure your ownership pattern supports this. You can do this in either of these ways:

  1. Use an entity which owns the objects; make sure all lists of pointers to objects get deleted before the "owner of everything" is deleted. Often, the semantics of your application demand this anyway.

  2. Use shared ownership. For that, you should apply shared ownership right at the creation point of your objects.

     std::shared_ptr<Object> obj1 = std::make_shared(whatever1);
     std::shared_ptr<Object> obj2 = std::make_shared(whatever2);
     std::shared_ptr<Object> obj3 = std::make_shared(whatever3);
     std::vector<std::shared_ptr<Object>> objectList(obj1, obj2, obj3);
    

    You should think about a situation where the only "link" to the objects is your list of pointers. Does this make sense in your application? If yes, use shared ownership. If not, this is not a good idea, because it hides bugs.

anatolyg
  • 26,506
  • 9
  • 60
  • 134
1

You can use reference_wrapper:

list<int> l {1, 2, 3, 4, 5};
vector<reference_wrapper<int>> v (l.begin(), l.end());

// you can't use subscription in list
// but as you are seeing elements by vector containing their references
// so operator[] works here.

cout << v[3] << '\n'; // outputs 4

for (auto& num : l) {
    num *= 2; // original list elements are doubled
}

cout << v[2] << '\n'; // outputs 6


frozenca
  • 839
  • 4
  • 14