为什么要在类外部定义运算符 + 或 +=,以及如何正确执行?

问题描述 投票:0回答:4

我对之间的差异有点困惑

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);

哪种方式是首选,它们是什么样的以及何时应该使用?

c++ operators operator-overloading
4个回答
34
投票

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

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
现在执行此操作,而最初它没有执行此操作。

结论

对付操作员

+

+=
最好的方法是:

在班级中将
    operator+=
  1. 定义为
    T& T::operator+=(const T&);
    。这是实施添加的地方。
    在班级中将 
  2. operator+
  3. 定义为
    T T::operator+(const T&) const;
    。该运算符将根据前一个运算符来实现。
    在类之外、但在同一命名空间内提供一个自由函数
  4. T operator+(const T&, const T&);
  5. 。该函数将调用成员
    operator+
    来完成工作。
    
    
    
  6. 您可以省略第 2 步并直接调用自由函数
T::operator+=

,但根据个人喜好,我希望将所有添加逻辑保留在类中。

    


7
投票
NRVO

和移动语义),实现运算符的正确方法是: 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

first
相同。更简单、更安全的方法是将其分开。
    


0
投票
friend

说明符。


如果您的运算符将在类本身中定义,请使用第一种方法;如果它是一个独立的函数,请使用第二个。


0
投票

我还想提一下另一个重要的情况:

存在一个类,但它不支持您想要拥有的某个运算符。

这是一个例子:

std::string & operator += (std::string & lhs, char32_t rhs) { lhs += (...rhs converted to UTF-8...); return lhs; }

系统没有定义该运算符,因此如果您有许多函数在整个系统中执行此类操作,那么能够将其添加到您自己的标头中并允许将 rhs 从 Unicode 字符自动转换为 UTF-8 是非常实用的地点。

在这种情况下,您别无选择,只能选择独立的函数,因为您不能只扩展您无法控制的现有类。

我的libutf8库中的功能示例:

https://github.com/m2osw/libutf8/blob/c516aa954004cbbaca401e6aba3eb867fd329acd/libutf8/libutf8.h#L89-L132

© www.soinside.com 2019 - 2024. All rights reserved.