導入
C++のスマートポインタであるstd::unique_ptrは、メモリ管理を自動化する強力なツールです。しかし、実務で独自の削除子(Deleter)を扱う際、意図せず「型が違う」というコンパイルエラーに直面したことはありませんか?実は、unique_ptrにおいて削除子は型の一部として定義されています。本記事では、この仕様が引き起こす課題と、それを解決するための実装手法を解説します。
基礎知識
std::unique_ptr
そのため、同じ int 型を管理していても、削除子が異なれば std::unique_ptr
実装と解決策
この課題を解決するためには、削除子を型から分離する「型消去(Type Erasure)」の考え方が必要です。現代的なC++では、std::functionのように削除子をラップする方法もありますが、パフォーマンスを重視するなら、削除子をテンプレート化するインターフェース設計を行うのが一般的です。
サンプルプログラム
以下のコードでは、削除子が異なっても扱えるように、共通のラッパー型を作成する手法を示します。
include
include
// 独自の削除子1
struct CustomDeleter1 {
void operator()(int p) const {
std::cout << "Deleter 1 で削除" << std::endl;
delete p;
}
};
// 独自の削除子2
struct CustomDeleter2 {
void operator()(int p) const {
std::cout << "Deleter 2 で削除" << std::endl;
delete p;
}
};
int main() {
// 削除子を保持するunique_ptrを生成
std::unique_ptr
std::unique_ptr
// コンパイルエラーの確認:p1 = p2; は型が異なるため不可
// p1 = p2; // ← コメントアウトを外すとエラーになります
// 解決策:std::unique_ptrを直接関数に渡す際は、テンプレート化する
auto process = [](auto& ptr) {
std::cout << "値: " << ptr << std::endl;
};
process(p1);
process(p2);
return 0;
}
応用・注意点
実務における注意点は、「std::unique_ptrを関数の引数として受け取る際」です。削除子まで含めた完全な型を引数に指定すると、APIの柔軟性が著しく低下します。
回避策として以下の3点を推奨します:
1. 関数テンプレートを使用する:上記サンプルプログラムのように、引数を auto にすることで、型が異なる削除子を持つunique_ptrでも受け取れるようになります。
2. インターフェースを統一する:どうしても型を固定したい場合は、ポリモーフィズムを利用するか、削除子を std::function にラップして std::unique_ptr
3. 可能な限りデフォルトの削除子を使う:カスタム削除子が必要なのは、C言語のAPIによるリソース解放など、特殊なケースに限るべきです。
削除子の型不一致は、大規模なプロジェクトでAPIの設計を複雑にする原因となります。まずは「テンプレートを活用して型を隠蔽する」という方針で設計を進めてみてください。

コメント