配列でunique_ptrを使用することはありますか?


238

std::unique_ptr たとえば、配列をサポートしています。

std::unique_ptr<int[]> p(new int[10]);

しかし、それは必要ですか?おそらく、std::vectorまたはを使用する方が便利std::arrayです。

その構成要素の用途はありますか?


6
完全を期すためにstd::shared_ptr<T[]>、がないことを指摘する必要がありますが、誰かが提案を書くことに迷惑をかける可能性がある場合は、C ++ 14にあるはずです。その間、常にありboost::shared_arrayます。
仮名

13
std::shared_ptr<T []>は現在c ++ 17です。
陳力

あなたはコンピュータ上で何かを行うための複数の方法を見つけることができます。このコンストラクトは、特にホットパスで使用します。これは、アレイをターゲットにする方法を正確に知っていれば、コンテナー操作のオーバーヘッドをなくすためです。さらに、連続したストレージを疑うことなく文字配列を作成します。
kevr

回答:


256

一部の人々はstd::vector、アロケータを使用しても、を使用する余裕がありません。一部の人々は動的にサイズ設定された配列を必要とするのでstd::array、外にあります。また、配列を返すことが知られている他のコードから配列を取得する人もいます。そのコードはvector、何かを返すように書き直されることはありません。

を許可するとunique_ptr<T[]>、それらのニーズに対応できます。

つまり、必要なunique_ptr<T[]>ときに使用します。代わりの方法がうまくいかない場合。それは最後の手段のツールです。


27
@NoSenseEtAl:「一部の人々がそれを行うことを許可されていない」のどの部分があなたを見逃しているのかわかりません。一部のプロジェクトには非常に具体的な要件があり、その中には「使用できない」ことがありますvector。それらが妥当な要件であるかどうかは議論できますが、それらが存在することを否定することはできません。
Nicol Bolas 2013年

21
世界で、std::vectorもし使える人が使えない理由なんてありませんstd::unique_ptr
Miles Rout 2014

66
vectorを使用しない理由は次のとおりです。sizeof(std :: vector <char>)== 24; sizeof(std :: unique_ptr <char []>)== 8
Arvid

13
@DanNissenbaumこれらのプロジェクトは存在します。航空や防衛などの非常に厳格な調査が行われている一部の業界では、規制を設定している統治機関が正しいことを検証および証明することが難しいため、標準ライブラリは立ち入り禁止です。標準ライブラリは十分にテストされているとあなたは主張するかもしれませんが、私はあなたに同意しますが、あなたと私はルールを作りません。
エミリーL.

16
@DanNissenbaumまた、一部のハードリアルタイムシステムでは、システムコールによって発生する遅延が理論的に制限されず、プログラムのリアルタイム動作を証明できないため、動的メモリ割り当てをまったく使用できません。または、境界が大きすぎてWCETの制限を超える可能性があります。ここでは適用されませんが、unique_ptrどちらも使用しないため、この種のプロジェクトは実際に存在します。
エミリーL.

124

トレードオフがあり、必要なものに一致するソリューションを選択します。私の頭の上から:

初期サイズ

  • vectorそして、unique_ptr<T[]>サイズは、実行時に指定することを可能にします
  • array コンパイル時にのみサイズを指定できます

サイズ変更

  • arrayそして、unique_ptr<T[]>リサイズはできません。
  • vector する

ストレージ

  • vectorunique_ptr<T[]>オブジェクトの外(通常はヒープ)にデータを格納する
  • array データをオブジェクトに直接格納します

コピーしています

  • arrayvectorコピーを許可する
  • unique_ptr<T[]> コピーを許可しない

スワップ/移動

  • vectorおよびunique_ptr<T[]>O(1)時間有しswap、移動操作を
  • arrayO(n)の時間swapおよび移動操作があります。ここで、nは配列の要素数です。

ポインター/リファレンス/イテレーターの無効化

  • array オブジェクトが生きている間でも、ポインタ、参照、イテレータが無効化されないことを保証します。 swap()
  • unique_ptr<T[]>イテレータはありません。ポインタと参照はswap()、オブジェクトが生きている間だけ無効になります。(交換後、ポインターは交換した配列を指すので、その意味では「有効」です。)
  • vector 再割り当てのポインタ、参照、イテレータを無効にする場合があります(再割り当ては特定の操作でのみ発生することが保証されています)。

概念とアルゴリズムとの互換性

  • arrayそしてvector両方のコンテナです
  • unique_ptr<T[]> コンテナではありません

