C++vector push_back和emplace_back的区别

1. 概述

C++ STL库中,往vector末尾添加元素,一般使用push_back和emplace_back方法,那么他们有什么区别?

本文不会探讨STL源码的实现,而是从黑盒的角度,通过代码测试来分析区别。

2. 区别分析

2.1 先说说背景

  • 在C++11之前,只支持push_back。
  • 从C++11开始新增了emplace_back,同时也对push_back做了优化,底层会直接调用emplace_back。
  • 所以,从C++11开始,两者可以随意使用,区别不大。

下面通过测试代码来分析:

2.2 C++98测试

测试代码如下:

#include <vector>
#include <iostream>

class Object {
    int num;

public:
    Object(int n) : num(n)
    {
        std::cout << "构造函数" << std::endl;
    }

    Object(const Object& p) : num(p.num)
    {
        std::cout << "拷贝构造函数" << std::endl;
    }
};

int main()
{
    std::vector<Object> objArray;

    std::cout << "创建测试对象:" << std::endl;
    Object obj = Object(1);
    std::cout << std::endl;

    std::cout << "push_back(obj):" << std::endl;
    objArray.push_back(obj);
    std::cout << std::endl;

    std::cout << "push_back(1):" << std::endl;
    objArray.push_back(1);
    std::cout << std::endl;


    std::cout << "xxxx_back(Object(1)):" << std::endl;
    objArray.push_back(Object(1));
    std::cout << std::endl;
}

输出结果:

创建测试对象:
构造函数

push_back(obj):
拷贝构造函数

push_back(1):
构造函数
拷贝构造函数
拷贝构造函数

xxxx_back(Object(1)):
构造函数
拷贝构造函数
拷贝构造函数
拷贝构造函数

可以看到,往末尾添加元素时,会通过构造函数或者拷贝构造函数创建新的对象,有的场景甚至要构造好几次对象,然后才能添加到vector末尾。如果对象本身有几兆甚至几十兆,十分浪费性能。

下面看看在C++11上的情况。

2.2 c++11代码测试

这里要先提一下C++11新增的移动构造函数,简单理解就是创建对象时,直接把原来对象的数据拿过来,不用拷贝一份,原来的对象之后就不再使用了。 测试代码如下:

#include <vector>
#include <iostream>

class Object {
    int num;

public:
    Object(int n) : num(n)
    {
        std::cout << "构造函数" << std::endl;
    }

    Object(const Object& p) : num(p.num)
    {
        std::cout << "拷贝构造函数" << std::endl;
    }

    Object(const Object&& p) noexcept : num(p.num)
    {
        std::cout << "移动构造函数" << std::endl;
    }
};

int main()
{
    std::vector<Object> objArray;

    std::cout << "创建测试对象:" << std::endl;
    auto obj = Object(1);
    std::cout << std::endl;

    auto obj2 = Object(1);
    std::cout << std::endl;

    std::cout << "1. xxxx_back(obj):" << std::endl;
    objArray.emplace_back(obj);
    std::cout << std::endl;

    objArray.push_back(obj2);
    std::cout << std::endl;

    std::cout << "2. xxxx_back(std::move(obj)):" << std::endl;
    objArray.emplace_back(std::move(obj));
    std::cout << std::endl;

    objArray.push_back(std::move(obj2));
    std::cout << std::endl;

    std::cout << "3. xxxx_back(1):" << std::endl;
    objArray.emplace_back(1);
    std::cout << std::endl;

    objArray.push_back(1);
    std::cout << std::endl;

    std::cout << "4. xxxx_back(Object(1)):" << std::endl;
    objArray.emplace_back(Object(1));
    std::cout << std::endl;

    objArray.push_back(Object(1));
    std::cout << std::endl;
}

输出结果:

创建测试对象:
构造函数

构造函数

1. xxxx_back(obj):
拷贝构造函数

拷贝构造函数
移动构造函数

2. xxxx_back(std::move(obj)):
移动构造函数
移动构造函数
移动构造函数

移动构造函数
移动构造函数
移动构造函数
移动构造函数

3. xxxx_back(1):
构造函数
移动构造函数
移动构造函数
移动构造函数
移动构造函数

构造函数
移动构造函数

4. xxxx_back(Object(1)):
构造函数
移动构造函数
移动构造函数
移动构造函数
移动构造函数
移动构造函数
移动构造函数
移动构造函数

构造函数
移动构造函数

可以看到,不管是使用push_back,还是emplace_back,最多只会调用一次构造函数或者拷贝构造函数,其他都是调用的移动构造函数,而移动构造函数的性能消耗几乎可以忽略不计,所以两者区别不大。

3. 总结

综上所述:

  • C++11之前没的选,只能用push_back,如果一定要优化性能,那就尽量用指针吧。
  • C++11开始,可以随意使用,区别不大。
  • 当然,对于大对象,最好实现移动构造函数,否则,还是会出现大量的数据拷贝。比如我把上面2.2节里的移动构造函数删除后执行,就会出现大量拷贝,结果如下:
创建测试对象:
构造函数

构造函数

1. xxxx_back(obj):
拷贝构造函数

拷贝构造函数
拷贝构造函数

2. xxxx_back(std::move(obj)):
拷贝构造函数
拷贝构造函数
拷贝构造函数

拷贝构造函数
拷贝构造函数
拷贝构造函数
拷贝构造函数

3. xxxx_back(1):
构造函数
拷贝构造函数
拷贝构造函数
拷贝构造函数
拷贝构造函数

构造函数
拷贝构造函数

4. xxxx_back(Object(1)):
构造函数
拷贝构造函数
拷贝构造函数
拷贝构造函数
拷贝构造函数
拷贝构造函数
拷贝构造函数
拷贝构造函数

构造函数
拷贝构造函数
建站  ·  开发  ·  读书  ·  教程
↑长按或扫码关注“编程之海”公众号↑
↑长按或扫码关注“编程之海”公众号↑
编程之海 版权所有丨如未注明,均为原创丨转载请注明转自:https://codingsea.com/cvector-push_back%e5%92%8cemplace_back%e7%9a%84%e5%8c%ba%e5%88%ab/
0 0 投票数
文章评分
订阅评论
提醒
guest
0 评论
内联反馈
查看所有评论

0

0

336

0
希望看到您的想法,请您发表评论x