導入
C++のスマートポインタ(std::shared_ptr)は、メモリ管理を自動化し、メモリリークを防ぐ強力なツールです。しかし、複数のオブジェクトが互いに参照し合う「循環参照」が発生すると、参照カウントがゼロにならず、オブジェクトが永遠に解放されないという致命的な問題が起こります。本記事では、この問題を回避するための標準的な手法であるstd::weak_ptrの活用方法を解説します。
基礎知識
std::shared_ptrは、所有権を共有するためのスマートポインタで、参照しているオブジェクトの数がゼロになった時にメモリを解放します。一方、std::weak_ptrは「所有権を持たない」参照です。これを使うことで、参照カウントを増やさずに他のオブジェクトを覗くことができます。循環参照とは、AがBを指し、BがAを指すような構造で、お互いが「相手が生きている限り自分も生き残る」状態になり、結果として両者がメモリから消えなくなる現象を指します。
実装/解決策
循環参照を解決する基本戦略は、「どちらか一方の参照をstd::weak_ptrに置き換える」ことです。親子関係で言えば、親から子へはshared_ptrで所有し、子から親への逆参照にはweak_ptrを使用するのが定石です。これにより、子から親を見た時に参照カウントを増やさないため、所有権のループを断ち切ることができます。
サンプルプログラム
以下のコードは、循環参照が発生するケースと、それをweak_ptrで修正する実用例です。
include
include
include
// 親クラス
struct Parent {
std::shared_ptr
~Parent() { std::cout << "Parentが破棄されました" << std::endl; }
};
// 子クラス
struct Child {
// 循環を防ぐためにweak_ptrを使用する
std::weak_ptr
~Child() { std::cout << "Childが破棄されました" << std::endl; }
};
int main() {
// スコープを作成してメモリ解放を確認
{
auto p = std::make_shared
auto c = std::make_shared
// 相互参照を設定
p->child = c;
c->parent = p;
std::cout << "スコープを抜けます..." << std::endl; } // ここでParentとChildの両方が正しくデストラクタを呼び出す return 0; }
応用・注意点
現場での開発において注意すべき点は、std::weak_ptrを使う際は「必ずロックが必要」ということです。weak_ptr自体は直接オブジェクトのメンバにアクセスできません。アクセスする際はlock()メソッドを呼び出し、一時的にstd::shared_ptrに昇格させてから使用します。
また、デバッグ中に循環参照が発生しているかどうかを確認するには、std::shared_ptrのuse_count()を出力するのが有効です。予期せぬタイミングでカウントが減らない場合、どこかで意図しない循環が生まれている可能性が高いです。設計段階で「どちらが所有者か(shared_ptr)」と「どちらが参照者か(weak_ptr)」を明確に決めておくことが、バグを未然に防ぐ最大のコツです。

コメント