Gobble up pudding

プログラミングの記事がメインのブログです。

MENU

スマートポインタ(shared_ptr)を使ってみる C++11

スポンサードリンク

f:id:fa11enprince:20200701032520j:plain
スマポだよ!スマポ!ナマポまだ使ってんの?ぷぷぷ(๑˃̵ᴗ˂̵)و
大半の用途でスマポに置換えるべきだと最近思います。
布教もかねて、C++ってアレだよね?レガシーなあれだよね?
と思われている方もいると思いますが……誤解です。
確かにC++03までだと古臭い言語という感じがしますが、
C++11からそんなことなくなっています。
Javaのほうがむしろ古臭くなってしまっています。
簡単なサンプルコードで比較してみます。

C++03まで

#include <iostream>
#include <vector>
#include <string>

struct CTest
{
    int foo;
    int bar;
    CTest(int f, int b)
        : foo(f), bar(b)
    {
    }
};

int main()
{
    using namespace std;

    vector<CTest *> ct;
    ct.push_back(new CTest (10, 20));
    ct.push_back(new CTest (11, 21));
    ct.push_back(new CTest (12, 22));
    for (vector<CTest *>::iterator it = ct.begin();
         it != ct.end();
         ++it)
    {
        cout << (*it)->foo << "," << (*it)->bar << endl;
    }
    for (vector<CTest *>::iterator it = ct.begin();
         it != ct.end();
         ++it)
    {
        delete *it;
        *it = NULL;
    }
    
    return 0;
}

なんともvectorをpush_backで入れてnewしないといけないあたりや
iteratorがものすごくめんどくさい(;´・ω・)
しかも他の言語からきた人は(*it)->fooとか
なんだこのシンタックスと思うに違いない。

C++11から

同じコードを書く場合(生ポインタ)を使う場合、だいぶましに書けるようになりました。

#include <iostream>
#include <vector>
#include <string>

struct CTest
{
    int foo;
    int bar;
    // 今回の例では引数を取るコンストラクタはいらない
};

int main()
{
    using namespace std;

    // メンバ初期化リストを利用
    vector<CTest *> ct
    {
        new CTest {10, 20},
        new CTest {11, 21},
        new CTest {12, 22},
    };
    for (const auto c : ct)
    {
        cout << c->foo << "," << c->bar << endl;
    }
    for (auto c : ct)
    {
        delete c;
        c = nullptr;
    }
    
    return 0;
}

だいぶすっきりしましたね。メンバ初期化リスト大活躍。
さらに、型推論も便利。
んで、ここでconst auto cの部分なのですが、
この推論はポインタまでやってくれるらしく*1*は不要ですが、別に書いてもいいことになっているらしい。const auto *cでも意味は同じみたいな。
stackoverflow.com

スマートポインタを使った例

本題のスマポ
スマートポインタの中でも扱いやすいshared_ptrを使ってみます。

#include <iostream>
#include <vector>
#include <string>
#include <memory>   // shared_ptr

struct CTest
{
    int foo;
    int bar;
    CTest(int f, int b)
        : foo(f), bar(b)
    {
    }
};

int main()
{
    using namespace std;
    // 参照カウンタ方式のshared_ptr
    vector<shared_ptr<CTest>> ct {
        make_shared<CTest>(10, 20),
        make_shared<CTest>(11, 21),
        make_shared<CTest>(12, 22),
    };

    for (const auto c : ct)
    {
        // アクセスは*を介して行う(この場合は->)
        cout << c->foo << "," << c->bar << endl;
    }
    return 0;
}

make_sharedはなんか注意点があるらしいですが、下記にまとまってる感じでした。
cflat-inc.hatenablog.com

shared_ptrとunique_ptrの使い分け

理由がなければ基本的にunique_ptrを使いましょう。
vectorの内部でポインタを保持したいときでstd::moveを使いたくない場合はshared_ptrで良いかもしれません。まぁ、でもなるべくunique_ptrを使いましょうってことで。
blog.ikappio.com
postd.cc
qiita.com

*1:参照は明示しないとダメ。どういうルールかは良く知らない