私は認めざるを得ません。これは、ポリシーベースのデザインでリファクタリングする機会のように見えます。


1
ポインタの無効化のコンテキストであなたが何を意味しているのか理解できません。これは、オブジェクト自体へのポインタ、または要素へのポインタについてですか?または、他の何か?ベクトルからではなく、配列からどのような保証が得られますか?
jogojapan 2013年

3
イテレータ、ポインタ、またはの要素への参照があるとしvectorます。次に、そのサイズまたは容量を増やしてvector、再割り当てを強制します。その場合、そのイテレータ、ポインタ、または参照は、もはやのその要素を指していませんvector。これが「無効化」の意味です。array「再割り当て」がないため、この問題は発生しません。実際、細部に気づき、それに合わせて編集しました。
仮名

1
OK、配列の再割り当ての結果として、またはunique_ptr<T[]>再割り当てがないため、無効化はできません。ただし、もちろん、配列がスコープ外になった場合でも、特定の要素へのポインタは無効になります。
jogojapan 2013年

はい、オブジェクトが存在しなくなった場合、すべての賭けは無効になります。
仮名

1
@rubenvbもちろん可能ですが、(たとえば)範囲ベースのforループを直接使用することはできません。ちなみに、通常のとは異なり、配列の要素を正しく破棄するにはT[]、サイズ(または同等の情報)がどこかにぶら下がっている必要がありますoperator delete[]。プログラマがそれにアクセスできればいいのですが。
仮名

73

aを使用する理由の1つunique_ptrは、配列の値を初期化する実行時のコストを支払いたくない場合です。

std::vector<char> vec(1000000); // allocates AND value-initializes 1000000 chars

std::unique_ptr<char[]> p(new char[1000000]); // allocates storage for 1000000 chars

std::vectorコンストラクタとは、std::vector::resize()初期値になるT-しかし、new場合があることしないだろうTPODです。

C ++ 11のValue-Initialized Objectsおよびstd :: vectorコンストラクターを参照してください

ここでvector::reserveは代替手段ではないことに注意してください:std :: vector :: reserve safeの後にrawポインターにアクセスしていますか?

これは、Cプログラマが選択する場合があります同じ理由ですmalloc以上calloc


しかし、この理由だけが解決策ではありません
ルスラン

@Ruslanリンクされたソリューションでは、動的配列の要素はまだ値で初期化されますが、値の初期化は何もしません。1000000回何もしないとコードなしで実装できることを認識できないオプティマイザは10セントに値しないことに同意しますが、この最適化にまったく依存しない方がよいかもしれません。
マルクファンレーウェン2017年

さらに別の可能性は、である型の構築とであるオブジェクトの破棄を回避std::vectorするカスタムアロケーターを提供することstd::is_trivially_default_constructibleですがstd::is_trivially_destructible、厳密にはC ++標準に違反します(そのような型はデフォルトで初期化されていないため)。
Walter、

またstd::unique_ptr、多くのstd::vector実装とは異なり、境界チェックは提供されません。
ダイアピル2017

@diapir実装に関するものではありません。std::vector標準での境界をチェックするために必要です.at()。実装によっては、チェックインするデバッグモードもあるとおっしゃっていたと思います.operator[]が、これは、移植性の高いコードを書くためには役に立たないと考えています。
underscore_d

30

std::vectorしながら、周りにコピーすることができるunique_ptr<int[]>配列のユニークな所有権を発現することができます。std::array一方、ではサイズをコンパイル時に決定する必要がありますが、状況によってはこれが不可能な場合があります。


2
何かが理由だけでできるの周りにコピーされ、それがなければならないという意味ではありません。
Nicol Bolas 2013年

4
@NicolBolas:わかりません。のunique_ptr代わりにを使用するのと同じ理由で、これを防止することができますshared_ptr。何か不足していますか?
Andy Prowl 2013年

4
unique_ptr偶発的な誤用を防ぐだけではありません。また、よりも小さく、オーバーヘッドも低くなっていますshared_ptr。重要なのは、「誤用」を防止するセマンティクスをクラスに含めるのは良いことですが、それだけが特定の型を使用する理由ではありません。そして、それがサイズを持っているという事実以外に理由がなければvector、配列ストレージとしてはるかに有用です。unique_ptr<T[]>
Nicol

