#はじめに
C++はメモリ管理が難しい、ガーベージコレクタがないからリークする
だからC++を使いたくない
という話をよく聞くので
基本的に 生ポインター(生ポ)を使わず、賢いポインタ(smart pointer)を使いましょう
私はC++の扱いに慣れている! という人でも、例外時に解放忘れたりと
ありがちなので、慣れた人でも smart pointerを使う事がよろしいかと思います
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 がダメな理由
#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では
_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 が出来ます
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 使いましょう!