mallocで確保したいポインタにstd::unique_ptrを適用する(カスタムDeleter関数)
C++にはmalloc/freeとnew/deleteという2つの動的メモリ確保/解放のパターンがあります。
malloc/freeは、元々 C 言語の関数で、そのまま C++ に入っています。以下のように使います。
int* numbers = static_cast<int*>(malloc(100 * sizeof(int)));
free(ptr);
new/deleteは、C++ で新しく追加された関数です。配列をアロケートする場合は、new/deleteかnew[]/delete[]になります。以下のように使います。
int number = new int;
delete ptr;
int* numbers = new int[100];
delete[] numbers;
mallocかnew/new[]で確保したメモリはfreeかdelete/delete[]で解放しないと、メモリーリークになってしまうため、自動的にデストラクタで解放してくれるstd::unique_ptrのようなスマートポインタを使うことがお勧めされています。
new/deleteとnew[]/delete[]の場合は、以下のようにstd::unique_ptrをそのまま使えます。
#include <memory>
std::unique_ptr<int> number(new int); // delete で解放してくれる
std::unique_ptr<int[]> numbers(new int[100]); // delete[] で解放してくれる
でもmalloc/freeの場合はそう簡単にいきません。なぜかというと、std::unique_ptrのデストラクタでメモリ解放してくれるデフォルトの Deleter 関数std::default_deleteはdeleteかdelete[]を使うようになっているからです。mallocで確保したメモリをdeleteかdelete[]で解放すると未定義動作になって、何が起こるか分かりません。
{
std::unique_ptr<int[]> number(static_cast<int*>(malloc(100 * sizeof(int))));
} // 解放時に delete[] が使われるため未定義動作となる
幸いstd::unique_ptrには、カスタムな Deleter 関数を指定できるコンストラクタも存在します。
以下のようにカスタムな Deleter を構造体のメンバー関数として定義してから、std::unique_ptrのコンストラクタにテンプレート引数として指定します。
struct CustomDeleter
{
void operator()(int* p) { free(p); }
};
std::unique_ptr<int[], CustomDeleter> numbers(static_cast<int*>(malloc(100 * sizeof(int))));
でも毎回こういう構造体は定義したくないと思います。その代わりに、以下のようにstd::unique_ptrのコンストラクタにカスタムな Deleter 関数をラムダ式関数として指定できます。
クラスの定義はこうなっていますね。
template <class T, class Deleter> class unique_ptr<T[], Deleter>; // unique_ptr の定義
unique_ptr(pointer p, Deleter& d) noexcept; // コンストラクタの定義
実際に書いてみると、こうなると思います。
#include <functional>
#include <memory>
std::unique_ptr<int[], std::function<void(int*)>> numbers
( static_cast<int*>(malloc(100 * sizeof(int)))
, [] (int* p) { free(p); }
);
このやり方はmallocに限定するというよりは、delete/delete[]以外で解放しないといけないメモリに対すしてstd::unique_ptrを使うテクニック、となると思います。是非参考にしてください。
Discussion