LoginSignup
82

More than 5 years have passed since last update.

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

Posted at

はじめに

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 使いましょう!

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
82