copy constructor called. // 执行m1.operator=(m2)时pass by value传参,发生一次拷贝;由于return by reference,函数返回时不发生拷贝
m1: 2
m2: 2
copy constructor called. // 执行m3.operator=(m4.operator=(m5))中的m4.operator=(m5)时pass by value传参,发生一次拷贝
copy constructor called. // m4.operator=(m5)返回时,return by reference,不发生拷贝;但返回的m4的引用需要按值传递给m3.operator=(CMeter rhs),因此在传参时发生一次拷贝;m3的调用返回时,由于是return by reference,故不发生拷贝
m3: 5
m4: 5
m5: 5
这里多说一句,对于 a = b =c; ,在情况1中,共发生了3次对象拷贝,其中,由于传参发生了一次拷贝,由于函数返回发生了两次拷贝。相比于情况1,情况2是return by reference,所以那不应该是发生3-2=1次拷贝吗?为什么实际上是两次呢?因为 m4.operator=(m5) 返回的是 m4 的引用,而该引用需要按值传递给 m3.operator=(CMeter rhs) 的形参,因此这里需要一次拷贝。
这种情况是pass by reference & return by value。本小节的测试代码为:将情况1中代码第18行改为 CMeter operator=(const CMeter &rhs) 或 const CMeter operator=(const CMeter &rhs) ,其余均保持不变。
结果如下:
结果分析如下:
copy constructor called. // 执行m1.operator=(m2)时pass by reference传参,不发生拷贝;但由于return by value,函数返回时发生一次拷贝
m1: 2
m2: 2
copy constructor called. // 执行m3.operator=(m4.operator=(m5))中的m4.operator=(m5)时pass by reference传参,不发生拷贝;但由于return by value,函数返回时发生一次拷贝
copy constructor called. // m4.operator=(m5)返回(return by value)的临时对象作为实参传递给m3.operator=(const CMeter &rhs),因为是pass by reference传参,不发生拷贝;但m3.operator=(const CMeter &rhs)返回时,由于是return by value,故发生一次拷贝
m3: 5
m4: 5
m5: 5
这里需要特别说明的是,参数中的const是必须的。如果将函数原型写成 CMeter operator=(CMeter &rhs) 或 const CMeter operator=(CMeter &rhs) , m3 = m4 = m5; 或 m3.operator=(m4.operator=(m5)); 这样的连续赋值是编译不过去的,g++下会报类似 cannot bind non-const lvalue reference of type 'CMeter&' to an rvalue of type 'CMeter' 这样的错误。
原因是:由于return by value, m4 = m5 或 m4.operator=(m5) 返回的是一个匿名的临时对象,即一个右值(rvalue),而 CMeter &rhs 这样的non-const左值引用(non-const lvalue reference)是不能绑定到右值的。但是,正如预备知识中的第2点所示, const CMeter &rhs 这样的const lvalue reference却是可以绑定到右值的。
这种情况是pass by reference & return by reference。本小节的测试代码为:将情况1中代码第18行改为 CMeter &operator=(CMeter &rhs) 或 CMeter &operator=(const CMeter &rhs) 或 const CMeter &operator=(const CMeter &rhs) ,其余均保持不变。
结果如下:
结果分析如下:因为传参和返回都是by reference的,所以不会发生拷贝。
这里需要特别说明的是,如果将函数原型改成 const CMeter &operator=(CMeter &rhs) , m3 = m4 = m5; 或 m3.operator=(m4.operator=(m5)); 这样的连续赋值是编译不过去的,g++下会报类似 binding reference of type 'CMeter&' to 'const CMeter' discards qualifiers 这样的错误。原因也很简单, m4 = m5 或 m4.operator=(m5) 返回的是const reference,将const reference传递给non-const reference,属于“放宽政策”,肯定是不允许的。
(a = b) = c简单讨论
尽管C++的内置类型都支持这种写法,但很显然,这是一句写了会被扣钱的代码,因此本文不会深入讨论它。为了文章的完整性,本文给出下面这张您几乎从来不会用到的表格,它存在的唯一意义是:如果您完全搞懂了表格中的内容,那说明您对Copy Assignment Operator、函数参数传递、函数返回、const、lvalue、rvalue 等知识点已经掌握得不错了。
本小节测试代码为:在情况1代码的基础上,将38行语句改为 m3.operator=(m4).operator=(m5); ,将39行语句改为 (m3 = m4) = m5; ,同时删除 m1 = m2; 相关的测试代码,然后更换不同原型的Copy Assignment Operator。
能够使 (m3 = m4) = m5; 或 m3.operator=(m4).operator=(m5); (两者结果是一样的)编译通过的函数原型以及对应的结果如下表所示。
pass by value &
return by value
CMeter operator=(CMeter rhs) // or
CMeter operator=(const CMeter rhs)
① m3.operator=(m4) 传参和返回各发生一次拷贝, m3 的值被修改为 m4 的值;
② m3.operator=(m4) 返回的是函数调用者,即 m3 的副本(临时对象),这个临时对象
作为caller调用 operator=(m5) ,传参和返回各发生一次拷贝;
③由于caller(即被赋值者)是 m3 的副本而非 m3 本身,因此 m3 没有被修改为 m5 的值。
① m3.operator=(m4) 返回时发生一次拷贝, m3 的值被修改为 m4 的值;
② m3.operator=(m4) 返回的是函数调用者,即 m3 的副本(临时对象),这个临时对象
作为caller调用 operator=(m5) ,返回时发生一次拷贝;
③由于caller(即被赋值者)是 m3 的副本而非 m3 本身,因此 m3 没有被修改为 m5 的值。
① m3.operator=(m4) 传参时发生一次拷贝, m3 的值被修改为 m4 的值;
② m3.operator=(m4) 返回的是函数调用者,即 m3 的引用,这个引用
作为caller调用 operator=(m5) ,传参时发生一次拷贝;
③由于caller(即被赋值者)是 m3 的引用,因此 m3 最终被修改为 m5 的值。
首先,pass by reference & return by reference省去了对象拷贝的开销;其次,const reference作为参数保证了“赋值给别人的那个对象”不会在函数中被修改;最后, a = b = c; 这样的连续赋值也能够被支持。当然, (a = b) = c; 这样的赋值也是支持的,如果您想要禁止它,就给返回值也加上const吧。
至此,本文的所有内容都介绍完了。再次感谢各位读者的批评指正,但由于在下才疏学浅,错误纰漏之处依然在所难免,如果您在阅读过程中发现了本文的任何错误或不足,请您务必指出。您的批评就是在下前进的不竭动力!