RAIIによるリソース管理
RAII(Resource Acquisition Is Initialization)は、オブジェクトの生存期間にリソースの確保と解放をバインドする設計パターンです。メモリ、ファイルハンドル、スレッドなどのリソースは、コンストラクタで確保され、デストラクタで自動的に解放されます。これにより、例外や早期returnがあってもリソース漏れを防ぎます。
スマートポインタとは
スマートポインタは、RAIIを実装したクラスで、ヒープ上に動的に確保されたメモリを安全に管理します。内部に生ポインタを保持し、所有権の移動や参照カウントによって解放タイミングを制御します。
なぜスマートポインタが必要か
生ポインタでは、例外発生時や関数の早期終了時にdeleteが呼び出されず、メモリリークが発生します。スマートポインタはスコープを抜ける際に自動的に解放されるため、安全なリソース管理を実現します。
主要なスマートポインタの種類
1. std::unique_ptr
排他的な所有権を持つスマートポインタです。コピーは禁止されていますが、ムーブセマンティクスをサポートしており、所有権を明示的に移動できます。ムーブ後は元のポインタはnullptrになります。
auto ptr1 = std::make_unique<int>(42);
auto ptr2 = std::move(ptr1); // 所有権移動
// ptr1 は nullptr になる
2. std::shared_ptr
複数のインスタンスが同じリソースを共有できるスマートポインタです。内部で参照カウンタを管理し、最後のshared_ptrが破棄された時点でリソースを解放します。
use_count():現在の参照数を取得unique():参照数が1かどうかを判定reset():現在のリソースを解放し、新しいリソースを設定get():内部の生ポインタを取得(解放責任はshared_ptrが保持)
C++14以降の推奨初期化手法
std::make_unique / std::make_shared
new演算子の代わりにmake_*系関数を使用することが推奨されます。理由は以下の通りです:
- パフォーマンス向上:
make_sharedはデータ領域と制御ブロックを一度のメモリ確保で済ませるため、効率的です。 - 例外安全性:関数引数の評価順序に依存せず、途中で例外が発生してもリソース漏れが起きません。
// 非推奨:例外安全ではない可能性あり
auto sp = std::shared_ptr<T>(new T(init_value()), compute_extra());
// 推奨:例外安全
auto sp = std::make_shared<T>(init_value(), compute_extra());
循環参照とstd::weak_ptr
shared_ptr同士が相互に参照し合うと、参照カウンタが永久に0にならず、メモリリークが発生します。これを解決するためにweak_ptrが用意されています。
class Node;
class Edge;
class Node {
public:
std::weak_ptr<Edge> connection; // 循環を避けるためにweak_ptrを使用
};
class Edge {
public:
std::shared_ptr<Node> start;
std::shared_ptr<Node> end;
};
weak_ptrの特徴
- リソースの所有権を持たず、参照カウントには影響しない
lock()で一時的にshared_ptrに昇格可能expired()で監視対象がまだ生存しているか確認可能make_shared使用時は、weak_ptrの存在がメモリ解放を遅延させる可能性あり(制御ブロックとデータが一体のため)