为什么拷贝分配操作符必须返回引用/const引用?

时间:2021-11-10 05:33:27

In C++, the concept of returning reference from the copy assignment operator is unclear to me. Why can't the copy assignment operator return a copy of the new object? In addition, if I have class A, and the following:

在c++中,我不清楚从拷贝赋值操作符返回引用的概念。为什么复制分配操作符不能返回新对象的副本?另外,如果我有A类,并且有以下几点:

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

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

The operator= is defined as follows:

算子=定义如下:

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

6 个解决方案

#1


54  

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). 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. 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. 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).

严格来说,复制赋值运算符的结果不需要返回一个引用,尽管模仿c++编译器使用默认行为,它应该返回一个non-const引用的对象被分配给(隐式生成复制赋值运算符将返回一个non-const参考- C + + 03:12.8/10)。我见过一些代码,它们从复制分配重载中返回void,我不记得是什么时候引起了严重的问题。返回void会阻止用户“分配链接”(a = b = c;),并防止在测试表达式中使用赋值的结果。虽然这类代码并非闻所未闻,但我也不认为它特别常见——尤其是对于非原始类型(除非类的接口用于这些类型的测试,比如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.

这些其他的问题都是相关的(可能不是完全的废话),它们都有你可能感兴趣的信息/观点。

#2


45  

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.

对于为什么用引用返回操作符=而不是按值返回更可取进行一点澄清——因为如果返回一个值,链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!!

但是,如果您按值返回运算符=,每次调用赋值操作符时,您将调用构造函数和析构函数!!

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)

(注意:这并不是说让赋值操作符返回一个lvalue的好处。阅读其他文章,了解为什么这样更好)

#3


8  

When you overload operator=, you can write it to return whatever type you want. 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. This is generally highly inadvisable though.

当重载操作符=时,可以将其编写为返回所需的任何类型。如果你想做得足够糟糕,你可以重载X::运算符=返回(例如)某个完全不同的类Y或z的实例,但这通常是不可取的。

In particular, you usually want to support chaining of operator= just like C does. For example:

特别是,您通常希望支持操作符的链接,就像C一样。例如:

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).

在这种情况下,您通常希望返回被分配的类型的lvalue或rvalue。这只留下了一个问题:是返回对X的引用、对X的引用,还是返回X(按值)。

Returning a const reference to X is generally a poor idea. In particular, a const reference is allowed to bind to a temporary object. 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. 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.

返回一个const引用到X通常是一个糟糕的想法。特别是,允许const引用绑定到临时对象。临时的生命周期被扩展到它所绑定的引用的生命周期——但不会递归到任何可能被分配到的对象的生命周期中。这使得返回悬浮引用变得很容易——const引用绑定到临时对象。该对象的生命周期被扩展到引用的生命周期(在函数的末尾结束)。当函数返回时,引用和临时引用的生命周期已经结束,因此分配的是一个悬空引用。

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. 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."

返回一个值而不是引用,既有理论问题也有实践问题。在理论方面,在= normal意味着和它在本例中的含义之间有一个基本的脱节。特别是,赋值通常意味着“获取现有的源并将其值赋给现有的目标”,它开始表示类似于“获取现有的源,创建它的一个副本,并将该值赋给现有的目标”。

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. 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.

从实际的观点来看,特别是在rvalue引用被发明之前,这可能会对性能产生重大影响——在将a复制到B的过程中,创建一个完整的新对象是意料之外的,而且通常非常缓慢。例如,如果我有一个小的向量,并将它分配给一个大的向量,我期望它最多需要时间来复制小向量的元素,再加上一个(小的)固定开销来调整目标向量的大小。如果它包含两个副本,一个从源到临时文件,另一个从临时文件到目的地,以及(更糟的是)临时文件的动态分配,那么我对操作复杂性的预期将完全被破坏。对于一个小的向量,动态分配的时间很容易比复制元素的时间高很多倍。

The only other option (added in C++11) would be to return an rvalue reference. This could easily lead to unexpected results--a chained assignment like a=b=c; could destroy the contents of b and/or c, which would be quite unexpected.

惟一的其他选项(在c++ 11中添加)是返回一个rvalue引用。这很容易导致意想不到的结果——像a=b=c这样的链式分配;可能会破坏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引用)成为(合理地)依赖于产生大多数人通常想要的东西的惟一选项。

#4


4  

It's partly because returning a reference to self is faster than returning by value, but in addition, it's to allow the original semantics that exist in primitive types.

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

#5


4  

operator= can be defined to return whatever you want. 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.

可以定义为返回您想要的任何东西。你需要更具体地说明问题到底是什么;我怀疑在内部有复制构造函数使用操作符=,这会导致堆栈溢出,因为复制构造函数调用操作符=,它必须使用复制构造函数返回一个从值到无穷小的值。

#6


3  

There is no core language requirement on the result type of a user-defined operator=, but the standard library does have such a requirement:

对于用户定义的运算符=的结果类型没有核心语言要求,但是标准库确实有这样的要求:

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.

“存储在这些组件中的对象类型必须满足可复制类型(20.1.3)和可分配类型的附加要求。

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是用于实例化容器的类型,T是T的值,u是T的值(可能是const)。

为什么拷贝分配操作符必须返回引用/const引用?


Returning a copy by value would still support assignment chaining like a = b = c = 42;, because the assignment operator is right-associative, i.e. this is parsed as a = (b = (c = 42));. But returning a copy would prohibit meaningless constructions like (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).

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

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.

