意外的复制构造函数
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 文章解释得更好。
在下面的示例中,我希望只有一个复制构造,因为我认为中间副本会 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 文章解释得更好。