web-dev-qa-db-ja.com

std :: vector <std :: string>へのpush_backとemplace_back

同様の質問が herehere に存在しますが、明確な答えが得られなかったと思うので、質問を作り直します。

C++ 11標準では、std :: vector :: Push_backとstd :: vector :: emplace_backの2つの方法でベクターの最後に新しい要素を追加しています。

それらの違いは、std :: vector :: emplace_backがオブジェクトを適切な場所に構築し、std :: vector :: Push_backが基本的にオブジェクト(またはプリミティブ型)をコピーするか、ベクターの最後に移動することです。

次に、std :: vector :: Push_backは、std :: vectorにプリミティブ型を追加するためのより良いオプションを探します。例えば:

std::vector<int> vVec;
vVec.Push_back(10);
int iVar 30;
vVec.Push_back(iVar);

しかし、オブジェクトについて話すとき、私にはそれほど明確ではありません。たとえば、std :: stringのベクターを見てみましょう。リテラル文字列を追加する場合はstd :: vector :: emplace_backを使用し、文字列オブジェクトをコピーまたは移動する場合はstd :: vector :: Push_back?

私が何を求めているかをより明確にするために、いくつかのシナリオを見てみましょう:

最初のシナリオ:

std::vector<string> vVec;
std::string sTest(“1st scenario”);
vVec.Push_back(sTest);  
vVec.emplace_back(sTest); 

Push_backはsTestのコピーを作成し、vVecの末尾にある新しい要素に関連付けます。一方、emplace_backはvVecの末尾に文字列オブジェクトを作成し、sTestのコンテンツをその中にコピーします。この場合、どちらがより効率的か、重要ではありませんか?

2番目のシナリオ:

std::vector<string> vVec;
std::string sTest1(“2st scenario”);
std::string sTest2(“2st scenario”);
vVec.Push_back(move(sTest1));   
vVec.emplace_back(move(sTest2)); 

Push_backはセマンティックを移動する準備ができていますが、emplace_backはどうなりますか?この場合、どちらがより効率的ですか?

3番目のシナリオ:

std::vector<string> vVec;
std::string sTest("3st scenario");
vVec.Push_back(sTest.substr(4,4));
vVec.emplace_back(sTest.substr(4,4));

Std :: string :: substrが別のstd :: stringを返すため、2番目のシナリオと同じ状況になりますか?

結論

std :: vector :: emplace_backがstd :: vector :: Push_backよりもリテラルが含まれている場合(その場での構築)にのみ効率的である場合、それを使用することは本当に正当ですか?もしそうなら、私にいくつかの例を挙げてもらえますか?

Std :: vector :: emplace_backがc ++ 0xではなくc ++ 11に実装されたことに注意することが重要です。それが存在するのには十分な理由があると思います。

7
TheArquitect

Ascheplerが言ったようにstd :: vector :: emplaceを使用する利点を実際に理解するには少し時間がかかります。

私はそれが構築されたときにいくつかのデータを受け取る独自のクラスがある場合に使用するより良いシナリオを見つけました。

より明確にするために、次のように仮定します。

  1. MyObjectのベクトル
  2. MyObjectは、構築するために3つの引数を受け取る必要があります
  3. 関数get1stElem()、get2ndElem()およびget3rdElem()は、MyObjectインスタンスを構築するために必要な要素を提供します

次に、次のような行を作成します。

vVec.emplace(get1stElem(), get2ndElem(), get3rdElem());

次に、std :: vector :: emplaceは、std :: vector :: Push_backよりも効率的にMyObjectを適切に構築します。

2
TheArquitect

両方の関数がシナリオ1で1つのコピーコンストラクターを呼び出し、シナリオ2または3で1つの移動コンストラクターを呼び出すため、3つのシナリオで大きな違いはありません。

しかし、10 'x'文字の文字列を作成したい場合はどうでしょうか。この場合、選択肢は

vVec.Push_back(std::string(10, 'x'));
vVec.emplace_back(10, 'x');

この場合、Push_backはカスタムstringコンストラクターを呼び出してから移動コンストラクターを呼び出しますが、emplace_backはカスタムstringコンストラクターを直接呼び出して、移動へ​​の呼び出しを保存しますコンストラクタ。

std::stringの移動コンストラクターは大したことではないかもしれませんが、emplace関数は、オブジェクトに効率的な移動コンストラクターがない場合に保存でき、クラス型に削除された移動がある場合でも使用できます何らかの理由でコンストラクタ。 (OK、std::vectorはmoveコンストラクタとcopyコンストラクタを削除すると満足できませんが、他のコンテナはそれで問題ありません。)

13
aschepler

最初に明確にします:

emplaceファミリーはargumentsをオブジェクトではなくコンストラクタに受け入れます自身

次に、それらの引数を使用してオブジェクトを代わりに構築します。一時オブジェクトを構築してから、それをコンテナにコピーまたは移動することはありません。

つまり、引数として同じ型のオブジェクトを取ることは、コピーと移動の構築が行うこととまったく同じです。そのため、例では、それらは同じコンストラクタを呼び出します。それらは、すでに構築されたstringで呼び出されます。

emplacePushが完全に異なる場合は、emplaceが、オブジェクト自体ではないコンストラクター引数を使用して呼び出されます。emplaceは一時オブジェクトを構築する必要はありません。 そしてコンテナにコピーします。

std::vector<std::string> strvec;
strvec.emplace_back("abc")  //calls string(const char*)

strvec.Push_back("abc")          //is equivalent to...
strvec.Push_back(string("abc"))  //which calls string(const char*) then string(string&&)
10
Passer By