直到我了解了标准库的要求,我才允许操作符= 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类型来默认赋值操作符,因为

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-qualifiers和除了拷贝构造函数或复制赋值运算符的情况下,参数类型可能是“参考non-const T”,其中T是成员函数的类)的名字好像是隐式声明

#1


54  

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). 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. 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. 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).

严格来说,复制赋值运算符的结果不需要返回一个引用,尽管模仿c++编译器使用默认行为,它应该返回一个non-const引用的对象被分配给(隐式生成复制赋值运算符将返回一个non-const参考- C + + 03:12.8/10)。我见过一些代码,它们从复制分配重载中返回void,我不记得是什么时候引起了严重的问题。返回void会阻止用户“分配链接”(a = b = c;),并防止在测试表达式中使用赋值的结果。虽然这类代码并非闻所未闻,但我也不认为它特别常见——尤其是对于非原始类型(除非类的接口用于这些类型的测试,比如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.

这些其他的问题都是相关的(可能不是完全的废话),它们都有你可能感兴趣的信息/观点。

#2


45  

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.

对于为什么用引用返回操作符=而不是按值返回更可取进行一点澄清——因为如果返回一个值,链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!!

但是,如果您按值返回运算符=,每次调用赋值操作符时,您将调用构造函数和析构函数!!

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)

(注意:这并不是说让赋值操作符返回一个lvalue的好处。阅读其他文章,了解为什么这样更好)

#3


8  

When you overload operator=, you can write it to return whatever type you want. 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. This is generally highly inadvisable though.

当重载操作符=时,可以将其编写为返回所需的任何类型。如果你想做得足够糟糕,你可以重载X::运算符=返回(例如)某个完全不同的类Y或z的实例,但这通常是不可取的。

In particular, you usually want to support chaining of operator= just like C does. For example:

特别是,您通常希望支持操作符的链接,就像C一样。例如:

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).

在这种情况下,您通常希望返回被分配的类型的lvalue或rvalue。这只留下了一个问题:是返回对X的引用、对X的引用,还是返回X(按值)。

Returning a const reference to X is generally a poor idea. In particular, a const reference is allowed to bind to a temporary object. 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. 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.

返回一个const引用到X通常是一个糟糕的想法。特别是,允许const引用绑定到临时对象。临时的生命周期被扩展到它所绑定的引用的生命周期——但不会递归到任何可能被分配到的对象的生命周期中。这使得返回悬浮引用变得很容易——const引用绑定到临时对象。该对象的生命周期被扩展到引用的生命周期(在函数的末尾结束)。当函数返回时,引用和临时引用的生命周期已经结束,因此分配的是一个悬空引用。

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. 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."

返回一个值而不是引用,既有理论问题也有实践问题。在理论方面,在= normal意味着和它在本例中的含义之间有一个基本的脱节。特别是,赋值通常意味着“获取现有的源并将其值赋给现有的目标”,它开始表示类似于“获取现有的源,创建它的一个副本,并将该值赋给现有的目标”。

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. 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.

从实际的观点来看,特别是在rvalue引用被发明之前,这可能会对性能产生重大影响——在将a复制到B的过程中,创建一个完整的新对象是意料之外的,而且通常非常缓慢。例如,如果我有一个小的向量,并将它分配给一个大的向量,我期望它最多需要时间来复制小向量的元素,再加上一个(小的)固定开销来调整目标向量的大小。如果它包含两个副本,一个从源到临时文件,另一个从临时文件到目的地,以及(更糟的是)临时文件的动态分配,那么我对操作复杂性的预期将完全被破坏。对于一个小的向量,动态分配的时间很容易比复制元素的时间高很多倍。

The only other option (added in C++11) would be to return an rvalue reference. This could easily lead to unexpected results--a chained assignment like a=b=c; could destroy the contents of b and/or c, which would be quite unexpected.

惟一的其他选项(在c++ 11中添加)是返回一个rvalue引用。这很容易导致意想不到的结果——像a=b=c这样的链式分配;可能会破坏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引用)成为(合理地)依赖于产生大多数人通常想要的东西的惟一选项。

#4


4  

It's partly because returning a reference to self is faster than returning by value, but in addition, it's to allow the original semantics that exist in primitive types.

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

#5


4  

operator= can be defined to return whatever you want. 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.

可以定义为返回您想要的任何东西。你需要更具体地说明问题到底是什么;我怀疑在内部有复制构造函数使用操作符=,这会导致堆栈溢出,因为复制构造函数调用操作符=,它必须使用复制构造函数返回一个从值到无穷小的值。

#6


3  

There is no core language requirement on the result type of a user-defined operator=, but the standard library does have such a requirement:

对于用户定义的运算符=的结果类型没有核心语言要求,但是标准库确实有这样的要求:

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.

“存储在这些组件中的对象类型必须满足可复制类型(20.1.3)和可分配类型的附加要求。

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是用于实例化容器的类型,T是T的值,u是T的值(可能是const)。

为什么拷贝分配操作符必须返回引用/const引用?


Returning a copy by value would still support assignment chaining like a = b = c = 42;, because the assignment operator is right-associative, i.e. this is parsed as a = (b = (c = 42));. But returning a copy would prohibit meaningless constructions like (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).

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

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.

直到我了解了标准库的要求,我才允许操作符= 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类型来默认赋值操作符,因为

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-qualifiers和除了拷贝构造函数或复制赋值运算符的情况下,参数类型可能是“参考non-const T”,其中T是成员函数的类)的名字好像是隐式声明