45

Effective C++ ”第 3 条说“尽可能使用 const”,并给出了如下示例:

const Rational operator*(const Rational& lhs, 
                            const Rational& rhs);

为了防止客户能够犯下这样的暴行:

Rational a, b, c;
...
(a * b) = c;   // invoke operator= on the result of a*b!

但是函数的非引用返回值不是已经是右值了吗?那么为什么要麻烦做这件事呢?

4

5 回答 5

52

关键是对于类类型(但不是内置类型),a = b它只是 的简写a.operator=(b),其中 operator= 是成员函数。并且成员函数可以(a * b)在由创建的右值上调用Rational::operator*。为了强制执行与内置右值(“ do as the ints do ”)类似的语义,一些作者(包括 Meyers)在 C++98 中建议使用 const-rvalue 为具有此类运算符的类返回。

但是,在 C++11 中,通过 const-rvalue 返回是一个坏主意,因为它会抑制移动语义,因为 const rvalues 不能绑定到T&&.

在他的笔记An overview of the new C++ (C++11)中,Scott Meyers 给出了与他的旧书中完全相同的示例,并得出结论认为添加 const 返回值现在被认为是糟糕的设计。现在推荐的签名是

Rational operator*(const Rational& lhs, const Rational& rhs);

更新:正如@JohannesSchaub-litb 在评论中暗示的那样,在 C++11 中,您还可以在赋值运算符上使用引用限定符,以便它只接受左值作为其左参数(即*this指针,这就是为什么这个特性也称为“*this 的右值引用”)。您需要 g++ >= 4.8.1(刚刚发布)或 Clang >= 2.9 才能使用它。

于 2013-05-30T11:30:24.760 回答
8

返回值上的 const 修饰符不是必需的,并且会妨碍移动语义。在 C++11 中防止分配给右值的首选方法是使用“ ref-qualifiers ”。

struct Rational
{
  Rational & operator=( Rational other ) &; // can only be called on lvalues
};

Rational operator*( Rational const & lhs, Rational const & rhs );

Rational a, b, c;

(a * b) = c; // error
于 2013-06-01T01:22:05.910 回答
5

也许这会花费我的代表点数,但我不同意。不要修改重载运算符的预期返回类型,因为它会惹恼你的类的用户。即使用

Rational operator*(const Rational& lhs, const Rational& rhs);

(当然,consting参数是一种很好的做法,并且具有常量引用参数甚至更好,因为这意味着编译器不会进行深度复制。但是在这种情况下不要有一个常量引用返回值,因为你会得到一个悬空引用,这是灾难性的。但请注意,有时,获取引用比传递值要慢。我认为doubles 和ints 在许多平台上都属于该类别。)

于 2013-05-30T11:30:15.467 回答
3

因为您可能打算改写(a * b) == c,即

if ((a * b) = (c + d)) // executes if c+d is true

但你想

if ((a * b) == (c + d)) // executes if they're equal
于 2013-05-30T11:29:56.657 回答
1

我想根据您的问题,您想要做的是声明相应的 operator= private ,使其不再可访问。

因此您想重载匹配的签名(a*b) = c。我同意左侧部分是一个表达式,因此右值将是一个更好的匹配。但是,您忽略了这样一个事实,即这是函数的返回值,如果您重载函数以返回右值,编译器将抱怨重载无效,因为重载规则不考虑返回值。

如此处所述,用于赋值的运算符重载始终是内部类定义。如果有像重载决议这样的非成员签名void operator=(foo assignee, const foo& assigner);可以将第一部分匹配为右值(那么您可以删除它或将其声明为私有)。

因此,您可以从两个世界中进行选择:

  • 接受这样一个事实,即用户可以编写愚蠢的东西,例如(a*b) = c这并没有错,但将 c 的值存储在无法访问的临时文件中
  • 使用const foo operator*(const foo& lhs, const foo& rhs)不允许(a*b) = c和牺牲移动语义的签名

代码

#include <utility>

class foo {
    public:
    foo() = default;
    foo(const foo& f) = default;
    foo operator*(const foo& rhs) const {
        foo tmp;
        return std::move(tmp);
    }
    foo operator=(const foo& op) const {
        return op;
    }
    private:
    // doesn't compile because overloading doesn't consider return values.
    // conflicts with foo operator=(const foo& op) const;
    foo && operator=(const foo& op) const; 
};


int main ( int argc, char **argv ) {
    foo t2,t1;
    foo t3 = t2*t1;
    foo t4;
    (t2 * t1) = t4;
    return 0;
}
 
于 2013-05-30T20:31:26.180 回答