C++ – Polymorphic Template Container: shared_ptr vs reference_wrapper

cc++11polymorphismreferencesmart-pointer

Assuming we have two classes:

class A
{
    ...
}
class B : public A
{
    ...
}

Would it be better to write

std::deque<shared_ptr<A> > container;

or

std::deque<reference_wrapper<A> > container;

to create a container which is able to contain references to both of these classes and why?

In my case, the class containing the container wouldn't care, if a reference somehow became invalid.

However are there any caveats to take into account, when choosing one approach over the other?

Best Answer

Here's my opinion on the matter:

  1. consider whether both variants can actually be used in your case. reference_wrapper is, by design, not default-constructible. That means you will not be able, for example, to call container.resize() when using the reference wrapper. A shared_ptr, on the other hand, is default-constructed to an invalid/NULL state. So, for some use cases, you can't use a reference_wrapper. To give some examples:

    int i = 42;
    std::reference_wrapper<int> iref(i);             // OK
    std::reference_wrapper<int> uninitialized_iref;  // Not OK, but OK with shared_ptr
    
    iref = 23;                     // Not OK, but OK on a "normal" reference
    int& iref2 = iref; iref2 = 32; // OK
    static_cast<int&>(iref) = 23;  // OK
    iref.get() = 23;               // OK
    
    std::vector<std::reference_wrapper<int> > irefs; // OK
    irefs.resize(10);    // Not OK!
    irefs.resize(10, i); // OK
    
  2. The above examples already hint at something else: Usability and readability. Depending on your use case, you have to decide whether you prefer pointer-style notation (shared_ptr) or more-or-less-reference-style notation (reference_wrapper) and whether that makes the code more maintainable. Personally I value this aspect quite highly.

  3. As pointed out in a comment to your question, the object lifetime also plays a role. When the container's lifetime is intended to exceed the lifetime of any of the referenced objects, you have to use shared_ptr (or maybe find a different system design). With references you need to be certain about the respective scopes. I also suspect that in your specific case, you should actually care about references becoming invalid -- if that can happen, you'd be in trouble, and it would usually be difficult to debug.

  4. Storing shared_ptr elements of course implies that the values you want to refer to are already stored in shared pointers. You can not create a shared pointer referencing an element created by some other means. So, if you need to refer to existing, non-shared elements, you can not use shared_ptr and would have to go with reference_wrapper or plain old pointers.

  5. As far as performance goes, I would not expect much of a difference. reference_wrapper usually uses a pointer internally, and the overhead for a shared_ptr is usually negligible.

All in all, I would base my decision around (a) my concrete use case, and (b) usability/maintainability/readability of the code using the container.

Related Topic