为什么要定义运算符 +或者 += 在课堂之外,如何正确地做到这一点?

发布于 2024-10-11 01:32:46 字数 353 浏览 4 评论 0原文

首选哪种方式有点困惑

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

,它们是什么样的以及何时应该使用?

I am a bit confused about the differences between

Type  operator +  (const Type &type);
Type &operator += (const Type &type);

and

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

which way is preferred, what do they look like and when should either be used?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

携君以终年 2024-10-18 01:32:46

运算符的第一种形式是您在类 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+ 定义为 TT::operator+(const T&) const; 。该运算符将根据前一个运算符来实现。
  3. 在类外部但在同一命名空间内提供一个自由函数 T operator+(const T&, const T&);。该函数将调用成员operator+来完成工作。

您可以省略第 2 步并直接让自由函数调用 T::operator+=,但根据个人喜好,我希望将所有加法逻辑保留在类中。

The first form of the operators is what you would define inside class Type.

The second form of the operators is what you would define as free-standing functions in the same namespace as class Type.

It's a very good idea to define free-standing functions because then the operands to those can take part in implicit conversions.

Example

Assume this class:

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

You could then write:

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

But you could NOT write:

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

Having operator+ as a free function allows the last case as well.

What the second form of the operator does wrong though is that it performs the addition by directly tweaking the private members of its operands (I 'm assuming that, otherwise it would not need to be a friend). It should not be doing that: instead, the operators should also be defined inside the class and the free-standing functions should call them.

To see how that would turn out, let's ask for the services of a guru: http://www.gotw.ca/gotw/004.htm. Scroll at the very end to see how to implement the free-standing functions.

Update:

As James McNellis calls out in his comment, the two forms given also have another difference: the left-hand-side is not const-qualified in the first version. Since the operands of operator+ should really not be modified as part of the addition, it's a very very good idea to const-qualify them all the time. The class Type in my example now does this, where initially it did not.

Conclusion

The best way to deal with operators + and += is:

  1. Define operator+= as T& T::operator+=(const T&); inside your class. This is where the addition would be implemented.
  2. Define operator+ as T T::operator+(const T&) const; inside your class. This operator would be implemented in terms of the previous one.
  3. Provide a free function T operator+(const T&, const T&); outside the class, but inside the same namespace. This function would call the member operator+ to do the work.

You can omit step 2 and have the free function call T::operator+= directly, but as a matter of personal preference I 'd want to keep all of the addition logic inside the class.

半﹌身腐败 2024-10-18 01:32:46

相对于 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 += secondaryfirst 相同。更简单、更安全的方法是将其分开。

The proper way to implement operators, with respect to C++03 and C++0x (NRVO and move-semantics), is:

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

Note it's tempting to combine the above into:

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

But sometimes (in my testing) the compiler doesn't enable NRVO (or move semantics) because it can't be certain (until it inlines the mutating operator) that first += second is the same as first. Simpler and safer is to split it up.

想你的星星会说话 2024-10-18 01:32:46

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

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

The friend specifier is used when the thing being declared isn't a member of the class, but needs access to the private members of the class's instances in order to do its job.

If your operator will be defined in the class itself, use the first way; if it'll be a standalone function, use the second.

想挽留 2024-10-18 01:32:46

在许多情况下,您可以针对类中的类类型 T 编写运算符。主要是作为一个约定。然而,正如乔恩的回答所述,独立的函数可以帮助解决某些情况,而不需要更多的实现。

我还想提一下另一个重要的情况:存在一个类,但它不支持您想要的某个运算符。

这是一个示例:

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/斑点/c516aa954004cbbaca401e6aba3eb867fd329acd/libutf8/libutf8.h#L89-L132

In many cases, you write operators against a class type T within the class. Mostly as a convention. However, as noted in Jon's answer, free-standing functions can help in resolving certain cases without the need for more implementations.

There is also another important case I wanted to mention: a class exists and it does not support a certain operator that you would like to have.

Here is an example:

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

The system does not have that operator defined, so having the ability to add it in your own header and allowing the automatic conversion of the rhs from a Unicode character to UTF-8 is very practical if you have many functions doing such all over the place.

In this case, you do not have a choice but for a free-standing function since you can't just extend an existing class you do not control.

Functional examples in my libutf8 library:
https://github.com/m2osw/libutf8/blob/c516aa954004cbbaca401e6aba3eb867fd329acd/libutf8/libutf8.h#L89-L132

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文