C++
C++11
C++17
shared_ptr

enable_shared_from_this の C++2011(14) と C++2017 との相違点

クラステンプレート std::enable_shared_from_this とは、std::shared_ptr で所有権を管理している場合、これを継承するとインスタンス自身から shared_ptr を逆引きすることができる(shared_from_this() メンバ関数を基底クラスに持つ)という便利なテンプレートです。
なぜこのようなものが必要になるのかは、下のオージス総研さんの記事が詳しいです(2009年の記事なので boost ライブラリの説明ですが、内容は後に追加された標準ライブラリにも通用します)。

さて、このテンプレートは C++11 で shared_ptr と共に追加されたのですが、C++17 で微妙ながら重要な変更があることに気づいたのでご紹介します。

C++11

shared_ptr を逆引きするという性質上、当然ながら shared_ptr で所有権を管理されていないインスタンスからは取得することができません。
ここまではいいのですが、C++11 では shared_from_this() 関数の制約として、shared_ptr で所有権を管理されていることを「必須 (Requirements)」としました。
これが何を意味するかと言うと、shared_ptr で管理されていない場合、shared_from_this() の動作は 未定義動作 であるということです。
それにも関わらず、enable_shared_from_this には、今現在 shared_ptr で管理されているかどうかを判別する方法が存在しません
この2つから、enable_shared_from_this は少し使い方を誤っただけで容易に未定義動作を引き起こしうるという問題が存在しました。

C++17

これが C++17 で以下のように変わりました。

  • shared_ptr で管理されていることが「必須」ではなくなった
  • enable_shared_from_this に新しいメンバ関数 weak_from_this() が追加され、さらにメンバ変数 std::weak_ptr<T> weak_this があることが明示された
  • shared_ptr のコンストラクタで enable_shared_from_this::weak_this に参照を格納することが明示された
  • shared_from_this() の戻り値は、weak_thisshared_ptr に変換したものであることが明示された

この仕様変更によって、以下のように状況が変わりました。

  • weak_from_this() の戻り値を使って、今現在 shared_ptr で管理されているかどうかを判別することができるようになった(weak_ptr::use_count()expired() などを使えばよい)
  • shared_ptr で管理されていないインスタンスの shared_from_this() を呼んだ場合、std::bad_weak_ptr 例外が(未代入の weak_ptrshared_ptr に変換したときに)発生することが保証され、未定義動作ではなくなった

結論

C++17 はいいぞ