Cスタイルの配列からstd :: vectorを初期化する方法は?


174

std::vectorCスタイルの配列からを初期化する最も安い方法は何ですか?

例:次のクラスにはがvectorありますが、外部の制限により、データはCスタイルの配列として渡されます。

class Foo {
  std::vector<double> w_;
public:
  void set_data(double* w, int len){
   // how to cheaply initialize the std::vector?
}

言うまでもなく、呼び出しw_.resize()て要素をループするか、を呼び出すことができますstd::copy()。より良い方法はありますか?


11
問題の核心は、Cスタイルの配列を作成するために同じアロケーターが使用されたかどうかをベクトルが知る方法がないことです。そのため、ベクターは独自のアロケーターを使用してメモリを割り当てる必要があります。それ以外の場合は、基になる配列を交換して、自分の配列に置き換えるだけです。
Void

回答:


233

ポインタをイテレータとして扱うことができることを忘れないでください:

w_.assign(w, w + len);

3
これは実装の品質の問題です。イテレータにはカテゴリを指定するタグがあるため、の実装は、assignそれらを使用して最適化することは確かに自由です。少なくともVC ++では、まさにそれを実行します。
Pavel Minaev 2010年

38
簡単な解決策はstd :: vector <double> w_(w、w + len);です。
jamk 2013年

1
これにより、「w_」用に新しく作成されたストレージに要素がコピーされます。「w_.data」は「w」を指しません。それでも「w」の割り当てを解除する必要があります。所有権の譲渡はありません
Indy9000 2014年

1
それが最後を過ぎた1つの要素である場合、それは問題ないはずです(v.end()同様のケースでベクトルを使用して最後を過ぎた1つを指すイテレータと同じように)。アサーションを取得した場合、何かが他の場所から外れています。
Pavel Minaev

1
すぐに、ベクトルがスコープから外れると、配列メモリの割り当てが解除されますか?
Adrian Koch

41

これは1回限りの割り当てであるのか、複数回発生する可能性があるのか​​がはっきりしないため、「初期化」という単語を使用します。

1回だけの初期化が必要な場合は、それをコンストラクターに入れ、2つの反復子ベクトルコンストラクターを使用できます。

Foo::Foo(double* w, int len) : w_(w, w + len) { }

それ以外の場合は、以前に提案したように割り当てを使用します。

void set_data(double* w, int len)
{
    w_.assign(w, w + len);
}

1
私の場合、割り当ては繰り返し発生します。
フランク

12

配列のサイズを自動的に「学習」できます。

template<typename T, size_t N>
void set_data(const T (&w)[N]){
    w_.assign(w, w+N);
}

うまくいけば、上記のようにインターフェイスをset_dataに変更できます。それでも、最初の引数としてCスタイルの配列を受け入れます。たまたまそれを参照するだけです。


使い方

[更新:サイズの学習に関するより包括的な議論については、こちらを参照してください]

これはより一般的な解決策です:

template<typename T, size_t N>
void copy_from_array(vector<T> &target_vector, const T (&source_array)[N]) {
    target_vector.assign(source_array, source_array+N);
}

これは、配列が配列への参照として渡されるため機能します。C / C ++では、配列を関数として渡すことはできません。代わりに、配列がポインターに減衰し、サイズが失われます。ただし、C ++では、配列への参照を渡すことができます。

参照によって配列を渡すには、型が完全に一致する必要があります。配列のサイズはその型の一部です。これは、テンプレートパラメータNを使用してサイズを学習できることを意味します。

ベクトルを返すこの関数を使用する方が簡単な場合があります。適切なコンパイラ最適化が有効になっていると、これは見かけよりも速くなるはずです。

template<typename T, size_t N>
vector<T> convert_array_to_vector(const T (&source_array)[N]) {
    return vector<T>(source_array, source_array+N);
}

1
最後のサンプルでreturn { begin(source_array), end(source_array) };も可能です
MM

12

まあ、Pavelは近かったですが、シーケンシャルコンテナーをacスタイルの配列から初期化するためのさらにシンプルでエレガントなソリューションがあります。

あなたの場合:

w_ (array, std::end(array))
  • arrayは配列の先頭へのポインタを取得します(名前をキャッチしませんでした)、
  • std :: end(array)は、配列の最後までのイテレータを取得します。

1
これにはC ++のどのインクルード/バージョンが必要ですか?
Vlad

1
これは、少なくともc ++ 98以降のstd :: vectorのコンストラクターの1つです。これは「範囲コンストラクター」と呼ばれます。cplusplus.com/reference/vector/vector/vector試してください。
ムグレル2015年

1
より独立したバージョンは次のとおりです:w_(std :: begin(array)、std :: end(array)); (将来的には、C ++コンテナーのC配列を変更することができます)。
Andrew Romanov、

13
注意してください、これは実際のarray(通常はグローバルまたはローカル(現在の関数で宣言されている)配列からコピーしていることを意味します)がある場合にのみ機能します。OPの場合、彼はポインターと長さを受け取っていますが、長さにテンプレート化されていないため、サイズ付き配列などへのポインターを受け取るように変更できないため、機能しstd::endません。
ShadowRanger 2016年

2
vectorオーバーロードしないoperator()ため、これはコンパイルされません。std::endポインターで呼び出されても意味がありません(質問は、ポインターと別の長さ変数からベクトルを割り当てるように求めます)。それはあなたが提案しようとしていることについてより多くの文脈を示すためにあなたの答えを改善するでしょう
MM

2

簡単な一般的な答え:

std::vector<double> vec(carray,carray+carray_size); 

または質問固有:

std::vector<double> w_(w,w+len); 

上記に基づく:ポインタをイテレータとして扱うことができることを忘れないでください


0

std::vector<double>::assignそれは小さなコードなので、行く方法です。しかし、実際にはどのように機能しますか?サイズを変更してからコピーしませんか?STLのMS実装では、まさにそれを使用しています。

を実装する(再)初期化するより速い方法はありませんstd::vector


ベクトルと配列の間でデータを共有する場合はどうなりますか?この場合、何かをコピーする必要がありますか?
Vlad

それは答えですか、それとも質問ですか?それは既存の答えに何をもたらしますか?
ジャン=フランソワ・ファーブル

@Jean-FrançoisFabreとあなたのコメントは何をもたらしますか?;)確かに、それは何年も前に与えられた貧しい答えです。
Janusz Lenar
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.