操作符重载 [英] Operator overloading

查看:167
本文介绍了操作符重载的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

C ++中的操作符重载的基本规则和惯用语是什么?



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




  • 重载新的和删除




  • (注意:这是一个入门 Stack Overflow的C ++常见问题。如果您想批评在此表单中提供常见问题解答的想法,请在meta上发布所有这一切将是做到这一点的地方。在 C ++聊天室中监控对该问题的答案,其中首先开始了常见问题解答,因此,您的答案很可能会由提出此想法的人阅读。)

    解决方案

    要重载的常见运算符



    重载运算符的大部分工作都是样板代码。这不奇怪,因为运算符只是语法糖,他们的实际工作可以通过(并且经常转发)普通函数。但重要的是你得到这个锅炉板代码正确。



    分配操作员

    如果失败,您的操作员代码将无法编译或用户的代码无法编译或用户的代码会出现令人惊讶的行为。 >

    有很多关于作业的话。但是,大部分内容已在 GMan着名的复制和交换常见问题解答 ,因此我将跳过这里的大部分内容,只列出完美的赋值运算符以供参考:

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



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



    位移操作符< >> 在硬件接口中用于它们从C继承的位操作功能,在大多数应用中变得更加普遍作为重载流输入和输出操作符。对于作为位操作运算符的引导过载,请参见下面有关二进制算术运算符的部分。要在使用iostreams时实现自己的自定义格式和解析逻辑,请继续。



    流运算符是最常见的重载运算符之一,其中语法不指定它们是否应该是成员或非成员的限制。
    因为他们改变他们的左参数(他们改变流的状态),他们应该根据经验,被实现为其左操作数类型的成员。然而,它们的左操作数是来自标准库的流,虽然由标准库定义的大多数流输出和输入操作符确实被定义为流类的成员,但是当为您自己的类型实现输出和输入操作时,无法更改标准库的流类型。这就是为什么你需要为自己的类型实现这些运算符作为非成员函数。
    这两个的规范形式是:

      std :: ostream& <<<<<<<<<<<<<(std :: ostream& os,const T& obj)
    {
    //将obj写入流

    return os;
    }

    std :: istream& < />>(std :: istream& is,T& obj)
    {
    //从流读取obj

    if stream * /)
    is.setstate(std :: ios :: failbit);

    return is;
    }

    执行 operator>>> ,手动设置流的状态只有当读取本身成功时才是必要的,但结果不是预期的结果。



    函数调用操作符



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



    在整个C ++标准库中,函数对象总是被复制。因此,您自己的函数对象应该便宜地复制。如果函数对象绝对需要使用复制代价高昂的数据,最好将数据存储在别处,并让函数对象引用它。



    比较运算符



    二进制中缀比较运算符应根据经验规则实现为非成员函数 1 。一元前缀否定应该(根据相同的规则)被实现为成员函数。 (但通常不是重载它的好主意。)



    标准库的算法(例如 std :: sort())和类型(例如 std :: map )将总是只期望存在运算符< 。但是,您的类型的用户也希望所有其他运算符都存在,因此,如果您定义运算符< ,请务必关注运算符重载的第三个基本规则,并且还定义所有其他布尔比较运算符。实现它们的规范方法是:

      inline bool operator ==(const X& lhs,const X& rhs){ / *做实际的比较* /} 
    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);}

    这里需要注意的是,



    重载其余二进制布尔运算符的语法( || &&& )遵循比较运算符的规则。不过,这些 2 不太可能会找到合理的使用案例。



    1 与所有的经验法则一样,有时也可能有理由打破这一个。如果是这样,不要忘记二进制比较运算符的左边操作数,对于成员函数 * this ,需要 const 。因此,作为成员函数实现的比较运算符必须具有这个签名:

      bool operator< ; rhs)const {/ *与* this * /}做实际比较

    const 在结尾。)



    2 应当注意, || && 的内置版本使用快捷语义。当用户定义了一个(因为它们是方法调用的语法糖)不使用快捷语义。用户期望这些操作符具有快捷语义,并且它们的代码可以依赖于它。因此,强烈建议绝不要定义它们。

    算术运算符



    一元运算符



    一元递增和递减运算符同时包含前缀和后缀。要告诉另一个,后缀变体接受一个额外的int int参数。如果您重载递增或递减,请务必始终实施前缀和后缀版本。
    这里是增量的规范实现,减量遵循相同的规则:

      class X {
    X& ; operator ++()
    {
    //实际增加
    return * this;
    }
    X运算符++(int)
    {
    X tmp(* this);
    operator ++();
    return tmp;
    }
    };

    请注意,后缀变体是根据前缀实现的。还要注意,postfix有一个额外的副本。 2



    重载一元减号和加号不是很常见,如果需要,它们应该作为成员函数重载。



    2 还要注意,后缀变体执行更多工作,因此使用比前缀变体效率低。这是一个很好的理由,通常喜欢前缀增量超过后缀增量。虽然编译器通常可以优化掉内置类型的后缀增量的额外工作,但是它们可能无法对用户定义的类型执行相同的操作(这可能是无关紧要的列表迭代器)。一旦你习惯了做 i ++ ,它变得很难记住 ++ i 而不是 i 不是内置类型(加上你必须在更改类型时更改代码),所以最好是使用前缀增量的习惯,除非后缀是明确需要的。



    二进制算术运算符



    对于二进制算术运算符,不要忘记如果你提供 + ,也提供 + = ,如果你提供 - ,不要省略 - = 等。Andrew Koenig据说是第一个观察到复合赋值运算符可以用作它们的非化合物对应物的碱。也就是说,运算符 + 是以 + = - 按照 - = 等实现。



    根据我们的经验法则, + 和它的同伴应该是非成员,而他们的复合赋值对等体( + = 等)应该是会员。下面是 + = + 的示例代码,其他二进制算术运算符应以相同的方式实现:

      class X {
    X&运算符+ =(const X& rhs)
    {
    //实际添加的rhs到* this
    return * this;
    }
    };
    inline X运算符+(X lhs,const X& rhs)
    {
    lhs + = rhs;
    return lhs;
    }

    其结果每个引用,而 operator + 返回其结果的副本。当然,返回一个引用通常比返回一个副本更有效,但是在 operator + 的情况下,没有办法复制。当你写 a + b 时,你希望结果是一个新的值,这就是为什么 operator + 一个新值。 3
    还要注意, operator + 使用左边的操作数 by copy / strong>而不是const引用。其原因与给出 operator = 每个副本参数的原因相同。



    操作操作符 & | ^ < >> 作为算术运算符。然而,除了对于输出和输入的重载< >>

    3 再次,从中获得的教训是 a + = b 通常比 a + b 更有效,如果可能,应该首选。



    数组下标



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

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

    除非您不希望类的用户能够更改 operator [] (在这种情况下你可以省略非const变量),你应该总是提供两种变体的运算符。



    如果value_type已知是指内置类型,则操作符的const变量应返回一个副本而不是const引用。



    类型



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

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

    注意,这些也几乎总是需要一个const和一个非const版本。
    对于 - > 运算符,如果 value_type code>(或 struct union )类型,另一个 operator-> ()被递归调用,直到 operator->()返回非类类型的值。

    $ b $



    operator-> *()

    code>请参阅此问题。它很少使用,因此很少重载。事实上,即使迭代器也不会超载它。






    继续转换运算符


    What are the basic rules and idioms for operator overloading in C++?

    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:

    (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.

    Assignment Operator

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

    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.

    Function call operator

    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.

    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.

    Comparison operators

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

    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.

    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 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 */ }
    

    (Note the const at the end.)

    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.

    Arithmetic Operators

    Unary arithmetic operators

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

    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 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.

    Binary arithmetic operators

    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+= 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 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.

    Array Subscripting

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

    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.

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

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

    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.

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


    Continue to Conversion Operators

    这篇关于操作符重载的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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