std::unique_ptr は、RAII イディオムに従って、そのリソースの有効期間を自動的かつ排他的に管理します。 std::unique_ptr は、メモリやパフォーマンスのオーバーヘッドなしで機能するため、最初に選択する必要があります。
std::unique_ptr の使用法を示す前に、特徴的な箇条書きをいくつか紹介します。
std::unique_ptr
- リソースの有無にかかわらずインスタンス化できます。
- 単一のオブジェクトのライフサイクルを管理しますが、オブジェクトの配列です.
- 基礎となるリソースのインターフェースを透過的に提供します。
- 独自の削除関数でパラメーター化できます。
- 移動できます (移動セマンティック)。
- ヘルパー関数 std::make_unique で作成できます。
使い方
std::unique_ptr の重要な問題は、基礎となるリソースをいつ削除するかです。これは、 std::unique_ptr が範囲外になるか、新しいリソースを取得したときに正確に発生します。 2 つの使用例を次に示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | // uniquePtr.cpp #include <iostream> #include <memory> #include <utility> struct MyInt{ MyInt(int i):i_(i){} ~MyInt(){ std::cout << "Good bye from " << i_ << std::endl; } int i_; }; int main(){ std::cout << std::endl; std::unique_ptr<MyInt> uniquePtr1{ new MyInt(1998) }; std::cout << "uniquePtr1.get(): " << uniquePtr1.get() << std::endl; std::unique_ptr<MyInt> uniquePtr2; uniquePtr2= std::move(uniquePtr1); std::cout << "uniquePtr1.get(): " << uniquePtr1.get() << std::endl; std::cout << "uniquePtr2.get(): " << uniquePtr2.get() << std::endl; std::cout << std::endl; { std::unique_ptr<MyInt> localPtr{ new MyInt(2003) }; } std::cout << std::endl; uniquePtr2.reset(new MyInt(2011)); MyInt* myInt= uniquePtr2.release(); delete myInt; std::cout << std::endl; std::unique_ptr<MyInt> uniquePtr3{ new MyInt(2017) }; std::unique_ptr<MyInt> uniquePtr4{ new MyInt(2022) }; std::cout << "uniquePtr3.get(): " << uniquePtr3.get() << std::endl; std::cout << "uniquePtr4.get(): " << uniquePtr4.get() << std::endl; std::swap(uniquePtr3, uniquePtr4); std::cout << "uniquePtr3.get(): " << uniquePtr3.get() << std::endl; std::cout << "uniquePtr4.get(): " << uniquePtr4.get() << std::endl; std::cout << std::endl; } |
クラス MyInt (7 行目から 17 行目) は、数値の単純なラッパーです。 MyInt のライフサイクルを観察するために、11 ~ 13 行目のデストラクタを調整しました。
24 行目で std::unique_ptr を作成し、27 行目でそのリソース (new MyInt(1998)) のアドレスを返します。その後、uniquePtr1 を uniquePtr2 に移動します (29 行目)。したがって、uniquePtr2 はリソースの所有者です。これは、30 行目と 31 行目のプログラムの出力を示しています。37 行目のローカル std::unique_ptr は、スコープの終わりでその有効範囲に達しています。したがって、localPtr のデストラクタ、つまりリソース (new MyInt(2003)) のデストラクタが実行されます。これがスクリーンショットです。
最も興味深いのは 42 行目から 44 行目です。最初に、uniquePtr1 に新しいリソースを割り当てます。したがって、MyInt(1998) のデストラクタが実行されます。 43 行目のリソースが解放された後、デストラクタを明示的に呼び出すことができます。
プログラムの残りの部分は、非常に簡単に取得できます。 48 ~ 58 行で 2 つの std::unique_ptr を作成し、それらのリソースを交換します。 std::unique_ptr はコピー セマンティックをサポートしないため、std::swap は内部でムーブ セマンティックを使用します。 main 関数が終了すると、uniquePtr3 と uniquePtr4 は範囲外になり、それらのデストラクタが自動的に実行されます。
それが全体像でした。 std::unique_ptr の詳細をいくつか掘り下げてみましょう。
オブジェクトと配列の有効期間の扱い
std::unique_ptr は配列に特化しています。アクセスは完全に透過的です。つまり、std::unique_ptr がオブジェクトの有効期間を管理する場合、オブジェクト アクセスの演算子はオーバーロードされます (operator* および operator->)。 std::unique_ptr が配列の有効期間を管理する場合、インデックス演算子 operator[] がオーバーロードされます。したがって、オペレーターの呼び出しは、基礎となるリソースに完全に透過的に転送されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | // uniquePtrArray.cpp #include <iomanip> #include <iostream> #include <memory> class MyStruct{ public: MyStruct(){ std::cout << std::setw(15) << std::left << (void*) this << " Hello " << std::endl; } ~MyStruct(){ std::cout << std::setw(15) << std::left << (void*)this << " Good Bye " << std::endl; } }; int main(){ std::cout << std::endl; std::unique_ptr<int> uniqInt(new int(2011)); std::cout << "*uniqInt: " << *uniqInt << std::endl; std::cout << std::endl; { std::unique_ptr<MyStruct[]> myUniqueArray{new MyStruct[5]}; } std::cout << std::endl; { std::unique_ptr<MyStruct[]> myUniqueArray{new MyStruct[1]}; MyStruct myStruct; myUniqueArray[0]=myStruct; } std::cout << std::endl; { std::unique_ptr<MyStruct[]> myUniqueArray{new MyStruct[1]}; MyStruct myStruct; myStruct= myUniqueArray[0]; } std::cout << std::endl; } |
22 行目で std::unique_ptr を逆参照し、そのリソースの値を取得します。
7 ~ 15 行目の MyStruct は、std::unique_ptr の配列のベースです。 MyStruct オブジェクトをインスタンス化すると、そのアドレスが取得されます。出力はデストラクタによって与えられます。これで、オブジェクトのライフ サイクルを簡単に観察できます。
26 行目から 28 行目で、MyStruct の 5 つのインスタンスを作成して破棄します。 32 行目から 36 行目はもっと興味深いです。ヒープ (33 行目) とスタック (34 行目) に MyStruct インスタンスを作成します。したがって、両方のオブジェクトが異なる範囲のアドレスを持っています。その後、ローカル オブジェクトを std::unique_pr に割り当てます (35 行目)。 40 行目から 54 行目も同様の戦略に従います。ここで、ローカル オブジェクトに myUniqueArray の最初の要素を割り当てます。 35 行目と 43 行目の std::unique_ptr へのインデックス アクセスは、おなじみの配列へのインデックス アクセスのように感じます。
ユーザー提供のデリータ
std::unique_ptr はユーザー提供のデリータを持つことができます:std::unique_ptr
std::auto_ptr の置き換え
古典的な C++ には既に std::auto_ptr があります。その仕事は std::unique_ptr の仕事に似ています。 std::auto_ptr は、基になるリソースの有効期間を排他的に管理します。しかし、 std::auto_ptr は非常に奇妙です。 std::auto_ptr をコピーすると、そのリソースが移動されます。つまり、コピー セマンティックを使用した操作は、フード ムーブ セマンティックの下で実行されます。これが std::auto_ptr が推奨されない理由であり、代わりに std::unique_ptr を使用する必要があります。 std::unique_ptr は移動のみ可能で、コピーはできません。 std::unique_ptr で std::move を明示的に呼び出す必要があります。
この図は、std::auto_ptr と std::unique_ptr の違いを示しています。
次のコード スニペットを実行すると、
std::auto_ptr<int> auto1(new int(5)); std::auto_ptr<int> auto2(auto1);
std::auto_ptr auto1 はそのリソースを失います。
std::unique_ptr はコピーできません。したがって、ムーブ セマンティックを使用する必要があります。
std::unique_ptr<int> uniqueo1(new int(5)); std::unique_ptr<int> unique2(std::move(unique1));
std::unique_ptr は STL のコンテナーに移動でき、内部でコピー セマンティックを使用しない場合は、後で STL のアルゴリズムで使用できます。
正確には。 std::auto_ptr のコピーは未定義の動作です。 std::unquiue_ptr の移動により、ソースは明確に定義された状態になりますが、正確に指定された状態にはなりません。しかし、描かれている行動はかなりありそうです。
ヘルパー関数 std::make_unique
C++11 には std::make_shared がありますが、std::make_unique はありません。これは C++14 で修正されています。 Microsoft Visual Studio 2015 は正式に C++11 をサポートしていますが、std::make_unique を使用できます。 std::make_unique のおかげで、new に手を加える必要はありません。
std::unique_ptr<int> uniqPtr1= std::make_unique<int>(2011); auto uniqPtr2= std::make_unique<int>(2014);
std::make_unique を自動型推定と組み合わせて使用すると、入力が最小限に抑えられます。それは std::unique_ptr uniqPtr2 を証明します。
常に std::make_unique を使用する
std::make_unique を使用するもう 1 つの微妙な理由があります。 std::make_unique は常に正しいです。
使用する場合
func(std::make_unique<int>(2014), functionMayThrow()); func(std::unique_ptr<int>(new int(2011)), functionMayThrow());
および functionMayThrow がスローされた場合、次の可能な一連の呼び出しに対して new int(2011) でメモリ リークが発生します:
new int(2011) functionMayThrow() std::unique_ptr<int>(...)
次は?
次回はstd::shared_ptrについてです。したがって、この投稿は独占的所有権についてであり、次の投稿は共有所有権についてです。