std::unique_ptr
は配列をサポートしています。例えば:
std::unique_ptr<int[]> p(new int[10]);
しかしそれは必要ですか?おそらくstd::vector
またはstd::array
を使うほうが便利です。
その構文の用途はありますか?
アロケータを使っても、std::vector
を使う贅沢がない人もいます。動的サイズの配列が必要な人もいるので、std::array
が出ています。そして何人かの人々は配列を返すことが知られている他のコードから彼らの配列を得ます。そのコードはvector
などを返すように書き直されることはありません。
unique_ptr<T[]>
を許可することで、あなたはそれらのニーズに応えます。
要するに、あなたは必要をするときにunique_ptr<T[]>
を使います。代替案が単にあなたのために働かないだろうとき。それは最後の手段のツールです。
トレードオフがあります、そしてあなたはあなたが望むものと一致する解決策を選びます。私の頭の上から:
初期サイズ
vector
およびunique_ptr<T[]>
により、実行時にサイズを指定できますarray
では、サイズはコンパイル時にのみ指定できますサイズ変更
array
およびunique_ptr<T[]>
はサイズ変更を許可しませんvector
は行いますストレージ
vector
およびunique_ptr<T[]>
は、オブジェクトの外部(通常はヒープ上)にデータを格納します。array
は、データを直接オブジェクトに格納しますコピーする
array
とvector
はコピーを許可しますunique_ptr<T[]>
はコピーを許可しませんスワップ/移動
vector
およびunique_ptr<T[]>
にはO(1) time swap
および移動操作がありますarray
にはO(n) time swap
およびmove操作があります。ここで、nは配列内の要素数です。ポインタ/参照/イテレータ無効化
array
は、たとえswap()
上であっても、オブジェクトが生きている間、ポインタ、参照、およびイテレータが決して無効化されないことを保証しますunique_ptr<T[]>
にはイテレータはありません。オブジェクトが生きている間、ポインタと参照はswap()
によって無効化されるだけです。 (交換後、ポインタは交換した配列を指すので、その意味ではまだ有効です。)vector
は、任意の再割り当てでポインタ、参照、および反復子を無効にする可能性があります(そして、再割り当ては特定の操作でのみ発生する可能性があることを保証します)。概念およびアルゴリズムとの互換性
array
とvector
はどちらもコンテナですunique_ptr<T[]>
はコンテナではありません私は認めざるを得ませんが、これはポリシーベースの設計に何らかのリファクタリングを行う機会のように見えます。
あなたがunique_ptr
を使うかもしれない一つの理由はあなたが value-initializing 配列の実行時コストを払いたくない場合です。
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
がPODの場合はT
は初期化しません。
C++ 11の値初期化オブジェクトおよびstd :: vectorコンストラクタ を参照してください。
vector::reserve
はここでは代替手段ではないことに注意してください。 std :: vector :: reserveの後に生のポインタにアクセスしても安全ですか?
Cプログラマがmalloc
よりcalloc
を選ぶのと同じ理由です。
std::vector
はコピーすることができ、unique_ptr<int[]>
は配列のユニークな所有権を表現することを可能にします。一方、std::array
では、コンパイル時にサイズを決定する必要があります。これは、状況によっては不可能な場合があります。
Scott MeyersはこれをEffective Modern C++で言います。
std::unique_ptr
、std::array
、std::vector
は、実際には常に生の配列よりも優れたデータ構造の選択であるため、配列に対するstd::string
の存在は、知的にのみ関心があるものです。std::unique_ptr<T[]>
が意味をなすのは、所有権を前提としているヒープ配列への生のポインタを返すC風のAPIを使用している場合だけです。
Charles Salviaの答えは関係あると思いますが、std::unique_ptr<T[]>
はコンパイル時にサイズがわからない空の配列を初期化する唯一の方法です。 std::unique_ptr<T[]>
を使う動機について、Scott Meyersはどう言うべきでしょうか。
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());
}
一般的なパターンは、 some WindowsWin32 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...
...
私はunique_ptr<char[]>
を使用して、ゲームエンジンで使用される事前割り当てメモリプールを実装しました。このアイデアは、各フレームでメモリを割り当てたり解放したりする必要なく、衝突要求の結果やその他の素粒子物理学のようなものを返すために動的割り当ての代わりに使用される事前割り当てメモリプールを提供することです。破壊ロジックを必要としない限られた寿命(通常1、2または3フレーム)でオブジェクトを割り当てるためにメモリプールが必要な場合(メモリ割り当て解除のみ)、この種のシナリオには非常に便利です。
一言で言えば、それははるかに最もメモリ効率が良いです。
std::string
はポインタ、長さ、そして "short-string-optimize"バッファを持っています。しかし、私の状況では、何十万もの構造体で、ほとんど常に空の文字列を格納する必要があります。 Cでは、私はchar *
を使うだけでよく、それはほとんどの場合nullです。 char *
がデストラクタを持たず、自分自身を削除することを知らないことを除いて、これはC++でも動作します。対照的に、std::unique_ptr<char[]>
は、範囲外になると自分自身を削除します。空のstd::string
は32バイトを占有しますが、空のstd::unique_ptr<char[]>
は8バイトを占有します。つまり、正確にそのポインタのサイズです。
最大の欠点は、文字列の長さを知りたいときはいつもstrlen
を呼び出さなければならないことです。
私はstd::unique_ptr<bool[]>
を使わなければならないケースに直面しました。それはHDF5ライブラリ(効率的なバイナリデータストレージのためのライブラリで、科学でよく使われていました)にありました。いくつかのコンパイラ(私の場合はVisual Studio 2015) std::vector<bool>
の圧縮を提供する (各バイトに8個のブールを使用することによって)、これはその圧縮については気にしないHDF5のような何かのための破滅です。 std::vector<bool>
を使うと、HDF5はその圧縮のために結局ゴミを読んでいました。
std::vector
がうまくいかず、動的配列をきれいに割り当てる必要がある場合に、誰が救助を求めていたのでしょうか。 :-)
あなたがunique_ptr
の代わりにvector
name__を使わなければならないと思っている人々に答えるために、GPUでのCUDAプログラミングでは、Deviceにメモリを割り当てる際に(cudaMalloc
name__を使って)ポインタ配列を使わなければなりません。その後、Hostでこのデータを取得するときには、ポインタをもう一度探す必要があります。unique_ptr
は、ポインタを簡単に処理するのに問題ありません。 double*
をvector<double>
に変換する追加の費用は不要であり、perfの損失につながります。
std::unique_ptr<T[]>
を許可して使用するもう1つの理由は、これまでのところ応答では説明されていませんでした。それは、配列要素型を前方宣言できるようにするためです。
これは、(ビルドのパフォーマンスを最適化するために)ヘッダー内の連鎖した#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"
に必要な内部実装の依存関係を含める必要なしに、MyClass::m_InternalArray
およびMyClass
を使用できます。
m_InternalArray
が代わりにstd::array<ALargeAndComplicatedClassWithLotsOfDependencies>
またはstd::vector<...>
としてそれぞれ宣言されている場合 - 結果は不完全型の使用を試みることになり、これはコンパイル時エラーです。
new[]
で割り当てられたメモリを返すAPIとインターフェースする必要があります。std::vector
の使用に対して一般的な規則を持っていますC++コンテナーは、ポインターを使用した独自のローリングよりも優先されるという一般的な規則があります。それは一般的な規則です。例外があります。もっとあります。これらは単なる例です。
ハッチの向こう側に「キャッチ」された後にある程度の寿命を持つ既存のAPI(ウィンドウメッセージまたはスレッド関連のコールバックパラメータを考える)を通して単一のポインタを突くだけで得られる場合、それらは可能な最も正しい答えかもしれません。しかし、これは呼び出しコードとは無関係です。
unique_ptr<byte[]> data = get_some_data();
threadpool->post_work([](void* param) { do_a_thing(unique_ptr<byte[]>((byte*)param)); },
data.release());
私たちは皆、物事が私たちにとって素晴らしいものになることを望んでいます。 C++はそれ以外の場合です。
unique_ptr<char[]>
は、CのパフォーマンスとC++の便利さが必要な場合に使用できます。何百万もの文字列を操作する必要があるとします。それぞれを別々のstring
name__またはvector<char>
オブジェクトに格納することは、メモリー(ヒープ)管理ルーチンにとっては厄介なことになります。特に、異なる文字列を何度も割り当てたり削除したりする必要がある場合は、.
ただし、その多くの文字列を格納するために単一のバッファを割り当てることができます。明白な理由でchar* buffer = (char*)malloc(total_size);
を好まないでください(明白でない場合は、 "smart ptrsを使用する理由"を検索してください)。 unique_ptr<char[]> buffer(new char[total_size]);
をご希望ですか
同様に、同じパフォーマンスと便利さの考慮がnon-char
name__データにも当てはまります(何百万ものベクトル/行列/オブジェクトを考慮してください)。
コピー構築不可能なオブジェクトの動的配列が必要な場合は、配列へのスマートポインタが適しています。たとえば、アトミックの配列が必要な場合はどうでしょうか。