【问题标题】:push_back is more efficient than emplace_back?push_back 比 emplace_back 更高效?
【发布时间】:2020-09-12 14:35:09
【问题描述】:

我想看看 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;
}

输出是

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

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

【问题讨论】:

  • 现在换个顺序,会发生什么? (也不要使用 const 作为构造函数的缩写...ctor 更好,因为它不会与 const 冲突)。
  • 当您插入第二个元素时,向量会重新分配其存储空间(因此它必须将第一个元素复制到新的存储空间)。先做emplace_back,比较结果。
  • 1.移动构造函数不是 noexcept 所以向量调用复制。 2.你重复使用相同的向量并导致重新分配。 3. 你用一个临时的 emplace_back 来调用它,这违背了目的。
  • 您可以尝试另一种变体来完善这个实验,它是emplace_back() - 请注意缺少参数
  • 现在,只需调用va.emplace_back();,无需构造一个完全无用的临时对象,用于移动构造向量中的值。

标签: c++ c++11 vector


【解决方案1】:

push_back 效率并不高,而且您观察到的结果是由于向量本身调整大小造成的。

当您在push_back 之后调用emplace 时,向量必须调整自身大小以便为第二个元素腾出空间。这意味着它必须移动最初在向量内的A,使emplace 显得更复杂。

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

#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;
}

对应的输出是:

push:
A const
A move const
A dest

emplace:
A const
A move const
A dest

end:
A dest
A dest

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

va.emplace_back(A());

va.emplace_back(); // No arguments necessary since A is default-constructed

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

push:
A const
A move const
A dest

emplace:
A const

end:
A dest
A dest

关于向量大小调整的说明: 请务必注意std::vector 的实现很聪明。如果A 是一个可简单复制的类型,std::vector 可能能够使用类似于realloc 的系统函数就地调整大小而无需额外复制。但是因为As 的构造函数和析构函数都包含代码,所以这里不能使用realloc

【讨论】:

  • 您还应该包含正确调用 emplace_back 的版本以进行比较
  • 非常感谢!所以除了这方面,当我将对象传递给 emplace_back 时,它与 push_back 相同,对吧?一个, a1; va.emplace_back(a); va.push_back(a1);两者都调用复制构造函数。虽然我认为 emplace_back 应该默认采取行动。
  • 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 就地构造,速度最快。对于示例中的简单类,复制可能比移动更快。这是一个交换。
  • push_backemplace_back 在提供A&amp;&amp; 类型的对象时都会移动。移动对象的问题在于它不会阻止调用析构函数,因为可能仍需要进行清理。有一些建议允许程序员指出对象是可移动的,以避免多余的析构函数调用,但这些还没有被标准接受。
  • 在一次移动中,对象被交换并且析构函数仍在被调用。在这个例子中,析构函数很简单。
猜你喜欢
  • 2014-07-06
  • 2011-05-17
  • 1970-01-01
  • 1970-01-01
  • 2021-10-12
  • 2018-11-15
  • 2015-01-15
  • 2015-05-19
相关资源
最近更新 更多