保证在 C++17 和 emplace_back(..) 中复制省略

Guaranteed copy elision in C++17 and emplace_back(...)

本文关键字:复制省 back C++17 emplace      更新时间:2023-10-16

emplace_back(...( 是在 C++11 中引入的,以防止创建临时对象。现在有了 C++17 纯值,就更纯净了,这样它们就不会再导致临时的产生(有关更多信息,请参阅此问题(。现在我仍然不完全了解这些变化的后果,我们还需要emplace_back(...)还是可以回去再次使用push_back(...)

push_backemplace_back成员函数都会在预分配缓冲区的某个位置创建其value_typeT的新对象。这是由向量的分配器完成的,默认情况下,它使用放置新机制进行此构造(放置新基本上只是在内存中指定位置构造对象的一种方法(。

然而:

  • emplace_back将其参数完全转发给T的构造函数,因此选择与这些参数最匹配的构造函数。
  • push_back(T&&)内部使用move 构造函数(如果存在且未引发(来初始化新元素。移动构造函数的这种调用不能省略,并且始终使用。

请考虑以下情况:

std::vector<std::string> v;
v.push_back(std::string("hello"));

std::string的移动构造函数总是在这里调用,它跟在转换构造函数之后,转换构造函数从字符串文字创建一个字符串对象。在这种情况下:

v.emplace_back("hello");

没有调用移动构造函数,向量的元素由std::string的转换构造函数直接初始化。


这并不一定意味着push_back效率较低。编译器优化可能会消除所有其他指令,最终这两种情况可能会生成完全相同的汇编代码。只是不能保证。


顺便说一下,如果push_back按值(void push_back(T param);(传递参数,那么这将是应用复制省略的一个案例。即,在:

v.push_back(std::string("hello"));

参数param将由临时构造函数构造。这种移动结构将是复制省略的候选者。然而,这种方法根本不会改变push_back体内矢量元素的强制移动构造。

你可能会在这里看到:std::vector::p ush_back,这种方法需要CopyInsertable或MoveInsertable,它也需要const T& valueT&& value,所以我看不出elision在这里如何使用。

在以下示例中使用了强制复制 ellision 的新规则:

struct Data {
Data() {}
Data(const Data&) = delete;
Data(Data&&) = delete;
};
Data create() {
return Data{};  // error before c++17   
}
void foo(Data) {}
int main()
{
Data pf = create();
foo(Data{});  // error before c++17
}

因此,您有一个不支持复制/移动操作的类。为什么,因为也许它太贵了。上面的例子是一种始终有效的工厂方法。使用新规则,您无需担心编译器是否实际使用 elision - 即使您的类支持复制/移动。

我不认为新规则会让push_back更快。 emplace_back仍然更有效,但不是因为复制,而是因为它创建对象并将参数转发到它。