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

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

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

を使用する理由は何ですかpush_back?

ベストアンサー1

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

昨年、私はC++Nowでプレゼンテーションを行いました。C++14 における型推論push_back13:49 からvs.について話し始めます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暗黙的に構築できる場合、 はすべての情報を損失なく保持できるということになります。 を渡すことはほぼすべての状況で安全であり、代わりに にしても誰も気にしません。暗黙的なコンストラクタの良い例は、からへの変換です。暗黙的な変換の悪い例はへの変換です。UTUTTUstd::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_backemplace_back

代わりに、より安全な を使用するようにコードを残しておけばpush_back、この長年のバグをすぐに見つけることができ、C++11 へのアップグレードの成功と見なされたでしょう。しかし、私はバグを隠蔽し、数か月後まで発見しませんでした。

おすすめ記事