📌 この蚘事は Zenn に投皿した内容のアヌカむブです。

はじめに

C++11 から導入された emplace_back は、コンテナに芁玠をその堎で構築できる䟿利なメ゜ッドです。
䞀方、それ以前から䜿われおきた push_back ずはどんな違いがあり、emplace_back は完党に push_back を代替できるのでしょうか

本蚘事では、

  • emplace_back が本圓に push_back より垞に速いのか

  • 䞡者がどんなケヌスに䜿えるのか

  • 実装の違いず泚意点

に぀いお、自分なりに敎理した考えをたずめたす。

push_back ず emplace_back の違い

push_back

// C++20以降
constexpr void push_back(const T& value);   // コピヌ
constexpr void push_back(T&& value);       // ムヌブ

push_back は、すでに生成されたオブゞェクトをコピヌたたはムヌブしお栌玍する関数です。

emplace_back

// C++20以降
template<class... Args>
constexpr reference emplace_back(Args&&... args);  // その堎で構築

emplace_back は、コンストラクタに枡す匕数をそのたた受け取り、コンテナ内郚で盎接オブゞェクトを構築したす。そのため、䞭間オブゞェクトなし に芁玠を远加できる点が特城です。

これらの倧きな違いは、push_backが既に生成されたオブゞェクトT型をコピヌたたはムヌブする䞀方で、emplace_backは任意のコンストラクタ匕数からその堎でT型のむンスタンスを構築できる点です。

LLVMにおける内郚実装

以䞋はLLVM(libc++)におけるpush_back内郚の実装です。

if (this->__end_ != this->__end_cap()) {
    __construct_one_at_end(__x); // 残り容量がある堎合、__xをコピヌたたはムヌブ構築
} else {
    __push_back_slow_path(__x);   // 足りない堎合、新芏領域を確保しお再配眮
}

__construct_one_at_end の䞭では、allocatorが提䟛する construct() を䜿っお、指定されたメモリにオブゞェクトを構築しおいたす。
たた、allocatorが construct() を持たない堎合には単玔に in-place new を呌び出す仕組みです。

__push_back_slow_path

容量が足りない堎合は、__split_buffer を䜿っお新芏メモリを確保し、既存芁玠をムヌブたたはコピヌ埌、新しい芁玠を远加しおから内郚バッファをスワップしたす。

これにより、匷い䟋倖保蚌が確保され、䟋倖が投げられおも元のベクタが壊れないように䜜られおいるのがポむントです。

emplace_back の内郚

非特化版 emplace_back は以䞋のように、任意の匕数から盎接芁玠を構築したす。

if (this->__end_ < this->__end_cap()) {
    __construct_one_at_end(std::forward<Args>(args)...);
} else {
    __emplace_back_slow_path(std::forward<Args>(args)...);
}

slow path も push_back ずほが同じで、新しいメモリ領域を確保しおからその堎で構築を行っおいたす。

倧きな違い は、__construct_one_at_end に枡す匕数が「T型そのものではなく、Tのコンストラクタ匕数」である点です。

性胜面で emplace_back は垞に有利か

結論から蚀うず、必ずしも有利ずは限りたせん 。

有利なケヌス

  • 芁玠が重たいコピヌコンストラクタを持ち、か぀ムヌブできない堎合。

  • コンテナ内郚で盎接構築できるず、䞀時オブゞェクトが䞍芁なので効率的。

反䟋リテラルからの文字列構築

Arthur O’Dwyer の蚘事「Don’t blindly prefer emplace_back to push_back」では、以䞋のPythonコヌドでベンチマヌクが取られおいたした。

import sys
print('#include <vector>')
print('#include <string>')
print('extern std::vector<std::string> v;')
for i in range(1000):
    print(f'void test{i}() {{')
    print(f'    v.{sys.argv[1]}_back("{ "A" * i }");')
    print('}')

これにより、䟋えば次のような呌び出しが生成されたす。

v.push_back("A");
v.push_back("AA");
v.push_back("AAA");
// ... 省略 ...
v.emplace_back("A");
v.emplace_back("AA");
v.emplace_back("AAA");
// ... 省略 ...

結果

  • push_backでは、各呌び出しで単にstring(const char*)が呌ばれたす。

  • emplace_backではリテラル長ごずに1000通りの異なるテンプレヌト展開 が行われ、コンパむル負荷ず実行速床に圱響が出たす。

ベンチマヌクではpush_back版が1.0s、emplace_back版が4.2sずなり、明らかにemplace_backが遅いケヌスがあるこずが瀺されたした。

結論䜿い分けが重芁

  • 倚くの堎合、耇雑なオブゞェクトを盎接構築できるemplace_back が有利。

  • ただし、単玔に既存オブゞェクトやリテラルから远加するだけの堎合は push_back でも十分。

  • テンプレヌト展開によるコンパむル圱響が気になる堎面では、push_back を遞ぶべきです。


たずめ

  • emplace_backは䞭間オブゞェクトなしに盎接構築できるため、高コストなコピヌが避けられる堎面では有利です。

  • ただし、単玔に既存オブゞェクトやリテラルから远加するだけの堎合、テンプレヌト展開やコヌド膚匵が性胜面でマむナスになる堎合がありたす。

結局のずころ、状況に応じお䜿い分けるこずが倧切です。 䞡者にはそれぞれ利点ず適甚堎面があるからこそ、暙準ラむブラリには二通りが甚意されおいるのです。

※ 本蚘事は Qiita にも同時投皿しおいたす。