为什么内置赋值在C ++中返回非const引用而不是const引用?

时间:2017-08-10 18:34:52

标签: c++ return-type assignment-operator rvalue lvalue

(注意原始问题标题有"而不是右值"而不是"而不是const参考"。下面的答案之一是回应旧标题。是为了清楚而修好了)

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的引用,然后将j设置为1据我了解,这是因为表达式j=k返回 - const int&,例如一般来说是一个左值。

此约定通常也建议用户定义的类型,如"第10项中所述:赋值运算符返回对* this"的非(非常量)引用。在Meyers Effective C++(括号加法矿)。本书的这一部分并不试图解释为什么引用是非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 ++之间是否有意添加这个习惯用法?如果是这样,哪种情况可以证明其使用的合理性?换句话说,这种功能扩展能带来什么非虚假的好处呢?

3 个答案:

答案 0 :(得分:3)

根据设计,C和C ++之间的一个基本区别是C是左值丢弃语言,C ++是左值保留语言。

在C ++ 98之前,Bjarne添加了对该语言的引用,以便使运算符重载成为可能。为了有用,引用要求保留表达式的左值而不是丢弃。

这种保留左值的想法在C ++ 98之前并未真正形式化。在C ++ 98标准之前的讨论中,引用要求保留表达式的左值的事实被注意到并形式化,并且当C ++从C进行一次主要且有目的的中断并成为左值保留语言时。

C ++努力保持&#34;左值&#34;任何表达式结果只要有可能。它适用于所有内置运算符,也适用于内置赋值运算符。当然,没有启用写(a = b) = c这样的表达式,因为它们的行为是未定义的(至少在原始的C ++标准下)。但是由于C ++的这个属性,你可以编写像

这样的代码
int a, b = 42;
int *p = &(a = b);

它是多么有用是一个不同的问题,但同样,这只是左值保留设计C ++表达式的一个结果。

至于为什么它不是const左值......坦率地说,我不明白它为什么会这样。与C ++中任何其他保留左值的内置运算符一样,它只保留给它的任何类型。

答案 1 :(得分:1)

我会回答标题中的问题。

我们假设它返回了一个右值引用。以这种方式返回对新分配的对象的引用是不可能的(因为它是一个左值)。如果无法返回对新分配的对象的引用,则需要创建副本。对于重物,例如容器,这将是非常低效的。

考虑类似于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&,所以它看起来更像是一个惯例。

注意:问题的标题是指内置插件,而我的答案涵盖自定义类。我相信这是关于一致性的。如果内置和自定义类型的行为不同,那将是非常令人惊讶的。

答案 2 :(得分:0)

内置运营商不会&#34;返回&#34;什么,更不用说&#34;返回参考&#34;。

表达主要表现为两件事:

  • 他们的类型
  • 他们的价值类别。

例如k + 1的类型为int,值类别为&#34; prvalue&#34;,但k = 1的类型为int,值类别为&#34;左值& #34 ;.左值是指定内存位置的表达式,k = 1指定的位置与声明int k;分配的位置相同。

C标准只有价值类别&#34;左值&#34;并且&#34;不是左值&#34;。在C k = 1中,类型int和类别&#34;不是左值&#34;。

您好像在暗示k = 1应该有const int类型和值类别lvalue。也许它可以,语言会略有不同。它会禁止令人困惑的代码,但也可能禁止使用有用的代码。这是一个对语言设计师或设计委员会难以评估的决定,因为他们无法想到语言可以使用的各种可能方式。

他们错误地认为没有引入可能导致问题的限制。一个相关的例子是Should implicitly generated assignment operators be & ref-qualified?

想到的一种可能情况是:

void foo(int& x);

int y;
foo(y = 3);

y设置为3,然后调用foo。根据你的建议,这是不可能的。当然你可以说y = 3; foo(y);无论如何更清楚,但这是一个滑坡:也许不应该允许增量运算符在更大的表达式等内等。