What is it, naokirin?

C++0xの機能(shared_ptr/スマートポインタ)

さて、今回は"General-purpose smart pointers"(汎用高性能なスマートポインタ)の中でも所有権を共有できるshared_ptrについて。

現在のC++では、auto_ptrという「所有権を一つしか持てない」スマートポインタが存在します。(色々と文句を言われ続けたスマートポインタです。スマートポインタのイメージを損ねた元凶とか言われてたりします)

ここで、auto_ptrの使用法とその動作を確認してみます。(ちなみにヘッダをインクルードする必要があります)

#include <iostream>
#include <memory>

class SampleClass
{
public:
    SampleClass(const int num = 0) : number(num)
    {
        std::cout << "Number(" << number;
        std::cout << "):constructor" << std::endl; 
    }
    
    ~SampleClass() 
    { 
        std::cout << "Number(" << number;
        std::cout << "):destructor" << std::endl; 
    }
    
    int getNumber()
    {
        return Number;
    }

private:
    int number;
};

int main (int argc, char* argv[])
{
    std::auto_ptr<item> one(new SampleClass(1));
    std::auto_ptr<item> two(new SampleClass(2));
        
    /* ここでoneの指すインスタンスがdeleteされ、*/
    /* twoの指すインスタンスの所有権がoneに移る */
    one = two;

    std::cout << "Pointer one:";
    std::cout << (one.get() != 0 ? one->getNumber() : "Pointer is null.");
    std::cout << std::endl;

    std::cout << "Pointer two:" 
    std::cout << (two.get() != 0 ? two->getNumber() : "Pointer is null.");
    std::cout << std::endl;

    return 0;
}


実行結果

Number(1):constructor
Number(2):constructor
Number(1):destructor
Pointer one:2
Pointer two:Pointer is null.
Number(2):destructor



見てみると、ちゃんと参照がなくなったときにdeleteが自動的に行われています。このようにプログラマが逐一インスタンスの管理(つまりちゃんとdeleteを実行してメモリリークを防ぐ)をする必要がないというのがスマートポインタの利点です。
しかし、コピー時にコピー元のポインタが消えていることが分かります。これが「所有権を一つしか持てない」という意味です。同じインスタンスを持つことができるのは一つのauto_ptrであり、複数のauto_ptrが同じインスタンスを持つことはできません。

ここで、shared_ptrの出番です。
shared_ptrはauto_ptrと違い、「所有権を複数持てる」という特徴があります。(ちなみにヘッダをインクルードする必要があります)

/* 省略(上のauto_ptrのサンプル参照) */

int main(int argc, char* argv[])
{
    /* ここで参照数が1になる */
    std::shared_ptr<SampleClass> one(new SampleClass(1));
    std::cout << "one:" << one->getNumber() << std::endl;
    {
        /* ここで参照数が2になる */
        std::shared_ptr<SampleClass> two = one;
        std::cout << "two:" << two->getNumber() << std::endl;
        /* ↓ここで参照数が1になる */
    }
    std::cout << "one: " << one->getNumber() << std::endl;
    /* ↓ここで参照数が0となるのでインスタンスがdeleteされる */
}


実行結果

Number(1):constructor
one:1
two:1
one:1
Number(1):destructor



auto_ptrの動作と比べてみるとコピー元のポインタが消えていません。
インスタンスをdeleteするタイミングについては、そのインスタンスへの参照の数が0になった時です。

生ポインタの利点を生かしつつ、不正なアドレス参照やインスタンスの手動管理の手間を省くことができるのがスマートポインタです。ただ、用途によって使い分ける必要がありますのでそのあたりの挙動については注意が必要かもしれません。


追記:@銀天すばるさんからのご指摘があったので、注意書きとして追記しておきます。
auto_ptrの問題点は単一所有になる点ではなく、勝手にmoveされてしまう点、だそうです。

これを防ぐためには、Const auto_ptrというイディオムをつかってリソースの所有権の移動を防止することが重要となります。

上の例はmoveすることを示したかったのであえて行っていませんが、普通にauto_ptrを使う場合には注意が必要です。


あと、最初に書いていたauto_ptrですが(久しぶりすぎて忘れてましたが)、

std::auto_ptr p = new SampleClass();



のようにすると、(すべてのコンパイラではないかもしれませんがVS2005あたりだったと思いますが)コンパイラによっては"Access Viilation"で実行時エラーを発生する可能性があるそうです。
コンパイルに通らないのが標準的な動きだそうです。このあたりも私の知識は怪しいですね。(基本的に上のコードがダメだったことは思い出したんですが、標準規格でどのようになっているかまでは確認していませんでした。@銀天すばるさん訂正ありがとうございます)


ちなみに、auto_ptrのmoveの問題を解消したunique_ptrというものがC++0xに追加される予定です。


旧コメント:
No title


std::auto_ptr の生ポインタを取るコンストラクタは explicit なので、

std::auto_ptr p = new SampleClass();

っていうコードは、そもそもコンパイル通らないはずです。

2011-01-21 19:46 | SubaruG

Re: No title


> std::auto_ptr の生ポインタを取るコンストラクタは explicit なので、
>
> std::auto_ptr p = new SampleClass();
>
> っていうコードは、そもそもコンパイル通らないはずです。

実行時エラーになるというよりは上記のようなコンパイルが通るコンパイラがおかしいということですね。
訂正ありがとうございます。

ブログの文章は修正しておきました。

2011-01-22 05:44 | Naoki Rin