topazの遊び場

いろいろやってみる

そろそろC++やるぞパート18 ポインター ~ スマートポインタ/weak_ptr ~

そろそろC++やります

そろそろC++をやります。そろそろC++をやりたいからです。
何回やるかはわかりません。基礎を理解するまではやろうと思います。

という感じでやっています。

※ 初心者がメモレベルで記録するので、技術記事としてはお力になれないかもしれません。

内容

  • 今回は、Microsoftドキュメントと他の記事を参考にして、スマートポインタの一つweak_ptrについて行います。

weak_ptrの宣言

weak_ptrは shared_ptrのリソースと同じクラステンプレートを持ちいて、shared_ptrから生成されます。

auto sp = make_shared<int>(145);
weak_ptr<int> wp = sp;

weak_ptrとは

shared_ptrが持つリソースへの弱参照を保持するクラスです。
忘れていたのですが、unique_ptrshared_ptrweak_ptrも全てクラスなんですよね。

そして、weak_ptrはshared_ptrへの所有権を持つのでなく、そのリソースを覗き見、監視します。そのため、shared_ptrの参照カウントは増えません。

auto sp = make_shared<int>(145);
cout << sp.use_count() << endl;
weak_ptr<int> wp = sp;
cout << sp.use_count() << endl;

Output

1
1

リソースの使用

リソースを使用する場合は、メンバー関数lockを呼び出すことによって作成された、shared_ptrオブジェクトを介して、使用することができます。

auto sp = make_shared<int>(145);
weak_ptr<int> wp = sp;
if (auto p = wp.lock())
{
    cout << *p << endl;
}

Output

145

lock関数で返されるshared_ptrは所有権を持ちます。

cout << sp.use_count() << endl;
if (auto p = wp.lock())
{
cout << sp.use_count() << endl;
}

Output

1
2

なぜ、「lock」という命名なのかは、分かりませんでした。

if (auto p = wp.lock())
{
    cout << sp.use_count() << endl;
    cout << *sp << endl;
    auto sp2 = p;
    if (auto p = wp.lock())
    {
        cout << sp.use_count() << endl;
        cout << *sp << endl;
    }
}

以下のようなことは問題なくできました。

  • lockの中で再度lock
  • lockの中で元のshared_ptrの使用
  • lockで得られたshared_ptrからshared_ptrの作成

get() みたいな名前でも良かったような気もしなくもないですが、lockでやって行きましょう。

循環参照 なぜweak_ptrが必要か

「weak_ptr使わないで、全てshared_ptrでやれば良い」という意見が出そうですが、循環参照に落ちいった時にweak_ptrが必要となります。

お互いをshare_ptr型のメンバ変数として持つPairClassを考えてみます。

class PairClass
{
public:
    PairClass(string _name) : name(_name)
    {
    }
    string name;
    shared_ptr<PairClass> pair;
};

int main()
{
    PairClass p1 = PairClass("pair1");
    PairClass p2 = PairClass("pair2");

    auto p1_sp = make_shared<PairClass>(p1);
    auto p2_sp = make_shared<PairClass>(p2);

    p2.pair = p1_sp;
    p1.pair = p2_sp;

    return 0;
}

こちらのp2.pair = p1_sp; p1.pair = p2_sp;の部分で、お互いに相手をshared_ptrとして保持しています。

本来であればスコープ{ }を抜けた時に、p1, p2が解放され同時に変数であるshared_ptrも解放されるはずです。しかし、p1が解放されるタイミングでp1のメンバ変数pairは使用されているため、解放されません。同様な現象がp2でも生じます。

よって、お互いに参照する循環参照が生じた時は適切にメモリが解放されないという現象が起きてしまいます。

これを対策するために、weak_ptrを使用します。

参考リスト

感想

今回はweak_ptrについて触れて行きましたが、shared_ptrの循環参照救済処置用のポインタだと分かり、使う場面も限定しそうです。

ポインタが一通り終わったので次回は一度、ポインタのまとめに入りたいと思います。