78

在 C++ 中,我不清楚从复制赋值运算符返回引用的概念。为什么复制赋值运算符不能返回新对象的副本?另外,如果我有 class 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;
}
4

7 回答 7

72

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

我不建议您这样做,只是指出这是允许的,并且似乎不会引起很多问题。

这些其他 SO 问题是相关的(可能不完全是骗人的),它们具有您可能感兴趣的信息/意见。

于 2010-06-23T23:31:58.493 回答
70

稍微澄清一下为什么最好通过引用返回而不是按值返回——因为如果返回一个值operator=,链将正常工作。a = b = c

如果您返回参考,则完成的工作最少。一个对象的值被复制到另一个对象。

但是,如果按值返回 for 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!

总而言之,价值回归并没有得到什么,反而会失去很多。

注意:这并不是为了解决让赋值运算符返回左值的优点。阅读其他帖子了解为什么这可能更可取)

于 2011-01-23T01:41:42.527 回答
11

当你重载时operator=,你可以写它来返回你想要的任何类型。如果你想要的不够严重,你可以重载X::operator=以返回(例如)某个完全不同的类的实例YZ. 不过,这通常是非常不可取的。

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

int x, y, z;

x = y = z = 0;

在这种情况下,您通常希望返回被分配类型的左值或右值。这只留下了是否返回对 X 的引用、对 X 的 const 引用或 X(按值)的问题。

返回对 X 的 const 引用通常不是一个好主意。特别是,允许​​ const 引用绑定到临时对象。临时对象的生命周期延长到它所绑定的引用的生命周期——但不是递归地延长到可能分配给的任何引用的生命周期。这使得返回悬空引用变得容易——const 引用绑定到一个临时对象。该对象的生命周期延长到引用的生命周期(在函数末尾结束)。到函数返回时,引用和临时的生命周期已经结束,所以分配的是一个悬空引用。

当然,返回一个非常量引用并不能提供完全的保护,但至少会让你更加努力。您仍然可以(例如)定义一些本地,并返回对它的引用(但大多数编译器也可以并且也会对此发出警告)。

返回值而不是引用具有理论和实际问题。在理论上,您在=通常的意思和在这种情况下的意思之间存在基本的脱节。特别是,赋值通常意味着“获取这个现有的源并将其值分配给这个现有的目标”,它开始意味着更像“获取这个现有的源,创建它的副本,并将该值分配给这个现有的目标。 "

从实际的角度来看,尤其是在右值引用被发明之前,这可能会对性能产生重大影响——在将 A 复制到 B 的过程中创建一个全新的对象是出乎意料的,而且通常很慢。例如,如果我有一个小向量,并将其分配给一个更大的向量,我希望最多花费时间来复制小向量的元素加上(小)固定开销来调整大小目标向量。如果这涉及到两个副本,一个从源到临时,另一个从临时到目标,以及(更糟)临时向量的动态分配,我对操作复杂性的期望将完全是被摧毁。对于一个小的向量,动态分配的时间很容易比复制元素的时间长很多倍。

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

这使得返回一个正常的引用(不是对 const 的引用,也不是一个右值引用)作为(合理地)可靠地产生大多数人通常想要的东西的唯一选项。

于 2014-11-18T02:04:07.830 回答
5

这部分是因为返回对 self 的引用比按值返回要快,但另外,它允许原始类型中存在的原始语义。

于 2010-06-23T21:52:12.277 回答
4

operator=可以定义为返回任何你想要的。您需要更具体地了解问题实际上是什么;我怀疑您在operator=内部使用了复制构造函数,这会导致堆栈溢出,因为复制构造函数调用operator=必须使用复制构造函数A按值无限返回。

于 2010-06-23T22:07:09.193 回答
3

对 user-defined 的结果类型没有核心语言要求operator=,但标准库确实有这样的要求:

C++98 §23.1/3:

这些组件中存储的对象的类型必须满足CopyConstructible 类型的要求(20.1.3),以及Assignable类型的附加要求。

C++98 §23.1/4:

在表 64 中,T是用于实例化容器的类型,t是 的值T,并且u是(可能const)的值T

在此处输入图像描述


按值返回副本仍然支持像 一样a = b = c = 42;的赋值链,因为赋值运算符是右关联的,即它被解析为a = (b = (c = 42));。但是返回一个副本将禁止无意义的结构,如(a = b) = 666;. 对于返回副本的小类可以想象是最有效的,而对于通过引用返回的较大类通常是最有效的(并且副本,效率极低)。

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


对于 C++11,对赋值运算符 -ing还需要T&结果类型,因为default

C++11 §8.4.2/1:

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

于 2014-11-18T02:14:00.690 回答
0

我猜,因为用户定义的对象应该表现得像内置类型。例如:

char c;
while ((c = getchar()) != -1 ) {/* do the stuff */}
于 2021-07-12T10:30:43.243 回答