为什么在具有带有通用引用参数的构造函数的类中不进行右值优化?
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();
}
输出:
Ctr
Ctr
Dstr
Ctr
Dstr
Dstr
我认为问题在于
template<class ...Args>
C(Args&& ... args) {std::cout << "Ctr\n";}
就语言而言,它们不是复制/移动构造函数,因此编译器无法消除对其的调用。从§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)。
换句话说,作为模板的构造函数永远不能是副本或移动构造函数。
返回值优化是复制省略的一种特殊情况,它描述为(§12.8[class.copy] / p31):
当满足某些条件时,即使为复制/移动操作选择的构造函数和/或对象的析构函数具有副作用,也允许实现忽略类对象的复制/移动构造。
这使得实现可以消除“复制/移动构造”;使用既不是复制构造函数也不是move构造函数的对象构造对象不是“复制/移动构造”。
由于C
具有用户定义的析构函数,因此不会生成隐式move构造函数。因此,重载解析将选择Args
推导为的模板化构造函数C
,这比右值的隐式副本构造函数更好。但是,编译器无法取消对此构造函数的调用,因为它具有副作用,并且既不是复制构造函数也不是move构造函数。
如果改为使用模板构造函数
template<class ...Args>
C(Args ... args) {std::cout << "Ctr\n";}
然后它不能用Args
=实例化C
以生成副本构造函数,因为那样会导致无限递归。标准中有一条特殊规则禁止此类构造函数和实例化(第12.8节[class.copy] / p6):
如果类的构造函数声明的
X
第一个参数是类型(可选,用cv限定),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;
};
我们仅看到对D
的析构函数的调用,表明仅C
构造了一个对象。这里C
的move构造函数是由过载解析隐式生成和选择的,您会再次看到RVO插入。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句