【问题标题】:How is the move constructor of member variable invoked without using std::forward?如何在不使用 std::forward 的情况下调用成员变量的移动构造函数?
【发布时间】:2016-02-12 20:38:40
【问题描述】:

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]

提到

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

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

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

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

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

?

因为正如在 std:forward 案例中指出的那样,

所有命名的值都应该评估为左值

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

【问题讨论】:

  • 您应该在构造函数中使用std::move。见:How to define a move constructor?
  • “然而,典型的移动构造函数看起来像......”不,它没有。此外,std::forward 用于 转发 事物作为它们的原始值类别,可以是右值或左值,具体取决于与 std::forward 一起使用的模板参数。只是说 std::forward(other._data) 没有模板参数列表甚至不会编译。如果您只想要一个右值,请使用std::move 而不是std::forward
  • @JonathanWakely 但是如果参数是左值 ClassName(ClassName&& other) 甚至不会被调用,因为函数原型不匹配。
  • @user557583,它可能以右值开始,但一旦你进入构造函数,它就有一个名称并且是一个左值。阅读转发引用的工作原理。这不是您使用std::forward 的方式,也不是您编写移动构造函数的方式。

标签: c++ c++11 move-semantics rvalue move-constructor


【解决方案1】:

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

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

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

std::forward&lt;T&gt;(obj)总是与显式模板参数一起使用。在实践中,类型是为转发参考推导出的。这些看起来非常像右值引用,但它们完全不同!特别是,转发引用可以引用左值。

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

【讨论】:

  • 我明白了。嗯,实际上我从 msdn 页面获取了(错误的?)移动构造函数 sn-p:msdn.microsoft.com/en-us/library/dd293665.aspx。那MS是不是搞错了?
  • @user557583:该页面似乎没有任何移动构造函数来显示如何移动本身具有移动构造函数的成员。它确实显示了复制赋值运算符的不明智的实现(它不是异常安全的,因为分配可能会引发双重删除)和移动构造函数(没有必要首先初始化初始化列表中的成员和然后立即将它们放入体内)。 “鲁棒编程”也是不明智的,因为它会产生强制开销(自分配检查和不必要的初始化和删除)。这是一个非常糟糕的页面!
  • 库尔我明白了。我很惊讶微软的官方编码指南具有误导性。
  • @user557583:请注意,移动构造函数是正确的(尽管以一种愚蠢的方式编写)。它没有显示案例,但是,它甚至没有按照您的要求进行操作(从移动构造函数的对应成员构造一个成员)。即使有,也没关系,因为该成员恰好是一个指针,并且内置类型无论如何都没有特殊的移动行为:即使std::move()d 它也只是一个副本。关于想要显示的页面没有错。
  • 对,使用move构造函数只是为了移动指针值并不是很有意义的例子来展示move构造函数的优势。
【解决方案2】:

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

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

最重要的是要意识到何时拥有forwarding reference 和何时拥有rvalue reference。它们看起来相似但实际上并不相似。

转发引用始终采用以下形式:

T&amp;&amp; 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,并注意在撰写本文时使用了“通用参考”一词(实际上是由斯科特本人介绍的)。现在一致认为“转发参考”更好地描述了它的目的和用法。


所以你的例子应该是:

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

因为otherrvalue reference,因为ClassName 不是推导类型。

【讨论】:

  • Scott 文章的链接已失效或根本不正确。请检查
  • @Patryk ty,不知道发生了什么。我现在放了正确的链接。
  • 我个人认为必须具有确切的 T&& 类型参数的通用引用过于微妙,因此难以捉摸。很好,我现在明白了。
【解决方案3】:

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

以下构造函数仅接受右值。然而,由于参数“其他”得到了一个名称,它失去了它的“右值”,现在是一个左值。要将其转换回 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 是 Rvalue,_data 构造函数将被 Rvalue 调用。如果 other 是左值,_data 将用左值构造。这就是为什么它被称为perfect-forwarding.

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

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

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

【讨论】:

  • std::forward(other._data) 不会编译,所以你不能用它来代替
  • @JonathanWakely std::forward&lt;T&gt;std::move 相同,如果 T 不是来自转发引用
  • @M.M 但多余,在这种情况下只需使用 std::move 。
  • @José,不,它不会编译。您的示例没有实例化模板,因为它不使用该构造函数。尝试调用它,它将无法编译。 std::forward 必须与模板参数列表一起使用,以说明要转发的类型。
猜你喜欢
  • 1970-01-01
  • 2020-12-30
  • 2017-01-31
  • 2017-04-17
  • 1970-01-01
  • 2020-11-22
  • 2011-09-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多