事前アナウンス
- ここではあくまでnew/delete、スマートポインタ(例:
std::unique_ptr
)について記述します。
std::vector
等その他の便利機能については触れません。 - スマートポインタの詳細についてここでは触れません(たとえば所有権とか、
std::auto_ptr
がダメな理由とか)。 - 確保と解放に言及するため、ポインタへのアクセスについては記載しません(別記事で)。
目次
- はじめに
-
new
/delete
を直接使用する際の問題点 - スマートポインタ(例:
std::unique_ptr
) の紹介 - 結論
はじめに
C++を学んでいくと、メモリを動的に確保したくなる場合があります。
これについては、適切に使用していれば「おかしい」と言われる筋合いはありません。
bool func()
{
// ClassAというクラスが定義されているものとする
ClassA* cls = new ClassA(); // ★動的確保
// 配列を動的に確保したい
int* vec = new int[100]; // ★動的確保
// 何か処理
// ・・・
// 終わり
delete cls; // ★動的確保したメモリの解放
delete[] vec; // ★動的確保したメモリの解放
return true;
}
new/delete を直接使用する際の問題点
動的に確保したものは、解放してあげる必要があります。
前章のコードでは、関数が終了する直前に、 delete
/delete[]
を呼び出して解放しています。
しかし、これは処理が単純な場合は良いのですが、分岐や関数の終了位置が複雑になってくると破綻してきます。
例えば下記(※本来下記のような処理になるならそもそものロジックを見直すべきです)。
bool func()
{
// ★動的確保
ClassA* cls = new ClassA();
if(/* 何か条件A */) {
// ★解放
delete cls;
return false;
}
// 変数の用意
int* vec = nullptr;
if(/* 何か条件B */) {
// ★動的確保
vec = new int[100];
// 何か処理その1
}
else {
if(/* 何か条件C */) {
// ★動的確保
vec = new int[200];
// 何か処理その2
}
else {
// ★解放
delete cls;
return false;
}
}
// 何か処理その3
// ★解放
delete cls;
delete[] vec;
return true;
}
何が何だか。。。
このレベルのコードであれば目で追えますが、「何か処理その1、2、3」とコメントした箇所にこれ以上の処理や分岐が入ってきた場合、解放忘れや二重解放の恐れが増し、可読性や保守性も悪化します。
つまり、
その時、適切に処理されていようと、new
/delete
を直接使用したコードはバグを生みやすい
と考えてください。
スマートポインタ(std::unique_ptr) の紹介
C++11からは私たちに代わって、メモリの解放を行ってくれる素晴らしい機能が追加されました。
その名は std::unique_ptr
です。
(他にも std::share_ptr
や C++03からあったもののC++11で非推奨/C++14で削除となった std::auto_ptr
がありますが、ここでは割愛します)
std::unique_ptr とは
スマートポインタ
スマートポインタは、ポインタを管理してくれるものクラスです。
確保したメモリを渡しておくことで、メモリの解放を自動的に行ってくれるものです。
なお、解放の必要がなければ、解放しないという判断で動いてくれます。
メモリの解放忘れや二重解放などを我々人間が気にする必要がなくなります。
(若干気にしなければならないこともありますがここでは割愛)
なぜ勝手に解放してくれるのか
スマートポインタに限らず、生成されたオブジェクトはスコープを抜けたときなど、オブジェクトが破棄されるタイミングでデストラクタが呼ばれます。
スマートポインタは、自身のデストラクタ内で、渡されていたメモリを解放するため、結果的に自動で解放が行われるのです。
unique_ptr<T>
std::unique_ptr
を使用する際には、テンプレート引数で保持するポインタ型を指定してオブジェクトを生成します。
配列の動的確保も行えますし、デリーターを指定することも可能です。詳細は別記事で。
#include <memory>
int main()
{
std::unique_ptr<ClassA*> ptr(new ClassA());
ptr->funcA();
}
// 上記スコープを抜けた後
// 変数ptrのデストラクタが呼ばれ、
// そのデストラクタ内で動的に確保されたメモリが解放されます。
// そのため、意図的に解放する必要はありません。
コンストラクタでメモリを渡すこともできますし、後からreset()
を用いて渡したりすることもできます。
#include <memory>
int main()
{
std::unique_ptr<ClassA*> ptr(new ClassA());
ptr.reset(new ClassA());
}
// reset()内では、それまで確保していたメモリを解放したうえで、
// 次に、引数で指定されたメモリを管理してくれます。
// 二度目に確保したメモリはmain()を抜けた時点で解放してくれます。
new/deleteをやめてstd::unique_ptrを使用した場合
先ほどのコードをstd::unique_ptr
を使用した場合、
#include <memory>
bool func()
{
std::unique_ptr<ClassA*> pClsA(new ClassA());
if(/* 何か条件A */) {
return false;
}
// 変数の用意
std::unique_ptr<int[]> pVec(nullptr);
if(/* 何か条件B */) {
pVec.reset(new int[100]);
// 何か処理その1
}
else {
if(/* 何か条件C */) {
pVec.reset(new int[200]);
// 何か処理その2
}
else {
return false;
}
}
// 何か処理その3
return true;
}
のようになり、我々人間が明示的にdelete
する必要はなくなります。
結論
「確保/解放を自分で管理する」という危ない橋を渡るくらいならスマートポインタに頼ろう
※ここで省いた詳細、その他機能についてはいつか別記事で。