3
私はポイントを明確にしたと思った:それ以外に特定のタイプを使用する理由他にもある。ちょうど好む理由があるようなvectorオーバーunique_ptr<T[]>の可能なだけではなく、「あなたはそれをコピーすることはできません」ので、選ぶ、というのは、unique_ptr<T[]>あなたがコピーをしたくないとき。誰かが間違ったことをするのを止めることは、必ずしもクラスを選ぶ最も重要な理由ではありません。
Nicol Bolas 2013年

8
std::vectoraよりもオーバーヘッドが大きく、std::unique_ptr1の代わりに3ポインタを使用します。 std::unique_ptrコピーの構築をブロックしますが、移動の構築を有効にします。これは、意味上、処理中のデータが移動のみでコピーはできない場合class、データを含むファイルに感染します。無効なデータに対する操作があると実際にはコンテナクラスが悪化し、「使用しないでください」ではすべての罪が洗い流されることはありません。std::vectorクラスのすべてのインスタンスを手動で無効にする必要moveがあるのは頭痛の種です。std::unique_ptr<std::array>を持っていsizeます。
Yakk-Adam Nevraumont 2013年

22

スコット・マイヤーズはこれをEffective Modern C ++で言っています

存在std::unique_ptr配列のためには、あなただけに知的関心のあるべき理由std::arraystd::vectorstd::string事実上、常に生の配列よりも優れたデータ構造の選択肢です。std::unique_ptr<T[]>あなたが所有権を想定しているヒープ配列への生のポインタを返すCのようなAPIを使用しているときが、私が理解できる唯一の状況について考えられます。

Charles Salviaの答えは妥当だと思いますstd::unique_ptr<T[]>。コンパイル時にサイズが不明な空の配列を初期化する唯一の方法です。スコット・マイヤーズはこの使用の動機について何を言わなければならないstd::unique_ptr<T[]>でしょうか?


4
サイズが固定されているがコンパイル時に不明なバッファや、コピーが許可されていないバッファなど、いくつかのユースケースを思い描いていなかったようです。vector stackoverflow.com/a/24852984/2436175よりも優先する理由として、効率性もあります。
アントニオ

17

std::vectorおよびとstd::arrayは異なりstd::unique_ptr、NULLポインタを所有できます。
これは、配列またはNULLのいずれかを期待するC APIを操作するときに便利です。

void legacy_func(const int *array_or_null);

void some_func() {    
    std::unique_ptr<int[]> ptr;
    if (some_condition) {
        ptr.reset(new int[10]);
    }

    legacy_func(ptr.get());
}

10

私が使用しているunique_ptr<char[]>ゲームエンジンで使用される事前に割り当てられたメモリプールを実装します。アイデアは、各フレームでメモリを割り当てたり解放したりせずに、衝突要求の結果やその他の物理学のようなものを返すために、動的割り当ての代わりに使用される事前割り当てされたメモリプールを提供することです。破棄ロジックを必要とせず(メモリの割り当て解除のみ)、ライフタイムが制限されたオブジェクト(通常は1、2、または3フレーム)を割り当てるためにメモリプールが必要なこの種のシナリオには、非常に便利です。


9

一般的なパターンは、一部の Windows Win32 API呼び出しで見つかりstd::unique_ptr<T[]>ます。たとえば、一部のWin32 APIを呼び出すときに、出力バッファーの大きさが正確にわからない場合(内部にデータを書き込む)を使用すると便利です。そのバッファ):

// Buffer dynamically allocated by the caller, and filled by some Win32 API function.
// (Allocation will be made inside the 'while' loop below.)
std::unique_ptr<BYTE[]> buffer;

// Buffer length, in bytes.
// Initialize with some initial length that you expect to succeed at the first API call.
UINT32 bufferLength = /* ... */;

LONG returnCode = ERROR_INSUFFICIENT_BUFFER;
while (returnCode == ERROR_INSUFFICIENT_BUFFER)
{
    // Allocate buffer of specified length
    buffer.reset( BYTE[bufferLength] );
    //        
    // Or, in C++14, could use make_unique() instead, e.g.
    //
    // buffer = std::make_unique<BYTE[]>(bufferLength);
    //

    //
    // Call some Win32 API.
    //
    // If the size of the buffer (stored in 'bufferLength') is not big enough,
    // the API will return ERROR_INSUFFICIENT_BUFFER, and the required size
    // in the [in, out] parameter 'bufferLength'.
    // In that case, there will be another try in the next loop iteration
    // (with the allocation of a bigger buffer).
    //
    // Else, we'll exit the while loop body, and there will be either a failure
    // different from ERROR_INSUFFICIENT_BUFFER, or the call will be successful
    // and the required information will be available in the buffer.
    //
    returnCode = ::SomeApiCall(inParam1, inParam2, inParam3, 
                               &bufferLength, // size of output buffer
                               buffer.get(),  // output buffer pointer
                               &outParam1, &outParam2);
}

