Push_backとEmplace_back

  • 3
    いいね
  • 0
    コメント

この記事は初心者 C++er Advent Calendarの1日目の記事です。

はじめに

みなさんはstd::vectorやstd::listで要素を追加するときに、「push_back関数よりも、
emplace_back関数を使った方が良い」と言われたりしたことがあると思います。
この記事ではその二つの関数の動作などを解説していきます。

どこがちがうの?

std:vectorのpush_backは以下に示すように、実引数が左辺値用と右辺値用にオーバーロードされています。

template<class T,
         class Allocator = allocator<T>>
class vector {
public:
    ...
    void push_back(const T& param);
    void push_back(T&& param);
    ...
};

左辺値の場合はコピーされて、右辺値の場合はムーブされます。
(ムーブについての詳細はここでは説明しません。
他のWebページ
を参考にしてください。)

ここで一つパフォーマンス面で問題が発生します。

例えば、整数を引数にとるコンストラクタをもつクラスを要素とするstd::vectorがあるとしましょう。


class Minute{
    ...
public:
    Minute(int time);
    ...
};

std::vector<Minute> vec;

vec.push_back(12);

このとき、内部では以下のように動作しています。

  1. 一時オブジェクトが12からコンストラクトされる

  2. それがpush_backの仮引数へムーブされ、それがstd::vectorの新規オブジェクトとしてコンストラクトされる。

  3. 一時オブジェクトが破棄される。

このようにコンストラクトが2回起きています。

これは無駄ですよね。

なので、C++11以降、コンストラクタの引数から直接コンテナの新規オブジェクトが生成できる関数が用意されました。

それがemplace_backなのです!!

では実装を確認してみましょう。

template<class T,
         class Allocator = allocator<T>>
class vector {
public:
    ...
    template<class ...U>
    void emplace_back(U&&... param);
    ...
};

この関数は内部で完全転送というものを行っています。
(詳しくはこちらの記事をご覧ください。)

これはコンストラクタの引数を受け取ってコンテナに直接コンストラクトします。
emplace_back関数がパフォーマンス面以外で動作が変わるのは以下の場合です。

  • エラーが出ない(間違っていることに気づかせてくれない)
  • 追加される要素は、丸括弧で初期化されます。(波括弧での初期化ではない)

まとめ

emplace_backは実引数がコンテナの要素型と異なるときに、push_backよりも動作が早くなり、
それ以外の場合ではほとんど変わりません。(実引数が単に変数のときなど)