在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;
}
答案 0 :(得分:63)
严格地说,复制赋值运算符的结果不需要返回引用,但是为了模仿C ++编译器使用的默认行为,它应该返回对分配给的对象的非const引用(隐式生成的复制赋值运算符将返回非const引用 - C ++ 03:12.8 / 10)。我已经看到了一些从复制赋值重载返回void
的代码,我不记得何时引起严重问题。例如,返回void
将阻止用户进行“分配链接”(a = b = c;
),并且会阻止在测试表达式中使用赋值结果。虽然这种代码绝不是闻所未闻,但我也认为它并不常见 - 特别是对于非原始类型(除非类的接口打算进行这类测试,例如iostream)。 p>
我不建议您这样做,只是指出它是允许的,并且它似乎不会导致很多问题。
这些其他SO问题与您可能感兴趣的信息/意见有关(可能不完全是愚蠢)。
答案 1 :(得分:52)
有一点澄清,为什么最好通过引用返回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!
总而言之,通过价值回归并没有获得任何好处,但却失去了很多。
(注意:这并不是为了解决让赋值运算符返回左值的优点。请阅读其他帖子,了解为什么这可能更合适)
答案 2 :(得分:8)
当您重载operator=
时,可以编写它以返回您想要的任何类型。如果您想要足够严重,可以重载X::operator=
以返回(例如)某个完全不同的类Y
或Z
的实例。这通常是高度不可取的。
特别是,您通常希望像C一样支持operator=
的链接。例如:
int x, y, z;
x = y = z = 0;
在这种情况下,您通常希望返回所分配类型的左值或右值。这只留下了是否返回对X的引用,对X的const引用或X(按值)的问题。
返回对X的const引用通常是一个糟糕的主意。特别是,允许const引用绑定到临时对象。临时的生命周期延长到它所绑定的引用的生命周期 - 但不会递归到任何可能分配给它的生命周期。这使得返回悬空引用变得容易 - const引用绑定到临时对象。该对象的生命周期延长到引用的生命周期(在函数结束时结束)。当函数返回时,引用和临时的生命周期已经结束,因此分配的是悬空引用。
当然,返回非const引用并不能提供完全的保护,但至少会让你更加努力。您仍然可以(例如)定义一些本地,并返回对它的引用(但大多数编译器都可以并且也会对此发出警告)。
返回值而不是引用具有理论和实际问题。在理论方面,=
通常意味着在这种情况下它意味着什么。特别是,在分配通常意味着&#34;采用这个现有的源并将其值分配给这个现有的目的地&#34;,它开始意味着更像是&#34;获取这个现有的源,创建它的副本,以及将该值分配给此现有目的地。&#34;
从实用的角度来看,特别是在发明右值引用之前,这可能会对性能产生重大影响 - 在复制A到B的过程中创建一个完整的新对象是出乎意料的并且通常很慢。例如,如果我有一个小向量,并将其分配给一个更大的向量,我希望最多花时间复制小向量的元素加上(小)固定开销来调整目标矢量的大小。如果它涉及两个副本,一个从源到临时,另一个从临时到目标,以及(更糟)临时向量的动态分配,我对操作复杂性的期望是完全被摧毁。对于小向量,动态分配的时间很容易比复制元素的时间高很多倍。
唯一的另一个选项(在C ++ 11中添加)将返回一个右值引用。这很容易导致意外结果 - 像a=b=c;
这样的链式分配可能会破坏b
和/或c
的内容,这是非常意外的。
这使得返回正常引用(不是对const的引用,也不是rvalue引用)作为(合理地)可靠地产生大多数人通常想要的唯一选项。
答案 3 :(得分:5)
部分原因是返回对self的引用比按值返回更快,但另外,它是允许原始类型中存在的原始语义。
答案 4 :(得分:4)
operator=
以返回您想要的任何内容。你需要更具体地说明问题究竟是什么;我怀疑你的复制构造函数在内部使用operator=
并导致堆栈溢出,因为复制构造函数调用operator=
,它必须使用复制构造函数以无限值的方式返回A
。
答案 5 :(得分:3)
用户定义的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=
返回void
,以提高效率并避免支持基于副作用的错误代码的荒谬。
使用C ++ 11还需要T&
结果类型default
- 赋值运算符,因为
C ++11§8.4.2/ 1:
“明确默认的函数应具有相同的声明函数类型(可能不同的 ref-qualifiers 除外) 在复制构造函数或复制赋值运算符的情况下,参数类型可以是“引用非const
T
”,其中T
是成员函数的类的名称),就好像它是隐式的一样声明
答案 6 :(得分:0)
我猜,因为用户定义的对象应该表现得像内置类型。 例如:
char c;
while ((c = getchar()) != -1 ) {/* do the stuff */}