if (Failed(returnCode))
{
    // Handle failure, or throw exception, etc.
    ...
}

// All right!
// Do some processing with the returned information...
...

std::vector<char>これらの場合にのみ使用できます。
Arthur Tacca

@ArthurTacca-...コンパイラーがバッファー内のすべての文字を1つずつ0に初期化してもかまわない場合。
TED

9

私はstd::unique_ptr<bool[]>HDF5ライブラリ(効率的なバイナリデータストレージ用のライブラリで、科学でよく使用されている)にあるを使用しなければならない場合に直面しました。一部のコンパイラー(私の場合はVisual Studio 2015)は、std::vector<bool>(各バイトで8つのブール値を使用して)圧縮を提供します。これは、HDF5のような、その圧縮を気にしない問題です。を使用するとstd::vector<bool>、その圧縮のためにHDF5は最終的にゴミを読み取っていました。

std::vectorうまくいかなかった場合、救助のためにそこに誰がいて、動的配列をきれいに割り当てる必要があると思いますか?:-)


9

一言で言えば、これは、最もメモリ効率が高いことです。

Aにstd::stringは、ポインター、長さ、および「short-string-optimization」バッファーが付属しています。しかし、私の状況は、ほとんど常に空の文字列を、何十万もの構造に格納する必要があるということです。Cでは、私はを使用するだけchar *で、ほとんどの場合nullになります。これはC ++でも機能しますが、a char *にはデストラクタがなく、自分自身を削除する方法がわかりません。対照的に、a std::unique_ptr<char[]>はスコープ外になると自分自身を削除します。空std::stringは32バイトを占有しますが、空std::unique_ptr<char[]>は8バイトを占有します。つまり、ポインタのサイズとまったく同じです。

最大の欠点は、文字列の長さを知りたいときはいつでも、strlenそれを呼び出さなければならないということです。


3

Deviceでメモリを割り当てるときに、GPUでのCUDAプログラミングのケースのvector代わりに「使用する必要がある」と考える人に答えるunique_ptrには、ポインタ配列(を使用cudaMalloc)に移動する必要があります。次に、Hostでこのデータを取得するときは、もう一度ポインターを取得する必要があり、ポインターをunique_ptr簡単に処理できます。double*への変換の追加コストvector<double>は不要であり、パフォーマンスの低下につながります。


3

を許可して使用するもう1つの理由std::unique_ptr<T[]>は、これまでのところ応答で言及されていません。これにより、配列要素の型を前方宣言できるようになります。

これは、連鎖を最小限にしたい場合に便利です。 #include、ヘッダー内ステートメント(ビルドのパフォーマンスを最適化するため)。

例えば ​​-

myclass.h:

class ALargeAndComplicatedClassWithLotsOfDependencies;

class MyClass {
   ...
private:
   std::unique_ptr<ALargeAndComplicatedClassWithLotsOfDependencies[]> m_InternalArray;
};

myclass.cpp:

#include "myclass.h"
#include "ALargeAndComplicatedClassWithLotsOfDependencies.h"

// MyClass implementation goes here

上記のコード構造により、で必要な内部実装の依存関係を含める必要なしに、誰でも#include "myclass.h"を使用できますMyClassMyClass::m_InternalArrayます。

m_InternalArraystd::array<ALargeAndComplicatedClassWithLotsOfDependencies>、またはとしてstd::vector<...>それぞれ宣言されている場合-結果は、コンパイル時エラーである不完全な型の使用が試行されます。


この特定のユースケースでは、Pimplパターンを選択して依存関係を解消します。プライベートでのみ使用する場合は、クラスメソッドが実装されるまで定義を延期できます。公開で使用する場合、クラスのユーザーはに関する具体的な知識をすでに持っているはずclass ALargeAndComplicatedClassWithLotsOfDependenciesです。したがって、論理的にはそのようなシナリオに遭遇すべきではありません。

3

受け入れられた答えの精神に強く反対することはできません。「最後の手段」?それとは程遠い!

