1. 導入:なぜ「制御ブロック」を知る必要があるのか?
C++のメモリ管理を自動化してくれる便利な「スマートポインタ(std::shared_ptr)」。しかし、ただ便利に使っているだけでは、思わぬメモリ不足やパフォーマンスの低下を招くことがあります。その鍵を握るのが「制御ブロック」です。この仕組みを理解することで、より安全で効率的なプログラムを書けるようになり、メモリ管理のプロへの一歩を踏み出すことができます。
2. 基礎知識:制御ブロックとは何か?
std::shared_ptrを宣言すると、プログラムは実体となるオブジェクトの管理を行うための「制御ブロック」という特別な領域をメモリ(ヒープ)上に作成します。
この制御ブロックには、主に以下の情報が格納されています。
・参照カウンタ:今、何個のshared_ptrがそのオブジェクトを指しているか。
・削除子(デリータ):オブジェクトが不要になった時に、どのようにメモリを解放するかという情報。
つまり、shared_ptrを使うということは、「オブジェクト本体」と「それを管理する制御ブロック」という2つの領域を扱うことになります。
3. 実装/解決策:make_sharedで効率化する
通常、new演算子を使ってshared_ptrを作成すると、オブジェクト用と制御ブロック用で2回メモリ確保が行われます。しかし、std::make_shared関数を使用すると、これらを一度のメモリ確保で済ませることができます。
これにより、メモリ確保の回数が減るだけでなく、メモリの断片化を防ぎ、キャッシュ効率も向上するというメリットがあります。
4. サンプルプログラム
以下のコードをコピー&ペーストして、コンパイルして実行してみてください。
include <iostream>
include <memory>
class MyClass {
public:
MyClass() { std::cout << "コンストラクタ実行" << std::endl; }
~MyClass() { std::cout << "デストラクタ実行" << std::endl; }
};
int main() {
// 推奨される方法: make_sharedを使用
// オブジェクトと制御ブロックがまとめて確保され、効率的です
std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>();
// 参照カウンタの確認
std::cout << "現在の参照カウンタ: " << ptr1.use_count() << std::endl;
{
// 別のshared_ptrで共有
std::shared_ptr<MyClass> ptr2 = ptr1;
std::cout << "共有後の参照カウンタ: " << ptr1.use_count() << std::endl;
} // ここでptr2が破棄される
std::cout << "ptr2破棄後の参照カウンタ: " << ptr1.use_count() << std::endl;
return 0;
}
5. 応用・注意点:現場で陥りやすい罠
循環参照に注意:
shared_ptr同士が互いを指し合ってしまうと、参照カウンタが0にならず、メモリが解放されない「メモリリーク」が発生します。これを防ぐには、親子関係のような双方向参照が必要な場合にstd::weak_ptrを併用するのが鉄則です。
make_sharedの制限:
make_sharedは非常に便利ですが、カスタムデリータ(独自の削除処理)を指定したい場合や、コンストラクタがprivateなクラスを扱う場合には直接std::shared_ptrのコンストラクタを使う必要があります。状況に応じて使い分けるのが、C++エンジニアとしての腕の見せ所です。

コメント