不使用std::forward如何调用成员变量的move构造函数?

How is the move constructor of member variable invoked without using std::forward?

一个例子 here std::forward,

// forward example
#include <utility>      // std::forward
#include <iostream>     // std::cout

// function with lvalue and rvalue reference overloads:
void overloaded (const int& x) {std::cout << "[lvalue]";}
void overloaded (int&& x) {std::cout << "[rvalue]";}

// function template taking rvalue reference to deduced type:
template <class T> void fn (T&& x) {
  overloaded (x);                   // always an lvalue
  overloaded (std::forward<T>(x));  // rvalue if argument is rvalue
}

int main () {
  int a;

  std::cout << "calling fn with lvalue: ";
  fn (a);
  std::cout << '\n';

  std::cout << "calling fn with rvalue: ";
  fn (0);
  std::cout << '\n';

  return 0;
}

Output:
calling fn with lvalue: [lvalue][lvalue]
calling fn with rvalue: [lvalue][rvalue]

提到

the fact that all named values (such as function parameters) always evaluate as lvalues (even those declared as rvalue references)

然而,典型的移动构造函数看起来像

ClassName(ClassName&& other)
   : _data(other._data)
{
}

看起来像 _data(other._data) 应该调用 _data 的 class 的移动构造函数。但是,不使用 std::forward 怎么可能呢?也就是说,不应该是

ClassName(ClassName&& other)
   : _data(std::forward(other._data))
{
}

?

因为,正如 std:forward 案例中所指出的,

all then named values should evaluate as lvalue

我越来越喜欢 C++,因为像这样的问题很深,而且这种语言足够大胆,可以提供这样的功能 :) 谢谢!

一个典型的移动构造函数看起来像这样(假设它是显式实现的:你可能更喜欢 = default):

ClassName::ClassName(ClassName&& other)
    : _data(std::move(other._data)) {
}

没有 std::move() 成员被复制:因为它有一个名字 other 是一个左值。但是,引用绑定到的对象是右值或被视为右值的对象。

std::forward<T>(obj) 总是 与显式模板参数一起使用。实际上,该类型是为 转发引用 推导出来的。这些看起来非常像右值引用,但完全不同!特别是,转发引用可能引用左值。

您可能对我的 Two Daemons 文章感兴趣,其中详细描述了差异。

这个 Ideone example 应该让你很清楚了。如果没有,请继续阅读。

以下构造函数只接受右值。然而,由于参数 "other" 得到了一个名字,它失去了它的 "rvalueness",现在是一个左值。要将其转换回 Rvalue,您必须使用 std::move。没有理由在这里使用 std::forward 因为这个构造函数不接受左值。如果你试图用左值调用它,你会得到编译错误。

ClassName(ClassName&& other)
     : _data(std::move(other._data))
{
    // If you don't use move, you could have: 
    //    cout << other._data;
    // And you will notice "other" has not been moved.    
}

以下构造函数接受左值和右值。 Scott Meyers 将其称为 "Universal Rerefences",但现在称为 "Forwarding References"。这就是为什么在这里必须使用 std::forward 的原因,这样如果 other 是右值,_data 构造函数将使用右值调用。如果 other 是左值,则 _data 将使用左值构造。这就是为什么它被称为 perfect-forwarding.

template<typename T>
ClassName(T&& other)
   : _data(std::forward<decltype(_data)>(other._data))
{
}

我尝试以您的构造函数为例,以便您理解,但这并非特定于构造函数。这也适用于函数。

对于第一个例子,因为你的第一个构造函数只接受右值,你可以完美地使用 std::forward 来代替,两者会做同样的事情。但最好不要这样做,因为人们可能会认为您的构造函数接受了 forwarding reference,而实际上并没有。

std::forward 应与 forwarding reference.
一起使用 std::move 应与 rvalue reference 一起使用。

构造函数没有什么特别之处。这些规则同样适用于任何函数、成员函数或构造函数。

最重要的是要意识到什么时候有forwarding reference,什么时候有rvalue reference。它们看起来很相似,但实际上并非如此。

转发引用总是,格式为:

T&& ref

对于T一些推导类型

例如,这是转发引用:

template <class T>
auto foo(T&& ref) -> void;

所有这些都是右值引用:

auto foo(int&& ref) -> void; // int not deduced

template <class T>
auto foo(const T&& ref); // not in form `T&&` (note the const)

template <class T>
auto foo(std::vector<T>&& ref) -> void; // not in form `T&&`

template <class T>
struct X {
    auto foo(T&& ref) -> T; // T not deduced. (It was deduced at class level)
};

更多请查看this excellent in-depth article by Scott Meyers,注意写文章时使用了"universal reference"这个词(实际上是Scott自己介绍的)。现在一致认为 "forwarding reference" 更好地描述了它的目的和用法。


所以你的例子应该是:

ClassName(ClassName&& other)
   : _data(std::move(other._data))
{
}

因为 other 是一个 rvalue reference 因为 ClassName 不是推导类型。