Most C++ programmers known more or less about std::shared_ptr, but what is its limitations?

C++ standard library smart pointer std::shared_ptr is based on the RAII and reference counting, an ingenious way to manange memory than C. As long as be careful with the circular reference (not 100% to avoid them virtually), you can always coding without too much concerns.

Supposed you need to manage a DAG, which is very common in organizing software architecture. Usually, DAG would be used along with Observer Pattern. Since one need to response to changes or events from its dependencies. It has a bidirectional link between one node and its successor.

DAG
DAG

It’s okay in a programming with GC, but in C++, there is usually a strong reference point (solid arrow in the chart) to its successor and at the same time, a weak reference point to the predecessor. (dash arrow in the chart) in order to avoid cycling referencing.

Saying that you want to create two objects with this kind of relationship as the fowlling chart, you may allocate one successor and observe it immediately after it being created in constructor, which is straightforward.

DAG
cycling reference

The code is like this


struct Node : public std::enable_shared_from_this<Node> {
  std::vector<std::weak_ptr<Node>> subscribers;
  void observe(std::shared_ptr<Node> sub) {
    // ... do some check

    sub->subscribers.push_back(this->shared_from_this());
    // ...
  }
};

struct Bar : public Node {};

struct Foo : public Node {
  std::shared_ptr<Node> object2;
  Foo() {

    object2 = std::make_shared<Bar>();
    observe(object2);
  }
};

int main() { auto object1 = std::make_shared<Foo>(); }

If you run the code, it will throw std::bad_weak_ptr and terminate the program. The reason is that the object2 is not constructed yet when the shared_from_this is called in the constructor of Foo. So the weak reference is not constructed yet. That’s the limitation of std::shared_ptr when it comes to this case. You may even notice that using auto object1 = std::make_shared<Foo>() to create a shared object needs two steps rather than one. The first step is construct the object itself, and the second step, creating shared_ptr<T> wrapper and putting the object into it.

DAG
smart pointer layout