C++ 为啥赋值运算符应该返回一个 const ref 以避免 (a=b)=c

Posted

技术标签:

【中文标题】C++ 为啥赋值运算符应该返回一个 const ref 以避免 (a=b)=c【英文标题】:C++ why the assignment operator should return a const ref in order to avoid (a=b)=cC++ 为什么赋值运算符应该返回一个 const ref 以避免 (a=b)=c 【发布时间】:2011-06-10 01:15:27 【问题描述】:

我正在读一本关于 C++ 的书,更准确地说是关于运算符重载的书。

示例如下:

const Array &Array::operator=(const Array &right)

// check self-assignment
// if not self- assignment do the copying
return *this; //enables x=y=z

书中提供的关于返回 const ref 而不是 ref 的解释是为了避免像 (x=y)=z 这样的赋值。我不明白我们为什么要避免这种情况。我知道在此示例中首先评估 x=y ,并且由于它返回一个 const 引用,因此 =z 部分无法执行。但为什么呢?

【问题讨论】:

哪本书?这对我来说似乎是不必要的预防措施。我无法想象有人写(x=y)=z——他们为什么要写?如果没有括号,x=y=z 将被解析为 x=(y=z),这非常合理,因此没有风险。 但是为什么呢?为什么它是一个常量引用?为什么按这个顺序执行?为什么不能将z 分配给 (x=y)? @antronis:获得更好的 C++ 书籍。 这是deitel c++如何编程第7版 我同意这不是你通常会写的东西,但如果你的意思是(x = y) == z 并且打错了怎么办?我也不赞同那个特定的表达式,但是做更多的事情 const 有助于将运行时错误转化为编译时错误,这通常是有帮助的。 【参考方案1】:

没有必要避免这种情况,除非本书的目标读者是那些通常写成(x=y)=z程序员,而他们的意思是x=y=z。在实践中,没有人在他们正常的头脑中写下这个,所以预防措施是完全没有必要的。它还禁止一些其他简洁的结构,例如 (x=y).nonConstMember(),几乎没有人写,但在某些情况下可能有用(尽管它们不应该被过度使用)。

@ybungalobill 是对的,买一本更好的书。

【讨论】:

+1。程序员“不小心”写 (x=y)=z 似乎与程序员在意为 x=y 时不小心写 x=y+system("rm -rf /") 一样可能(并且有必要防止)。 但我见过if ((x = y) = z) ...,作者的意思是if ((x = y) == z) ...【参考方案2】:

我能看到的唯一原因是这本书是为了向 C 程序员(或对 C 理解优于 C++ 理解的作者)解释 C++ 而写的。因为对于 C 程序员来说,表达式(x = y) = z 对于内置类型是无效的,他可能会尝试使用它的用户定义类型来获得相同的行为。

但是,C 和 C++ 是不同的语言,在 C++ 中,表达式 (x = y) = z 即使对于内置类型也是有效的。因此,如果您希望用户定义的类型具有相同的行为,您应该在 operator = 中返回一个非常量引用。

我建议你买一本更好的书,一本不会混淆 C 和 C++ 的书。它们不是同一种语言,即使它们来自一个共同的基础。

【讨论】:

【参考方案3】:

我会看看内置类型的行为。

在定义您自己的类型时,运算符的行为方式最好与内置类型相同。这样可以轻松地采用您的类,而无需深入研究您的代码以了解它们的行为与预期不同的原因。

所以如果我们看整数:

int main()

    int x = 5;
    int y = 6;
    int z = 7;

    (x = y) = z;
    std::cout << x << " " << y << " " << z << "\n";

这适用于 y 不变且 x 被分配 7。在您的代码中,我希望您的赋值运算符以相同的方式工作。标准赋值运算符定义:

Array& Array::operator=(Array const& rhs)

    /* STUFF */
    return *this;

应该这样做就好了(假设 /* STUFF */ 是正确的)。

【讨论】:

【参考方案4】:

据我所知,赋值运算符在惯用的 C++ 中不返回 const 引用。标准类型也不返回 const 引用。

std::string a, b, c;
(a = b).clear(); // no objection from compiler

我所有的自定义赋值运算符都返回了一个非常量引用。

如有疑问,请查看标准库。它不是完美无缺的,但它肯定会正确地处理这样的基本问题。

【讨论】:

Boost 人通常也返回可变引用。【参考方案5】:

(x=y) 表示x.operator=(y),它返回对象x。因此,(x=y)=z 表示(x.operator=(y)).operator=(z)。括号中的表达式将x 设置为y 并返回x,然后外部位将x 设置为z。它没有像您所期望的那样将y 设置为z,也没有像表达式x = y = z 那样设置。

这种行为是违反直觉的(赋值后它们应该都相等,对吧?);返回一个 const 引用使其成为不可能并避免了该问题。

【讨论】:

虽然这种行为听起来违反直觉,但这并不是一个“问题”,因为这是程序员有意为之(这就是他们使用括号的原因) 谁会期望(x=y)=zy 设置为z?这是一个非常人为的“问题​​”,一本 C++ 书籍不应该花时间解释预防措施。 (x=5)=z 没有将 5 设置为 z 如果你期望赋值是关联的(就像比较一样),你会期望最后三个都是相等的。幼稚,但合理的第一眼解释。我同意这种形式一般不应该使用,也不应该在书中强调,但是帮助编译器帮助你尽可能地在脚上开枪是很好的=) 关于赋值首先要理解的是它不是关联的。我看过的每一本命令式编程教科书都在第 1 章或第 2 章中解释了这一点。您对错误输入 (x=y) == z 的评论更有意义。 可能不直观。 但是 这正是它处理整数的方式。我认为模仿标准类型的行为比试图保护人们免受晦涩的情况更重要。

以上是关于C++ 为啥赋值运算符应该返回一个 const ref 以避免 (a=b)=c的主要内容,如果未能解决你的问题,请参考以下文章

c++中重载输出操作符,为啥要返回引用

c++中为啥赋值运算符重载返回类型是引用

C++中赋值运算操作符和=重载有啥区别?

为啥移动赋值运算符应该返回对 *this 的引用 [重复]

为啥赋值运算符要返回对对象的引用?

C++类和对象(构造函数析构函数拷贝构造函数赋值运算符重载Const成员)详细解读