C++の基礎 - スマートポインタ(weak ptr)

提供:MochiuWiki - SUSE, Electronic Circuit, PCB
ナビゲーションに移動 検索に移動

概要

C++11では、unique_ptr<T>、shared_ptr<T>、weak_ptr<T>の3種のスマートポインタが追加された。
これらのスマートポインタは、メモリの動的確保の利用の際に生じる多くの危険性を低減する目的で使用されるが、
それぞれ独自の考え方と機能を持っている。

3種のスマートポインタを適切に使い分けることで、安全性と開発速度の向上が見込めるだけでなく、
プログラマの意図に合わせてポインタを記述し分けることができる、非常に強力なツールとなる。

ここでは、スマートポインタについて初めて学ぶ人を対象に、
C++11で追加された3種のスマートポインタの機能と使い方、および3種をどのように考えて使うかについて、初歩的な解説を行う。


weak_ptrとは

weak_ptr<T>は、shared_ptr<T>の循環参照によって生じる問題を防ぐために導入されたスマートポインタである。
先の2つのスマートポインタと違い、weak_ptr<T>はメモリへの所有権を持つことはない。
その代わりに、weak_ptr<T>はshared_ptr<T>の指すメモリを参照することができる。

循環参照の例で見てみる。

 #include<memory>
 
 class CHoge
 {
    public:
       // shared_ptrで所有権を得る代わりに、weak_ptrで参照する
       std::weak_ptr<CHoge> ptr;
 };
 
 int main()
 {
    std::shared_ptr<CHoge> pHoge1 = std::make_shared<CHoge>();
    std::shared_ptr<CHoge> pHoge2 = std::make_shared<CHoge>();
 
    // Hoge1のweak_ptrで、pHoge2を参照する
    pHoge1->ptr = pHoge2;
 
    // Hoge2のweak_ptrで、pHoge1を参照する
    pHpge2->ptr = pHoge1;
 } // 確保したメモリへの所有権はpHoge1、pHoge2しかそれぞれ持っていないので、循環参照が生じず、正しく解放される


このように、 weak_ptr<T>は所有権を持たずにメモリへの参照のみ保持することによって、確保したメモリへアクセスするスマートポインタである。
所有権を持たないため、自身が参照しているメモリが解放されてしまうことはあるが、
weak_ptr<T>は、すでにshared_ptr<T>によって参照先のメモリが解放されたかどうかを確認することができる。


weak_ptrの使い方

具体的な使い方を見てみる。
weak_ptr<T>は shared_ptr<T>が所有権を持つメモリしか管理できない。

 // コンストラクタや代入演算子でshared_ptrを受け取る
 std::shared_ptr<T> sptr = std::make_shared<int>(10);
 std::weak_ptr<T> wptr1(sptr); 
 std::weak_ptr<T> wptr2;
 wptr2 = sptr;
 
 // ポインタを直接受け取ることはできない
 // std::weak_ptr<T> wptr3(new int(10)); // コンパイルエラー


 // weak_ptr<T>は、コピー、ムーブともに使用することができる。
 std::shared_ptr<int> sptr = std::make_shared<int>(10);
 std::weak_ptr<int> wptr1(sptr);
 
 // コピーコンストラクタやコピー代入演算子も可能
 std::weak_ptr<int> wptr2(wptr1);
 std::weak_ptr<int> wptr3;
 wptr3 = wptr1;

 // ムーブコンストラクタやムーブ代入演算子も可能
 // この時、wptr2とwptr3は参照を失う
 std::weak_ptr<int> wptr4(std::move(wptr2));
 std::weak_ptr<int> wptr5;
 wptr5 = std::move(wptr3);


 // 参照の開放は、デストラクタやreset関数を使う
 std::shared_ptr<int> sptr = std::make_shared<int>(10);
 {
    std::weak_ptr<int> wptr1(sptr);
 } // デストラクタで参照を開放
 
 std::weak_ptr<int> wptr2(sptr);
 
 // reset関数で明示的に解放
 wptr2.reset();


 // 所有権はそもそも保持していないので、operator bool()は使用できない
 std::shared_ptr<int> sptr = std::make_shared<int>(10);
 std::weak_ptr<int> wptr(sptr);
 
 // 参照先が解放されているか確認するには、expired()を使う(戻り値がtrueの場合は解放されている)
 if(wptr.expired())
 {
    std::cout<<"expired"<<std::endl;
 }
 
 // use_count関数を使って、参照先のメモリに所有権を持つshared_ptr<T>の数を調べることができる
 std::cout << "use_count = " << wptr.use_count() << std::endl;


 // 参照するメモリにアクセスするのに、operator*()やoperator->()は使用できない
 // メモリにアクセスするためには、まず、lock()によって参照先を保持するshared_ptr<T>を取得し、そこからアクセスする
 // これは、使用中に解放されてしまうのを避けるためである。
 std::shared_ptr<int> sptr = std::make_shared<int>(10);
 std::weak_ptr<int> wptr(sptr);
 {
    // lock関数によって、参照先を保持するshared_ptrを取得する
    std::shared_ptr<int> ptr = wptr.lock();
 }