1. 導入
C++におけるメモリ管理の基本は「所有権の明確化」です。しかし、複数のオブジェクトを動的に確保し、かつ生存期間を適切に管理するのは非常に煩雑になりがちです。特に、動的なオブジェクトの集合を扱う際、手動のdelete忘れは致命的なメモリリークを招きます。本記事では、std::unique_ptrとstd::vectorを組み合わせることで、安全かつ効率的にオブジェクトのライフサイクルを管理する手法を解説します。
2. 基礎知識
std::unique_ptrは、特定のオブジェクトに対して「唯一の所有権」を持つスマートポインタです。スコープを抜けると自動的にdeleteが呼ばれるため、明示的な解放処理が不要になります。
std::vectorは動的配列です。この二つを組み合わせることで、「メモリ管理はスマートポインタに任せ、コンテナの操作(追加・削除・反復)はvectorに任せる」という、責務の分離が可能になります。
3. 実装/解決策
std::vectorにstd::unique_ptrを格納する場合、最大のポイントはstd::unique_ptrが「コピー不可」であるという特性です。そのため、要素を追加する際はstd::moveを使用して所有権を移動させる必要があります。これにより、コンテナ内の各要素が確実に単一の所有者を持つことが保証されます。
4. サンプルプログラム
以下のコードは、std::unique_ptrをvectorで管理する標準的な実装例です。
include <iostream>
include <vector>
include <memory>
include <string>
struct Task {
std::string name;
Task(std::string n) : name(n) { std::cout << name << " を生成\n"; }
~Task() { std::cout << name << " を破棄\n"; }
};
int main() {
// 1. unique_ptrを格納するvectorの宣言
std::vector<std::unique_ptr<Task>> taskList;
// 2. make_uniqueを使用して生成し、所有権を移動(std::move)して追加
taskList.push_back(std::make_unique<Task>("タスクA"));
taskList.push_back(std::make_unique<Task>("タスクB"));
// 3. 範囲ベースforループでアクセス(参照で取得して所有権は保持したままにする)
for (const auto& task : taskList) {
std::cout << "実行中: " << task->name << "\n";
}
// 4. スコープを抜けると、vector内の全てのTaskが自動的に解放される
return 0;
}
5. 応用・注意点
実務でこの構成を利用する際、以下の点に注意してください。
・所有権の移動に注意: vectorの要素を別のコンテナに移動させたい場合、必ずstd::moveを使用してください。誤ってコピーしようとするとコンパイルエラーになります。
・ポリモーフィズムへの活用: 基底クラスのポインタをvectorで持ちたい場合、std::vector<std::unique_ptr<Base>>とすることで、派生クラスのオブジェクトを混在させて管理できます。その際、基底クラスのデストラクタには必ずvirtualを付与してください。
・パフォーマンス: 大量にpush_backを行う際は、事前にreserve()を呼ぶことで、メモリ再確保時の移動コストを抑えることが可能です。
この手法は、現代のC++開発においてメモリ安全性を担保するための「定石」ですので、ぜひプロジェクトで活用してください。

コメント