简体   繁体   English

为什么复制赋值运算符必须返回引用/ const引用?

[英]Why must the copy assignment operator return a reference/const reference?

In C++, the concept of returning reference from the copy assignment operator is unclear to me. 在C ++中,我不清楚从复制赋值运算符返回引用的概念。 Why can't the copy assignment operator return a copy of the new object? 为什么复制赋值运算符不能返回新对象的副本? In addition, if I have class A , and the following: 另外,如果我有A级,以及以下内容:

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

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

The operator= is defined as follows: operator=定义如下:

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

Strictly speaking, the result of a copy assignment operator doesn't need to return a reference, though to mimic the default behavior the C++ compiler uses, it should return a non-const reference to the object that is assigned to (an implicitly generated copy assignment operator will return a non-const reference - C++03: 12.8/10). 严格地说,复制赋值运算符的结果不需要返回引用,但是为了模仿C ++编译器使用的默认行为,它应该返回对分配给的对象的非const引用(隐式生成的副本)赋值运算符将返回非const引用 - C ++ 03:12.8 / 10)。 I've seen a fair bit of code that returns void from copy assignment overloads, and I can't recall when that caused a serious problem. 我已经看到了相当多的代码从复制赋值重载返回void ,我不记得何时引起严重问题。 Returning void will prevent users from 'assignment chaining' ( a = b = c; ), and will prevent using the result of an assignment in a test expression, for example. 返回void将阻止用户进行“分配链接”( a = b = c; ),并且将阻止在测试表达式中使用赋值结果。 While that kind of code is by no means unheard of, I also don't think it's particularly common - especially for non-primitive types (unless the interface for a class intends for these kinds of tests, such as for iostreams). 虽然这种代码绝不是闻所未闻,但我也认为它并不常见 - 特别是对于非原始类型(除非类的接口打算进行这类测试,例如iostreams)。

I'm not recommending that you do this, just pointing out that it's permitted and that it doesn't seem to cause a whole lot of problems. 我不是建议你这样做,只是指出它是允许的,它似乎并没有引起很多问题。

These other SO questions are related (probably not quite dupes) that have information/opinions that might be of interest to you. 这些其他SO问题与您可能感兴趣的信息/意见相关(可能不是完全愚蠢)。

A bit of clarification as to why it's preferable to return by reference for operator= versus return by value --- as the chain a = b = c will work fine if a value is returned. 稍微澄清为什么最好通过引用返回operator=与值返回---因为如果返回一个值,链a = b = c将正常工作。

If you return a reference, minimal work is done. 如果您返回参考,则完成最少的工作。 The values from one object are copied to another object. 来自一个对象的值将复制到另一个对象。

However, if you return by value for operator= , you will call a constructor AND destructor EACH time that the assignment operator is called!! 但是,如果按值返回operator= ,则会调用构造函数和析构函数每次调用赋值运算符!!

So, given: 所以,给定:

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

Then, 然后,

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

But, 但,

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!

In sum, there is nothing gained by returning by value, but a lot to lose. 总而言之,通过价值回归没有任何好处,但要失去很多。

