これからC++11を学ぶ君に。生ポとおさらば unique_ptr編

More than 1 year has passed since last update.

はじめに

C++はメモリ管理が難しい、ガーベージコレクタがないからリークする
だからC++を使いたくない
という話をよく聞くので

基本的に 生ポインター(生ポ)を使わず、賢いポインタ(smart pointer)を使いましょう

私はC++の扱いに慣れている! という人でも、例外時に解放忘れたりと
ありがちなので、慣れた人でも smart pointerを使う事がよろしいかと思います

exeption.cpp
void hoge(){
  char *p = new char[100];

  // ここで例外発生したら pはリークしますよ!

  delete p;
}

よくある、みつけにくいメモリリークですよね。
ちゃんと try/catch でかこみ finalizeで解放し、例外をthrowし直さないとダメですね
面倒だし、無駄な例外処理が必要になり速度的にも あれですよね。

まずは auto_ptr

STLにはauto_ptrというものがあり、スマートポインタとして使われてましたが
auto_ptrは deprecated (非推奨、削除予定)なので、 unique_ptr を使いましょう
unique_ptrは、昔 boost::scoped_ptrって呼ばれてたやつですね。

ってことで、auto_ptr がダメな理由

auto_ptr.cpp

#include <iostream>
#include <string>
#include <memory>



using namespace std;


class test{
public:
    string s;

    test( const char* c ){ 
        s = c; 
        cout << "new:" << val() << endl; 
    }

    virtual ~test(){ 
        cout << "delete:" << val() << endl; 
    }

    const char* val(){
        return( s.c_str() ); 
    }
};

template<class T>
void print(const T& c) {
  c.get() ? cout<<c->val() : cout << "(null)";
  cout << endl;
}

void exec()
{
    auto_ptr<test> p1(new test( "p1" ));
    auto_ptr<test> p2(new test( "p2" ));
    p1 = p2;

    print<auto_ptr<test> >(p1);
    print<auto_ptr<test> >(p2);

}

int main()
{
    cout << "start" << endl;
    exec();

    getch();


    return 0;
}

出力結果

start
new:p1
new:p2
delete:p1
p2
(null)
delete:p2

ちゃんと解放はされますが
p1 = p2;
の部分で p1が保持していた値は deleteされ p2のポインタに上書きされ、p2のポインタは null_ptr になります

つまりこれは 所有権の移動、ムーブの動作ですが = なのでコピーと間違えやすい。

unique_ptr:コピー

上記のソースを unique_ptrにかえると
p1 = p2;
の部分でコンパイルエラーが発生します!!

例えばVisualStudioでは

unique_ptr
    _Myt& operator=(_Myt&& _Right) _NOEXCEPT
        {   // assign by moving _Right
        if (this != &_Right)
            {   // different, do the move
            reset(_Right.release());
            this->get_deleter() = _STD forward<_Dx>(_Right.get_deleter());
            }
        return (*this);
        }


    unique_ptr(const _Myt&);    // not defined
    _Myt& operator=(const _Myt&);   // not defined

右辺参照は実装され
コピーコンストラクタ、代入演算子が禁止されています

auto_ptrと同じく ムーブをしたければ std::move を明示的に使いましょう!

unique_ptr: 配列

auto_ptrでは ポインタ配列をちゃんと解放出来なかった (deleteが呼ばれる・・)けど
unique_ptrでは
unique_ptr p(new test[10]);
等と書くと、デリート時にちゃんと delete[]が呼ばれます

配列
template<class _Ty,
    class _Dx>
    class unique_ptr<_Ty[], _Dx>
        : private _Unique_ptr_base<_Ty, _Dx,
            is_empty<_Dx>::value
                || is_same<default_delete<_Ty[]>, _Dx>::value>
    {   // non-copyable pointer to an array object

unique_ptr: カスタムデリータ

delete時の挙動を変更する事ができます
これの何が便利かというと、delete以外で終了処理をする場合
例えば FILE は fclose、mallocはfree で解放しますが
コンストラクタの第二引数に deleteオブジェクトを登録する事で
スマートに fclose、free が出来ます

custom_deleta
    unique_ptr<FILE,decltype(&fclose)> p1( fopen("p1","w"), fclose );
    unique_ptr<void,decltype(&free)> p2( malloc(100), free  );

    unique_ptr<FILE,decltype(&fclose)> p3( fopen("p1","w"), ( [](FILE *fp){ cout << "fclose" << endl; return(fclose(fp));} ) );
    unique_ptr<void,decltype(&free)> p4( malloc(100), [](void*p){ cout << "free" << endl; free(p);} );

上2つが実際に使った例ですが、コンストラクト時に fopen、mallocをし、デストラクト時に fclose、free が出来ます
dectypeが凄く便利ですね!

下2つはそれを ラムダにし、複数の命令を書けるようにしました。

もちろん、スレッドハンドル、リソース 等なんでもつかえるので便利です

是非この機会に、リソースID等のプリミティブなものも 生で使わず スマートに使う事を心がけましょう

(リソースを生で使うのは アンチデザパタです!!)

まとめ

生ポに頼るのはもうやめよう。
auto_ptrは deprecated なので使わない
unique_ptr を使おう!!
配列も正しく処理してくれるよ!!
カスタムデリータで、リソースもスマートに管理しよう!!

ただし、名前の通り unique です。所有権はただ1人。
ポインタを共有する事は出来ないので
複数から参照される場合は

shared_ptr、weak_ptr 使いましょう!