使用return by value在c ++中重载赋值运算符

时间:2013-02-22 02:15:10

标签: c++ overloading operator-keyword

考虑:

    class MyObject{
    public:
     MyObject();
     MyObject(int,int);
     int x;
     int y;
     MyObject operator =(MyObject rhs);
    };
    MyObject::MyObject(int xp, int yp){
     x = xp;
     y = yp;
    }
    MyObject MyObject::operator =(MyObject rhs){
     MyObject temp;
     temp.x = rhs.x;
     temp.y = rhs.y;
     return temp;
    }

    int main(){
     MyObject one(1,1);
     MyObject two(2,2);
     MyObject three(3,3);
     one = two = three;
     cout << one.x << ", " << one.y;
     cout << two.x << ", " << two.y;
     cout << three.x << ", " << three.y;

    }

通过这样做,一,二和三中的变量x和y保持不变。我知道我应该更新MyObject的成员变量并使用return by reference并返回* this以获得正确的行为。但是,在一个= 2 = 3的返回值中实际发生了什么?返回温度实际上在链中的哪个位置,就像一步一步一样?

1 个答案:

答案 0 :(得分:4)

中对赋值运算符的调用
two = three

将临时对象作为rvalue返回。它的类型为MyObject,并传递给赋值运算符的下一次调用

one = t

(我使用t来引用临时对象。)

不幸的是,这不会编译,因为赋值运算符需要引用MyObject&,而不是MyObject类型的右值。

(您的代码不会因各种原因编译,包括大写Class和拼写错误。)

但是,如果您要定义一个带有rvalue的赋值运算符(即,如果使用C ++ 11,则通过value,const-reference或实际上通过rvalue引用MyObject&&获取参数),会工作,临时对象将被复制到该功能。在内部,将进行分配,并返回另一个临时对象。

最终的临时对象将超出范围,即不再存在。无法访问其内容。


感谢Joachim Pileborg和Benjamin Lindley的有益评论。


要回答更多详细信息的请求:MyObject是类类型,C ++标准包含关于类类型临时对象生命周期的整个部分(第12.2节)。有各种各样复杂的情况详细说明,我不会全部解释。但基本概念如下:

  1. C ++具有表达式的概念。表达式与声明和语句一起构成程序代码的基本单元。例如,函数调用f(a,b,c)是一个表达式,或类似a = b的赋值。表达式可能包含其他表达式:a = f(b,c),嵌套在赋值表达式中的函数调用。 C ++还引入了全表达式的概念。在上一个示例中,c是表达式f(b,c)的一部分,也是a = f(b,c)的一部分,如果不是嵌套在另一个表达式中,我们说a = f(b,c)词法包含c

  2. 的完整表达式
  3. 标准定义了可以创建临时对象的各种情况。其中一种情况是通过函数调用(又名返回prvalue ,§6.6.3)从值返回

  4. 标准规定,当包含它的完整表达式得到充分评估时,这种临时对象的生命周期结束:

      

    [...]临时对象作为评估全表达式(1.9)的最后一步被销毁,该表达式(词法上)包含创建它们的点。 [...]

    (注意:标准然后继续定义此规则的几个例外。但是,赋值运算符的返回值的情况不是例外。)

  5. 现在,一个对象(类类型)被销毁是什么意思?首先,它意味着它的析构函数被调用(§12.2/ 3)。这也意味着无法再安全地访问该对象的存储。因此,如果以某种方式设法在完整表达式的求值结束之前将临时对象的地址存储在指针中,则在评估结束后对该指针进行解除反射通常会导致未定义的行为。

  6. 在实践中,这可能在很多情况下意味着以下内容 - 我在一个可能的场景中描述了临时对象的整个生命周期:

    1. 为了提供临时存储,编译器确保在输入包含full-expression的函数时分配足够的堆栈空间(这在实际评估完整表达式之前发生)。
    2. 在评估赋值表达式期间,将创建临时表达式。编译器确保调用其构造函数来初始化为其分配的空间。
    3. 然后可以在评估其所属的完整表达的过程中访问或修改临时的内容。
    4. 当表达式已经完全评估时(在您的情况下,此时刻对应于包含赋值表达式的行的末尾),将调用临时表的析构函数。之后,访问为其分配的内存不再安全,尽管实际上空间将继续成为当前堆栈帧的一部分,直到对所有这些发生的函数的评估结束为止。
  7. 但是,这只是可能发生的一个例子。临时的创造在很多情况下实际上并不需要。编译器可以执行优化,这意味着永远不会实际创建临时。在这种情况下,编译器必须确保可以被创建,例如,它必须确保存在所需的构造函数和析构函数(尽管它们可能永远不会被调用)。