push_back 比 emplace_back 更有效率?

Doe*_*ohn 5 c++ vector c++11

我想看看 push_back 和 emplace_back 之间的区别,因为在几个地方我读到了建议,因为现在最好使用 emplace_back,因为“它可以完成 push_back 可以做的所有事情,甚至更多”,所以我希望 ti 更有效。但令我惊讶的是

#include <iostream>     
#include <vector>    

class A
{
    public:
    A() {std::cout << "A const" << std::endl;}
    ~A() {std::cout << "A dest" << std::endl;}
    A(const A& a) {std::cout << "A copy const" << std::endl;}
    A(A&& a) {std::cout << "A move const" << std::endl;}
    A& operator=(const A& a) {std::cout << "A copy operator=" << std::endl; return *this; }
    A& operator=(A&& a) {std::cout << "A move operator=" << std::endl;  return *this; }
};

int main () {
    std::vector<A> va;
    std::cout <<"push:" << std::endl;
    va.push_back(A());
    std::cout <<std::endl<< "emplace:" << std::endl;
    va.emplace_back(A());

    std::cout <<std::endl<< "end:" << std::endl;

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

输出是

push:
A const
A move const
A dest

emplace:
A const
A move const
A copy const
A dest
A dest

end:
A dest
A dest
Run Code Online (Sandbox Code Playgroud)

emplace_back 调用 move 构造函数,然后在 push_back 只调用一个 move const 时复制一个。我检查了 g++ (Ubuntu 7.4.0-1ubuntu1~16.04~ppa1) 7.4.0 和online C++ shell。我错过了什么吗?

J. *_*rez 10

push_back 效率不高,您观察到的结果是由于矢量本身调整了大小。

当您调用emplaceafter 时push_back,向量必须调整自身大小以为第二个元素腾出空间。这意味着它必须移动A原来在向量内的emplace看起来更复杂。

如果您事先在向量中保留了足够的空间,则不会发生这种情况。注意对va.reserve(2)afterva创建的调用:

#include <iostream>     
#include <vector>    

class A
{
    public:
    A() {std::cout << "A const" << std::endl;}
    ~A() {std::cout << "A dest" << std::endl;}
    A(const A& a) {std::cout << "A copy const" << std::endl;}
    A(A&& a) {std::cout << "A move const" << std::endl;}
    A& operator=(const A& a) {std::cout << "A copy operator=" << std::endl; return *this; }
    A& operator=(A&& a) {std::cout << "A move operator=" << std::endl;  return *this; }
};

int main () {
    std::vector<A> va;
    // Now there's enough room for two elements
    va.reserve(2);
    std::cout <<"push:" << std::endl;
    va.push_back(A());
    std::cout <<std::endl<< "emplace:" << std::endl;
    va.emplace_back(A());

    std::cout <<std::endl<< "end:" << std::endl;

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

对应的输出是:

push:
A const
A move const
A dest

emplace:
A const
A move const
A dest

end:
A dest
A dest
Run Code Online (Sandbox Code Playgroud)

我们能让事情变得更有效率吗?是的!emplace_back接受您提供的任何参数,并将它们转发到A的构造函数。因为A有一个不带参数的构造函数,所以你也可以emplace_back不带参数使用!换句话说,我们改变

va.emplace_back(A());
Run Code Online (Sandbox Code Playgroud)

va.emplace_back(); // No arguments necessary since A is default-constructed
Run Code Online (Sandbox Code Playgroud)

这导致没有复制,也没有移动:

push:
A const
A move const
A dest

emplace:
A const

end:
A dest
A dest
Run Code Online (Sandbox Code Playgroud)

关于向量调整大小的说明:重要的是要注意 的实现std::vector是智能的。如果A一直是平凡复制的类型,std::vector可能是就地能调整大小,无需额外复制使用类似的系统功能realloc。但是因为As的构造函数和析构函数都包含代码,realloc这里不能使用。

  • 您还应该包含正确调用 emplace_back 的版本以进行比较 (3认同)
  • 当插入第一个元素时,std::vector 会为超过 2 个元素分配空间。性能命中是由于 OP 使用 emplace_back() 的方式造成的。emplace_back() 将参数作为构造函数的参数。使用默认构造对象作为参数调用 emplace_back 会调用移动构造函数。因此,emplace_back(A{]) 是 1 个构造和 1 个移动。调用push_back(A{}) 是1 次构造和1 次复制。调用 v.resize(v.size() + 1) 是 1 就地构造,并且速度最快。对于示例中的简单类,复制可能比移动更快。这是一个交换。 (2认同)