构造函数中的C通用引用和返回值优化(rvo)
作者:互联网
为什么在具有带有通用引用参数的构造函数的类中不进行右值优化?
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开始,增加了重点,并省略了示例:
A non-template constructor for class
X
is a copy constructor if
its first parameter is of typeX&
,const X&
,volatile X&
orconst volatile X&
,
and either there are no other parameters or else all
other parameters have default arguments (8.3.6).A non-template constructor for class
X
is a move constructor if
its first parameter is of typeX&&
,const X&&
,volatile X&&
, or
const volatile X&&
, and either there are no other parameters or else all
other parameters have default arguments (8.3.6).
换句话说,作为模板的构造函数永远不能是副本或移动构造函数.
返回值优化是复制省略的一种特殊情况,它描述为(§12.8[class.copy] / p31):
When certain criteria are met, an implementation is allowed to omit
the copy/move construction of a class object, even if the constructor
selected for the copy/move operation and/or the destructor for the
object have side effects.
这允许实现消除“复制/移动构造”;使用既不是复制构造函数也不是move构造函数的对象构造对象不是“复制/移动构造”.
由于C具有用户定义的析构函数,因此不会生成隐式move构造函数.因此,重载解析将选择Args推导为C的模板化构造函数,这比rvalues的隐式副本构造函数更好.但是,编译器无法取消对此构造函数的调用,因为它具有副作用,并且既不是复制构造函数也不是move构造函数.
如果改为使用模板构造函数
template<class ...Args>
C(Args ... args) {std::cout << "Ctr\n";}
然后就不能使用Args = C实例化它来生成副本构造函数,因为那样会导致无限递归.标准中有一条特殊规则禁止此类构造函数和实例化(第12.8节[class.copy] / p6):
A declaration of a constructor for a class
X
is ill-formed if its
first parameter is of type (optionally cv-qualified)X
and either
there are no other parameters or else all other parameters have
default arguments. A member function template is never instantiated to
produce such a constructor signature.
因此,在那种情况下,唯一可行的构造函数将是隐式定义的副本构造函数,并且可以省略对该构造函数的调用.
如果改为使用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的move构造函数是由过载解析隐式生成和选择的,您会看到RVO再次插入.
标签:rvo,universal-reference,c,templates,rvalue-reference 来源: https://codeday.me/bug/20191010/1887641.html