为什么在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返回一个-constint&,例如,一般来说是一个左值。

这种约定通常也被推荐用于用户定义的类型,如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标准只有"左值"one_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);无论如何都更清晰,但这是一个滑坡:也许不应该在更大的表达式等中允许增量运算符。

最新更新