はじめに
C++のstd::shared_ptr
は、複数のポインタで一つのオブジェクトの所有権を共有できる、非常に便利なスマートポインタです。しかし、時には「オブジェクトの所有権は持ちたくないが、そのオブジェクトがまだ存在しているかどうかだけを知り、もし存在するなら安全にアクセスしたい」という場面があります。
この「所有権なき監視」を実現するのが std::weak_ptr
です。weak_ptr
は、shared_ptr
によって管理されているオブジェクトを指しますが、所有者(参照カウント)の数には含まれません。
この記事では、weak_ptr
の基本的な使い方と、それがどのようにして「ダングリングポインタ(無効なメモリを指すポインタ)」の問題を解決するのかを解説します。
【前提】C++11とは?
C++11は、2011年に正式化されたC++言語のメジャーアップデート版です。shared_ptr
やweak_ptr
といったスマートポインタはこのC++11で導入されたため、利用するにはC++11以降に対応したコンパイラが必要です。
weak_ptr
を使ったサンプルコード
このコードは、shared_ptr
がスコープの内外で生存・消滅する様子を、weak_ptr
を使って監視します。
完成コード
#include <iostream>
#include <memory> // shared_ptr, weak_ptr
#include <string>
using namespace std;
class Widget {
public:
string name;
Widget(string n) : name(n) { cout << "Widget「" << name << "」が生成されました。" << endl; }
~Widget() { cout << "Widget「" << name << "」が破棄されました。" << endl; }
};
int main() {
// 1. weak_ptrを準備
weak_ptr<Widget> weak_observer;
cout << "--- ブロックに入ります ---" << endl;
{
// 2. shared_ptrでオブジェクトを生成
shared_ptr<Widget> strong_owner = make_shared<Widget>("データA");
// 3. weak_ptrにshared_ptrを代入して、監視を開始
weak_observer = strong_owner;
// 4. lock()で、監視対象が生きているか確認し、安全にアクセス
if (shared_ptr<Widget> temp_strong_ptr = weak_observer.lock()) {
cout << "ブロック内: オブジェクトは生きています。名前: " << temp_strong_ptr->name << endl;
}
} // このブロックを抜けるとき、strong_ownerが破棄され、Widgetオブジェクトも消滅する
cout << "--- ブロックを出ました ---" << endl;
// 5. ブロックを抜けた後、再度監視対象の状態を確認
if (shared_ptr<Widget> temp_strong_ptr = weak_observer.lock()) {
// このブロックは実行されない
cout << "ブロック外: オブジェクトは生きています。" << endl;
} else {
cout << "ブロック外: オブジェクトは既に破棄されています。" << endl;
}
return 0;
}
コードの解説
1. weak_ptr<Widget> weak_observer;
Widget
オブジェクトを監視するためのweak_ptr
を宣言します。この時点では何も指していません。
2. shared_ptr<Widget> strong_owner = ...
shared_ptr
を使って、Widget
オブジェクトを動的に生成します。strong_owner
が、このオブジェクトの唯一の所有者です。
3. weak_observer = strong_owner;
weak_ptr
にshared_ptr
を代入します。これにより、weak_observer
はstrong_owner
が管理するオブジェクトを「監視」するようになりますが、参照カウントは増加しません。
4. if (shared_ptr<Widget> p = weak_observer.lock())
これがweak_ptr
の最も重要な使い方です。
.lock()
:weak_ptr
が指しているオブジェクトがまだ存在していれば、そのオブジェクトを所有する新しいshared_ptr
を返します。オブジェクトが既に破棄されていれば、空のshared_ptr
(nullptr
相当)を返します。if (shared_ptr<Widget> p = ...)
:lock()
が返すshared_ptr
をif
文の条件式内で宣言・初期化しています。- オブジェクトが存在する場合:
p
は有効なshared_ptr
となり、if
の条件はtrue
と評価されます。このif
ブロック内では、p
を通じてオブジェクトに安全にアクセスできます。 - オブジェクトが破棄されている場合:
p
は空のshared_ptr
となり、if
の条件はfalse
と評価されます。else
ブロックが実行されます。
- オブジェクトが存在する場合:
5. スコープとオブジェクトの消滅
{}
ブロックを抜けると、strong_owner
はそのスコープの終わりで破棄されます。他に所有者がいないため、strong_owner
が管理していたWidget
オブジェクトもこのタイミングで破棄され、デストラクタが呼ばれます。
その後、ブロックの外で再度weak_observer.lock()
を呼ぶと、監視対象はもう存在しないため、空のshared_ptr
が返されます。
まとめ
今回は、C++11のstd::weak_ptr
を使って、オブジェクトの生存を安全に監視する方法を解説しました。
- **
std::weak_ptr
**は、shared_ptr
が管理するオブジェクトを、所有権を持たずに参照する。 .lock()
メソッドを呼び出すことで、監視対象のオブジェクトがまだ存在するかを確認し、存在すれば安全にアクセスするためのshared_ptr
を取得できる。- これにより、ダングリングポインタ(無効なメモリを指すポインタ)の問題を回避できる。
weak_ptr
は、オブジェクト同士が互いをshared_ptr
で指し合ってメモリが解放されなくなる「循環参照」を解決するためにも不可欠なツールです。