Google
 
Webcprogramming.com




An Affiliate of AIHorizon

Operator Overloading

by Andrei Milea

In C++ the overloading principle applies not only to functions, but to operators too. That is, the meaning of operators can be extended from built-in types to user-defined types. In this way a programmer can provide his or her own operator to a class by overloading the built-in operator to perform some specific computation when the operator is used with objects of that class. One question may arise here: is this really useful in real world implementations? Some programmers consider that overloading is not useful most of the time. This and the fact that overloading makes the language more complicated is the main reason why operator overloading is banned in Java.





Even if overloading adds complexity to the language it can provide a lot of syntactic sugar, and code written by a programmer using operator overloading can be easy, but sometimes misleading, to read. We can use operator overloading easily without knowing all the implementation's complexities. A short example will make things clear:
Complex a(1.2,1.3);     //this class is used to represent complex numbers
Comblex b(2.1,3);       //notice the construction taking 2 parameters for the real and imaginary part
Complex c = a+b;        //for this to work the addition operator must be overloaded
The addition without having overloaded operator + could look like this:
a.Add(b);
Complex c(a);
This piece of code is not as suggestive as the first one and the readability becomes poor. Using operator overloading is a design decision, so when we deal with concepts where some operator seems fit and its use intuitive, it will make the code more clear than using a function to do the task. However, there are many cases when programmers abuse this technique, when the concept represented by the class is not related to the operator (like using + and - to add and remove elements from a data structure). In this cases operator overloading is a bad idea, creating confusion.

In order to be able to write the above code we must have the "+" operator overloaded to make the proper addition between the real members and the imaginary ones and also the assignment operator. The overloading syntax is quite simple, similar to function overloading, the keyword operator followed by the operator we want to overload as you can see in the next code sample:
class Complex
{
public:
        Complex(double re,double im)
                :real(re),imag(im)
                {};
        Complex operator+(Complex);
        Complex operator=(Complex);
private:
        double real;
        double imag;
}
Complex Complex::operator+(Complex  num)
{
        real = real + num.GetRealPart();
        imag = imag + num.GetImagPart();        
        return *this;
}
The assignment operator can be overloaded similarly. Notice that we had to call the accessor function in order to get the real and imaginary parts from the parameter since they are private. In order to bypass this difficulty we could have made the operator + a friend (a friend function is a function which is permitted to access the private members of a class) in the complex class:
friend Complex operator+(Complex); 
We could have defined the addition operator globally and called a member to do the actual work:
Complex operator+(Complex  &num1,Complex &num2)
{
        Complex temp(num1);             //note the use of a copy constructor here
        temp.Add(num2);
        return temp;
}
The motivation for doing so can be understood by examining the difference between the two choices: when the operator is a member the first object in the expression must be of that particular type, when it's a global function, the implicit or user-defined conversion can allow the operator to act even if the first operand is not exactly of the same type:
Complex c = 2+b;        //if the integer 2  can be converted by the Complex class, this expression is valid
The number of operands can't be overridden, that is, a binary operator takes two operands, a unary only one. The same restriction acts for the precedence too, for example the multiplication takes place before addition. There are some operators that need the first operand to be left value: operator=, operator(), operator[] and operator->, so their use is restricted just as member functions(non-static), they can't be overloaded globally. The operator=, operator& and operator, (sequencing) have already defined meanings by default for all objects, but their meanings can be changed by overloading or erased by making them private.

Another intuitive meaning of the "+" operator from the STL string class which is overloaded to do concatenation:
string prefix("de");
string word("composed");
string composed = prefix+word;
Using "+" to concatenate is also allowed in Java, but note that this is not extensible to other classes, and it's not a user defined behavior. Almost all operators can be overloaded in C++:
        
        +       -       *       /       %       ^       &       |
        ~       !       ,       =       <       >       <=      >=
        ++      --      <<      >>      ==      !=      &&      ||
        +=      -=      /=      %=      ^=      & =     |=      *=
        <<=     >>=     [ ]     ( )     ->      ->*     new     delete
exceptions are the operators for scope resolution (::), member selection (.), and member selection through a pointer to a function(.*). Overloading assumes you specify a behavior for an operator that acts on a user defined type and it can't be used just with general pointers. The standard behavior of operators for built-in (primitive) types cannot be changed by overloading, that is, you can't overload operator+(int,int).

The logic(boolean) operators have by the default a short-circuiting way of acting in expressions with multiple boolean operations. This means that the expression:
if(a && b && c) 
will not evaluate all three operations and will stop after a false one is found. This behavior does not apply to operators that are overloaded by the programmer.

Even the simplest C++ application, like a "hello world" program, is using overloaded operators. This is due to the use of this technique almost everywhere in the standard library (STL). Actually the most basic operations in C++ are done with overloaded operators, the IO(input/output) operators are overloaded versions of shift operators(<<, >>). Their use comes naturally to many beginning programmers, but their implementation is not straightforward. However a general format for overloading the input/output operators must be known by any C++ developer. We will apply this general form to manage the input/output for our Complex class:
friend ostream &operator<<(ostream &out, Complex c)     //output
{
        out<<"real part: "<>(istream &in, Complex &c)     //input
{
        cout<<"enter real part:\n";
        in>>c.real;
        cout<<"enter imag part: \n";
        in>>c.imag;
        return in;
}
Notice the use of the friend keyword in order to access the private members in the above implementations. The main distinction between them is that the operator>> may encounter unexpected errors for incorrect input, which will make it fail sometimes because we haven't handled the errors correctly. A important trick that can be seen in this general way of overloading IO is the returning reference for istream/ostream which is needed in order to use them in a recursive manner:
Complex a(2,3);
Complex b(5.3,6);
cout<

Related article

Overloading Operator New and Delete



-----
Subscribe to cprogramming.com by Email
Interested in advertising with us?
Please read our privacy policy.
Copyright © 1997-2009 Cprogramming.com. All rights reserved.