“ 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 ?那么为什么还要这么做?
答案 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;
}