c++ - 为什么 "Guaranteed Copy Elision"不表示 push_back({arg1, arg2}) 与 emplace_back(arg1, arg2) 相同?

标签 c++ c++17 language-lawyer copy-elision prvalue

首先,我听说Guaranteed Copy Elision 用词不当(据我目前了解,它更多是关于从根本上重新定义基本值(value)类别,r/l -values 到 l/x/pr-values,这从根本上改变了拷贝的含义和要求),但由于这是通常所说的,我也会这样做。

在阅读了一些有关该主题的内容后,我想我终于理解了它 - 至少足够好地认为:

my_vector.push_back({arg1, arg2});

从 c++17 开始,等同于:

my_vector.emplace_back(arg1, arg2);

我最近试图让我的同事相信这一点。唯一的问题是他告诉我我完全错了!他写了一些 godbolt 代码 ( like this ),其中程序集显示 push_back 创建了一个被移动到 vector 中的临时文件。


所以,为了完成我的问题,我必须首先证明这里有一些混淆的理由。我会引用 the well-regarded stackoverflow answer关于这个主题(强调我的):

Guaranteed copy elision redefines the meaning of a prvalue expression. [...] a prvalue expression is merely something which can materialize a temporary, but it isn't a temporary yet.

If you use a prvalue to initialize an object of the prvalue's type, then no temporary is materialized. [...]

The thing to understand is that, since the return value is a prvalue, it is not an object yet. It is merely an initializer for an object [...]

在我的例子中,我认为 auto il = {arg1, arg2} 会调用 std::initializer_list 的构造函数,但是 { push_back({arg1, arg2}) 中的 arg1, arg2} 将是纯右值(因为它未命名),因此将成为 vector 元素的初始化器,而无需自行初始化。

When you do T t = Func();, the prvalue of the return value directly initializes the object t; there is no "create a temporary and copy/move" stage. Since Func()'s return value is a prvalue equivalent to T(), t is directly initialized by T(), exactly as if you had done T t = T().

If a prvalue is used in any other way, the prvalue will materialize a temporary object, which will be used in that expression (or discarded if there is no expression). So if you did const T &rt = Func();, the prvalue would materialize a temporary (using T() as the initializer), whose reference would be stored in rt, along with the usual temporary lifetime extension stuff.

Guaranteed elision also works with direct initialization

有人可以向我解释为什么保证复制省略没有按照我预期的方式应用于我的示例吗?

最佳答案

but that {arg1, arg2} in push_back({arg1, arg2}) would be a prvalue (as it's unnamed) and so would be an initiliser for the vector object without being initialised itself.

我假设“ vector 对象”在这里指的是 vector 元素,该对象将存储在由vector 管理的存储器中。哪个push_back/emplace_back应该添加到它。

{arg1, arg2}它本身不是一个表达式,它只是一个braced-init-list,一个不同的语法结构。所以它本身没有值(value)类别。然而,它有关于它如何在重载决议中起作用以及它如何初始化对象和引用的规则。

push_back 选择的过载将是

void push_back(value_type&&);

哪里value_type是 vector 的元素类型。此重载中的引用参数需要引用一些 value_type 类型的对象.因此必须使用花括号初始化列表来构造类型为 value_type 的(临时)对象。将此引用绑定(bind)到。但是,这不能 是存储在 vector 中的对象,因为它是在调用者的上下文中创建的临时对象。调用者不知道哪里 push_back将构造 vector 的实际元素。因此 push_back将需要从绑定(bind)到参数引用的临时对象到放置在 vector 存储中的实际对象进行移动构造。

如此有效my_vector.push_back({arg1, arg2});my_vector.push_back(value_type{arg1, arg2});相同, 只有那个value_type{arg1, arg2}是一个实际的纯右值表达式,在初始化 push_back 时将具体化为一个临时对象的引用参数。由于这种几乎相同的行为,人们可能会草率地说 {arg1, arg2} “是纯右值”或“是临时值”,即使这在技术上是不正确的。

push_back没有任何不引用 value_type 的重载,所以这总是不可避免的。 emplace_back另一方面,将任何类型作为参数,然后将它们直接转发到 vector 存储中存储的对象的构造。

转发braced-init-lists也是不可能的。没有语法可以在保留单个列表元素的类型的同时捕获它们。您只能从整个列表中初始化指定类型的对象,如 push_back或初始化数组或 std::initializer_list具有同质元素类型(每个列表元素中的一个元素),这是初始化列表构造函数将执行的操作(同质类型是 vector 的元素类型)。

关于c++ - 为什么 "Guaranteed Copy Elision"不表示 push_back({arg1, arg2}) 与 emplace_back(arg1, arg2) 相同?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/74561478/

相关文章:

c++ - Protobuf 消息中的消息标识符

c++从decltype返回类型中删除noexcept

c++ - 如何转发声明模板实例成员函数

c++ - 使用 memcpy 将 int 复制到 char 数组,然后打印其成员 : undefined behaviour?

将指向数组的指针转换为指针

c++ - 在构造函数中捕获异常

c++ - 将整数放入字符串

c++ - c++ chrono是否具有正常的舍入功能,该值在平局时从0舍入?

c++ - 如何在模板函数中使用不同的结构作为模板参数?

c++ - 对结构成员的临时绑定(bind)引用