C++ Operator: How to deal with operator overloading?
CodeGuru Home VC++ / MFC / C++ .NET / C# Visual Basic VB Forums Developer.com
Results 1 to 1 of 1

Thread: C++ Operator: How to deal with operator overloading?

Threaded View

  1. #1
    Join Date
    Feb 2005
    Location
    "The Capital"
    Posts
    5,306

    C++ Operator: How to deal with operator overloading?

    Q: How to deal with operator overloading?

    A: Operator overloading looks like a high hill for starters but the key point in their understanding is not considering operator overloading anything other/different from a simple function. This helps a lot especially for beginners!

    Consider them normal functions with pre-defined prototypes (for syntactical reasons/benefits while putting them to use so that they just look the way they are used for the fundamental types).

    The arithmetic operators work on two operands, for example +, -, /, *, etc. Since there are two things that they work on the function needs to know what those two things(operands) are. The operands are passed to them as arguments to the function. For member functions we show only one argument because of the implicit "this" argument. So, member overloads work on "this" and the second argument commonly named as "rhs" from right-hand-side (of course, you are free to name it differently). As for non-member overloads, there is no "this" involved, hence you give two arguments. The operators would work for your types as the same way they work with the pre-defined types (like say any integral type). They will work for the same way for your class' objects as well. That's it!

    (Note that there are also operators that work on just one operand ++, --. Please refer to this FAQ - C++ Operator: How to overload postfix increment and decrement operators?)

    That's all there is in operator overloading 101 - the basics. Operator overloading is not something more than mere functions.


    Q: How to better overload arithmetic operators?

    A: It is recommended to write operator 'op' in terms of their operator 'op='. That is, operator+ in terms of operator+=, operator- in terms of operator-= and similarly operator* and operator/. There are basically two advantages of doing it that way:
    1. You would need to only maintain 'op=' version for your classes.
    2. There could be a gain in performance owing to Return Value Optimization or RVO (see references).

    Take a look at the following sample:
    Code:
    // just need to maintain this function... 
    template<typename T> 
    T& operator+=(const T& rhs)
    { 
      // do stuff.. 
      return *this; 
    } 
    
    // no maintenance required... once += is maintained well 
    template<typename T> 
    const T operator+(const T& lhs, const T& rhs)
    { 
      return T(lhs)+=rhs; //RVO 
    } 
    
    // first argument by value 
    template<typename T> 
    const T operator+(T lhs, const T& rhs)
    { 
      // copy constructor called as well 
      return lhs+=rhs; //no RVO as the return object is named 
    }
    There is no problem with having overloads independently but why ignore the above mentioned advantages? 'operator+=' and 'operator+' can be completely independently defined as well. But don't you think that is a kind of rework/duplicacy (writing same logic twice) when there is an alternative way that may give you an extra bit of performance and also keep your worries away while maintaining the code that you only need to modify 'operator+=' when you need to and leave operator+ untouched?

    Another point to note above are two versions of 'operator+'. You only need 1 of those. Even if 'operator+' is or is not defined in terms of 'operator+=' what happens is the object gets "named" and hence all possibilities of "un-named RVO" are lost. There could be gains on certain compilers due to "named RVO". In that case (named RVO), both would be equivalent. And the conclusion that we can derive is that however there (while comparing first argument passed as 'T' or 'const T&') may be no performance boost on such compilers (with both named and unnamed RVO) still you get the advantage related to code maintainance. Doing it this way also makes it unnecessary to make the overloads 'op' as friends.


    Q: Why do we need operator overloads as friends?

    A: There are basically 2 options - member overloads or non-member overloads. Non-static member functions have this implicit "this" pointer as an argument. Refer to this FAQ - C++ General: What is the 'this' pointer?. This makes functions that require the first argument to be something different that the same object's type to be written as members. For example, consider 'operator<<'. It is usually overloaded for 'ostream' operations. The prototype is similar to this:
    Code:
    template <typename T>
    std::ostream& operator<<(std::ostream& ostr, const Vector<T>& vect);
    It could also be written the following way because there is no "special" restriction about keeping 'std::ostream&' as the first operand/argument:
    Code:
    template<typename T>
    std::ostream& operator<<(const Vector<T>& vect, std::ostream& ostr);
    But now let us see how that affects us. For the first case, you can use the operator the following way:
    Code:
    std::cout << Vector<int>(); //considering class template Vector<T> has a default constructor
    For the second definition of 'operator<<', you would not be able to use the above syntax. The primary objective of operator overloading is to keep the semantics as close to their counter-parts for fundamental types as possible. For the second version, we would need to call the operator as follows:
    Code:
    operator<<(Vector<int>(), std::cout);
    which is not we want. Otherwise, what's the use of operator overloading when we can even write a function 'void DisplayVectorContents()' to do that?

    Hence, 'operator&lt< cannot be a member - it has to be a non-member for us to be able to specify the first argument as 'std::ostream'. Take a look at the following working sample for illustration:
    Code:
    #include<string>
    #include<complex>
    #include<iostream>
    
    // forward declaration
    template <typename T>
    class Vector;
    
    template <typename T> std::ostream& operator<<(std::ostream&, const Vector<T>&);
    
    template <typename T>
    class Vector
    {
    private:
      std::complex<T> mycomplex;
    
    public:
      Vector() {}
      Vector(T i_component, T j_component)  : mycomplex(i_component, j_component) {}
    
      // member operators
      Vector& operator+=(const Vector& rhs)
      {
        mycomplex+=rhs.mycomplex;
        return *this;
      }
    
      Vector& operator-=(const Vector& rhs)
      {
        mycomplex-=rhs.mycomplex;
        return *this;
      }
    
      Vector& operator*=(const Vector& rhs)
      {
        mycomplex*=rhs.mycomplex;
        return *this;
      }
    
      Vector& operator/=(const Vector& rhs)
      {
        mycomplex/=rhs.mycomplex;
        return *this;
      }
    
      friend std::ostream& operator<< <T>(std::ostream&, const Vector<T>&);
    };
    
    template <typename T>
    const Vector<T> operator+(const Vector<T>& lhs, const Vector<T>& rhs)
    {
      return Vector<T>(lhs)+=rhs;
    }
    
    template <typename T>
    const Vector<T> operator-(const Vector<T>& lhs, const Vector<T>& rhs)
    {
      return Vector<T>(lhs)-=rhs;
    }
    
    template <typename T>
    const Vector<T> operator*(const Vector<T>& lhs, const Vector<T>& rhs)
    {
      return Vector<T>(lhs)*=rhs;
    }
    
    template <typename T>
    const Vector<T> operator/(const Vector<T>& lhs, const Vector<T>& rhs)
    {
      return Vector<T>(lhs)/=rhs;
    }
    
    template <typename T>
    std::ostream& operator<<(std::ostream& ostr, const Vector<T>& vect)
    {
      ostr << "i component : " << vect.mycomplex.real() << "\n";
      ostr << "j component : " << vect.mycomplex.imag() << "\n";
      return ostr;
    }
    
    int main()
    {
      Vector<double> first_vector(10, 100);
      Vector<double> second_vector(10, 100);
      std::cout << first_vector;
      std::cout << second_vector;
      std::cout << "Sum  : \n" << (first_vector+second_vector);
      std::cout << "Diff : \n" << (first_vector-second_vector);
      std::cout << "Prod : \n" << (first_vector*second_vector);
      std::cout << "Div  : \n" << (first_vector/second_vector);
      return 0;
    }
    You can remove the templatization of the code if it bothers you and write 'class Vector' for a type where 'T' is double.


    References:
    1. Return value optimization at comp.lang.c++.moderated
    2. Named return value optimization at msdn.microsoft.com
    3. Operator overloading



    Last edited by cilu; April 16th, 2007 at 02:48 AM.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  


Windows Mobile Development Center


Click Here to Expand Forum to Full Width

This is a CodeGuru survey question.


Featured


HTML5 Development Center