【问题标题】:C++ function returns a rvalue, but that can be assigned a new value?C ++函数返回一个右值,但是可以分配一个新值吗?
【发布时间】:2013-03-27 07:50:33
【问题描述】:

代码如下:

 #include <iostream>
 using namespace std;

 class A {

 };

 A rtByValue() {
return A();
 }

 void passByRef(A &aRef) {
    // do nothing
 }

 int main() {
    A aa;
    rtByValue() = aa;            // compile without errors
    passByRef(rtByValue());      // compile with error 

    return 0;
 }

g++编译器报错:

d.cpp: In function ‘int main()’:
d.cpp:19:23: error: invalid initialization of non-const reference of type ‘A&’ from an rvalue of type ‘A’
d.cpp:12:6: error: in passing argument 1 of ‘void passByRef(A&)’

它说我不能将右值作为非常量引用的参数传递,但我很困惑的是为什么我可以分配给这个右值,正如代码所示。

【问题讨论】:

  • 是的,非常量引用不能绑定到临时对象。
  • 奇怪。我可以在 MS VS 2010 中编译和运行它。
  • @chris 但为什么我可以分配给它。我可以分配给它,为什么我不能将它作为参考传递?
  • 你用的是什么编译器?
  • @0x499602D2 他使用 g++ 而我是 VS 2010

标签: c++ assignment-operator temporary rvalue


【解决方案1】:

将右值 rtByValue() 传递给需要左值引用的函数不起作用,因为这需要从右值初始化左值引用参数。 §8.5.3/5 描述了如何初始化左值引用——我不会完整引用它,但它基本上说可以初始化左值引用

  • 来自另一个左值引用
  • 或可以转换为中间类型的左值引用的东西
  • 或来自右值,但前提是我们初始化的左值引用是常量引用

由于我们需要初始化的参数不是常量引用,所以这都不适用。

另一方面,

rtByValue() = aa; 

即,分配给临时对象是可能的,因为:

(§3.10/5) 对象的左值是修改对象所必需的,但在某些情况下,类类型的右值也可用于修改其所指对象。 [ 示例:为对象调用的成员函数(9.3)可以修改该对象。 —结束示例]

所以这只是因为A 是类类型,并且(隐式定义的)赋值运算符是成员函数。 (详情请参阅this related question。)

(因此,如果 rtByValue() 返回例如 int,则分配将不起作用。)

【讨论】:

    【解决方案2】:

    因为您可以(但不应该!)覆盖 operator= 以便在右值上调用它是有意义的。考虑以下代码:

    #include<iostream>
    
    using namespace std;
    
    class foo;
    
    foo* gotAssigned = NULL;
    int assignedto = -1;
    
    class foo {
    public:
      foo(int v) : val(v) {}
      foo& operator=(int v) {
        assignedto=v;
        gotAssigned = this;
        val = v;
        return *this;
      }
      int val;
    };
    
    foo theFoo(2);
    
    foo returnTheFooByValue() {
      return theFoo;
    }
    
    main() {
      returnTheFooByValue()=5;
      cout << "[" << assignedto << "] " << theFoo.val << " versus " << gotAssigned->val << endl;
    }
    

    现在让我们用几种方式编译它:

    $ g++ -O0 -o rveq rveq.cc && ./rveq
    [5] 2 versus 5
    $ g++ -O1 -o rveq rveq.cc && ./rveq
    [5] 2 versus 2
    $ g++ -O4 -o rveq rveq.cc && ./rveq
    [5] 2 versus -1218482176
    

    我不能保证你会看到同样的结果。

    如您所见,分配发生了,但任何尝试使用已分配的对象都会导致特定于实现的行为。

    顺便说一下,这个适用于用户定义的类型。这段代码:

    int v(){
      return 2;
    }
    
    main(){
      v()=4;
    }
    

    无法编译。

    【讨论】:

    • 它不是特定于实现的,它是未定义的行为。您正在访问一个已销毁的临时对象。
    【解决方案3】:

    @ddriver 如我所料,这会输出数字 7。

    #include <iostream>
     using namespace std;
    
     class A {
     public:
         int i;
         A() {i = 0x07;}
     };
    
     A rtByValue() {
    return A();
     }
    
     void passByRef(A &aRef) {
         cout << aRef.i;
     }
    
     int main() {
        passByRef(rtByValue());
        return 0;
     }
    

    【讨论】:

    • 仅仅因为你得到了值并不意味着它不是未定义的行为。我一直从数组的越界索引中获取正确的值,该数组属于同一类别。我认为标准不允许这样做,MS 可能对此有不同的看法,就像他们在许多其他方面所做的那样。
    • 我不明白为什么它不能工作。类 A 的实际实例正在制作中,并且在被传递的同时被引用,所以应该没有问题。在passByRef 函数中分配给i 没有问题。
    • 这是 Visual C++ 中众所周知的编译器错误,可能永远无法修复。
    • 这是一个错误,因为标准要求编译器生成诊断,而 Visual C++ 不需要。
    • 无论如何你会用它做什么?使其成为 const 引用可以正常工作,如果您需要修改它,您始终可以通过值或右值引用来获取它。修改原件是没有意义的,因为原件是临时的,即将被销毁。
    猜你喜欢
    • 1970-01-01
    • 2011-02-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多