push_back和emplace_back的区别

默认分类 · 2024-04-29 · 37 人浏览

C++11 之后,vector 容器中添加了新的方法:emplace_back() ,和 push_back() 一样的是都是在容器末尾添加一个新的元素进去,不同的是 emplace_back() 在效率上相比较于 push_back() 有了一定的提升。

同样是在容器尾部加入一个元素,push_backemplace_back 是不一样的;

push_back
push_back函数的参数实际上是对应类型的左值或右值引用。传递的参数都会被形参直接引用,然后会直接通过拷贝构造或者移动构造在容器末尾增加一个元素。

但是如果在传参处构建临时对象传入,则过程就会如下

  • 构造一个临时对象
  • 调用移动构造函数把临时对象的副本拷贝到容器末尾增加的元素中
  • 调用析构释放临时对象
    假如它的元素类型有单参数的构造函数构造函数,可以直接给 push_back 传递单个参数
    例如

    class Test{
    public:
    
      Test(int a) {
          cout << "Test(int a)" << endl;
          this->a = a;
      }
      Test(int a, int b) {
          cout << "Test(int a)" << endl;
          this->a = a;
      }
      Test(const Test&) {
          cout << "Test(const Test&)" << endl;
      }   
      Test(Test&&) {
          cout << "Test(Test&&)" << endl;
      } 
      ~Test() {
          cout << "~Test()" << endl;
      }
    
    };
    
    
    int main() {
      vector<Test> vec;
      vec.push_back(1); // 这里实际上是发生了隐式类型转换
      vec.push_back({1,2}); // 这种也是发生了隐式类型转换
    }

    这两个push_back就会报错, 因为禁用了隐式类型转换

当我们想调用多参数的情况时,就不可以直接传递了, 因为 push_back 只接受单个参数, 我们必须在此处创建临时对象传入
例如

vec.push_back(Test{1, 2});
vec.push_back(Test(1, 2));

这样就避免不了临时对象构造和析构的过程。

emplace_back
emplace_back 的参数实际上是一个模板参数包, 他直接将构造元素的参数传递给对应的构造函数, 所以就少了创建一个临时对象的过程,比如像传递多个构造函数的参数, emplace_back 就可以这样

vec.emplace_back(1, 2);

因为 emplace_back是把多个参数作为一个整体, 传递给对应的构造函数了

emplace_back 的过程

  • 调用构造函数在容器末尾增加一个元素

同样是在容器尾部增加一个元素,emplace_back 在直接传递参数创建元素时比 push_back 少了一次临时对象的构造和析构以及移动构造, 所以,emplace_back 相比 push_back 高效一些。
验证一下
还是Test这个类

class Test{
public:

    Test(int a) {
        cout << "Test(int a)" << endl;
        this->a = a;
    }
    Test(const Test&) {
        cout << "Test(const Test&)" << endl;
    }   
    Test(Test&&) {
        cout << "Test(Test&&)" << endl;
    } 
    ~Test() {
        cout << "~Test()" << endl;
    }
    
};
  1. 用已经存在的对象插入 vector
  • push_back

    vector<Test> vec;
    Test a(1);
    vec.push_back(a);

    输出如下(忽略 a 对象以及vec析构时调用元素的析构)

    Test(const Test&)

    可以看到只有一次拷贝构造

  • emplace_back
    输出如下(忽略 a 对象以及vec析构时调用元素的析构)

    Test(const Test&)

    同样只有一次拷贝构造

  1. 新创建对象插入
  • push_back

    vector<Test> vec;
    vec.push_back(1); // 隐式转换, 会创建临时对象, 等价于 vec.push_back(Test(1))

    输出

    Test(int a)  临时对象构造
    Test(Test&&) 
    ~Test() 临时对象析构
  • emplace_back

    vector<Test> vec;
    vec.emplace_back(1);

    输出

    Test(int a) 直接将参数传递给构造函数了, 没有临时对象产生

    总结
    当我们想把一个已经存在的对象插入 vector 时, 使用二者都是相似的, 都只会调用一次拷贝或移动构造,
    但是我们想在 vector 添加一个本不存在的对象时,二者会有一些不同,push_back 不可避免的多了一次临时对象的创建和销毁,以及将临时对象移动到 vector 中的开销, 而 emplace_back 则可以在容器内直接构造。

Theme Jasmine by Kent Liao