【C++】emplace_back/emplaceとは何か【処理効率やpush_backとの違い】


emplace系関数

C++のvector等のコンテナクラスにはemplaceやemplace_back、emplace_front、emplace_hint等のemplace系メンバ関数が存在します。いずれもコンテナに要素を追加するためのメソッドであり、一般的なinsertやpush_backと似たような動作をしますが、使い方や処理効率が大きく異なっています。

関数名概要
push_back受け取った要素をコンテナに追加する
emplace_back要素型のコンストラクタ引数を元に、要素をコンテナ内で直接構築して追加する
スポンサーリンク

emplace系関数の特徴

emplace系の関数は、要素の生成をコンテナ内部で行うという特徴があります。そのため、emplace系の関数は要素を直接受け取るのではなく、要素型に対応するコンストラクタの引数を受け取るようになっています。

std::vector<std::string> v = {};

/* emplace_back */
const char* cstr = "a";
v.emplace_back(cstr); // コンストラクタ引数を受け取る

/* push_back */
std::string str = "b";
v.push_back(str);     // 要素を直接受け取る

そして実際のコンストラクタ呼び出しはコンテナ内部で行われます。

これらのemplace系関数によって、オブジェクトや一時オブジェクトの余計な生成やコピー/ムーブ処理、破棄処理が回避されるという利点が得られます。

具体的なしくみ

vectorクラスを例に、emplace_back関数とpush_back関数の違いを示します。

std::vector<std::string> v = {};

// emplace_back内部で行われるstd::string("a")オブジェクトの生成コストのみが発生
// オブジェクトは生成と同時にコンテナに追加されるため、コピー/ムーブ処理が発生しない
v.emplace_back("a");

// push_back内部ではコピー/ムーブ処理が発生する
// std::string("b")のデストラクタも呼ばれる
v.push_back(std::string("b"));

emplace_back

std::vector<std::string>というstd::stringを要素とするコンテナがあったとすると、上記のemplace_back関数はstd::stringのコンストラクタ引数であるconst char*型を受け取ることになります。

そしてemplace_back関数は受け取ったconst char*型を元に、std::string型の要素を生成し、自身のコンテナにその要素を追加します。

push_back

push_backやinsertによる要素の追加にはオブジェクトのコピー/ムーブ処理が伴います(具体的にはコピーコンストラクタやムーブコンストラクタが呼ばれてしまいます)。一時オブジェクトを渡した際にも同様にコピーやムーブが行われます。またv.push_back("a");という記述を行っても、暗黙の型変換、もとい変換コンストラクタが働くため、結局のところstd::stringの一時オブジェクトが生成されて同じことが起こります。

処理効率

emplace系関数は余計なオブジェクトの生成やコピーを避ける事ができるため、非常に有効な関数であると言えます。最適化を意識しなければ、理論上はpush_back関数よりもemplace_back関数を使ったほうが処理効率は良くなります。

しかしながらv.push_back(std::string("b"));のような、一時オブジェクトを挿入する処理については、ムーブコンストラクタが働くため、処理コストの違いはそれほど大きくならないことがほとんどです。

ユーザ定義型を要素型に持つコンテナの処理コストが問題になっている場合は、emplace系関数への移行や、ユーザ定義型に対するムーブコンストラクタの定義を検討してみると良いかもしれません。パフォーマンスの改善が期待できます。

広告