意外的复制构造函数

Unexpected copy constructor

在下面的示例中,我希望只有一个复制构造,因为我认为中间副本会 copy elided。唯一需要的(我想?)副本将在 B 的构造函数中初始化成员变量 a.

#include <iostream>

struct A
{
    A() = default;
    A(A const&) { std::cout << "copying \n"; }
};

struct B
{
    B(A _a) : a(_a) {}
    A a;    
};

struct C : B
{
    C(A _a) : B(_a) {}
};

int main()
{
    A a{};
    C c(a);
}

当我 execute this code(使用 -O3)时,我看到以下输出

copying 
copying 
copying 

为什么不删除这些中间副本?

您正在按值将 a 对象传递给 C 构造函数。然后按值传递给 B 构造函数。

以下是允许复制省略的情况 (class.copy/31):

  • in a return in a function with a class return type, when the expression is a name 具有相同 cv- 的非易失性自动对象(函数或 catch 子句参数除外) 非限定类型作为函数 return 类型, copy/move 操作可以通过构造省略 自动对象直接进入函数的return值

  • 在 throw 表达式中,当操作数是非易失性自动对象的名称时(除了 一个函数或 catch 子句参数),其范围不超出最内层的末尾 包含 try 块(如果有的话),从操作数到异常的 copy/move 操作 object(15.1)可以省略,直接构造自动对象到异常对象中

  • 当未绑定到引用 (12.2) 的临时 class 对象将 copied/moved 对于具有相同 cv-unqualified 类型的 class 对象,可以省略 copy/move 操作 将临时对象直接构造成省略的copy/move
  • 的目标
  • 当异常处理程序的异常声明(条款 15)声明相同类型的对象时 (cv-qualification除外)作为异常对象(15.1),copy/move操作可以省略

None 对于您的示例而言是正确的(我们不在 return 语句、抛出表达式或异常声明中。并且您的示例中根本没有临时对象。) ,因此每次您期望发生时都会发生复制。

请注意,在上述情况下允许复制省略,但不是强制性的。因此,即使在这些情况下,编译器也被允许发出副本(对于 C++11 来说是这样。在 C++17 中,在某些情况下,复制省略是强制性的。但是,none 你的示例案例也允许在 C++17 中省略。)

扩展另一个答案下的讨论,这里的副本有副作用(打印到控制台),因此不符合副本优化的条件。

但是,根据有关复制省略的 cppreference 文章,无论副作用如何,都允许复制省略。这里是不允许的,因为你一直都有左值。 None 您希望删除的副本是右值或纯右值。要使它们成为右值,您需要使用 std::move 进行转换或将它们构造为无名临时对象作为构造函数调用的一部分。

同样,cppreference 文章解释得更好。