Understanding of Scott Meyers' third example of std::weak_ptr
Asked Answered
A

2

5

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.

Avoidance answered 19/11, 2020 at 8:40 Comment(3)
if A is not handled by std::shared_ptr, there are no alternatives...Interrupted
I think this assumption is fair. weak_ptr is used here only as means of fixing shared_ptr loops. Either you already managed the lifetime of A via a shared pointer, in which case you'd want to use a weak pointer, or you didn't consider smart pointers at all, in which case B has no way of making sure A is alive anyway. There are no alternatives in any case.Analogical
@IllidanS4supportsMonica, I did not think about this. Please, feel free to post it as an answer.Avoidance
A
3

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).

Analogical answered 19/11, 2020 at 19:45 Comment(0)
J
4

You're right, and your third bullet point is correct. A std::weak_ptr always refers to an existing std::shared_ptr. So, it is once we've made the decision that A is going to contain a shared_ptr<BClass> and that the instances of A are being managed as shared_ptr<AClass>'s that we can use a weak_ptr<AClass> for the back-reference from B to A.

In a typical use case, you could have a D-class that manages three members shared_ptr<AClass> a;, shared_ptr<BClass> b; and shared_ptr<CClass> c;, and then some member function in D-class does a->SetB(b);, c->SetB(b);, b->SetA(a);, with the SetB member functions using shared_ptr's and SetA using a weak_ptr (or converting to weak_ptr within the member function). As you rightly say, if D did store the reference to A in any other way like, say, a raw pointer AClass* a; or an instance AClass a;, then using a weak_ptr would simply not be possible.

Jocelynjocelyne answered 19/11, 2020 at 10:17 Comment(1)
I was in doubt about the first always in your answer, because at page 134 Scott writes that _std::weak_ptrs are typically created from std::shared_ptrs. Maybe by non-typical he alludes to copying a std::weak_ptr (via the copy ctor).Avoidance
A
3

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).

Analogical answered 19/11, 2020 at 19:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.