我认为问题在于实例化
template<class ...Args>
C(Args&& ... args) {std::cout << "Ctr\n";}
就语言而言,不是复制/移动构造函数,因此编译器不能忽略对它们的调用。从 §12.8 [class.copy]/p2-3 开始,添加了强调并省略了示例:
A 非模板构造函数上课X
是一个复制构造函数,如果
它的第一个参数的类型X&
, const X&
, volatile X&
or const volatile X&
,
要么没有其他参数,要么全部
其他参数有默认参数(8.3.6)。
A 非模板构造函数上课X
是一个移动构造函数,如果
它的第一个参数的类型X&&
, const X&&
, volatile X&&
, or
const volatile X&&
,要么没有其他参数,要么全部
其他参数有默认参数(8.3.6)。
换句话说,作为模板的构造函数永远不能是复制或移动构造函数。
返回值优化是复制省略的一种特殊情况,描述为(§12.8 [class.copy]/p31):
当满足某些标准时,允许实施省略
类对象的复制/移动构造,即使构造函数
选择用于复制/移动操作和/或析构函数
对象有副作用。
这允许实现省略“复制/移动构造”;使用既不是复制构造函数也不是移动构造函数的东西构造对象不是“复制/移动构造”。
Because C
有用户定义的析构函数,不会生成隐式移动构造函数。因此,重载决策将选择模板化构造函数Args
推论为C
,这比右值的隐式复制构造函数更匹配。但是,编译器无法省略对此构造函数的调用,因为它有副作用,并且既不是复制构造函数也不是移动构造函数。
如果模板化构造函数是
template<class ...Args>
C(Args ... args) {std::cout << "Ctr\n";}
那么它不能被实例化Args
= C
生成一个复制构造函数,因为这会导致无限递归。标准中有一条特殊规则禁止此类构造函数和实例化(§12.8 [class.copy]/p6):
类的构造函数的声明X
如果它是格式错误的
第一个参数的类型(可选 cv 限定)X
以及
没有其他参数或者所有其他参数都有
默认参数。成员函数模板永远不会被实例化
产生这样的构造函数签名。
因此,在这种情况下,唯一可行的构造函数将是隐式定义的复制构造函数,并且可以省略对该构造函数的调用。
如果我们改为remove自定义析构函数来自C
,并添加另一个类来跟踪C
的析构函数被调用:
struct D {
~D() { std::cout << "D's Dstr\n"; }
};
template<class ...ArgsIn>
struct C {
template<class ...Args>
C(Args&& ... args) {std::cout << "Ctr\n";}
D d;
};
我们只看到一个调用D
的析构函数,表明只有一个C
对象被构造。这里C
的移动构造函数是通过重载决策隐式生成和选择的,您会看到 RVO 再次启动。