はじめに
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で指し合ってメモリが解放されなくなる「循環参照」を解決するためにも不可欠なツールです。
