如何在不使用 std::forward 的情况下调用成员变量的移动构造函数

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

本文关键字:调用 情况下 成员 变量 构造函数 移动 forward std      更新时间:2023-10-16

这里有一个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]

提到

所有命名值(如函数参数(始终 评估为左值(即使是那些声明为右值引用(

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

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

看起来_data(other._data)应该调用_data类的移动构造函数。但是,不使用std::forward怎么可能?换句话说,不应该是

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

因为,正如 std:forward case 中指出的那样,

所有命名值的计算结果应为左值

我越来越喜欢C++因为这样的问题的深度以及语言足够大胆以提供此类功能的事实:)谢谢!

典型的移动构造函数如下所示(假设它是显式实现的:您可能希望更喜欢= default(:

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

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

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

您可能对我的两个守护进程文章感兴趣,该文章详细描述了差异。

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)
};

欲了解更多信息,请查看斯科特·迈耶斯(Scott Meyers(的这篇出色的深度文章,并注意在撰写文章时使用了"通用参考"一词(实际上是由斯科特本人介绍的(。现在人们一致认为,"转发引用"更好地描述了它的目的和用法。


所以你的例子应该是:

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

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

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

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

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(称之为"通用Rerefences",但现在它被称为"转发参考"。这就是为什么在这里必须使用 std::forward,以便如果其他构造函数是 Rvalue,_data构造函数将使用 Rvalue 调用。如果 other 是左值,则_data将使用左值构造。这就是为什么它被称为perfect-forwarding.

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

我尝试使用您的构造函数作为示例,以便您可以理解,但这不是特定于构造函数的。这也适用于函数。

对于第一个示例,由于您的第一个构造函数只接受 Rvalues,因此您可以完全使用 std::forward,并且两者都会做同样的事情。但最好不要这样做,因为人们可能会认为您的构造函数接受forwarding reference,而实际上并非如此。

相关文章: