为什么在类之外定义operator +或+ =,以及如何正确地执行它?

时间:2011-01-11 00:04:27

标签: c++ operators operator-overloading

我对

之间的差异感到有些困惑
Type  operator +  (const Type &type);
Type &operator += (const Type &type);

friend Type  operator +  (const Type &type1, const Type &type2);
friend Type &operator += (const Type &type1, const Type &type2);

哪种方式更受欢迎,它们看起来像什么,何时应该使用?

3 个答案:

答案 0 :(得分:28)

第一种形式的运算符是您在类Type中定义的内容。

运算符的第二种形式是您在与类Type相同的命名空间中定义为独立函数的内容。

定义独立函数是一个非常好的主意,因为那些操作数可以参与隐式转换

实施例

假设这个类:

class Type {
    public:
    Type(int foo) { }

    // Added the const qualifier as an update: see end of answer
    Type operator + (const Type& type) const { return *this; }
};

然后你可以写:

Type a = Type(1) + Type(2); // OK
Type b = Type(1) + 2; // Also OK: conversion of int(2) to Type

但你不能写:

Type c = 1 + Type(2); // DOES NOT COMPILE

operator+作为自由函数也允许最后一种情况。

运算符的第二种形式错误的是它通过直接调整其操作数的私有成员来执行添加(我假设,否则它不需要是朋友)。它应该这样做:相反,运算符也应该在类中定义,并且独立函数应该调用它们。

要了解结果如何,让我们要求大师的服务:http://www.gotw.ca/gotw/004.htm。滚动到最后,看看如何实现独立功能。

<强>更新

正如James McNellis在评论中所说的那样,给出的两种形式也有另一个区别:左手边在第一个版本中不是const限定的。由于operator+的操作数实际上不应该作为添加的一部分进行修改,所以始终对它们进行const限定是一个非常好的主意。我的示例中的类Type现在执行此操作,最初它没有。

结论

处理运营商++=的最佳方式是:

  1. 在班级中将operator+=定义为T& T::operator+=(const T&);。这是增加的地方。
  2. 在班级中将operator+定义为T T::operator+(const T&) const;。该运营商将根据前一个运营商实施。
  3. 在类外提供一个自由函数T operator+(const T&, const T&);,但在同一名称空间内。此函数会调用成员operator+来完成工作。
  4. 您可以省略第2步并直接使用自由函数调用T::operator+=,但是出于个人喜好,我希望将所有添加逻辑保留在类中。

答案 1 :(得分:5)

关于C ++ 03和C ++ 0x(NRVO和move-semantics)实现运算符的正确方法是:

struct foo
{
    // mutates left-operand => member-function
    foo& operator+=(const foo& other)
    {
        x += other.x;

        return *this;
    }

    int x;
};

// non-mutating => non-member function
foo operator+(foo first, // parameter as value, move-construct (or elide)
                const foo& second) 
{
    first += second; // implement in terms of mutating operator

    return first; // NRVO (or move-construct)
}

请注意,将上述内容合并为:

foo operator+(foo first, const foo& second) 
{
    return first += second;
}

但有时(在我的测试中)编译器不启用NRVO(或移动语义),因为它不能确定(直到它内联变异运算符)first += second与{{1}相同}}。更简单,更安全的是拆分它。

答案 2 :(得分:0)

当声明的事物不是该类的成员时,使用friend说明符,但需要访问该类实例的私有成员才能完成其工作。

如果您的操作员将在班级中定义,请使用第一种方法;如果它是一个独立的功能,请使用第二个。