如何获得对右值的引用?

Posted

技术标签:

【中文标题】如何获得对右值的引用?【英文标题】:How is it possible to get a reference to an rvalue? 【发布时间】:2015-02-11 15:51:25 【问题描述】:

我在 C++ 中使用过std::movestd::forward。我的问题是:标准库实际上是如何实现这些功能的?

如果一个左值是你可以得到地址的东西,而一个右值完全不是一个左值,你怎么能真正实现这些引用?

这些新设施是否允许:

auto x = &(3); 

或类似的东西?您能否获得对不只是 std::move/forward 返回左值的右值的引用?

希望这些问题是有意义的。我在谷歌上找不到好的信息,只有完美转发的教程等。

【问题讨论】:

带有右值引用 :P 这意味着你没有搜索 :( 当然,我怎么没想到呢? 【参考方案1】:

如何获得对右值的引用?

从概念上讲,rvalue 表达式创建一个临时对象,或者有时表示一个现有对象。可以像任何其他对象一样绑定到引用;但是,为了避免混淆,该语言只允许 rvalueconst lvalue 引用。

我在 C++ 中使用过 std::move 和 std::forward。我的问题是编译器实际上是如何实现的?

move 只返回一个 rvalue 对其参数的引用,相当于

static_cast<typename remove_reference<T>::type&&>(t)

函数调用的结果是一个rvalue(具体来说,一个xvalue),所以它可以绑定到一个rvalue引用,其中函数参数不能。这允许您从 左值 显式移动,使用 move 将其转换为 右值,同时不允许您意外地从它移动。

forward 类似,但重载以返回对 rvaluervalue 引用的 rvalue 引用和 lvalue 引用其他任何东西。

如果一个左值是你可以得到的地址

这或多或少是正确的。官方的定义是“指定一个函数或一个对象”的表达,那些是有地址的东西。

并且右值完全不是左值

不是真的。稍微简化一下,表达式可以是 lvaluervalue,但可以从一个转换为另一个。 lvalue 可以隐式转换为 rvalue;像move 所做的那样,可以通过强制转换来转换另一种方式。

如何实际实现这些引用?

就像任何其他引用一样 - 作为它绑定到的对象的别名或指针。唯一的区别是可以使用哪些类型的表达式来表示(并可能创建)绑定到引用的对象。

这些新设施是否允许类似auto x = &amp;(3);

试图直接获取 rvalue 的地址,这是不允许的。由于问题是关于引用,而不是指针,因此允许将引用绑定到临时对象(其生命周期被延长以匹配引用):

auto && rvalue = 3;
auto const & const_lvalue = 3;

虽然不允许将其绑定到非 const lvalue 引用

auto & lvalue = 3;  // ERROR

【讨论】:

【参考方案2】:

我不能像这样调用函数:void foo(string* bar)foo(&amp;string("Hello World!")),否则我会收到错误:

错误:获取临时地址

我也不能像这样调用函数:void foo(string&amp; bar)foo(string("Hello World!")),否则我得到一个错误:

错误:从“std::string aka std::basic_string”类型的右值对“std::string& aka std::basic_string&”类型的非常量引用无效初始化

C++11 提供给我的能力是创建一个右值引用,所以我可以调用一个函数:void foo(string&amp;&amp; bar),像这样:foo(string("Hello World!"));

另外,在foo内部,我可以得到右值引用传入的对象的地址:

void foo(string&& bar)
    string* temp = &bar;

    cout << *temp << " @:" << temp << endl;

似乎 OP 对右值有很好的把握。但是this explanation of them 对我有帮助,可能对其他人也有帮助。它详细介绍了为什么 C++03 允许对右值进行常量引用,而不是 C++11 的右值引用。

【讨论】:

这绝对回答了我所有的问题。谢谢你。编辑:谢谢你的链接,我看看。 “此外,在foo 内部,我可以得到右值的地址:” 我不同意:右值,或者一般的表达式,没有地址- 对象有地址。因此,&amp;bar 不会为您提供右值的地址。您将获得由表达式 bar&amp;bar 的子表达式)引用的对象的地址,而此表达式 bar 是一个左值。 @dyp 好的,我同意你的说法......你对这个编辑感到满意吗:“此外,在 foo 内部,我可以通过一个r 值参考。” 嗯,我不认为这是不正确或不精确的。但它可能没有涵盖如何实现它以获取对象地址的本质:您需要一个引用对象的左值来获取其地址,因此您更改表达式的值类别,或者更确切地说,创建一个引用同一对象的新左值表达式。比如反招template&lt;class T&gt; T&amp; as_lvalue(T&amp;&amp; t) return t; @dyp 你的“反移动”很有趣。我从来没有想过这样做。我已经进行了第一次编辑......但是当你说:“你需要一个引用对象的左值来获取它的地址时,请帮助我。”你是说bar 实际上是foo 内部的左值吗?【参考方案3】:

基本上,编译器的魔法。标准描述了规则,编译器制造者只需要弄清楚如何实现规则。

实际上,引用要么被优化,要么被实现为 CPU 级别的指针。

std::move 在这个意义上并不特别。它有一个左值引用作为输入,一个右值引用作为输出。编译器只需要将右值引用规则应用于输入。

同样,std::forward&lt;T&gt; 的目标只是告诉编译器对参数应用一组不同的规则,这些规则恰好被定义为完美转发工作。函数本身什么都不做。

【讨论】:

对,我知道 std::move 和 std::forward 基本上就像“取一个 L 值,把它当作一个 r 值”。但是是否有可能真正获得“纯”r 值的地址?就像自动 x = &(i + 3);我猜。 可能吗?我仍然认为不会,对吧?编辑:你已经回答了我原来的问题。我应该提出一个主要问题,即我也对在没有 std::move/std::forward 的情况下获得 r 值引用感到好奇 "是否有可能实际获得一个“纯”右值的地址?"是的,通过创建一个临时对象来保存该值。该对象有一个地址。该语言不允许您直接使用&amp; 获取地址,但您可以将其绑定到各种类型的引用。要在没有 move 的情况下获得对 lvaluervalue 引用,您需要进行强制转换(这就是 move 所做的一切)。

以上是关于如何获得对右值的引用?的主要内容,如果未能解决你的问题,请参考以下文章

左值引用与右值引用

左值引用与右值引用

左值引用与右值引用

左值引用与右值引用

左值引用与右值引用

左值引用与右值引用