computer science goes bonk / all posts / rss / about

get yer filthy hands off my control block

Exposition

Yesterday, Logan pointed out that you can construct a shared_ptr<T> that manages some T but shares a control block with some completely different shared_ptr<U>:

template <typename T> class shared_ptr {
public:
  // constructs a shared_ptr that shares ownership with r and stores p
  template<typename U> shared_ptr(shared_ptr<U> const &r, T *p);
  // ...
};

This is a pretty cool use of shared_ptr that I had not seen before.

A Compelling Example

Say you have two objects -- one nested inside the other -- that need to be able to refer to eachother. If you give each object a shared_ptr to the other object, they'll leak-lock eachother until your program dies of consumption. You could break the symmetry by giving one a weak_ptr to the other, but then you run the risk of one object expiring before the other.

What we really want is both objects to stay alive as long as either is around:

#include <memory>
#include <iostream>

struct Foo;

struct Bar {
  Bar(Foo *f) : foo(f) { std::cout << "Bar\n"; }
  ~Bar() { std::cout << "~Bar\n"; }
  // goal: this Foo should be valid as long as this Bar is alive
  Foo *foo;
};

struct Foo : std::enable_shared_from_this<Foo> {
  Foo() : bar(new Bar(this)) { std::cout << "Foo\n"; }
  ~Foo() { std::cout << "~Foo\n"; }
  // give someone access to our Bar
  std::shared_ptr<Bar> getBar() {
    return std::shared_ptr<Bar>(shared_from_this(), bar.get());
  }
  std::unique_ptr<Bar> bar;
};

int main() {
  std::shared_ptr<Bar> bar;
  {
    std::shared_ptr<Foo> foo(new Foo);
    bar = foo->getBar();
  }
  std::cout << "foo is dead now...or is it?\n";
}

When run, the above code produces the following output:

Bar
Foo
foo is dead now...or is it?
~Foo
~Bar

The Thrilling Conclusion

By attaching the shared_ptr<Bar> to the control block for the shared_ptr<Foo>, we can enforce the idea that if we grab a Bar from a Foo, the Foo will live at least as long as the Bar does.