Entry

プログラミングメモ - std::auto_ptr の考え方とか

2009年03月11日

std::auto_ptr が使いづらいという話を時々聞いていて,代わりに,参照カウンタ付きの boost::shared_ptr が便利だよ,という話もよく聞きます。たしかに,少し遅いものの,参照カウンタ付きのポインタってのは便利です。

一方で,std::auto_ptr は,考え方(設計思想)がいまいち分かりづらいから,不便だとか言われちゃうのかな,とも。例えば,こちらの話のような。コードはリンク先を参照してください。

auto_ptrはそのままポインタ型(BIGSTRUCT*)としては渡せず、 get()関数を呼ぶ必要があります。イヤですね。透過的でありません。でも、これは意図的なのです。

(snip)

bar()内では、 引数として渡されたptrから新しいptr_barにメモリのアドレスが渡されます。しかしこれがくせ者で、 bar()から抜けるときにはこのptr_barが削除されてしまうため、 管理していたメモリも削除されます。 つまり、bar()から帰ってきた main()内ではもうptrの持っているアドレスにはメモリがありません。もしbar()の呼び出しの後に ptrを使おうと思っても不正なアドレスの参照になってしまうことでしょう。

auto_ptrはダメなのか? - munepi.com

std::auto_ptr は所有権移転型のスマートポインタと言われているので,これは設計思想上当たり前の挙動だったりします。bar() を抜けた後で,ptr_bar を削除してくれないとむしろ困る。

ご存知の人にとってみると常識なんでしょうけど,所有権移転型というのは,ポインタを管理する所有者が(ひとつだけ)あるということです。例えば,次のような場合。

#include <iostream>
#include <memory>

class Foo {
public:
    Foo() {
        std::cout << "Foo::Foo()" << std::endl;
    }
    virtual ~Foo() {
        std::cout << "Foo::~Foo()" << std::endl;
    }
private:
    Foo(const Foo& other);
    Foo& operator=(const Foo& rhs);
public:
    void hello() const {
        std::cout << "hello" << std::endl;
    }
};

int
main(int argc, char* argv[]) {
    std::auto_ptr<Foo> foo(new Foo());
    foo->hello();
    return 0;
}

このとき,Foo 型のオブジェクトへのポインタを所有しているのは,foo になる。

一方,foo を bar に代入するように変更すると,所有権は bar に移ります。

int
main(int argc, char* argv[]) {
    std::auto_ptr<Foo> foo(new Foo());
    foo->hello();
    std::auto_ptr<Foo> bar = foo;  // 所有権が移る
    bar->hello();
    return 0;
}

こうすると,ポインタの管理者は bar になるので,bar のデストラクタから Foo オブジェクトのデストラクタが起動されます。その証拠に(しては不十分だけれど)実行すると,Foo のデストラクタは1回だけ呼ばれます。auto_ptr の operator= は,普通の代入を表しているわけではなくて,所有権の移転を表している,と。

aian@TWEAKBOX ~/work
$ ./a
Foo::Foo()
hello
hello
Foo::~Foo()

んなもんで,くだんの「削除されてしまう auto_ptr」の話なんですけれど,auto_ptr を値で受け取るようにしたら,所有権が移転してしまうのは当たり前の話だったりします。

void
hoge(std::auto_ptr<Foo> p) {
    p->hello();
    // ここで消える
}

この関数はどういう意味かというと,「呼び元から std::auto_ptr<Foo> の所有権を受け取って,これを適宜処理する」という意味になります。つことで,hoge の p は,所有権を持っているにもかかわらず,自分が消える(スコープの終わりにきた)ときに,管理しているポインタを後始末しないことはできません。この関数で管理しているオブジェクトを消さないようにするには,他のスコープにある std::auto_ptr<Foo> 型の変数に所有権を渡す必要があります。

呼び元が所有権をとっておきたいなら,参照で受ければいい。

void
hoge(std::auto_ptr<Foo>& p) {
    p->hello();
    // ここでは消えない
}

あとは,中で作った後で外に所有権を渡す例とか。

// 値で返す
std::auto_ptr<Foo>
hero() {
    std::auto_ptr<Foo> ret(new Foo());
    return ret;
}

std::auto_ptr を使うとメモリ管理の責任を明確にしやすいので,あたしゃよく使います。boost::shared_ptr よりも厳密に設計/実装できるんじゃないかとも。

あとは配列の管理なんですけど,これは自前でコンテナを作るなり,std::vector なんかを集約したクラスを新しく作るなりした方が,結果的に早いし都合がいい場合が多いんでねいかと思ったりします。無理に std::auto_ptr 使わなくてもね……。

Trackback
Trackback URL:
Ads
About
Search This Site
Ads
Categories
Recent Entries
Log Archive
Syndicate This Site
Info.
クリエイティブ・コモンズ・ライセンス
Movable Type 3.36
Valid XHTML 1.1!
Valid CSS!
ブログタイムズ

© 2003-2012 AIAN