Posted at

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

More than 5 years have 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 使いましょう!