💉

mallocで確保したいポインタにstd::unique_ptrを適用する(カスタムDeleter関数)

2024/12/14に公開

C++にはmalloc/freenew/deleteという2つの動的メモリ確保/解放のパターンがあります。

malloc/freeは、元々 C 言語の関数で、そのまま C++ に入っています。以下のように使います。

int* numbers = static_cast<int*>(malloc(100 * sizeof(int)));
free(ptr);

new/deleteは、C++ で新しく追加された関数です。配列をアロケートする場合は、new/deletenew[]/delete[]になります。以下のように使います。

int number = new int;
delete ptr;

int* numbers = new int[100];
delete[] numbers;

mallocnew/new[]で確保したメモリはfreedelete/delete[]で解放しないと、メモリーリークになってしまうため、自動的にデストラクタで解放してくれるstd::unique_ptrのようなスマートポインタを使うことがお勧めされています。

new/deletenew[]/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_deletedeletedelete[]を使うようになっているからです。mallocで確保したメモリをdeletedelete[]で解放すると未定義動作になって、何が起こるか分かりません。

{  
   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 関数をラムダ式関数として指定できます。

クラスの定義はこうなっていますね。
https://en.cppreference.com/w/cpp/memory/unique_ptr/unique_ptr

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を使うテクニック、となると思います。是非参考にしてください。


|cpp記事一覧へのリンク|

Discussion