美文网首页
我理解的vector、emplace_back与push_bac

我理解的vector、emplace_back与push_bac

作者: 王拓 | 来源:发表于2023-11-01 12:12 被阅读0次

关于emplace_back和push_back网上有种说法,“emplace_back比push_back效率更高”,具体原因是emplace_back相比push_back减少了一次临时变量的拷贝构造。事实上大多数情况下,emplace_back和push_back表现是一致的。

此外本文还验证了《Morden Effective C++》一书中条款14,“只要函数不会发射异常,就为其加上noexcept声明”。

实验准备

首先定义一个Element类型

class Element{
public:
    Element(){
        id_ = ++cnt_;
        std::cout << "default ctor " << id_ << std::endl;
    }

    ~Element(){
        std::cout << "dtor " << id_ << std::endl;
    }

    Element(int i){
        id_ = ++cnt_;
        std::cout << "ctor(int) " << id_ << std::endl;
    }

    Element(int i, char c){
        id_ = ++cnt_;
        std::cout << "ctor(int, char)" << id_ << std::endl;
    }

    Element(const Element& rhs){
        id_ = ++cnt_;
        std::cout << "copy ctor " << id_ << " from " << rhs.id_ << std::endl;
    }
    
    Element(Element&& rhs){
        id_ = ++cnt_;
        std::cout << "move ctor " << id_ << " from " << rhs.id_ << std::endl;
    }

    static int cnt_;
    int id_;
};

int Element::cnt_ = 0;

Element类定义了不同的构造函数,静态变量cnt_用来为不同的实例的id_赋值。

emplace_back和push_back

插入左值

首先测试插入左值的情况,下面是push_back的测试代码。

std::vector<Element> vec;
Element e;
vec.push_back(e);

下面是emplace_back的测试代码

std::vector<Element> vec;
Element e;
vec.emplace_back(e);

二者输出完全一致,可以看到当插入左值时,表现完全一致,都是调用拷贝构造。

default ctor 1
copy ctor 2 from 1
dtor 1
dtor 2

插入右值

首先测试插入默认构造函数返回的右值,首先是push_back版本

std::vector<Element> vec;
Element e;
vec.push_back(e);

下面是emplace_back版本

std::vector<Element> vec;
Element e;
vec.emplace_back(e);

两者输出完全一致,都是先调用构造函数,再调用移动构造

default ctor 1
move ctor 2 from 1
dtor 1
dtor 2

换一个构造函数试试

std::vector<Element> vec;
vec.push_back(Element(1, 'a'));
// vec.emplace_back(Element(1, 'a'));

两者的输出同样完全一致,都是先调用构造函数,再调用移动构造

ctor(int, char)1
move ctor 2 from 1
dtor 1
dtor 2

插入时构造

区别在于emplace_back是使用可变参数模板定义的

template<class... _Valty>
void emplace_back(_Valty&&... _Val)

当使用下面这样的插入形式时

vec.emplace_back(1, 'a');
// vec.push_back(1, 'a');

push_back版本会报错,因为push_back没有实现可变参数。emplace_back的输出为

ctor(int, char)1
dtor 1

可以看到,新的实例直接在vector内部调用了构造函数,省去了移动构造的步骤。

要想调用push_back函数,则需要通过下面的代码。

std::vector<Element> vec;
vec.emplace_back(Element(1, 'a'));

输出如下

ctor(int, char)1
move ctor 2 from 1
dtor 1
dtor 2

如你所料,先后调用了构造函数和移动构造函数,这也是push_backemplace_back在插入时表现的唯一区别,而这个区别是由程序员编码方式产生的。

验证noexcept

先说结论,如果实现了不抛异常的移动构造函数vector扩容时会调用移动构造函数,否则调用拷贝构造函数

参照Element类实现一个具有不抛异常的移动构造函数的ElementNoexp

class ElementNoexp{
public:
    ElementNoexp(){
        id_ = ++cnt_;
        std::cout << "default ctor " << id_ << std::endl;
    }

    ~ElementNoexp(){
        std::cout << "dtor " << id_ << std::endl;
    }

    ElementNoexp(int i){
        id_ = ++cnt_;
        std::cout << "ctor(int) " << id_ << std::endl;
    }

    ElementNoexp(int i, char c){
        id_ = ++cnt_;
        std::cout << "ctor(int, char)" << id_ << std::endl;
    }

    ElementNoexp(const ElementNoexp& rhs){
        id_ = ++cnt_;
        std::cout << "copy ctor " << id_ << " from " << rhs.id_ << std::endl;
    }
    
    ElementNoexp(ElementNoexp&& rhs) noexcept{
        id_ = ++cnt_;
        std::cout << "move ctor " << id_ << " from " << rhs.id_ << std::endl;
    }

    static int cnt_;
    int id_;
};

测试代码

std::vector<ElementNoexp> vec;
vec.resize(3);
std::cout << "----------------------------------" << std::endl;    
vec.emplace_back(ElementNoexp(1, 'a'));

没有noexcept声明版本的输出

default ctor 1
default ctor 2
default ctor 3
----------------------------------
ctor(int, char)4
move ctor 5 from 4
copy ctor 6 from 1
copy ctor 7 from 2
copy ctor 8 from 3
dtor 1
dtor 2
dtor 3
dtor 4
----------------------------------
dtor 6
dtor 7
dtor 8
dtor 5

noexcept版本输出

default ctor 1
default ctor 2
default ctor 3
----------------------------------
ctor(int, char)4
move ctor 5 from 4
move ctor 6 from 1
dtor 1
move ctor 7 from 2
dtor 2
move ctor 8 from 3
dtor 3
dtor 4
----------------------------------
dtor 6
dtor 7
dtor 8
dtor 5

可以看到声明了noexcept版本移动构造函数的ElementNoexp在扩容过程中是边移动边析构,而没有声明noexcept的函数则是先确保拷贝构造完成,再释放原成员。

因此,当确保移动构造不会抛出异常时,将移动构造声明为noexcept可以提高程序的执行效率。

相关文章

网友评论

      本文标题:我理解的vector、emplace_back与push_bac

      本文链接:https://www.haomeiwen.com/subject/loowidtx.html