Search code examples
c++c++11shared-ptrweak-ptreffective-c++

Understanding of Scott Meyers' third example of std::weak_ptr


The final example at page 137 of Effective Modern C++ draws the scenario of a data structure with objects A, B, and C in it, connected to each other via std::shared_ptr in the following way:

   std::shared_ptr       std::shared_ptr
A ─────────────────▶ B ◀───────────────── C

To me, this implies that the classes which objects A and C are instance of (two unrelated classes, in general) must contain a std::shared_ptr<classOfB> member.

Then the supposition is made that we need a pointer from B back to A, and the available options are listed: the pointer can be raw, shared, or weak, and the last one is picked up as the best candidate.

   std::shared_ptr       std::shared_ptr
A ─────────────────▶ B ◀───────────────── C
▲                    │
│    std::weak_ptr   │
└────────────────────┘

I do understand the weaknesses (ahahah) of the first two alternatives, but I also see that the third alternative requires that member A be already managed by some std::shared_ptr, otherwise how can a std::weak_ptr point to it at all?

However the book does not refer to this "limitation"/assumption/whatever, so the truth is either

  • I'm wrong
  • I'm right but that assumption is obvious for some reason I don't understand
  • The assumption is obvious for the exact reason that a std::weak_ptr needs an already existing std::shared_ptr to the same object, but it's a bit strange it's not even mentioned at the beginning of the example, I think.

and I'm asking this question to understand this.


Solution

  • The unfortunate consequence of the design of std::shared_ptr is that no cycles can appear in the dependency graph managed by shared pointers. This means that once A points to B via a shared pointer, B cannot point the same way to A since that will lead to a memory leak (both objects will keep themselves alive).

    std::weak_ptr serves its primary purpose as a weak reference, but most of the times, it is used only as a fix for this issue. However, if you don't manage the lifetime of A via a shared pointer in the first place, B has no way of tracking it anyway, so using a raw pointer, a reference (or some other exotic pointer) is the only option. Conversely, if you own A via a shared pointer, weak_ptr is the only option.

    In both cases, the choice completely depends on your prior decision of managing A, which is something you'd had to do here (possibly via an object that refers to both A and C).