构造函数中的C++通用引用和返回值优化(rvo)

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启动.

  • @tower120 不,这不是编译器错误。这是标准指定的行为。不允许编译器省略对模板化构造函数的调用,因为它既不是复制构造函数也不是移动构造函数。 (2认同)
  • @tower120 返回值优化*是*复制省略。这就是它的指定方式 - 因为允许编译器省略某些复制/移动操作。 (2认同)
  • 那么你完全错了.声明复制和移动构造函数,并在所有允许的情况下获得RVO,或使用SFINAE约束构造函数模板,因此它不能用于复制或移动`C`对象. (2认同)