導入
C++におけるリソース管理の要であるstd::unique_ptrですが、std::vectorなどのコンテナへ格納する際、初期化リスト({ … })を使おうとしてコンパイルエラーに直面した経験はないでしょうか。std::unique_ptrはコピー禁止という特性上、初期化リストによる一括初期化が制限されます。本記事では、この課題を回避しつつ、実務で安全かつスマートにスマートポインタを管理する方法を解説します。
基礎知識
std::unique_ptrは「所有権の排他」を保証するスマートポインタです。この仕組みを実現するために、コピーコンストラクタがdeleteされており、値を複製することができません。
一方、std::initializer_listは、{ … }で囲まれた要素のリストをコンテナのコンストラクタや関数に渡す際に使用されます。この際、内部的に要素のコピーやムーブが発生するため、コピー禁止であるstd::unique_ptrを直接リストに並べてコンテナを初期化しようとすると、コンパイラがコピーを試みて失敗するのです。
実装/解決策
実務においてstd::unique_ptrのリストを構築するには、コンテナのコンストラクタで初期化しようとするのではなく、空のコンテナを作成した後にstd::vector::push_backを使用して一つずつ要素を追加するのが正攻法です。
C++11以降であれば、std::make_uniqueを活用することで、例外安全性を確保しつつ簡潔に記述できます。
サンプルプログラム
以下のコードは、std::unique_ptrをvectorで管理する標準的な実装例です。
#include
include
include
include
// サンプル用クラス
struct Widget {
std::string name;
Widget(std::string n) : name(n) {}
};
int main() {
// 1. コンテナを宣言
std::vector
// 2. push_backを使用して個別に要素を追加
// std::make_uniqueを使うことで、例外安全かつ簡潔に所有権を移動できます
widgets.push_back(std::make_unique
widgets.push_back(std::make_unique
widgets.push_back(std::make_unique
// 確認用の出力
for (const auto& w : widgets) {
std::cout << "Widget: " << w->name << std::endl;
}
return 0;
}
応用・注意点
注意点1:emplace_backの活用
上記の例ではpush_backを使用していますが、std::vector::emplace_backを使用することも可能です。emplace_backは引数をコンストラクタに直接渡すため、スマートポインタを生成するための余計な一時オブジェクトの生成を避けることができ、より効率的です。
注意点2:C++17以降の改善
C++17以降であれば、一時オブジェクトを直接vectorにムーブして格納することが可能です。ただし、初期化リスト構文が使えないという制約自体は変わらないため、あくまで「逐次追加」を行うという方針は維持してください。
注意点3:設計の再考
もし「初期化リストで一括管理したい」という動機が非常に強い場合、その設計自体がstd::unique_ptrの用途(所有権の管理)に適していない可能性があります。もし動的な要素数ではなく固定の要素数であれば、std::unique_ptrではなくstd::arrayや、単なるスタック上のオブジェクト管理を検討するのも、コードの複雑性を下げる一つの手段です。

コメント