为什么复制赋值操作符必须返回一个引用/常量引用?

在C ++中,从复制赋值运算符返回引用的概念对我来说是不清楚的。 为什么复制赋值操作符不能返回新对象的副本? 另外,如果我有A类,并且有以下内容:

A a1(param); A a2 = a1; A a3; a3 = a2; //<--- this is the problematic line 

运营商=定义如下:

 AA::operator =(const A& a) { if (this == &a) { return *this; } param = a.param; return *this; } 

严格地说,复制赋值运算符的结果不需要返回引用,但是为了模仿C ++编译器使用的默认行为,它应该返回一个非const引用,以指定给该对象(隐式生成的副本赋值运算符将返回一个非const引用 – C ++ 03:12.8 / 10)。 我已经看到相当一部分代码从复制赋值重载中返回void ,我不记得是什么时候导致严重的问题。 返回void将阻止用户“分配链接”( a = b = c; ),并且将阻止在testingexpression式中使用赋值的结果。 虽然这种代码绝不是前所未有的,但我也不认为它是特别常见的 – 特别是对于非原始types(除非类的接口用于这种types的testing,例如iostream)。

我不build议你这样做,只是指出这是允许的,而且似乎并没有引起很多问题。

这些其他的SO问题与您可能感兴趣的信息/意见是相关的(可能不是非常愚蠢的)。

  • 有没有人发现需要声明复制赋值运算符const的返回参数?
  • 在C ++中重载赋值运算符

稍微澄清一下,为什么最好是通过引用返回operator =与数值的返回值 – 作为链a = b = c,如果返回一个值,将会正常工作。

如果你返回一个参考,最小的工作就完成了。 来自一个对象的值被复制到另一个对象。

但是,如果您按运算符=的值返回,那么您将每次调用构造函数和析构函数来调用赋值运算符!

所以给出:

 A& operator=(constA& rhs){ ... }; 

然后

 a=b=c;// calls assignment operator above twice. Nice and simple. 

 A operator=(constA& rhs){ ... }; a=b=c; // calls assignment operator twice, calls copy constructor twice, calls destructor type to delete the temporary values! Very wasteful and nothing gained! 

总而言之,没有任何东西通过价值回报而获得,而是失去很多。

(注意:这并不是为了解决赋值运算符返回一个l值的好处,请阅读其他文章为什么可能更好)

当你重载operator= ,你可以编写它来返回你想要的任何types。 如果你想足够糟糕,你可以重载X::operator=来返回(例如)一个完全不同的类YZ的实例。 这通常是非常不明智的。

特别是,你通常希望像C一样支持operator=链接。 例如:

 int x, y, z; x = y = z = 0; 

既然如此,你通常要返回一个左值或右值赋给的types。 这只留下了是否返回对X的引用,对X的const引用或X(按值)的问题。

将一个const引用返回给X通常是一个糟糕的主意。 特别是,一个const引用被允许绑定到一个临时对象。 临时的生命周期延伸到它所绑定的引用的生命周期 – 但不recursion到可能分配的任何生命周期。 这可以很容易的返回一个悬而未决的引用 – const引用绑定到一个临时对象。 该对象的生命周期扩展到引用的生命周期(在函数结束时结束)。 当函数返回时,引用和临时的生命期已经结束,所以赋值是一个悬挂的引用。

当然,返回一个非const引用并不能提供完整的保护,但至less会让你的工作更加困难。 你仍然可以(例如)定义一些本地的,并返回一个引用(但大多数编译器可以也会警告这个)。

返回一个值而不是一个参考既有理论上的问题,也有实践上的问题。 在理论方面,你有一个基本的断开=正常的意思是在这种情况下意味着什么。 特别是,在分配通常意味着“采取这个现有的来源,并将其价值分配给这个现有的目的地”,它开始意味着更像是“现有的来源,创build它的副本,并分配给这个现有的目的地的值。 “

从实际的angular度来看,特别是在发明了右值引用之前,这可能会对性能产生重大影响 – 在复制A到B的过程中创build一个全新的对象是出乎意料的,而且通常很慢。 例如,如果我有一个小向量,并将其分配给一个更大的向量,我希望最多需要时间来复制小向量的元素加上(less许)固定的开销来resize目的地vector。 如果涉及两个副本,一个是从源到temp,另一个是从temp到目的地,并且(更糟糕的)是临时向量的dynamic分配,那么我对操作复杂性的期望将被完全破坏。 对于一个小向量,dynamic分配的时间可能比复制元素的时间要多很多倍。

唯一的其他选项(在C ++ 11中添加)将返回一个右值引用。 这很容易导致意想不到的结果 – 像a=b=c;这样a=b=c;链接赋值a=b=c; 可能会破坏b和/或c的内容,这是非常意想不到的。

这留下了一个正常的参考(不是一个参考常数,也不是右参考)作为唯一的select(合理)可靠地产生大多数人通常想要的。

部分原因是因为返回对自身的引用比通过值返回更快,但是另外,它允许原始types中存在的原始语义。

operator=可以被定义为返回任何你想要的。 你需要更具体的问题是什么, 我怀疑你有复制构造函数使用operator=内部,并导致堆栈溢出,因为复制构造函数调用operator=必须使用复制构造函数返回值的价值无限。

对用户定义的operator=的结果types没有核心语言要求,但标准库确实有这样的要求:

C ++ 98§23.1/ 3:

存储在这些组件中的对象types必须满足CopyConstructibletypes(20.1.3)的要求,以及Assignabletypes的附加要求。

C ++ 98§23.1/ 4:

在表64中, T是用于实例化容器的types, tT的值,并且u是(可能是constT

在这里输入图像描述


按值返回副本仍然支持分配链接,如a = b = c = 42; ,因为赋值运算符是右联合的,即这被parsing为a = (b = (c = 42)); 。 但是,复制副本将禁止像(a = b) = 666;这样的无意义的构造(a = b) = 666; 。 对于返回副本的小class来说,可以想象得到最有效率,而对于通过引用返回的较大class级来说,通常是最有效的(并且副本,效率非常低)。

直到我了解到标准库的要求,我曾经让operator= return void ,以避免支持基于副作用的错误代码的荒谬性。


对于C ++ 11,还需要T&结果types的default赋值运算符,因为

C ++ 11§8.4.2/ 1:

明确默认的函数应该具有相同的声明函数types(除了可能不同的ref限定符以外,除了在复制构造函数或复制赋值运算符的情况下,参数types可以是“ const T “,其中T是成员函数的类的名称),就好像它已被隐式声明