除了写临时文件无效之外,为什么右值不能绑定到非常量左值引用?

时间:2018-08-14 08:05:49

标签: c++ c++11 rvalue-reference rvalue lvalue

我已阅读the SO question here并理解了答案的这一部分:“但是,如果将临时对象绑定到非const引用,则可以继续将它永久传递给“永久”对象,以便对对象进行操作消失了,因为沿途您完全忘记了这是暂时的。”

也就是说,在下面:

#include <iostream>

void modifyValue(int& rValue) {
    rValue++;
}

int main() {
    modifyValue(9899);

    return 0;
}

如果右值可以绑定到非常量左值引用,则可能会进行很多修改,最终将其丢弃(因为右值是临时的),这是没有用的。

但是,这似乎定义得很好(写入临时值就像写入任何值一样,生存期与写入的有效性无关)。

那是一个很好的理由,可以禁止指定的绑定(即使该绑定可以被很好地定义),但是一旦我认为禁止这种绑定迫使需要转发引用,我的问题便开始形成。

还有什么其他原因(除了写入临时值外)为何右值不能绑定到非常量左值引用?

1 个答案:

答案 0 :(得分:3)

简单的答案是,在大多数情况下,将临时变量传递给期望可变左值引用的函数表示逻辑错误,而c ++语言正在执行该操作最好地帮助您避免出错。

函数声明:void foo(Bar& b)给出以下叙述:

  

foo引用了将要修改的Bar b。因此,b既是输入又是输出

通常,传递一个临时字符作为输出占位符比调用一个返回对象的函数(仅丢弃未经检查的对象)的逻辑错误严重得多。

例如:

Bar foo();

void test()
{
  /*auto x =*/ foo();  // probable logic error - discarding return value unexamined
}

但是,在这两个版本中,没有问题:

void foo(Bar&& b)

  

foo拥有Bar引用的对象的所有权

void foo(Bar b)

  

foo从概念上讲是获取Bar的副本,尽管在许多情况下,编译器会认为不必要创建和复制Bar。

所以问题是,我们要努力实现什么?如果我们只需要工作的酒吧,我们可以使用Bar&& bBar b版本。

如果我们想也许使用一个临时的,而也许使用一个现有的Bar,那么我们可能需要两个foo的重载,因为它们在语义上会有所不同:

void foo(Bar& b);    // I will modify the object referenced by b

void foo(Bar&& b);   // I will *steal* the object referenced by b

void foo(Bar b);   // I will copy your Bar and use mine, thanks

如果需要这种可选性,我们可以通过将其中一个换行来创建它:

void foo(Bar& b)
{
  auto x = consult_some_value_in(b);
  auto y = from_some_other_source();
  modify_in_some_way(b, x * y);
}

void foo(Bar&& b)
{
  // at this point, the caller has lost interest in b, because he passed
  // an rvalue-reference. And you can't do that by accident.

  // rvalues always decay into lvalues when named
  // so here we're calling foo(Bar&)

  foo(b);   

  // b is about to be 'discarded' or destroyed, depending on what happened at the call site
  // so we should at lease use it first
  std::cout << "the result is: " << v.to_string() << std::endl;
}

有了这些定义,这些现在都合法了:

void test()
{
  Bar b;
  foo(b);              // call foo(Bar&)

  foo(Bar());          // call foo(Bar&&)

  foo(std::move(b));   // call foo(Bar&&)
  // at which point we know that since we moved b, we should only assign to it
  // or leave it alone.
}

好的,为什么要这么照顾呢?为什么修改无意义的临时文件会导致逻辑错误?

好吧,想象一下:

Bar& foo(Bar& b)
{
  modify(b);
  return b;
}

我们期望做这样的事情:

extern void baz(Bar& b);

Bar b;
baz(foo(b));

现在想象一下它可以编译:

auto& br = foo(Bar());

baz(br); // BOOM! br is now a dangling reference. The Bar no longer exists

由于我们被迫在foo的特殊重载中正确处理临时文件,因此foo的作者可以确信此错误永远不会在您的代码中发生。