やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

unique_ptrを使ってみた

スマートポインタのうちunique_ptrを使ってみた。

コード

ためしてみた。

宣言

宣言方法と解放の確認。 unique_ptrはクラスだから代入式でなく(...)の形になるのだと思われる。

配列

配列で使ってみた。 ちゃんと数の文だけ確保と解放がされている。

代入

unique_ptrは所有権(解放の責任)を一箇所だけにするもの。 ふつうに代入しようとしてもコンパイルエラーになる。 std::move()を使って代入できる。

reset()

新しい値でリセットする。 前の値はdeleteされる。

release()

unique_ptrは所有権を解放する。 生ポインタを返却するので、deleteしてメモリ解放する必要がある。

swap()

unique_ptr同士で参照するポインタを入れ替える。

deleter

deleterの指定

malloc/free

参考

deleterは削除処理のこと。

new/delete以外にも、malloc/free, open/close, などメモリの確保と解放の関数をセットできるっぽい。

実践的なことはこちらを参考にさせていただきました。

http://krustf.hateblo.jp/entry/20100827/1282915158
https://dokumaru.wordpress.com/2012/03/17/delete_smartly_using_cpp11_unique_ptr_custom_deleter/

テンプレートの特殊化は、おなじ型に対するdeleterが1つだけで固定になってしまう。 しかもstd名前空間に定義することになる。 std空間はSTLが用いる名前空間であり、コンパイラによって実装が異なるから触らないほうがいいとかなんとかどこかで見た。

関数ポインタで指定する方法はC++14から追加されたdelctypeが必要らしい。 VC++2010では使えないと思う。 使えるけど、バグが多いらしい。

http://www.ruche-home.net/program/cpp/msvc
https://cpplover.blogspot.jp/2010/04/visual-studio-2010c0x.html
https://cpplover.blogspot.jp/2010/04/visual-studio-2010.html
http://dev.activebasic.com/egtra/2009/06/04/202/
http://nyaruru.hatenablog.com/entry/20091114/p1
http://d.hatena.ne.jp/nagoya313/20100305/1267758837

なにやらMicrosoftコンパイラはいろいろ不備があるらしい。 ほかのコンパイラも使ってみたほうがよさそうか。

結局VC++2010では()演算子オーバーロードする方法しか使えない。

()演算子オーバーロード1

class A {
public:
    A() {cout << "A::A()" << endl;}
    ~A() {cout << "A::~A()" << endl;}
    int value;
};
class A_deleter {
public:
    A_deleter() {cout << "A_deleter::A_deleter()" << endl;}
    ~A_deleter() {cout << "A_deleter::~A_deleter()" << endl;}
    void operator() (A* target) { delete target; }
};
A* CreateA() {
    A* a = new A();
    std::random_device rnd;
    a->value = rnd() % 100;
    cout << "CreateA()" << endl;
    return a;
}
int main() {
    unique_ptr<A, A_deleter> up(CreateA());
}

()演算子オーバーロード2

実際はnew/deleteだけでいいならdeleterを指定する価値はない。 unique_ptr<A> up(new A);で十分。 new,deleteのときに何らかの処理をさせたいときにdeleterを指定する価値がある。 たとえば以下のようにするのかもしれない。

class A {
public:
    A() {cout << "A::A()" << endl;}
    ~A() {cout << "A::~A()" << endl;}
    void Initialize() {...}
    void Finalize() {...}
};
class A_deleter {
public:
    A_deleter() {cout << "A_deleter::A_deleter()" << endl;}
    ~A_deleter() {cout << "A_deleter::~A_deleter()" << endl;}
    void operator() (A* target) {
        target->Finalize();
        delete target;
    }
};
A* CreateA() {
    A* a = new A();
    a->Initialize();
    return a;
}
int main() {
    unique_ptr<A, A_deleter> up(CreateA());
}

関数ポインタで指定する

関数ポインタで指定すると以下のようになる。

A* CreateA() {
    A* a = new A();
    std::random_device rnd;
    a->value = rnd() % 100;
    cout << "CreateA()" << endl;
    return a;
}
void DestroyA(A* a) {
    delete a;
    a = NULL;
    cout << "DestroyA()" << endl;
}
int main() {
    unique_ptr<A, decltype( &DestroyA )> up2( CreateA(), DestroyA );
}

ポインタ変数の宣言は見づらさに拍車がかかった気がする。

Factoryパターン

デザインパターンにFactoryがあったと思うが、あれはどうなのか。 以下のようになるのか。

  • unique_ptr<A, A_deleter> up(Factory::CreateA());
  • unique_ptr<A, decltype( &Factory::DestroyA ) > up2( Factory::CreateA(), Factory::DestroyA );

どんどん見づらくなっていく。

妄想

new/deleteがセットなら、1つのクラスで実装できるようにしてくれるとわかりやすいのに。

class NewerAndDeleter<T>
{
    T* Newer() { return new T(); }
    void Deleter(T* target) { delete target; }
};

class NewerAndDeleter<SomeClass>
{
    SomeClass* Newer() { return new SomeClass(); }
    void Deleter(SomeClass* target) { delete target; }
};

これだとnew/delete以外の処理ができないか。

もしくは、以下のようにするとか。

class NewerAndDeleter<T>
{
    virtual T* Newer(...) = 0;
    virtual void Deleter(T* target) = 0;
};
class SomeClassNewerAndDeleter : NewerAndDeleter<SomeClass>
{
    virtual SomeClass* Newer(...) { return new SomeClass(); }
    virtual void Deleter(SomeClass* target) { delete target; }
};

あれ、テンプレートと純粋仮想関数は組み合わせられないんだっけ? 可変引数もこんな風に宣言できるっけ? いろいろダメっぽい。

とにかく、unique_ptr<SomeClassNewerAndDeleter> up;みたいにしただけで生成と破棄が指定できるようになってくれたらいいのに。 変数宣言のところで関数とかその引数までごちゃごちゃ書かれるとうっとおしい。 でも、引数を渡したいときもあるのか。

スマートポインタを使うべきか?

まだ何ともいえない。

ふつうに考えれば使うべき。解放し忘れを防げるからメモリリークが起こるリスクを下げることができる。

ただ、カスタムdeleterが複雑。これを使うとなるとコードが生ポインタのときとまるで別物になってしまう。可読性にも疑問がある。そしてC++11以上でないと使えない。

カスタムdeleterを使う場面は微妙だが、単にnew/deleteするところに関しては使ってもいいかもしれない。

所感

さらにshared_ptr, weak_ptr, もある。 各スマートポインタの使い方だけでなく、生ポインタと3つのスマートポインタを使い分ける必要もある。 つまり、すべてのスマートポインタを理解した上でなければポインタを使えない。

ポインタの難易度がうなぎのぼり。 ますます触れたくなくなるポインタ。 もう生ポインタで妥協したくなるほど。 一体どっちのほうがよいのか、その判断すらむずかしい。

しかも、コンパイラによって実装していないとか。 コンパイラ自体、他のも知りたくなってきた。

一体、いつになったら実装までこぎつけられることやら…。