关于重载运算符+的问题

3
请看下面的代码:

考虑以下代码:


class A
{
public:
    A& operator=( const A& );
    const A& operator+( const A& );
    const A& operator+( int m );
};

int main()
{
    A a;
    a = ( a + a ) + 5;   // error: binary '+' : no operator found which takes a left-hand operand of type 'const A'
}

有人能解释为什么会返回以上错误吗?
"( a + a )" 调用了 "const A& operator+( const A& )" 并返回一个常量引用,然后如果我没弄错的话就传递给了 "const A& operator+( int m )"。
如何修复上述错误(不创建全局二元运算符+或接受int的构造函数),以使main()内的语句被允许?

谢谢您的所有解释。我知道上面的实现运算符的方式不是最好的,但我只是想知道为什么编译器会在上面的代码中抱怨。 - jasonline
6个回答

7
不是的。由于左操作数是一个“const A&”,右操作数是一个“int”,所以它将调用*“const A& operator+( int m )”*。
[anyType] operator+ (int rhs) const
//                            ^^^^^ note the const here.

由于您只提供了非const版本的const A& operator+( int m ),因此编译器会报错。
*:或者operator+(const int& rhs) constoperator+(float rhs) const……关键点在于它必须是一个const方法。

6

operator+应该返回一个实例,而不是一个引用:

// as member function
A operator+(const A& other);

// as free function
A operator+(const A& left, const A& right);

解释具体问题是“返回一个常量引用,然后将其传递给const A& operator+( int m )”。由于您有一个const引用,它无法调用该函数,因为它不是一个const方法(即const A& operator+( int m ) const)。
话虽如此,这并不是修复operator+的方法。如果您返回一个引用,那么它是一个引用指向什么?在operator+中返回本地变量是不好的,因为您不应该返回对本地变量的引用。引用全局变量也不好,因为它会限制代码的正确使用方式。引用分配的内存也不好,因为它会泄漏内存。引用*this也不好,因为那么operator+就像operator +=一样操作。

@R Samuel:是的,我明白。这是我从一次考试中拿出来的,所以我没有改变它。 - jasonline
如果你真的在考试中遇到了这种烂代码,那么如果可以的话,请放弃这个考试。这段代码是关于 operator+ 最差的写法,我几乎找不到更劣的实现方式了。@jasonline - sbi

2

因为在添加时修改了左侧对象,所以无法使用 const 对象。顺便说一下,遵循 Samuel 的建议,惯用的方法是返回添加对象的新副本。


@AraK:是的,我明白它应该返回一个新对象。那段代码是从一次考试中拿来的,所以我没有费心去改变它。 - jasonline
@jasonline:你确定考试没有这样实现 += 吗?很难想象任何一场考试会如此错误地使用 + - sbi
@sbi:我猜考试只是想测试我是否能够识别哪个部分导致了错误,而不是真正尝试以正确的方式实现它。 - jasonline

1
问题是,(a+a) 返回所谓的 rvalue(基本上是一个临时值)。虽然您可以在 rvalue 上调用成员函数,但只能调用 const 成员函数。此外,所有人都正确地指出,operator+ 必须始终返回新值。
您的运算符应该像这样实现:
A operator+( const A& ) const;
A operator+( int m ) const;

然而,不修改其左参数的二元运算符可能更好地实现为自由函数。
class A { ... };

A operator+(const A& lhs, const A& rhs);
A operator+(const A& lhs, int rhs);

通常,它们是在operator+=的基础上实现的,该操作符被实现为成员函数:

class A {
  public:
   A& operator+=(const A& rhs);
   A& operator+=(int rhs);
};

inline A operator+(A lhs, const A& rhs) // note: lhs is passed by copy now
{
  lhs += rhs;
  return lhs;
}
A operator+(A lhs, int rhs) // note: lhs is passed by copy now
{
  lhs += rhs;
  return lhs;
}

@sbi:感谢提供信息,是的,我知道最好的方法应该是将其实现为全局变量,并添加一个带有 int 参数的构造函数进行转换。只是我之前不理解 const 部分。 - jasonline
@jasonline:通常最好的方法不是使用隐式转换构造函数,因为这将需要从“int”创建临时“A”。通常,最好的方法是为“int”定义不同的运算符重载,以避免创建临时对象。(如果构造函数是平凡的,并且涉及的所有代码都是内联的,则编译器可能能够消除临时对象。) - sbi
你的意思是定义两个重载运算符,其中一个int在lhs上,另一个int在rhs上?但是在这种情况下创建临时变量有什么不好的呢? - jasonline
@jasonline:是的,这就是我的意思。对于您的 A,无论是否临时,可能都不会有太大区别。但是考虑到 std::string:如果为了将字符串字面量附加到字符串(s1 + "blah")中,必须首先将字面量转换为 std::string(基本上是 s1 + std::string("blah")),那么可能会导致动态内存分配和删除,从而造成严重的运行时成本。 - sbi
哦,我只是觉得这样会更方便一些,也不会那么冗余。但如果这真的会导致严重的运行时成本,那我想你是有道理的。谢谢。 - jasonline

1

该函数需要是const的:

const A& operator+( int m ) const;


1

由于 const A& operator+( const A& ) 返回一个常引用,因此不能在常量对象上调用非常成员函数 const A& operator+( int m )。 因此,第一个运算符应定义为 A& operator+( const A& ) 或者第二个运算符应定义为 const A& operator+( int m )const; 但是,这些更改仅会使它们在技术上正确,而不是在大多数情况下美学上正确,因为二元运算符不应修改任何输入参数,而是计算结果并返回。因此,结果必须通过值返回或在C++0x的情况下,作为右值引用返回。 即 A operator+(const A& rhs)const 或者 A&& operator+(const A& rhs)const


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接