tow*_*120 9 c++ templates rvalue-reference rvo universal-reference
为什么在带有通用引用参数的构造函数的类中不会出现右值优化?
http://coliru.stacked-crooked.com/a/672f10c129fe29a0
#include <iostream>
template<class ...ArgsIn>
struct C {
template<class ...Args>
C(Args&& ... args) {std::cout << "Ctr\n";} // rvo occurs without &&
~C(){std::cout << "Dstr\n";}
};
template<class ...Args>
auto f(Args ... args) {
int i = 1;
return C<>(i, i, i);
}
int main() {
auto obj = f();
}
Run Code Online (Sandbox Code Playgroud)
输出:
Ctr
Ctr
Dstr
Ctr
Dstr
Dstr
Run Code Online (Sandbox Code Playgroud)
T.C*_*.C. 10
我认为问题在于实例化
template<class ...Args>
C(Args&& ... args) {std::cout << "Ctr\n";}
Run Code Online (Sandbox Code Playgroud)
就语言而言,不是复制/移动构造函数,因此编译器不能忽略对它们的调用.从§12.8[class.copy]/p2-3开始,增加了重点并省略了示例:
一个非模板的构造函数的类
X是拷贝构造函数,如果它的第一个参数是类型X&,const X&,volatile X&或const volatile X&,并且或者有没有其他参数,否则所有其他参数都默认参数(8.3.6).一个非模板的构造函数的类
X是移动构造函数,如果它的第一个参数是类型的X&&,const X&&,volatile X&&,或const volatile X&&,并且或者有没有其他参数,否则所有其他参数默认参数(8.3.6).
换句话说,作为模板的构造函数永远不能是复制或移动构造函数.
返回值优化是copy elision的一个特例,描述为(§12.8[class.copy]/p31):
当满足某些条件时,允许实现省略类对象的复制/移动构造,即使为复制/移动操作选择的构造函数和/或对象的析构函数具有副作用.
这允许实现省略"复制/移动构造"; 使用既不是复制构造函数也不是移动构造函数的东西构造对象不是"复制/移动构造".
因为C具有用户定义的析构函数,所以不会生成隐式移动构造函数.因此,重载决策将选择带有Args推导的模板化构造函数C,这比rvalues的隐式复制构造函数更好.但是,编译器不能忽略对此构造函数的调用,因为它具有副作用,既不是复制构造函数也不是移动构造函数.
如果是模板化的构造函数
template<class ...Args>
C(Args ... args) {std::cout << "Ctr\n";}
Run Code Online (Sandbox Code Playgroud)
然后它不能用Args= C来实例化以生成复制构造函数,因为这将导致无限递归.标准中有一条特殊规则禁止此类构造函数和实例化(§12.8[class.copy]/p6):
如果一个类的构造函数
X的第一个参数是类型(可选择cv-qualified)X并且没有其他参数,或者所有其他参数都有默认参数,那么它的构造函数声明是错误的.从不实例化成员函数模板以生成此类构造函数签名.
因此,在这种情况下,唯一可行的构造函数将是隐式定义的复制构造函数,并且可以省略对该构造函数的调用.
如果我们改为从中删除自定义析构函数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;
};
Run Code Online (Sandbox Code Playgroud)
我们只看到一个对D析构函数的调用,表明只C构造了一个对象.这里C的移动构造函数是由重载决策隐式生成和选择的,你再次看到RVO启动.