循環参照と所有権の共有

循環参照というと C++ では下のようなコードのことを指すわけですが、

struct A {
  shared_ptr<B> bp;
};

struct B {
  shared_ptr<A> ap;
};

shared_ptr<A> ap(new A());
shared_ptr<B> bp(new B());

ap->bp = bp;
bp->ap = ap;

これに対する解決として weak_ptr を使うというのがあります。

struct A {
  weak_ptr<B> bp;
};

struct B {
  weak_ptr<A> ap;
};

shared_ptr<A> ap(new A());
shared_ptr<B> bp(new B());

ap->bp = bp;
bp->ap = ap;

大体の weak_ptr による循環参照の解決に関するお話はここで終わってしまうわけですが、もう一歩進んでみましょう。

このコードには A は生きているのに B は死んでいる、もしくはその逆のような状態が発生しないように A と B の lifetime が同じである保証をプログラマが頑張ってしてやる必要がある、という問題があります(その必要がないならそもそも shared_ptr で循環に参照するようなコードを書くのは適切ではないです)。
何のためにスマートポインタ使ってるか分からなくなっちゃうし、そういうのは避けたいですよね。

そういう時には所有権の共有を行うコンストラクタを利用できます。

shared_ptr<A> ap(new A());
shared_ptr<B> bp(ap, new B()); // ownership sharing

これだけです。これで ap と bp の lifetime は同じになります。もちろんそれでも weak_ptr::lock メンバ関数を通さないといけないのでダサいですが、それが失敗することがない、という保証が簡単に得られるというのはいい話ですね。

現実にコードを書く時には、コンストラクタや weak_ptr の setter を private にしてファクトリを friend にする、とかしてもうちょっと頑強なコードにしときましょう。

【注意】boost::shared_ptr と C++0x の std::shared_ptr にはこの所有権の共有を行うコンストラクタが存在しますが std::tr1::shared_ptr にはありません多分。msdn になかったので…