为什么传递 rvalue reference (X&&) 就像传递 lvalue reference (X&) 一样?

3

在学习右值引用时,我在stackoverflow上发现了一个奇怪的答案。

提问者想要避免在一个接收左值引用参数的函数和另一个接收右值引用参数的函数之间重复代码。这两个函数都执行相同的操作。

问题如下:

void foo(X& x)  { /*complex thing*/       }      //#A
void foo(X&& x) { /*complex SAME thing*/  }      //#B

这里是建议的解决方案。我稍微修改了一下:

void foo(X& x)  {  /*complex thing*/ }      //#A
void foo(X&& x) { foo(x);            }      //#B

问题

为什么我的版本不会导致堆栈溢出异常?
在我的版本中,为什么foo#B调用了foo#A,而不是foo#B

更具体地说,哪个C++规则强制执行此行为?


“什么是强制执行这种奇怪行为的C++规则?”你的意思是指哪个规则或一组规则定义了这种行为? - juanchopanza
@juanchopanza 我的意思是语法的官方规则。这就像C++的一条法律。我不确定该怎么称呼它。 - javaLover
尝试使用void foo(X&& x) { foo(std::move(x)); } - Zereges
@Zereges 不,这是一种摧毁我脆弱电脑的太慷慨的方式。 - javaLover
你应该在问题中提供更多的上下文。你所引用的问题可能会被删除,那么这个问题就没有任何意义了。 - hyde
@hyde 同意,我已按照你的建议进行了编辑。谢谢。如果你有什么要补充的,请随意编辑。 :) - javaLover
2个回答

8
根据值类别规则,作为命名参数,x是一个lvalue。因此,将调用foo#A,因为x可以绑定到lvalue-reference,但不能绑定到rvalue-reference。
请注意,x声明为rvalue-reference与x的值类别无关。

lvalue

以下表达式是lvalue表达式:

  • 在作用域中的变量或函数的名称,不管类型如何,例如std::cin或std::endl。即使变量的类型是rvalue reference,由其名称组成的表达式也是lvalue表达式;
你可以使用std::move将其变为xvalue (右值),然后foo#B将被选择(正如你所期望的那样)。

只是好奇,从转换(X&& -> X&)中,是否会有任何(潜在昂贵的)对象复制?换句话说,是否会调用复制构造函数或复制赋值运算符? - javaLover
1
不,这里没有涉及复制。 - Sebastian Redl
1
@javaLover 当调用 foo#A 时,通过引用传递不会发生复制;规则不会改变。 - songyuanyao

5

虽然你已经有一个回答说明了 C++ 标准规定此事的位置,但让我也回答一下它为什么要这样规定。

rvalue 引用函数参数的背后思想是函数可以假设在函数返回后,引用的对象或其内容将不再被使用。

现在假设你有:

void f(const X&x); // doesn't modify x
void f(X&&x); // clears x's data

而你尝试着

void g(X&&x) {
    f(x);
    std::cout << "we just called f(" << x << ")" << std::endl;
}

如果称之为f(X&&),那么这并没有什么用,因为在打印输出之前,x的内容就已经消失了。

g需要明确告诉f它可以接管该对象。f不能假设g不会在后面再次使用它。

如果编译器可以确定当g的主体不再引用x时,x不再需要,则可能有效,但是这是一个非常复杂的问题,除非规则准确说明何时应该和不应该这样做,否则编译器不会以同样的方式实现它,很可能在一个编译器上工作的代码在下一个编译器上会出现错误。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接