C++11 のstd::unique_ptr
使用法と落とし穴は何ですか?
std::unique_ptr
動的に割り当てられた配列を格納するためにも使用できますか?
std::unique_ptr
カスタム削除メカニズムを使用するリソースでも使用できますか?
C++11 のstd::unique_ptr
使用法と落とし穴は何ですか?
std::unique_ptr
動的に割り当てられた配列を格納するためにも使用できますか?
std::unique_ptr
カスタム削除メカニズムを使用するリソースでも使用できますか?
使い方や落とし穴をQ&A形式で整理してみましょう。
Q1:クラスへのポインタをComponent
my class 内に格納したいと考えていますX
。「コンテナ」クラスをコピー可能
にしたくありません。インスタンスの唯一の所有者です。生のポインターを所有することは悪いことであり、「リークトロシティ」の潜在的な原因であること
を知っています (代わりに、生のポインターを観察することは問題ありません)。この目的のためにどのスマート ポインターを使用できますか?X
X
Component
A1: C++11std::unique_ptr
は確かに良い選択肢です。
一意の (共有されていない) 所有権の場合は問題なく、 のオーバーヘッドはありませんstd::shared_ptr
。
これは、以前の C++98/03 の優れた代替品ですboost::scoped_ptr
。
実際、さらには移動セマンティクスstd::unique_ptr
を提供します。
そのため、クラスにデータ メンバー (およびその他の移動可能なデータ メンバー) が含まれている場合、クラス全体が自動的に移動可能になります。X
unique_ptr<Component>
X
使用例を以下に示します。
#include <memory> // for std::unique_ptr
class X
{
std::unique_ptr<Component> m_pComponent;
....
public:
X()
: m_pComponent( new Component() )
{
....
}
}
(もちろん、スマートポインターであるため、含まれているクラス デストラクタで明示的に削除する必要はありません。)
Q2:すごい!明示的なデストラクタは必要なく、std::shared_ptr
オーバーヘッドもありません (典型的な C++ 哲学: 「使用しないものにはお金を払いません」 )。
ただし、問題があります。私のクラスには、インスタンスComponent
を作成する前にコンストラクター コードで計算する必要があるパラメーターを受け取るコンストラクター オーバーロードがあります。コンストラクターでComponent
通常の代入を使用して、新しく作成したを に代入しようとしましたが、エラー メッセージが表示されました。operator=
Component
unique_ptr
X::X()
{
....
const int param = CalculateCoolParameter();
// This assignment fails:
m_pComponent = new Component(param); // <---- Error pointing to '=' here
^--- error
}
A2:operator=
わかりました。おそらく、以前に所有されていたポインター (存在する場合) を解放し、新しく作成されたポインターに代入するオーバーロードを予期していたでしょう。
残念ながら、そのようなオーバーロードはありません。
しかし、std::unique_ptr::reset()
方法はうまくいきます!
m_pComponent.reset( new Component(param) );
Q3:おい!これunique_ptr
は本当にクールです!スマートで、自動的に移動でき、オーバーヘッドがかからないという事実が気に入っています。したがって、動的に割り当てられた一定サイズ (実行時に計算される) の配列
を格納するために使用したいと思います (コードのこの部分では、私は非常に制約があり、支払いたくありません)。動的にサイズを変更する機能やディープコピーなどをすべて使用したくないためです。)std::vector
std:vector
std::vector
私はこのようなことを試しました:
const size_t count = GetComponentsCount();
unique_ptr<Component> components( new Component[count] );
正常にコンパイルされますが、~Component
デストラクタが呼び出されるのは1 回count
だけであることに気付きました。代わりに、デストラクタの呼び出しを期待していました。ここで何がうまくいかないのですか?
A3:問題は、上記の構文では、std::unique_ptr
を使用delete
して割り当てられたオブジェクトを解放することです。しかし、それらは を使用して割り当てられたnew[]
ので、適切なクリーンアップ呼び出しは(括弧なしdelete[]
の単純なものではありません) です。delete
これを修正し、リソースの解放unique_ptr
に適切に使用するように指示するdelete[]
には、次の構文を使用する必要があります。
unique_ptr<Component[]> components( new Components[count] );
// ^^
//
// Note brackets "[]" after the first occurrence of "Component"
// in unique_ptr template argument.
//
Q4:すごい!ただしunique_ptr
、通常の C++ delete
(またはdelete[]
)を使用してリソース解放コードを実行するのではなく、Cファイル ( で開く) やWin32 ファイル( で作成)などのカスタム クリーンアップ関数を使用する場合にも使用できますか?fclose()
<stdio.h>
fopen()
CloseHandle()
HANDLE
CreateFile()
A4:それは間違いなく可能です。カスタムのデリータを指定できますstd::unique_ptr
。
例えば:
//
// Custom deleter function for FILE*: fclose().
//
std::unique_ptr<FILE, // <-- the wrapped raw pointer type: FILE*
int(*)(FILE*)> // <-- the custom deleter type: fclose() prototype
myFile( fopen("myfile", "rb"), // <-- resource (FILE*) is returned by fopen()
fclose ); // <-- the deleter function: fclose()
//
// Custom deleter functor for Win32 HANDLE: calls CloseHandle().
//
struct CloseHandleDeleter
{
// The following pointer typedef is required, since
// the raw resource is HANDLE (not HANDLE*).
typedef HANDLE pointer;
// Custom deleter: calls CloseHandle().
void operator()(HANDLE handle) const
{
CloseHandle(handle);
}
};
std::unique_ptr<HANDLE, CloseHandleDeleter> myFile( CreateFile(....) );