私の見たところ、Cおよび他のいくつかの同様の言語と比較してC ++の最も強力な機能の1つは、制約を表現して、コンパイル時にチェックし、偶発的な誤用を防ぐことができることです。そのため、構造を設計するときは、どのような操作を許可するかを自問してください。他のすべての使用は禁止する必要があります。そのような制限を静的に(コンパイル時に)実装して、誤用が原因でコンパイルエラーが発生するようにするのが最善です。

したがって、配列が必要な場合、次の質問への回答でその動作を指定します。1。サイズはa)実行時に動的、またはb)静的ですが、実行時にのみ既知、またはc)静的でコンパイル時に既知ですか?2.配列をスタックに割り当てることはできますか?

そして答えに基づいて、これはそのような配列に最適なデータ構造であると私が見ているものです:

       Dynamic     |   Runtime static   |         Static
Stack std::vector      unique_ptr<T[]>          std::array
Heap  std::vector      unique_ptr<T[]>     unique_ptr<std::array>

そうだね unique_ptr<std::array>も考慮する必要があると。どちらも最終手段ではありません。アルゴリズムに最適なものを考えてください。

これらはすべて、データ配列への生のポインタ(vector.data()/ array.data()/ uniquePtr.get())を介してプレーンC APIと互換性があります。

PS上記の考慮事項とは別に、所有権も1つstd::arrayありstd::vectorます。値のセマンティクス(値によるコピーと受け渡しをネイティブでサポート)があり、unique_ptr<T[]>移動のみが可能(単一の所有権を適用)です。どちらも、さまざまなシナリオで役立ちます。逆に、単純な静的配列(int[N])と単純な動的配列(new int[10])はどちらも提供しないため、可能であれば回避する必要があります。これは、大多数のケースで可能です。それだけでは不十分な場合、単純な動的配列でもサイズを問い合わせる方法がありません。メモリの破損やセキュリティホールが発生する可能性があります。


2

ハッチの反対側で "キャッチ"された後の有効期間の測定値を持つ既存のAPI(ウィンドウメッセージまたはスレッド関連のコールバックパラメーターを考える)を介して単一のポインターを突くだけの場合、これらは可能な最も正しい答えである可能性があります。しかし、これは呼び出しコードとは無関係です:

unique_ptr<byte[]> data = get_some_data();

threadpool->post_work([](void* param) { do_a_thing(unique_ptr<byte[]>((byte*)param)); },
                      data.release());

私たちは皆、自分たちにとっていいものになりたいと思っています。C ++はそれ以外の場合のためのものです。


2

unique_ptr<char[]>CのパフォーマンスとC ++の利便性が必要な場所で使用できます。数百万(信頼できない場合は数十億)の文字列を操作する必要があるとします。それらのそれぞれを別々にstringまたはvector<char>オブジェクトオブジェクト、メモリ(ヒープ)管理ルーチンの障害になります。特に、異なる文字列を何度も割り当てて削除する必要がある場合。

ただし、その数の文字列を格納するために単一のバッファを割り当てることができます。char* buffer = (char*)malloc(total_size);明らかな理由で気に入らない場合(明らかでない場合は、「スマートptrを使用する理由」を検索してください)。あなたはむしろしたいですunique_ptr<char[]> buffer(new char[total_size]);

類推すると、同じパフォーマンスと利便性の考慮事項が非charデータにも適用されます(数百万のベクトル/行列/オブジェクトを考慮してください)。


それらすべてを1つの大きなものにまとめませんvector<char>か?答えは、おそらくバッファを作成するときにゼロで初期化されるためだと思いますが、を使用する場合はそうではありませんunique_ptr<char[]>。しかし、この重要なナゲットはあなたの答えから欠落しています。
Arthur Tacca

2
  • バイナリ互換性の理由から、ポインタだけを含む構造が必要です。
  • で割り当てられたメモリを返すAPIとインターフェースする必要があります new[]
  • 会社またはプロジェクトには、std::vectorたとえば、不注意なプログラマが誤ってコピーを導入するのを防ぐために、を使用することに対する一般的なルールがあります。
  • この場合、不注意なプログラマが誤ってコピーを導入することを防ぎたいと考えています。

C ++コンテナは、ポインタを使用したローリング独自のものよりも優先されるという一般的なルールがあります。これは原則です。例外があります。さらにあります。これらは単なる例です。


0

コピー構築できないオブジェクトの動的配列が必要な場合は、配列へのスマートポインターが適しています。たとえば、アトミックの配列が必要な場合はどうでしょうか。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.