なぜemplace_backの代わりにpush_backを使用するのですか?


232

C ++ 11ベクトルには新しい関数がありますemplace_backpush_backコンパイラの最適化に依存してコピーを回避するとは異なり、emplace_back完全な転送を使用して引数をコンストラクターに直接送信し、オブジェクトをインプレースで作成します。私にemplace_backは、すべてのpush_backことができるように思えますが、場合によっては、それがうまくいくこともあります(悪くなることはありません)。

どのような理由で使用する必要がありますpush_backか?

回答:


162

過去4年間、私はこの問題についてかなり考えてきました。push_backvs に関するほとんどの説明emplace_backは全体像を見逃しているという結論に達しました。

昨年、私はC ++ Now でC ++ 14の型の演繹についてプレゼンテーションを行いました。push_backvs. についてemplace_back13:49 から話し始めますが、その前にいくつかの裏付けとなる証拠を提供する有用な情報があります。

実際の主な違いは、暗黙的なコンストラクターと明示的なコンストラクターに関係しています。push_backまたはに渡したい引数が1つある場合を考えemplace_backます。

std::vector<T> v;
v.push_back(x);
v.emplace_back(x);

最適化コンパイラーがこれを手に入れたら、生成されたコードに関してこれら2つのステートメントに違いはありません。従来の知識ではpush_back、一時的なオブジェクトが作成され、次にそのオブジェクトが移動されvますemplace_backが、引数は転送され、コピーや移動なしで直接配置されます。これは、標準ライブラリで記述されたコードに基づいて当てはまる可能性がありますが、最適化コンパイラの仕事は記述したコードを生成することであると誤って想定しています。最適化コンパイラの仕事は、実際には、プラットフォーム固有の最適化の専門家で、保守性を気にせず、パフォーマンスだけを気にしていた場合に記述したコードを生成することです。

これらの2つのステートメントの実際の違いは、より強力なものemplace_backはあらゆるタイプのコンストラクターpush_backを呼び出すのに対し、より注意深いのは暗黙的なコンストラクターのみを呼び出すということです。暗黙のコンストラクタは安全であると想定されています。Uからを暗黙的に作成できる場合T、はUすべての情報をT損失なく保持できると言っています。aを渡すことはほとんどすべての状況で安全でTあり、U代わりにa にしても誰も気にしません。暗黙的なコンストラクターの良い例は、からstd::uint32_tへの変換std::uint64_tです。暗黙的な変換の悪い例はdoubleへの変換ですstd::uint8_t

プログラミングには注意が必要です。強力な機能を使用するのは望ましくありません。強力な機能を使用するほど、誤って予期しないことを簡単に実行してしまうからです。明示的なコンストラクターを呼び出す場合は、の能力が必要ですemplace_back。暗黙のコンストラクタのみを呼び出したい場合は、の安全性を守ってくださいpush_back

std::vector<std::unique_ptr<T>> v;
T a;
v.emplace_back(std::addressof(a)); // compiles
v.push_back(std::addressof(a)); // fails to compile

std::unique_ptr<T>からの明示的なコンストラクタがありT *ます。emplace_backは明示的なコンストラクターを呼び出すことができるため、所有していないポインターを渡しても問題なくコンパイルされます。ただし、vスコープから外れると、デストラクタは、スタックオブジェクトであるためdelete割り当てられなかったポインタを呼び出そうとしnewます。これは未定義の動作につながります。

これは発明されたコードだけではありません。これは私が遭遇した本番のバグでした。コードはstd::vector<T *>でしたが、コンテンツを所有していました。C ++ 11への移行の一環として、ベクターがそのメモリを所有しているT *ことstd::unique_ptr<T>を示すように正しく変更しました。ただし、私はこれらの変更を2012年の理解に基づいていました。その間、「emplace_backはpush_backで実行できるすべてのことを行うので、なぜpush_backを使用するのですか?」と考えたため、もに変更しpush_backましたemplace_back

代わりに、より安全なを使用するようにコードを残した場合push_back、私はこの長年のバグを即座にキャッチし、C ++ 11へのアップグレードの成功と見なされたでしょう。代わりに、私はバグをマスクし、数か月後までそれを見つけませんでした。


あなたの例でemplaceが正確に何をするのか、なぜそれが間違っているのかを詳しく説明できれば、それは助けになるでしょう。
eddi

4
@eddi:これを説明するセクションを追加しました:std::unique_ptr<T>からの明示的なコンストラクターがありT *ます。emplace_backは明示的なコンストラクターを呼び出すことができるため、所有していないポインターを渡しても問題なくコンパイルされます。ただし、vスコープから外れると、デストラクタはdeleteそのポインタを呼び出そうとしますが、これnewは単なるスタックオブジェクトであるため、割り当てられていません。これは未定義の動作につながります。
David Stone

これを投稿してくれてありがとう。私は答えを書いたときそれについて知りませんでしたが、私は後でそれを学んだときに自分でそれを書いたいと思います:)私は本当に彼らが見つけることができる最もヒップなことをするために新しい機能に切り替える人々を平手打ちしたいです。みんな、C ++ 11の前にもC ++を使っていたので、問題のすべてが問題になるわけではありませんでした。機能を使用する理由がわからない場合は、使用しないください。あなたがこれを投稿してうれしいです、そして私がそれが私の投票数を上回ってもっと私の投票数が増えることを願っています +1
user541686 2017

1
@CaptainJacksparrow:私が暗示的および明示的に言っているように見えます。どの部分が混乱しましたか?
David Stone

4
@CaptainJacksparrow:explicitコンストラクターは、キーワードがexplicit適用されたコンストラクターです。「暗黙の」コンストラクターは、そのキーワードを持たないコンストラクターです。以下の場合std::unique_ptrからのコンストラクタT *の実装は、std::unique_ptrそのコンストラクタを書いたが、ここで問題があると呼ばれるそのタイプのユーザーというemplace_backこと明示的なコンストラクタと呼ばれ、。そうであった場合push_back、そのコンストラクターを呼び出す代わりに、暗黙的なコンストラクターのみを呼び出すことができる暗黙的な変換に依存することになります。
David Stone、

117

push_back常に均一な初期化の使用を許可します。これはとても気に入っています。例えば:

struct aggregate {
    int foo;
    int bar;
};

std::vector<aggregate> v;
v.push_back({ 42, 121 });

一方、v.emplace_back({ 42, 121 });動作しません。


58
これは、集約初期化と初期化リストの初期化にのみ適用されることに注意してください。{}構文を使用して実際のコンストラクターを呼び出す場合は、を削除して{}を使用できますemplace_back
Nicol Bolas

ばかげた質問時間:emplace_backは構造体のベクトルにまったく使用できないのですか?それとも、リテラル{42,121}を使用したこのスタイルではないのですか?
Phil H

1
@LucDanton:前述のように、これは集約および初期化リストの初期化にのみ適用されます。{}構文を使用して実際のコンストラクターを呼び出すことができます。aggregate2つの整数をとるコンストラクターを与えることができ、このコンストラクターは{}構文を使用するときに呼び出されます。重要なのは、コンストラクターを呼び出そうとしている場合emplace_backは、コンストラクターがインプレースで呼び出されるためです。そのため、タイプがコピー可能である必要はありません。
Nicol

11
これは規格の欠陥と見なされ、解決されました。cplusplus.github.io/LWG/lwg-active.html#2089
David Stoneを

3
@DavidStoneそれが解決された場合、それはまだ「アクティブ」リストにありません...いいえ?未解決の問題であるようです。向かった最新のアップデート、「[2018年8月23日バタビアの問題が処理]は、」、「と言うP0960(現在飛行中は)この問題を解決すべきである。」そして、私はまだ、コンパイルコードしようとすることはできませんemplace明示的に定型のコンストラクタを記述することなく集約。また、現時点でそれが欠陥として扱われ、バックポートの対象となるか、C ++ <20のユーザーがSoLのままであるかどうかも不明です。
underscore_d

80

C ++ 11以前のコンパイラとの下位互換性。


21
それはC ++の呪いのようです。新しいリリースごとにたくさんのクールな機能を手に入れますが、多くの企業は互換性のために古いバージョンを使用し続けたり、特定の機能の使用を(禁止しない場合は)阻止したりしています。
ダンアルバート

6
@Mehrdad:あなたが素晴らしいことができるのになぜ十分に満足するのですか?たとえそれで十分だったとしても、私は確かにblubでプログラミングしたくありません。これは特にこの例に当てはまるわけではありませんが、互換性を確保するためにC89でのプログラミングにほとんどの時間を費やしている人にとって、これは間違いなく実際の問題です。
ダンアルバート

3
これは実際の質問に対する答えではないと思います。私にとって彼push_backは、望ましい場合のユースケースを求めています。
Boy氏、

3
@ Mr.Boy:C ++ 11以前のコンパイラとの下位互換性を確保したい場合に適しています。それは私の答えでは不明確でしたか?
user541686 2016

6
これは私が予想していたよりもずっと注目されてきたので、皆さんがこれを読んでいる場合、の「素晴らしい」バージョンでemplace_backはありませんpush_back。それはそれの潜在的に危険なバージョンです。他の答えを読んでください。
user541686 2017

68

emplace_backの一部のライブラリ実装は、Visual Studio 2012、2013、2015に同梱されているバージョンを含め、C ++標準で指定されたとおりに動作しません。

既知のコンパイラのバグに対応するためにstd::vector::push_back()、パラメータがイテレータまたは呼び出し後に無効になる他のオブジェクトを参照する場合に使用することをお勧めします。

std::vector<int> v;
v.emplace_back(123);
v.emplace_back(v[0]); // Produces incorrect results in some compilers

1つのコンパイラでは、vには、予想される123と123ではなく、値123と21が含まれます。これは、2回目の呼び出しのemplace_back結果、サイズが変更され、その時点でv[0]無効になるためです。

上記のコードの実際の実装でpush_back()emplace_back()、次の代わりに使用します。

std::vector<int> v;
v.emplace_back(123);
v.push_back(v[0]);

注:intのベクターの使用は、デモンストレーションを目的としています。動的に割り当てられたメンバー変数を含むはるかに複雑なクラスでこの問題が見つかりemplace_back()、ハードクラッシュが発生しました。


待つ。これはバグのようです。push_backこの場合、どう違うのですか?
バルキ2015

12
emplace_back()の呼び出しは、完全な転送を使用して所定の位置で構築を実行するため、ベクトルのサイズが変更されるまで(v [0]は無効になります)、v [0]は評価されません。push_backは新しい要素を作成し、必要に応じて要素をコピーまたは移動します。v[0]は再割り当ての前に評価されます。
マルク・

1
@Marc:範囲内の要素に対してもemplace_backが機能することが規格によって保証されています。
David Stone、

1
@DavidStone:標準でこの動作が保証されている場所への参照を提供できますか?いずれにしても、Visual Studio 2012および2015は正しくない動作を示します。
Marc

4
@cameino:emplace_backは、パラメーターの評価を遅らせて不要なコピーを減らすために存在します。動作は未定義であるか、コンパイラのバグ(標準の分析は保留中)です。私は最近Visual Studio 2015に対して同じテストを実行し、リリースx64では123,3、リリースWin32では123,40、デバッグx64およびデバッグWin32では123、-572662307を取得しました。
マルク・
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.