运算符重载的基本规则和惯用法是什么? [英] What are the basic rules and idioms for operator overloading?

查看:97
本文介绍了运算符重载的基本规则和惯用法是什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

注意:答案是按照特定顺序给出的,但是由于许多用户是根据投票而不是给出时间来对答案进行排序的,因此这是 索引答案 (按最有意义的顺序排列):

Note: The answers were given in a specific order, but since many users sort answers according to votes, rather than the time they were given, here's an index of the answers in the order in which they make most sense:

  • C ++中运算符重载的一般语法
  • C ++中运算符重载的三个基本规则
  • 成员与非成员之间的决定
  • 常见的运算符要重载
    • 分配运算符
    • 输入和输出运算符
    • 函数调用运算符
    • 比较运算符
    • 算术运算符
    • 数组下标
    • 类似指针类型的运算符
    • 转化运算符
    • The General Syntax of operator overloading in C++
    • The Three Basic Rules of Operator Overloading in C++
    • The Decision between Member and Non-member
    • Common operators to overload
      • Assignment Operator
      • Input and Output Operators
      • Function call operator
      • Comparison operators
      • Arithmetic Operators
      • Array Subscripting
      • Operators for Pointer-like Types
      • Conversion Operators

      (注意:这是 Stack Overflow的C ++常见问题解答的条目.如果您要批评以这种形式提供FAQ的想法,然后在开始所有这些操作的元将是执行此操作的地方.在 C ++中可以监视该问题的答案.聊天室,它是FAQ创意首先出现的地方,因此,您的答案很可能会被提出该创意的人阅读.)

      (Note: This is meant to be an entry to Stack Overflow's C++ FAQ. If you want to critique the idea of providing an FAQ in this form, then the posting on meta that started all this would be the place to do that. Answers to that question are monitored in the C++ chatroom, where the FAQ idea started out in the first place, so your answer is very likely to get read by those who came up with the idea.)

      推荐答案

      要重载的常用运算符

      重载运算符中的大部分工作是样板代码.这也就不足为奇了,由于运算符仅仅是语法糖,它们的实际工作可以通过(通常被转发给)普通函数来完成.但是,重要的是您正确理解此样板代码.如果失败,则操作员的代码将无法编译,或者用户的代码将无法编译,或者用户的代码将表现出异常.

      Common operators to overload

      Most of the work in overloading operators is boiler-plate code. That is little wonder, since operators are merely syntactic sugar, their actual work could be done by (and often is forwarded to) plain functions. But it is important that you get this boiler-plate code right. If you fail, either your operator’s code won’t compile or your users’ code won’t compile or your users’ code will behave surprisingly.

      关于任务有很多要说的.但是,大多数内容已在 GMan著名的复制和交换常见问题解答中说过,因此我将在此处跳过大部分内容,仅列出完美的赋值运算符以供参考:

      There's a lot to be said about assignment. However, most of it has already been said in GMan's famous Copy-And-Swap FAQ, so I'll skip most of it here, only listing the perfect assignment operator for reference:

      X& X::operator=(X rhs)
      {
        swap(rhs);
        return *this;
      }
      

      Bitshift运算符(用于流I/O)

      尽管移位移位运算符<<>>仍用于它们从C继承的位处理功能的硬件接口中,但在大多数应用程序中,它们已成为重载的流输入和输出运算符而越来越普遍.有关作为位操作运算符的指导重载,请参见下面有关二进制算术运算符的部分.若要在将对象与iostream一起使用时实现自己的自定义格式和解析逻辑,请继续.

      Bitshift Operators (used for Stream I/O)

      The bitshift operators << and >>, although still used in hardware interfacing for the bit-manipulation functions they inherit from C, have become more prevalent as overloaded stream input and output operators in most applications. For guidance overloading as bit-manipulation operators, see the section below on Binary Arithmetic Operators. For implementing your own custom format and parsing logic when your object is used with iostreams, continue.

      在最常见的重载运算符中,流运算符是二进制中缀运算符,其语法对它们应为成员还是非成员不加限制. 由于他们更改了左参数(它们改变了流的状态),因此应根据经验法则将其实现为左操作数类型的成员.但是,它们的左操作数是标准库中的流,尽管标准库定义的大多数流输出和输入运算符的确定义为流类的成员,但是当您为自己的类型实现输出和输入操作时,无法更改标准库的流类型.因此,您需要针对自己的类型将这些运算符实现为非成员函数. 两种的规范形式是:

      The stream operators, among the most commonly overloaded operators, are binary infix operators for which the syntax specifies no restriction on whether they should be members or non-members. Since they change their left argument (they alter the stream’s state), they should, according to the rules of thumb, be implemented as members of their left operand’s type. However, their left operands are streams from the standard library, and while most of the stream output and input operators defined by the standard library are indeed defined as members of the stream classes, when you implement output and input operations for your own types, you cannot change the standard library’s stream types. That’s why you need to implement these operators for your own types as non-member functions. The canonical forms of the two are these:

      std::ostream& operator<<(std::ostream& os, const T& obj)
      {
        // write obj to stream
      
        return os;
      }
      
      std::istream& operator>>(std::istream& is, T& obj)
      {
        // read obj from stream
      
        if( /* no valid object of T found in stream */ )
          is.setstate(std::ios::failbit);
      
        return is;
      }
      

      实施operator>>时,仅当读取本身成功时才需要手动设置流的状态,但结果不是预期的.

      When implementing operator>>, manually setting the stream’s state is only necessary when the reading itself succeeded, but the result is not what would be expected.

      用于创建函数对象(也称为函子)的函数调用运算符必须定义为 成员 函数,因此它始终具有隐式this成员函数的参数.除此之外,可以重载任何数量的附加参数,包括零.

      The function call operator, used to create function objects, also known as functors, must be defined as a member function, so it always has the implicit this argument of member functions. Other than this, it can be overloaded to take any number of additional arguments, including zero.

      这是语法示例:

      class foo {
      public:
          // Overloaded call operator
          int operator()(const std::string& y) {
              // ...
          }
      };
      

      用法:

      foo f;
      int a = f("hello");
      

      在整个C ++标准库中,始终复制功能对象.因此,您自己的函数对象应该廉价复制.如果功能对象绝对需要使用复制成本高昂的数据,则最好将该数据存储在其他位置并让功能对象引用它.

      Throughout the C++ standard library, function objects are always copied. Your own function objects should therefore be cheap to copy. If a function object absolutely needs to use data which is expensive to copy, it is better to store that data elsewhere and have the function object refer to it.

      根据经验法则,二进制中缀比较运算符应实现为非成员函数 1 .一元前缀否定符!应该(根据相同的规则)实现为成员函数. (但通常不建议重载它.)

      The binary infix comparison operators should, according to the rules of thumb, be implemented as non-member functions1. The unary prefix negation ! should (according to the same rules) be implemented as a member function. (but it is usually not a good idea to overload it.)

      标准库的算法(例如std::sort())和类型(例如std::map)将始终只希望出现operator<.但是,您的类型的用户也希望所有其他运算符都存在,因此,如果定义operator<,请务必遵循运算符重载的第三条基本规则,并定义所有其他布尔比较运算符.实施它们的规范方法是:

      The standard library’s algorithms (e.g. std::sort()) and types (e.g. std::map) will always only expect operator< to be present. However, the users of your type will expect all the other operators to be present, too, so if you define operator<, be sure to follow the third fundamental rule of operator overloading and also define all the other boolean comparison operators. The canonical way to implement them is this:

      inline bool operator==(const X& lhs, const X& rhs){ /* do actual comparison */ }
      inline bool operator!=(const X& lhs, const X& rhs){return !operator==(lhs,rhs);}
      inline bool operator< (const X& lhs, const X& rhs){ /* do actual comparison */ }
      inline bool operator> (const X& lhs, const X& rhs){return  operator< (rhs,lhs);}
      inline bool operator<=(const X& lhs, const X& rhs){return !operator> (lhs,rhs);}
      inline bool operator>=(const X& lhs, const X& rhs){return !operator< (lhs,rhs);}
      

      这里要注意的重要一点是,这些运算符中只有两个运算符实际上会执行任何操作,其他运算符只是将其参数转发给这两个运算符中的任何一个以完成实际工作.

      The important thing to note here is that only two of these operators actually do anything, the others are just forwarding their arguments to either of these two to do the actual work.

      重载其余二进制布尔运算符(||&&)的语法遵循比较运算符的规则.但是,您不太可能为这些 2 找到合理的用例.

      The syntax for overloading the remaining binary boolean operators (||, &&) follows the rules of the comparison operators. However, it is very unlikely that you would find a reasonable use case for these2.

      1 与所有经验法则一样,有时也可能有理由打破这一规定.如果是这样,请不要忘记二进制比较运算符的左操作数(对于成员函数而言,该操作数将为*this)也必须也为const.因此,实现为成员函数的比较运算符必须具有以下签名:

      1 As with all rules of thumb, sometimes there might be reasons to break this one, too. If so, do not forget that the left-hand operand of the binary comparison operators, which for member functions will be *this, needs to be const, too. So a comparison operator implemented as a member function would have to have this signature:

      bool operator<(const X& rhs) const { /* do actual comparison with *this */ }
      

      (请注意最后的const.)

      2 应注意,||&&的内置版本使用快捷方式语义.尽管用户定义的语法(因为它们是方法调用的语法糖),却不使用快捷方式语义.用户将期望这些运算符具有快捷语义,并且它们的代码可能依赖于此,因此,强烈建议不要定义它们.

      2 It should be noted that the built-in version of || and && use shortcut semantics. While the user defined ones (because they are syntactic sugar for method calls) do not use shortcut semantics. User will expect these operators to have shortcut semantics, and their code may depend on it, Therefore it is highly advised NEVER to define them.

      一元递增和递减运算符采用前缀和后缀形式.为了彼此区分,postfix变体采用了额外的哑int参数.如果重载递增或递减,请确保始终同时实现前缀和后缀版本. 这是递增的规范实现,递减遵循相同的规则:

      The unary increment and decrement operators come in both prefix and postfix flavor. To tell one from the other, the postfix variants take an additional dummy int argument. If you overload increment or decrement, be sure to always implement both prefix and postfix versions. Here is the canonical implementation of increment, decrement follows the same rules:

      class X {
        X& operator++()
        {
          // do actual increment
          return *this;
        }
        X operator++(int)
        {
          X tmp(*this);
          operator++();
          return tmp;
        }
      };
      

      请注意,后缀变体是根据前缀实现的.另请注意,后缀会额外复制. 2

      Note that the postfix variant is implemented in terms of prefix. Also note that postfix does an extra copy.2

      重载一元减号和加号不是很常见,最好避免.如果需要,它们可能应该作为成员函数重载.

      Overloading unary minus and plus is not very common and probably best avoided. If needed, they should probably be overloaded as member functions.

      2 还请注意,postfix变体的工作量更大,因此使用效率比前缀变体低.这是一个很好的理由,通常优先选择前缀增量而不是后缀增量.尽管编译器通常可以优化内置类型的后缀增量的其他工作,但对于用户定义的类型,它们可能无法做到相同(这可能看起来像列表迭代器一样无辜).一旦习惯了i++,当i不是内置类型(加上更改类型时必须更改代码)时,就很难记住要执行++i了.最好养成始终使用前缀增量的习惯,除非明确需要后缀.

      2 Also note that the postfix variant does more work and is therefore less efficient to use than the prefix variant. This is a good reason to generally prefer prefix increment over postfix increment. While compilers can usually optimize away the additional work of postfix increment for built-in types, they might not be able to do the same for user-defined types (which could be something as innocently looking as a list iterator). Once you got used to do i++, it becomes very hard to remember to do ++i instead when i is not of a built-in type (plus you'd have to change code when changing a type), so it is better to make a habit of always using prefix increment, unless postfix is explicitly needed.

      对于二进制算术运算符,请不要忘记服从第三个基本规则运算符重载:如果提供了+,还提供了+=,如果提供了-,请不要省略-=,依此类推.据说安德鲁·科尼希(Andrew Koenig)是第一个观察到化合物赋值运算符可用作其非化合物对应物的基础的人.也就是说,运算符+是用+=来实现的,-是用-=等来实现的.

      For the binary arithmetic operators, do not forget to obey the third basic rule operator overloading: If you provide +, also provide +=, if you provide -, do not omit -=, etc. Andrew Koenig is said to have been the first to observe that the compound assignment operators can be used as a base for their non-compound counterparts. That is, operator + is implemented in terms of +=, - is implemented in terms of -= etc.

      根据我们的经验法则,+及其同伴应为非成员,而其复合赋值对应对象(+=等)(更改其左参数)应为成员.这是+=+的示例代码;其他二进制算术运算符应以相同的方式实现:

      According to our rules of thumb, + and its companions should be non-members, while their compound assignment counterparts (+= etc.), changing their left argument, should be a member. Here is the exemplary code for += and +; the other binary arithmetic operators should be implemented in the same way:

      class X {
        X& operator+=(const X& rhs)
        {
          // actual addition of rhs to *this
          return *this;
        }
      };
      inline X operator+(X lhs, const X& rhs)
      {
        lhs += rhs;
        return lhs;
      }
      

      operator+=返回每个引用的结果,而operator+返回其结果的副本.当然,返回引用通常比返回副本更有效,但是在operator+的情况下,无法进行复制.编写a + b时,您期望结果是一个新值,这就是operator+必须返回新值的原因. 3 另请注意,operator+通过复制 而不是通过常量引用获取其左操作数 .这样做的原因与给出operator=每份参数的原因相同.

      operator+= returns its result per reference, while operator+ returns a copy of its result. Of course, returning a reference is usually more efficient than returning a copy, but in the case of operator+, there is no way around the copying. When you write a + b, you expect the result to be a new value, which is why operator+ has to return a new value.3 Also note that operator+ takes its left operand by copy rather than by const reference. The reason for this is the same as the reason giving for operator= taking its argument per copy.

      位操作运算符~ & | ^ << >>应该以与算术运算符相同的方式实现.但是,(除了重载<<>>用于输出和输入),很少有合理的用例来重载这些.

      The bit manipulation operators ~ & | ^ << >> should be implemented in the same way as the arithmetic operators. However, (except for overloading << and >> for output and input) there are very few reasonable use cases for overloading these.

      3 同样,从中得到的教训是,a += b通常比a + b更有效,并且在可能的情况下应优先使用.

      3 Again, the lesson to be taken from this is that a += b is, in general, more efficient than a + b and should be preferred if possible.

      数组下标运算符是二进制运算符,必​​须将其实现为类成员.它用于类似容器的类型,这些类型允许通过键访问其数据元素. 提供这些的规范形式是这样的:

      The array subscript operator is a binary operator which must be implemented as a class member. It is used for container-like types that allow access to their data elements by a key. The canonical form of providing these is this:

      class X {
              value_type& operator[](index_type idx);
        const value_type& operator[](index_type idx) const;
        // ...
      };
      

      除非您不希望类的用户能够更改operator[]返回的数据元素(在这种情况下,您可以省略non-const变体),否则应始终提供运算符的两个变体.

      Unless you do not want users of your class to be able to change data elements returned by operator[] (in which case you can omit the non-const variant), you should always provide both variants of the operator.

      如果已知value_type引用内置类型,则运算符的const变体最好返回一个副本,而不是const引用:

      If value_type is known to refer to a built-in type, the const variant of the operator should better return a copy instead of a const reference:

      class X {
        value_type& operator[](index_type idx);
        value_type  operator[](index_type idx) const;
        // ...
      };
      

      类似指针类型的运算符

      要定义自己的迭代器或智能指针,您必须重载一元前缀解除引用运算符*和二进制中缀指针成员访问运算符->:

      Operators for Pointer-like Types

      For defining your own iterators or smart pointers, you have to overload the unary prefix dereference operator * and the binary infix pointer member access operator ->:

      class my_ptr {
              value_type& operator*();
        const value_type& operator*() const;
              value_type* operator->();
        const value_type* operator->() const;
      };
      

      请注意,这些几乎也总是需要const版本和非const版本. 对于->运算符,如果value_typeclass(或structunion)类型,则将递归调用另一个operator->(),直到operator->()返回非类类型的值.

      Note that these, too, will almost always need both a const and a non-const version. For the -> operator, if value_type is of class (or struct or union) type, another operator->() is called recursively, until an operator->() returns a value of non-class type.

      一元地址运算符绝对不能重载.

      The unary address-of operator should never be overloaded.

      对于operator->*(),请参见此问题.它很少使用,因此也很少过载.实际上,即使迭代器也不会使它过载.

      For operator->*() see this question. It's rarely used and thus rarely ever overloaded. In fact, even iterators do not overload it.

      继续转化运算符

      这篇关于运算符重载的基本规则和惯用法是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