1. 導入
C++のスマートポインタである std::shared_ptr は、リソース管理において極めて強力なツールです。特に「カスタム削除子(Custom Deleter)」を活用することで、メモリ解放だけでなく、ファイルハンドルのクローズやライブラリ特有の解放関数を安全に呼び出すことができます。しかし、削除子が異なる場合、通常であれば型が異なると考えがちです。本記事では、std::shared_ptr がどのように「型消去」を用いてこの問題を解決しているのか、実務で役立つ実装方法と共に解説します。
2. 基礎知識
std::shared_ptr は、保持するポインタの型(T)だけでなく、削除子の情報を内部的に保持しています。
通常、C++のテンプレートは型が異なれば別物として扱われますが、std::shared_ptr は「型消去(Type Erasure)」というテクニックを用いています。これにより、異なる関数オブジェクトやラムダ式を削除子として渡しても、それらを内部で共通のインターフェースにラップし、同じ型として同一のコンテナ(例えば std::vector
3. 実装/解決策
カスタム削除子を指定するには、std::shared_ptr のコンストラクタの第2引数に、削除処理を行う関数や関数オブジェクトを渡します。
実務では、動的な解放ロジックが必要な場合や、C言語のライブラリ(malloc/freeのペアなど)と連携する場合に非常に有効です。
4. サンプルプログラム
以下に、異なる削除子を持つ shared_ptr を同じコンテナで管理する例を示します。
include
include
include
// 削除子1: 標準的なデバッグ出力を行う
void deleter1(int p) {
std::cout << "デリータ1が実行されました" << std::endl;
delete p;
}
// 削除子2: ラムダ式で表現
auto deleter2 = [](int p) {
std::cout << "デリータ2(ラムダ)が実行されました" << std::endl;
delete p;
};
int main() {
// 異なる削除子を持つが、型はどちらも std::shared_ptr
std::shared_ptr
std::shared_ptr
// 型消去により、同じベクタに格納可能
std::vector
vec.push_back(sp1);
vec.push_back(sp2);
std::cout << "コンテナ内の処理を開始します" << std::endl; vec.clear(); // ここでそれぞれの削除子が呼び出される return 0; }
5. 応用・注意点
実務でカスタム削除子を使用する際の注意点は以下の3点です。
・循環参照の回避: 削除子内で shared_ptr 自身をキャプチャしようとすると、循環参照が発生しメモリリークの原因となります。必要であれば std::weak_ptr を活用してください。
・例外安全性: 削除子自体が例外を投げることは避けてください。デストラクタ内での例外はプログラムの異常終了(std::terminate)を招く可能性があります。
・パフォーマンス: 型消去は内部的に動的メモリ確保(制御ブロックの割り当て)を伴う場合があります。非常に多くの小さなオブジェクトを生成するような高頻度な処理では、オーバーヘッドに注意してください。
カスタム削除子は、単なる「deleteの代わり」ではなく、リソース管理のルールをスマートポインタに埋め込める強力な機能です。適切に活用して、安全かつクリーンなコードを目指しましょう。

コメント