web-dev-qa-db-ja.com

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

std::unique_ptrは配列をサポートしています。例えば:

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

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

その構文の用途はありますか?

197
fen

アロケータを使っても、std::vectorを使う贅沢がない人もいます。動的サイズの配列が必要な人もいるので、std::arrayが出ています。そして何人かの人々は配列を返すことが知られている他のコードから彼らの配列を得ます。そのコードはvectorなどを返すように書き直されることはありません。

unique_ptr<T[]>を許可することで、あなたはそれらのニーズに応えます。

要するに、あなたは必要をするときにunique_ptr<T[]>を使います。代替案が単にあなたのために働かないだろうとき。それは最後の手段のツールです。

219
Nicol Bolas

トレードオフがあります、そしてあなたはあなたが望むものと一致する解決策を選びます。私の頭の上から:

初期サイズ

  • vectorおよびunique_ptr<T[]>により、実行時にサイズを指定できます
  • arrayでは、サイズはコンパイル時にのみ指定できます

サイズ変更

  • arrayおよびunique_ptr<T[]>はサイズ変更を許可しません
  • vectorは行います

ストレージ

  • vectorおよびunique_ptr<T[]>は、オブジェクトの外部(通常はヒープ上)にデータを格納します。
  • arrayは、データを直接オブジェクトに格納します

コピーする

  • arrayvectorはコピーを許可します
  • 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は、任意の再割り当てでポインタ、参照、および反復子を無効にする可能性があります(そして、再割り当ては特定の操作でのみ発生する可能性があることを保証します)。

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

  • arrayvectorはどちらもコンテナです
  • unique_ptr<T[]>はコンテナではありません

私は認めざるを得ませんが、これはポリシーベースの設計に何らかのリファクタリングを行う機会のように見えます。

106
Pseudonym

あなたが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を選ぶのと同じ理由です。

60
Charles Salvia

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

28
Andy Prowl

Scott MeyersはこれをEffective Modern C++で言います。

std::unique_ptrstd::arraystd::vectorは、実際には常に生の配列よりも優れたデータ構造の選択であるため、配列に対するstd::stringの存在は、知的にのみ関心があるものです。 std::unique_ptr<T[]>が意味をなすのは、所有権を前提としているヒープ配列への生のポインタを返すC風のAPIを使用している場合だけです。

Charles Salviaの答えは関係あると思いますが、std::unique_ptr<T[]>はコンパイル時にサイズがわからない空の配列を初期化する唯一の方法です。 std::unique_ptr<T[]>を使う動機について、Scott Meyersはどう言うべきでしょうか。

20
newling

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());
}
12
george

一般的なパターンは、 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...
...
9
Mr.C64

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

9
Simon Ferquel

一言で言えば、それははるかに最もメモリ効率が良いです。

std::stringはポインタ、長さ、そして "short-string-optimize"バッファを持っています。しかし、私の状況では、何十万もの構造体で、ほとんど常に空の文字列を格納する必要があります。 Cでは、私はchar *を使うだけでよく、それはほとんどの場合nullです。 char *がデストラクタを持たず、自分自身を削除することを知らないことを除いて、これはC++でも動作します。対照的に、std::unique_ptr<char[]>は、範囲外になると自分自身を削除します。空のstd::stringは32バイトを占有しますが、空のstd::unique_ptr<char[]>は8バイトを占有します。つまり、正確にそのポインタのサイズです。

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

8
jorgbrown

私はstd::unique_ptr<bool[]>を使わなければならないケースに直面しました。それはHDF5ライブラリ(効率的なバイナリデータストレージのためのライブラリで、科学でよく使われていました)にありました。いくつかのコンパイラ(私の場合はVisual Studio 2015) std::vector<bool>の圧縮を提供する (各バイトに8個のブールを使用することによって)、これはその圧縮については気にしないHDF5のような何かのための破滅です。 std::vector<bool>を使うと、HDF5はその圧縮のために結局ゴミを読んでいました。

std::vectorがうまくいかず、動的配列をきれいに割り当てる必要がある場合に、誰が救助を求めていたのでしょうか。 :-)

あなたがunique_ptrの代わりにvectorname__を使わなければならないと思っている人々に答えるために、GPUでのCUDAプログラミングでは、Deviceにメモリを割り当てる際に(cudaMallocname__を使って)ポインタ配列を使わなければなりません。その後、Hostでこのデータを取得するときには、ポインタをもう一度探す必要があります。unique_ptrは、ポインタを簡単に処理するのに問題ありません。 double*vector<double>に変換する追加の費用は不要であり、perfの損失につながります。

3

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<...>としてそれぞれ宣言されている場合 - 結果は不完全型の使用を試みることになり、これはコンパイル時エラーです。

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

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

2
Jimmy Hartzell

ハッチの向こう側に「キャッチ」された後にある程度の寿命を持つ既存の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
Simon Buchan

unique_ptr<char[]>は、CのパフォーマンスとC++の便利さが必要な場合に使用できます。何百万もの文字列を操作する必要があるとします。それぞれを別々のstringname__またはvector<char>オブジェクトに格納することは、メモリー(ヒープ)管理ルーチンにとっては厄介なことになります。特に、異なる文字列を何度も割り当てたり削除したりする必要がある場合は、.

ただし、その多くの文字列を格納するために単一のバッファを割り当てることができます。明白な理由でchar* buffer = (char*)malloc(total_size);を好まないでください(明白でない場合は、 "smart ptrsを使用する理由"を検索してください)。 unique_ptr<char[]> buffer(new char[total_size]);をご希望ですか

同様に、同じパフォーマンスと便利さの考慮がnon-charname__データにも当てはまります(何百万ものベクトル/行列/オブジェクトを考慮してください)。

1
Serge Rogatch

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

0
Ilia Minkin