7

C++11 のstd::unique_ptr使用法と落とし穴は何ですか?

std::unique_ptr動的に割り当てられた配列を格納するためにも使用できますか?

std::unique_ptrカスタム削除メカニズムを使用するリソースでも使用できますか?

4

1 に答える 1

18

使い方や落とし穴をQ&A形式で整理してみましょう。


Q1:クラスへのポインタComponentmy class 内に格納したいと考えていますX。「コンテナ」クラスをコピー可能
にしたくありません。インスタンスの唯一の所有者です。生のポインターを所有することは悪いことであり、「リークトロシティ」の潜在的な原因であること を知っています (代わりに、生のポインターを観察することは問題ありません)。この目的のためにどのスマート ポインターを使用できますか?XXComponent

A1: C++11std::unique_ptrは確かに良い選択肢です。

一意の (共有されていない) 所有権の場合は問題なく、 のオーバーヘッドはありませんstd::shared_ptr
これは、以前の C++98/03 の優れた代替品ですboost::scoped_ptr
実際、さらには移動セマンティクスstd::unique_ptrを提供します。 そのため、クラスにデータ メンバー (およびその他の移動可能なデータ メンバー) が含まれている場合、クラス全体が自動的に移動可能になります。
Xunique_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=Componentunique_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::vectorstd:vectorstd::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()HANDLECreateFile()

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(....) );  
于 2013-07-30T10:52:45.350 に答える