这里不是const修饰符吗?

时间:2013-05-30 11:26:51

标签: c++ c++11 coding-style const rvalue

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!

但是,函数的非参考返回值是否已经 rvalue ?那么为什么还要这么做?

5 个答案:

答案 0 :(得分:51)

关键是类类型(但不适用于内置类型),a = b只是a.operator=(b)的简写,其中operator =是成员函数。并且可以在由(a * b)创建的Rational::operator*等rvalues上调用成员函数。为了强制执行类似于内置rvalues的语义(“做为ints做”),一些作者(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);

UPDATE:正如@ JohannesSchaub-litb在评论中所暗示的那样,在C ++ 11中你也可以在赋值运算符上使用reference qualifier,这样它就只接受左值它的左参数(即*this指针,这就是为什么这个特性也被称为“* this的rvalue references”)。你需要使用g ++> = 4.8.1(刚刚发布)或Clang> = 2.9才能使用它。

答案 1 :(得分:6)

返回值上的const修饰符不是必需的,可能会阻碍移动语义。防止在C ++ 11中赋值给rvalues的首选方法是使用“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

答案 2 :(得分:5)

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

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

(当然,const 参数是一种很好的做法,并且具有常量引用参数甚至更好,因为它意味着编译器不会采用深层副本。但是没有在这种情况下,一个常量的引用返回值,虽然你会得到一个灾难性的悬空引用。但请注意,有时候,引用比通过值慢。我认为{{ 1}} s和double在许多平台上进入该类别。)

答案 3 :(得分: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

答案 4 :(得分:1)

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

因此您希望重载与(a*b) = c匹配的签名。 我同意左边的部分是一个表达式,因此rvalue将是一个更好的匹配。 但是你忽略了这个函数的返回值,如果你重载函数返回一个rvalue,编译器会抱怨无效的重载,因为重载规则不考虑返回值。

如上所述here,赋值的运算符重载始终是内部类定义。如果会有像void operator=(foo assignee, const foo& assigner);这样的非成员签名,则重载决策可以将第一部分与rvalue匹配(然后你可以删除它或将其声明为私有)。

所以你可以选择两个世界:

  • 活着的事实是用户可以编写像(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;
}