回答:
過去4年間、私はこの問題についてかなり考えてきました。push_back
vs に関するほとんどの説明emplace_back
は全体像を見逃しているという結論に達しました。
昨年、私はC ++ Now でC ++ 14の型の演繹についてプレゼンテーションを行いました。push_back
vs. についてemplace_back
13: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へのアップグレードの成功と見なされたでしょう。代わりに、私はバグをマスクし、数か月後までそれを見つけませんでした。
std::unique_ptr<T>
からの明示的なコンストラクターがありT *
ます。emplace_back
は明示的なコンストラクターを呼び出すことができるため、所有していないポインターを渡しても問題なくコンパイルされます。ただし、v
スコープから外れると、デストラクタはdelete
そのポインタを呼び出そうとしますが、これnew
は単なるスタックオブジェクトであるため、割り当てられていません。これは未定義の動作につながります。
explicit
コンストラクターは、キーワードがexplicit
適用されたコンストラクターです。「暗黙の」コンストラクターは、そのキーワードを持たないコンストラクターです。以下の場合std::unique_ptr
からのコンストラクタT *
の実装は、std::unique_ptr
そのコンストラクタを書いたが、ここで問題があると呼ばれるそのタイプのユーザーというemplace_back
こと明示的なコンストラクタと呼ばれ、。そうであった場合push_back
、そのコンストラクターを呼び出す代わりに、暗黙的なコンストラクターのみを呼び出すことができる暗黙的な変換に依存することになります。
push_back
常に均一な初期化の使用を許可します。これはとても気に入っています。例えば:
struct aggregate {
int foo;
int bar;
};
std::vector<aggregate> v;
v.push_back({ 42, 121 });
一方、v.emplace_back({ 42, 121 });
動作しません。
{}
構文を使用して実際のコンストラクターを呼び出す場合は、を削除して{}
を使用できますemplace_back
。
{}
構文を使用して実際のコンストラクターを呼び出すことができます。aggregate
2つの整数をとるコンストラクターを与えることができ、このコンストラクターは{}
構文を使用するときに呼び出されます。重要なのは、コンストラクターを呼び出そうとしている場合emplace_back
は、コンストラクターがインプレースで呼び出されるためです。そのため、タイプがコピー可能である必要はありません。
emplace
明示的に定型のコンストラクタを記述することなく集約。また、現時点でそれが欠陥として扱われ、バックポートの対象となるか、C ++ <20のユーザーがSoLのままであるかどうかも不明です。
C ++ 11以前のコンパイラとの下位互換性。
push_back
は、望ましい場合のユースケースを求めています。
emplace_back
はありませんpush_back
。それはそれの潜在的に危険なバージョンです。他の答えを読んでください。
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
この場合、どう違うのですか?