构造函数中的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 type X&, 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).

A non-template constructor for class X is a move constructor if
its first parameter is of type X&&, 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再次插入.

转载注明原文:构造函数中的C通用引用和返回值优化(rvo) - 代码日志