为什么在C++中,内建赋值返回的是非常量引用而不是常量引用
why does builtin assignment return a non-const reference instead of a const reference in C++?
C和C++中的一个常见构造是用于链式分配,例如
int j, k;
j = k = 1;
首先执行第二个=
,表达式k=1
具有将k
设置为1的副作用,而表达式本身的值为1。
然而,一个在C++中合法(但在C中不合法)的构造如下,它对所有基类型都有效:
int j, k=2;
(j=k) = 1;
这里,表达式j=k
具有将j
设置为2的副作用,并且表达式本身变为对j
的引用,然后CCD_6将j
设置为1。据我所知,这是因为表达式j=k
返回一个非-const
int&
,例如,一般来说是一个左值。
这种约定通常也被推荐用于用户定义的类型,如Meyers Effective C++(括号中的addition-mine)中的"第10项:让赋值运算符返回对*This的(非常量)引用"中所解释的。本书的这一部分并没有试图解释为什么引用是非const
引用,甚至没有顺便注意到非const
引用。
当然,这当然增加了功能,但(j=k) = 1;
这句话至少显得有些尴尬。
如果约定使用内置赋值返回const引用,那么自定义类也将使用此约定,并且C中允许的原始链式构造仍然可以工作,没有任何无关的副本或移动。例如,以下程序正确运行:
#include <iostream>
using std::cout;
struct X{
int k;
X(int k): k(k){}
const X& operator=(const X& x){
// the first const goes against convention
k = x.k;
return *this;
}
};
int main(){
X x(1), y(2), z(3);
x = y = z;
cout << x.k << 'n'; // prints 3
}
优点是所有3个(C内置、C++内置和C++自定义类型)都是一致的,不允许使用(j=k) = 1
之类的习惯用法。
在C和C++之间添加这个习语是有意的吗?如果是这样的话,在什么样的情况下才有理由使用它?换句话说,这种功能扩展提供了什么非虚假的好处?
根据设计,C和C++之间的一个基本区别是,C是一种左值丢弃语言,而C++是一种左值保留的语言。
在C++98之前,Bjarne添加了对该语言的引用,以便使运算符重载成为可能。引用,为了有用,需要保留而不是丢弃表达式的左值。
这个保持左值的想法直到C++98才真正形式化。在C++98标准之前的讨论中,引用要求保留表达式的左值这一事实被注意到并形式化了,这时C++从C语言中进行了一次重大而有目的的突破,成为了一种保留左值的语言。
C++力求尽可能地保持任何表达式结果的"lvalueness"。它适用于所有内置运算符,也适用于内置赋值运算符。当然,还没有实现像(a = b) = c
这样的表达式的编写,因为它们的行为是未定义的(至少在最初的C++标准下是这样)。但由于C++的这种特性,你可以编写类似的代码
int a, b = 42;
int *p = &(a = b);
它有多有用是另一个问题,但同样,这只是C++表达式的左值保留设计的一个结果。
至于为什么它不是const
左值。。。坦率地说,我不明白为什么应该这样。就像C++中任何其他保留左值的内置运算符一样,它只保留给定的任何类型。
我将回答标题中的问题。
让我们假设它返回了一个右值引用。以这种方式返回对新分配对象的引用是不可能的(因为它是一个左值)。如果无法返回对新指定对象的引用,则需要创建一个副本。对于重型物体,例如集装箱来说,这将是非常低效的。
考虑一个类似std::vector的类的例子。
对于当前的返回类型,赋值是这样工作的(我没有故意使用模板和复制和交换习惯用法来保持代码尽可能简单):
class vector {
vector& operator=(const vector& other) {
// Do some heavy internal copying here.
// No copy here: I just effectively return this.
return *this;
}
};
让我们假设它返回了一个右值:
class vector {
vector operator=(const vector& other) {
// Do some heavy stuff here to update this.
// A copy must happen here again.
return *this;
}
};
您可能会考虑返回一个右值引用,但这也不起作用:您不能只移动*this
(否则,一系列分配a = b = c
将运行b
),因此还需要第二个副本来返回它。
你帖子正文中的问题不同:返回const vector&
确实是可能的,没有上面显示的任何复杂情况,所以对我来说,这更像是一种惯例。
注意:问题的标题指的是内置类,而我的回答涵盖了自定义类。我相信这关乎一致性。如果它对内置类型和自定义类型采取不同的操作,那将是非常令人惊讶的。
内置运算符不会"返回"任何内容,更不用说"返回引用"了。
表达式主要有两个特点:
- 他们的类型
- 他们的价值类别
例如,k + 1
的类型为int
,值类别为"prvalue",但k = 1
的类型为int
,值类别"lvalue"。左值是一个指定内存位置的表达式,k = 1
指定的位置与声明int k;
分配的位置相同。
C标准只有"左值"answers"非左值"两个值类别。在C中,k = 1
具有类型int
和类别"非左值"。
您似乎在建议k = 1
应该具有类型const int
和值类别lvalue
。也许可以,语言会略有不同。这将取缔混乱的代码,但也可能取缔有用的代码。对于语言设计师或设计委员会来说,这是一个很难评估的决定,因为他们无法想出使用语言的所有可能方式。
他们错误地认为没有引入限制,这可能会带来一个没有人预见到的问题。一个相关的例子是隐式生成的赋值运算符应该是&裁判合格?。
脑海中可能出现的一种情况是:
void foo(int& x);
int y;
foo(y = 3);
其将CCD_ 32设置为CCD_。在你的建议下这是不可能的。当然,你可以说y = 3; foo(y);
无论如何都更清晰,但这是一个滑坡:也许不应该在更大的表达式等中允许增量运算符。
- 什么时候在C++中返回常量引用是个好主意
- 我想将一个对T类型的非常量左值引用绑定到一个T类型的临时值
- 为什么我可以通过引用修改常量返回
- 返回常量对象引用 (getter) 和仅返回字符串有什么区别?
- 将常量指针引用绑定到非常量指针
- 通过常量引用传递参数的矩阵模板类
- 按值捕获引用时出现非常量
- 在C++中使用非常量引用作为常量
- 具有常量引用参数的函数模板专用化
- 多个"常量引用"变量可以共享同一个内存吗?
- 为什么 STL 容器适配器堆栈中的 top 返回常量引用?
- 为什么按值传递QStringView比引用常量更快?
- 通过引用常量函数调用另一个类的非常量函数
- 构造常量对象与引用常量对象
- 引用“常量value_type”时出错
- 为什么可以在 for 语句中重新分配引用常量
- 程序反馈:命名循环索引和引用常量数据
- 堆还是栈?在c++中函数调用中引用常量字符串时
- 为什么常量结构数组在按名称引用常量结构时不放在 .rodata 中?
- 为什么编译器允许在函数中发送对迭代器的引用,该函数引用常量迭代器