【问题标题】:Mixing Rvalue and LValue reference混合右值和左值引用
【发布时间】:2021-11-30 19:58:37
【问题描述】:

我试图更好地理解 LValue、RValue 以及 std::move 的工作原理。 我有以下代码

#include <string>

class A 
{
public:
A() = default;
A(std::string&& aString): myString(std::move(aString)) {}
std::string myString;
};

class B
{
public: 
void InitMembers(std::string& aString) { myA = A(std::move(aString));}
private:
A myA;
};


int main() 
{
 B b; 
std::string hello; 
b.InitMembers(hello);
}

我的问题是:

  • void InitMembers(string&amp; aString) { myA = A(std::move(aString));} 中,我知道我必须使用std::move 到aString 才能将aString 从LValue 引用转换为RValue 引用。但是我对 InitMember 范围内的 aString 的含义有些怀疑。 aString 作为 LValue 引用提供,但在方法范围内它被视为 LValue,这就是为什么我必须使用 std::move? Std::move 应该依赖于引用推导(对吗?),在这种情况下它如何推导类型?它会推导出一个类型 "string" o "string&",因为 aString 在方法的参数中作为 LValue 引用提供?
  • 为什么我还必须在 A 的构造函数初始化程序中使用 std::move? aString 是触发移动构造函数的 RValue 引用这一事实还不够吗?

下面的实现是不是和上面的一样好?

#include <string>
class A 
{
public:
A() = default;
A(std::string& aString): myString(std::move(aString)) {}
std::string myString;
};

class B
{
public: 
void InitMembers(std::string& aString) { myA = A(aString);}
private:
A myA;
};

int main() 
{
 B b; 
std::string hello; 
b.InitMembers(hello);
} 

谢谢:)

【问题讨论】:

  • 似乎有点类似于:stackoverflow.com/a/31213816/754407,“如果它有名字,它就是一个左值引用”。查看A class ctor:你必须使用std::move,因为值被命名,它可以被多次使用,因此你必须明确它。 std::move 是一个“右值转换”,但类型推导仅在使用模板类型 T&amp;&amp; 时才会发生,然后它可以绑定到任一类型的引用,std::move 可以同时处理两者。
  • "为什么我还必须在 A 的构造函数初始化器中使用 std::move?" -- 因为string&amp;&amp; aString 是“对 r 值的引用”,它本身就是一个 l -值。
  • 嗨@Tharsalys,您遇到了我的一个疑问,为什么它是LValue?我的意思是,如果我说“aString”是作为 RValue 引用提供的,但它实际上是 LValue,我说得对吗? RValue 引用是否在范围内被视为左值(在这种情况下它是初始化列表)?
  • @Evethir, aString 是一个左值,因为“它是一个有名字的东西”。
  • 嗨@TedLyngmo 我已经清理了代码,希望现在更清楚:)

标签: c++ rvalue-reference lvalue perfect-forwarding stdmove


【解决方案1】:

关于

A(std::string&& aString): myString(std::move(aString)) {}

std::string&amp;&amp; 表示对std::string 的右值引用。右值引用只绑定到右值(prvalues 和 xvalues),所以两个可能的调用点是这样的:

// assuming this is defined somewhere
std::string f(void); // returns by value, i.e. `f()` is an rvalue
// call site 1
A a1{f()}; // `f()` is an rvalue (precisely a prvalue)

// assuming this
std::string s{"ciao"}; // s is an lvalue
// call site 2
A a2{std::move(s)}; // `std::move(s)` is an rvalue (precisely an xvalue)
                    // i.e. with `std::move` we turn s into an rvalue argument,
                    // so it can bind to the rvalue reference parameter
// don't expect s to be the one it was before constructing a2

无论哪种情况,构造函数对aString做了什么?

好吧,aString 是一个左值,因为“它有一个名字”(实际的定义有点复杂,但这是最容易上手的,而且也没有那么错),所以如果你逐字使用它,编译器不会假定它是绑定到一个临时的,它不会让myString 窃取它的资源。

但是知道aString 绑定到一个临时的,因为你已经将它声明为std::string&amp;&amp;,所以你将它作为std::move(aString) 传递给告诉编译器“认为这是一个临时的”

是的,从技术上讲,编译器也知道aString 绑定到一个临时的,但它不能自动std::move 它。为什么?因为您可能想多次使用它:

A(std::string&& aString) : myString(aString/* can't move this */) {
  std::cout << aString << std::endl; // if I want to use it here
}
// yes, this is a very silly example, sorry

关于

void InitMembers(std::string& aString) { myA = A(std::move(aString));}

aString 表示对非const std::string 的左值引用,因此您只能将非const 左值传递给.InitMembers

然后在函数内部std::move 告诉A 的构造函数“看,这是临时的”。但这也意味着在调用站点 (b.InitMembers(hello);),您将输入 (hello) 留在移出状态,就像上面第一个示例中的 s。没关系,因为调用者知道InitMembers 通过const 左值引用获取其参数,因此它知道它们传递的参数可以通过调用更改。就像在前面的示例中一样,用户在 s 周围写了 std::move,所以他们应该知道自己在做什么。

有关std::move 的工作原理(以及std::forward)的更多详细信息,我想向您指出我的this answer

【讨论】:

  • 嗨 Enlico,感谢您的精彩回答,现在我对如何使用 RValue 和 LValue 引用有了更清晰的了解。多谢!我还有一个问题要问你。在我报告的第二种情况下,aString 是 A 构造函数是 LValue 引用,std::move 运算符仍会将其转换为 RValue 引用,我应该仍然能够窃取它的数据吧?
  • @Evethir,你指的是void InitMembers(std::string&amp; aString) { myA = A(std::move(aString));}吗?
猜你喜欢
  • 1970-01-01
  • 2021-03-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-08-17
  • 2011-02-14
  • 2020-08-24
  • 1970-01-01
相关资源
最近更新 更多