为什么一定要拷贝赋值运算符返回引用/常引用?为什么一定要拷贝赋值运算符返回引用/常引用?(Why m

2019-05-10 13:44发布

在C ++中,从拷贝赋值运算符返回引用的概念,我不清楚。 为什么不能拷贝赋值运算符返回新对象的副本? 另外,如果我有一流的A ,并执行以下操作:

A a1(param);
A a2 = a1;
A a3;

a3 = a2; //<--- this is the problematic line

operator=定义如下:

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

Answer 1:

严格地说,一个拷贝赋值运算符的结果并不需要返回一个参考,但要模仿C ++编译器使用默认的行为,它应该返回非const引用分配给(隐式生成的拷贝对象赋值运算符将返回一个非const引用 - C ++ 03:12.8 / 10)。 我见过的代码公平一点,返回void的拷贝赋值过载,我不记得那个时候造成了严重的问题。 返回void将防止“分配链接”(用户a = b = c;并且将防止使用在测试表达的分配的结果,例如。 虽然那种代码绝非闻所未闻的,我也不认为这是特别常见的 - 尤其是对非原始类型(除非一类接口打算为这些类型的测试,如输入输出流)。

我不建议您这样做,只是指出,它的许可,并且它似乎并没有引起一大堆的问题。

这些其他的SO问题是相关的(可能不太愚弄)具有信息/意见,可能是你的兴趣。

  • 有没有人发现需要声明一个拷贝赋值运算符常量的返回参数?
  • 重载赋值运算符在C ++


Answer 2:

澄清的位,为什么它优选通过参考用于向返回operator=与由值返回---作为链a = b = c将正常工作,如果被返回的值。

如果你返回一个引用,最少的工作就完成了。 从一个对象中的值被复制到另一个对象。

但是,如果通过值返回operator= ,你会调用构造函数和析构函数每一次的赋值运算符被称为!

因此,考虑到:

A& operator=(const A& rhs) { /* ... */ };

然后,

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

但,

A operator=(const A& 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!

总之,没有什么通过返回值获得,但失去的东西很多。

:这并不意味着,以解决其赋值操作符返回左值的优势阅读其他职位的原因,可能是可取的。)



Answer 3:

当重载operator= ,你可以写它返回任何你想要的类型。 如果您想不好的话,你可以重载X::operator=返回(例如)一些完全不同的类的实例YZ 。 这通常是不妥当,但。

特别是,你通常要支持的链接operator=就像C语言一样。 例如:

int x, y, z;

x = y = z = 0;

既然如此,你通常要返回类型的左值或右值分配给。 这只是叶是否参考返回X,const引用到X,或X(按价值计算)的问题。

返回一个常量引用X通常是一个贫穷的想法。 特别是,const引用被允许结合于临时对象。 临时的寿命延长至到它绑定参考的一生 - 但不是递归到任何的可能被分配到寿命。 这可以很容易地返回悬空参考 - 常量引用结合到一个临时对象。 该对象的寿命延长到参考的寿命(其在函数结束时结束)。 该函数返回时,参考的使用寿命,并暂时已经结束的时候,有啥分配是悬空的参考。

当然,返回非const引用不提供对这种完全的保护,但至少让你的工作在这有点困难。 您仍然可以(例如)定义了一些地方,并返回对它的引用(但大多数编译器能够并且将会警告这太)。

返回一个值,而不是一个参考具有重要的理论和实际问题。 在理论方面,您有以下两种基本脱节=通常意味着这意味着什么在这种情况下。 特别是,在分配通常是指“利用这个现有的源,并将其值分配到这个现有的目的地”,它开始意味着更多的东西,如“利用这个现有的源,创建一个副本,并将该值分配到这个现有的目的地。 “

从实用的角度来看,发明了右值引用尤其之前,这可能对性能有显著的影响 - 在创建复制的过程中一个全新的对象到B是意外的,往往相当缓慢。 如果,例如,我有一个小的矢量,并将其分配到一个较大的载体,我期望拿,最多一次复制小矢量的元素和一个(小)的固定开销来调整大小目的载体。 如果不是涉及到两个副本,一个从源头到温度,另一个从温度到目的地,和(差)动态分配的临时矢量,我对操作的复杂性预期将完全摧毁。 对于一个小矢量,对于动态分配的时间可以很容易地比复制元素时高许多倍。

唯一的其他选择(在C ++ 11加入)将返回一个rvalue参考。 这很容易导致意外的结果-一个链式分配如a=b=c; 可以摧毁的内容b和/或c ,这将是相当意外。

这使得恢复正常参考值(不是引用到常量,也不是一个右值引用)作为唯一的选项(合理的)可靠地生产的大多数人通常想要的。



Answer 4:

这部分是因为返回参考自我是不是按值返回更快,但除此之外,它是允许存在于原始类型的原始语义。



Answer 5:

operator=可以被定义为返回任何你想要的。 你需要更具体的,以什么问题实际上是; 我怀疑你有拷贝构造函数使用operator=内部并导致堆栈溢出,因为拷贝构造函数调用operator=必须使用拷贝构造函数返回A由值循环往复。



Answer 6:

存在对结果类型用户定义的无核心语言要求operator= ,但标准库确实有这样的要求:

C ++ 98§23.1/ 3:

类型存储在这些部件的对象必须满足的要求CopyConstructible类型(20.1.3),和的附加要求Assignable的类型。

C ++ 98§23.1/ 4:

以表64, T是用于实例化容器的类型, t是的值T ,和u是(可能的值constT


通过返回值的副本仍然会支持分配链接像a = b = c = 42; ,因为赋值运算符是右结合的,即,该被解析为a = (b = (c = 42)); 。 但是返回的副本将禁止无意义构造像(a = b) = 666; 。 对于一个小的类返回一个拷贝可以令人信服地最有效的,而对于较大的类通过引用返回一般将是最有效的(和复制,过于低效)。

直到我了解了标准库的要求,我习惯让operator=返回void ,以提高效率和避免的配套副作用基于糟糕的代码是愚蠢的。


用C ++ 11有另外的要求T&结果类型为default -ing赋值运算符,因为

C ++ 11§8.4.2/ 1:

被明确地默认函数应当[...]具有相同的声明的函数类型(除了可能不同REF-限定符和不同之处在于在一个拷贝构造的情况下,或复制赋值运算符,参数类型可以是“参照非常量T ”,其中T是成员函数的类的名称),就好像它已经隐含声明



文章来源: Why must the copy assignment operator return a reference/const reference?