【问题标题】:overlading assignment operator in c++ using return by value在 C++ 中使用按值返回重载赋值运算符
【发布时间】:2013-02-22 02:15:09
【问题描述】:

考虑:

    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 的成员变量并使用按引用返回并返回 *this 以获得正确的行为。但是, one = two = three 中的返回值实际发生了什么? return temp 实际上在链中的哪个位置结束,例如一步一步?

【问题讨论】:

    标签: c++ overloading operator-keyword


    【解决方案1】:

    对赋值运算符的调用

    two = three
    

    返回一个临时对象作为右值。这是MyObject 类型,并传递给赋值运算符的下一个调用

    one = t
    

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

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

    (您的代码由于各种原因无法编译,包括大写的Class 和拼写错误。)

    但是,如果您要定义一个接受右值的赋值运算符(即,如果使用 C++11,则按值、常量引用或实际上按右值引用 MyObject&amp;&amp; 接受参数),调用将起作用并且临时对象将被复制到函数中。在内部,将进行分配并返回另一个临时对象。

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


    感谢 Joachim Pileborg 和 Benjamin Lindley 提供帮助的 cmets。


    回答更多细节的请求:MyObject 是一个类类型,C++ 标准包括一个关于类类型临时对象生命周期的完整部分(第 12.2 节)。那里详细介绍了各种复杂的情况,我不会全部解释。但基本概念如下:

    1. C++ 具有表达式的概念。 表达式与声明和语句一起是构成程序代码的基本单元。例如,函数调用f(a,b,c) 是一个表达式,或者像a = b 这样的赋值。表达式可能包含其他表达式:a = f(b,c),嵌套在赋值表达式中的函数调用。 C++ 还引入了 full-expressions 的概念。在前面的例子中,c 是表达式 f(b,c) 的一部分,但也是 a = f(b,c) 的一部分,如果它没有嵌套在另一个表达式中,我们说 a = f(b,c) 是在词法上的 完整表达式包含c

    2. 标准定义了可以创建临时对象的各种情况。一种这样的情况是从函数调用返回对象按值(又名返回纯右值,§6.6.3)。

    3. 标准规定,当包含它的完整表达式已被完全评估时,此类临时对象的生命周期结束:

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

      (注意。标准接着定义了这个规则的几个例外。然而,你的赋值运算符的返回值的情况不是这样的例外。)

    4. 现在,(类类型)对象被销毁是什么意思?首先,它意味着它的析构函数被调用(第 12.2/3 节)。这也意味着无法再安全地访问该对象的存储空间。因此,如果您设法在完整表达式的评估结束之前将临时对象的地址存储在指针中,那么在评估结束后取消引用该指针通常会导致未定义的行为。

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

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

    【讨论】:

    • 除了使赋值运算符按值获取对象外,OP 还可以使用新的 C++11 rvalue-reference
    • @BenjaminLindley 也是真的!
    • 谢谢。是的,很抱歉在我的代码中包含拼写错误。它应该按值调用。我已经编辑了代码,以便它现在应该编译。但是:“最终的临时对象将超出范围,即不再存在。将无法访问其内容。” 这正是我想知道的。你能详细说明一下吗?因此,如果 one = t 如您的示例所示,“t”对象会超出范围吗?这意味着什么 ?会发生什么?
    • 非常感谢您的精彩回答 :) 不胜感激!
    猜你喜欢
    • 2012-04-26
    • 2012-08-17
    • 2013-03-30
    • 2016-08-30
    • 1970-01-01
    • 1970-01-01
    • 2018-08-03
    • 2012-04-22
    • 2015-06-01
    相关资源
    最近更新 更多