1. 導入
C++におけるオブジェクト指向設計において、多態性(ポリモーフィズム)は欠かせない要素です。しかし、メモリ管理を安全に行うためにスマートポインタ(std::unique_ptrやstd::shared_ptr)を導入する際、基底クラスのポインタで派生クラスを管理する方法に躓くエンジニアは少なくありません。本記事では、スマートポインタを用いた多態性の正しい実装方法と、現場で必ず守るべきメモリリークや未定義動作を防ぐためのルールについて解説します。
2. 基礎知識
C++における多態性とは、基底クラスのポインタや参照を通じて、派生クラスのメンバ関数を呼び出す仕組みです。
スマートポインタを使用する場合、基底クラスのポインタ型で派生クラスのインスタンスを保持することができます。ここで重要になるのが仮想デストラクタ(virtual destructor)です。もし基底クラスのデストラクタが仮想関数でない場合、基底クラスのポインタを介してオブジェクトを破棄しようとすると、派生クラスのデストラクタが呼び出されず、メモリリークやリソースの不完全な解放が発生するリスクがあります。
3. 実装/解決策
多態性をスマートポインタで安全に扱うための手順は以下の通りです。
1. 基底クラスのデストラクタに必ず「virtual」キーワードを付与する。
2. std::unique_ptrやstd::make_uniqueを使用してオブジェクトを生成する。
これにより、スコープを抜けた際に自動的に適切なデストラクタが順次呼び出されるようになります。
4. サンプルプログラム
include
include
include
// 基底クラス
class Shape {
public:
// 【重要】仮想デストラクタを定義することで、派生クラスの破棄を保証する
virtual ~Shape() {
std::cout << "Shapeのデストラクタ" << std::endl;
}
virtual void draw() const = 0;
};
// 派生クラス:円
class Circle : public Shape {
public:
~Circle() override {
std::cout << "Circleのデストラクタ" << std::endl;
}
void draw() const override {
std::cout << "円を描画します" << std::endl;
}
};
int main() {
// 基底クラスのスマートポインタで派生クラスを保持
std::unique_ptr
// 多態性によるメソッド呼び出し
shape->draw();
// スコープ終了時にShapeとCircleのデストラクタが正しく呼ばれる
return 0;
}
5. 応用・注意点
現場での開発において陥りやすい注意点を挙げます。
・インターフェース設計の徹底
抽象クラス(純粋仮想関数を持つクラス)を設計する際は、デストラクタをvirtualにするだけでなく、不要なコピーコンストラクタや代入演算子をdeleteして、意図しないコピーを防ぐことも検討してください。
・std::shared_ptrの場合の注意
std::shared_ptrを使用する場合も同様に仮想デストラクタが必須です。ただし、shared_ptrは参照カウンタを制御するため、unique_ptrよりもオーバーヘッドが大きい点に注意してください。基本的にはunique_ptrで所有権を明確にし、必要な場合にのみshared_ptrへ移行する設計をおすすめします。
・継承関係の確認
多態性を利用しないクラスであっても、将来的に継承される可能性がある場合は、念のため仮想デストラクタを定義しておくことが、後の不具合を防ぐ「防御的プログラミング」として有効です。

コメント