#きっかけ
スマートポインタってなんだ?
どういう機能があるのか?どう扱うべきなのか?注意点は何か知りたかったので調べました。
#スマートポインタ
確保したメモリを自動的に開放してくれるクラス。
通常のポインタでは例外などでメモリーリークが起きることが問題だった。
確保したメモリを自動的に開放してくれることで上記の問題が解決された。
※リソースを確保したらすぐにリソース管理オブジェクトであるスマートポインタに管理を任せるのがよい。
#auto_ptr
基本的には使わない。
機能
自身が所有権を持つメモリがあればデストラクタ時にdeleteを自動的に実行
int _tmain(int argc, _TCHAR* argv[])
{
auto_ptr<int> ptr(new int(0)); //or ptr.reset(new int(0));
//auto_ptr<int> ptrArray(new int[10]);
//ptrArray[0] = 1; //エラー 配列は使えない
cout << *ptr << endl;//0
//ptr.operator bool();//エラー使えない
return 0;//開放
}
問題点
①コピーコンストラクタで所有権が移動している場合に所有権を持っていないインスタンスでアクセスするとエラーとなる。メリットでもある。コピー元はNULLになり、他からアクセスできないようになっている。
②配列を保持できない
③deleterを指定できない
class AutoPtr
{
private:
auto_ptr<int>ptr;
public:
AutoPtr() :ptr(new int(10)) {};
int GetValue()
{
return *ptr;
}
};
int _tmain(int argc, _TCHAR* argv[])
{
AutoPtr b;
AutoPtr c(b);//所有権が移動
int val = b.GetValue();//エラー
cout << "AutptrVal:" << val << endl;
return 0;
}
#unique_ptr
機能
auto_ptr同様に所有権を持つメモリを自動的に開放する。
あるメモリに対する所有権を持つポインタがただ一つであることを保証
⇒コピーコントラスタが使えない。ムーブは可能。
⇒いつの間にか所有権が移動していて、許可されないアクセスをしてしまうなどがない。
配列も扱える。
※配列で確保しても開放はdeleteが呼ばれる為、自分でdelete[]を呼ぶようにdeleterを指定する必要がある。配列をdeleteで開放した場合は動作未定となる。1つは確実に開放されるが、残りは開放されない。
int _tmain(int argc, _TCHAR* argv[])
{
unique_ptr<int> ptr(new int(0));
unique_ptr<int> ptr2(ptr);//コピーできない
unique_ptr<int> ptr2(move(ptr));//明示的に移動させることはできる
return 0;
}
その他の機能
operator bool():所有権を保持しているか
.release():所有権破棄。自分でdeleateが必要。
.reset():明示的破棄。どこのメモリ領域も指定しない。
int _tmain(int argc, _TCHAR* argv[])
{
unique_ptr<int> ptr(new int(10));
cout << ptr.operator bool();//1 所有権を保持しているか
int* ptr2 = ptr.release();//所有権の破棄
delete ptr2;//自分で破棄必要
return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
unique_ptr<int> ptr(new int(10));
ptr.reset();//明示的開放
cout << ptr.operator bool();//0 resetでどこも指定されていない為
return 0;
}
#deleterの指定
delete以外でメモリを解放したい時にdelete時に使用するものを指定できる。
例えば、FILE型のリソースを確保した時に、FILE型の開放を使いたいとする。
テンプレートの第二引数で指定する。
struct OriginalDeleter {
void operator()(FILE* fp) {
fclose(fp);
}
};
int _tmain(int argc, _TCHAR* argv[])
{
//テンプレート第二引数で、deleterを指定する
std::unique_ptr<FILE, OriginalDeleter> ptr(MallocForFile());
//deleteではなく,fclose()がメモリ解放の際に呼ばれる。
return 0;
}
#shared_ptr
機能
auto_ptrやunique_ptr同様に所有権を持つメモリを自動的に開放する。
同一のメモリを複数のポインタで管理することができる。
所有権を持つポインタの数が0になると開放される。
配列も扱える。
コピーもムーブも可能。
int _tmain(int argc, _TCHAR* argv[])
{
shared_ptr<int> ptr(new int(0));
shared_ptr<int> ptr2(ptr);//コピー可能
cout << ptr2.use_count() << endl;//2
return 0;//2つとも開放
}
#newで生成したオブジェクトをスマートポインタに渡す時の注意点
下記コードはコンパイル時に
①priorityの呼び出し
②Objectの生成
③shared_ptrのコントラスタの呼び出し
を行っている。C++のコンパイラは引数の順番の評価を自由に決めていいことになっている為、②③の順番は変更できないが、①をどのタイミングで行ってもいいことになっている。
効率的なコード生成の為に②⇒①⇒③で呼ばれ、①で例外が発生した場合は②で生成したポインタがスマートポインタに格納されず、メモリリークの可能性がある。
このような状態を避けるためには、newで生成したオブジェクトをスマートポインタに代入する作業は独立して行うべきである。
int priotity(){}
void tmp(shared_ptr<int> pw, int priotiry) {}
int _tmain(int argc, _TCHAR* argv[])
{
tmp(shared_ptr<int>ptr(new Object), priority())
return 0;
}
#参考
- Effective C++
- C++11スマートポインタ入門