( Note : This isn't meant to address the advantages of having the assignment operator return an lvalue. Read the other posts for why that might be preferable) 注意 :这并不是为了解决赋值运算符返回左值的优点。请阅读其他帖子,了解为什么这可能更合适)

When you overload operator= , you can write it to return whatever type you want. 当你重载operator= ,你可以写它来返回你想要的任何类型。 If you want to badly enough, you can overload X::operator= to return (for example) an instance of some completely different class Y or Z . 如果你想要足够严重,你可以重载X::operator=来返回(例如)某个完全不同的类YZ的实例。 This is generally highly inadvisable though. 但这通常是非常不可取的。

In particular, you usually want to support chaining of operator= just like C does. 特别是,您通常希望支持operator=链接,就像C一样。 For example: 例如:

int x, y, z;

x = y = z = 0;

That being the case, you usually want to return an lvalue or rvalue of the type being assigned to. 在这种情况下,您通常希望返回所分配类型的左值或右值。 That only leaves the question of whether to return a reference to X, a const reference to X, or an X (by value). 这只留下了是否返回对X的引用,对X的const引用或X(按值)的问题。

Returning a const reference to X is generally a poor idea. 将const引用返回到X通常是一个糟糕的想法。 In particular, a const reference is allowed to bind to a temporary object. 特别是,允许​​const引用绑定到临时对象。 The lifetime of the temporary is extended to the lifetime of the reference to which it's bound--but not recursively to the lifetime of whatever that might be assigned to. 临时的生命周期延长到它所绑定的引用的生命周期 - 但不会递归到可能分配给它的任何生命周期。 This makes it easy to return a dangling reference--the const reference binds to a temporary object. 这使得返回悬空引用变得容易 - const引用绑定到临时对象。 That object's lifetime is extended to the lifetime of the reference (which ends at the end of the function). 该对象的生命周期延长到引用的生命周期(在函数结束时结束)。 By the time the function returns, the lifetime of the reference and temporary have ended, so what's assigned is a dangling reference. 到函数返回时,引用和临时的生命周期已经结束,因此分配的是悬空引用。

Of course, returning a non-const reference doesn't provide complete protection against this, but at least makes you work a little harder at it. 当然,返回非const引用并不能提供完全的保护,但至少会让你更加努力。 You can still (for example) define some local, and return a reference to it (but most compilers can and will warn about this too). 您仍然可以(例如)定义一些本地,并返回对它的引用(但大多数编译器可以并且也将对此发出警告)。

Returning a value instead of a reference has both theoretical and practical problems. 返回值而不是引用具有理论和实际问题。 On the theoretical side, you have a basic disconnect between = normally means and what it means in this case. 在理论方面,您有以下两种基本脱节=通常意味着这意味着什么在这种情况下。 In particular, where assignment normally means "take this existing source and assign its value to this existing destination", it starts to mean something more like "take this existing source, create a copy of it, and assign that value to this existing destination." 特别是,在赋值通常意味着“获取此现有源并将其值分配给此现有目标”时,它开始意味着更像“获取此现有源,创建它的副本,并将该值分配给此现有目标。 “

From a practical viewpoint, especially before rvalue references were invented, that could have a significant impact on performance--creating an entire new object in the course of copying A to B was unexpected and often quite slow. 从实用的角度来看,尤其是在发明右值引用之前,这可能会对性能产生重大影响 - 在复制A到B的过程中创建一个完整的新对象是出乎意料的并且通常很慢。 If, for example, I had a small vector, and assigned it to a larger vector, I'd expect that to take, at most, time to copy elements of the small vector plus a (little) fixed overhead to adjust the size of the destination vector. 例如,如果我有一个小向量,并将其分配给一个更大的向量,我希望最多花时间复制小向量的元素加上(小)固定开销来调整大小目的地矢量。 If that instead involved two copies, one from source to temp, another from temp to destination, and (worse) a dynamic allocation for the temporary vector, my expectation about the complexity of the operation would be entirely destroyed. 如果它涉及两个副本,一个从源到临时,另一个从临时到目的地,并且(更糟)动态分配临时向量,我对操作复杂性的期望将完全被破坏。 For a small vector, the time for the dynamic allocation could easily be many times higher than the time to copy the elements. 对于小向量,动态分配的时间可能比复制元素的时间高很多倍。

The only other option (added in C++11) would be to return an rvalue reference. 唯一的另一个选项(在C ++ 11中添加)将返回一个右值引用。 This could easily lead to unexpected results--a chained assignment like a=b=c; 这很容易导致意想不到的结果 - 像a=b=c;这样a=b=c;链式赋值a=b=c; could destroy the contents of b and/or c , which would be quite unexpected. 可以破坏b和/或c的内容,这是非常意外的。

That leaves returning a normal reference (not a reference to const, nor an rvalue reference) as the only option that (reasonably) dependably produces what most people normally want. 这使得返回正常引用(不是对const的引用,也不是rvalue引用)作为(合理地)可靠地产生大多数人通常想要的唯一选项。

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

operator= can be defined to return whatever you want. operator=可以定义为返回您想要的任何内容。 You need to be more specific as to what the problem actually is; 你需要更具体地说明问题究竟是什么; I suspect that you have the copy constructor use operator= internally and that causes a stack overflow, as the copy constructor calls operator= which must use the copy constructor to return A by value ad infinitum. 我怀疑你有复制构造函数使用operator= internal并导致堆栈溢出,因为复制构造函数调用operator= ,它必须使用复制构造函数以无限值的方式返回A

There is no core language requirement on the result type of a user-defined operator= , but the standard library does have such a requirement: 用户定义的operator=的结果类型没有核心语言要求,但标准库确实有这样的要求:

C++98 §23.1/3: C ++98§23.1/ 3:

The type of objects stored in these components must meet the requirements of CopyConstructible types (20.1.3), and the additional requirements of Assignable types. 存储在这些组件中的对象类型必须满足CopyConstructible类型(20.1.3)的要求,以及Assignable类型的附加要求。

C++98 §23.1/4: C ++98§23.1/ 4:

In Table 64, T is the type used to instantiate the container, t is a value of T , and u is a value of (possibly const ) T . 在表64中, T是用于实例化容器的类型, tT的值, u是(可能是constT

在此输入图像描述


Returning a copy by value would still support assignment chaining like a = b = c = 42; 按值返回副本仍然支持分配链接,如a = b = c = 42; , because the assignment operator is right-associative, ie this is parsed as a = (b = (c = 42)); ,因为赋值运算符是右关联的,即它被解析为a = (b = (c = 42)); . But returning a copy would prohibit meaningless constructions like (a = b) = 666; 但是返回副本会禁止像(a = b) = 666;那样无意义的结构(a = b) = 666; . For a small class returning a copy could conceivably be most efficient, while for a larger class returning by reference will generally be most efficient (and a copy, prohibitively inefficient). 对于一个小类来说,返回一个副本可能是最有效的,而对于一个更大的类,通过引用返回通常是最有效的(和副本,非常低效)。

Until I learned about the standard library requirement I used to let operator= return void , for efficiency and to avoid the absurdity of supporting side-effect based bad code. 直到我了解了标准库的要求,我曾经让operator= return void ,以提高效率并避免支持基于副作用的坏代码的荒谬性。


With C++11 there is additionally the requirement of T& result type for default -ing the assignment operator, because 对于C ++ 11,还需要T& result类型来default赋值赋值运算符,因为

C++11 §8.4.2/1: C ++11§8.4.2/ 1:

A function that is explicitly defaulted shall […] have the same declared function type (except for possibly differing ref-qualifiers and except that in the case of a copy constructor or copy assignment operator, the parameter type may be “reference to non-const T ”, where T is the name of the member function's class) as if it had been implicitly declared 明确默认的函数应具有相同的声明函数类型(除了可能不同的ref限定符之外,在复制构造函数或复制赋值运算符的情况下,参数类型可以是”对非const T “,其中T是成员函数类的名称),就像它已被隐式声明一样

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM