276

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

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

4

5 に答える 5

234

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

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

本当の主な違いは、暗黙的コンストラクターと明示的コンストラクターに関係しています。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と言っています。ほぼすべての状況でaを渡すのは安全であり、代わりにそれを作成しても誰も気にしません。暗黙のコンストラクターの良い例は、からへの変換です。暗黙の変換の悪い例はです。UTTUstd::uint32_tstd::uint64_tdoublestd::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へのアップグレードの成功と見なされていたでしょう。代わりに、私はバグをマスクし、数か月後までそれを見つけませんでした。

于 2016-04-28T15:48:17.697 に答える
126

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

struct aggregate {
    int foo;
    int bar;
};

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

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

于 2012-06-05T02:12:26.380 に答える
83

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

于 2012-06-05T02:03:21.127 に答える
72

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

あるコンパイラでは、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()ハードクラッシュが発生しました。

于 2015-02-10T15:35:58.990 に答える
0

C++-17コンパイラを使用したVisualStudio2019で何が起こるかを考えてみましょう。適切な引数が設定された関数にemplace_backがあります。次に、誰かがemplace_backによって呼び出されたコンストラクターのパラメーターを変更します。VSには警告はありません。コードも正常にコンパイルされ、実行時にクラッシュします。この後、コードベースからすべてのemplace_backを削除しました。

于 2020-08-27T15:59:21.880 に答える