私は過去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を渡すのは安全であり、代わりにそれを作成しても誰も気にしません。暗黙のコンストラクターの良い例は、からへの変換です。暗黙の変換の悪い例はです。U
T
T
U
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へのアップグレードの成功と見なされていたでしょう。代わりに、私はバグをマスクし、数か月後までそれを見つけませんでした。