9

为什么在具有通用引用参数的构造函数的类中不会发生右值优化?

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
4

1 回答 1

11

我认为问题在于

template<class ...Args>
C(Args&& ... args) {std::cout << "Ctr\n";}  

就语言而言,它们不是复制/移动构造函数,因此编译器不能省略对它们的调用。从 §12.8 [class.copy]/p2-3 开始,添加了重点并省略了示例:

如果类的第一个参数是、或类型,并且没有其他参数或者所有其他参数都具有默认参数(8.3.6),则类的非模板构造函数是一个复制构造函数。XX&const X&volatile X&const volatile X&

类的非模板构造函数X是移动构造函数,如果其第一个参数的类型为X&&const X&&volatile X&&const volatile X&&,并且要么没有其他参数,要么所有其他参数都有默认参数(8.3.6)。

换句话说,作为模板的构造函数永远不能是复制或移动构造函数。

返回值优化是复制省略的一种特殊情况,描述为 (§12.8 [class.copy]/p31):

当满足某些条件时,允许实现省略类对象的复制/移动构造,即使为复制/移动操作选择的构造函数和/或对象的析构函数具有副作用。

这允许实现省略“复制/移动构造”;使用既不是复制构造函数也不是移动构造函数的东西构造对象不是“复制/移动构造”。

因为C具有用户定义的析构函数,所以不会生成隐式移动构造函数。因此,重载决议将选择Args推导为的模板化构造函数C,这比右值的隐式复制构造函数更匹配。但是,编译器不能省略对这个构造函数的调用,因为它有副作用,既不是复制构造函数也不是移动构造函数。

如果模板化的构造函数是

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's 的析构函数,表明只C构造了一个对象。这里C的 move 构造函数是由重载决议隐式生成和选择的,你会看到 RVO 再次启动。

于 2014-07-24T04:59:35.270 回答