The World of Mayukh Bose

<< Back to Main Page Mayukh's world: e-mail me | about
Mayukh's World: Operator Overloading With C++ Thursday, November 23, 2017
Index
  • Introduction
  • What Operators?
  • Rules
  • Assignment Operator
  • More on Assignment
  • Arithmetic Operators
  • Arithmetic with Globals
  • Increment/Decrement
  • Operator-Assignment
  • Unary Operators
  • Relational Operators
  • Bitshift/Extraction
  • Subscript Operator
  • Function Call Operator
  • Bit and Logical Ops
  • Comma Operator
  • Pointer to Member
  • new and delete Ops
  • Credits and Thanks
  • My Free Software
  • Delphi/C++ Builder
  • Pocket PC
  • FreeSpeech Chat
  • C/C++ Freebies
  • Perl
  • Python
  • Ruby
  • C++ Operator Overloading Tutorial e-mail me

    Bitshift/Extraction Operators

    The C++ Bitshift operators are << and >>. When used on integral types (int, long, char, short), these operators serve to shift a variable's bits left or right by a required number of places.
        int x = 10;
        int y;
        y = x << 2; // Left shift x's bits by 2 places
        y = x >> 3; // Right shift x's bits by 3 places. 
        
    This is identical to how C treats these two operators. However, the C++ standard also overloads these two operators for stream objects. Thus, you can also do:
        int x;
        cin >> x; // Read the value from the standard input device
        cout << x; // Write the value to the standard output device
        
    This is why these operators are also referred to as Extraction operators in C++. Most of the time, when people overload these operators, they overload them to work with stream objects rather than as bitshift operators. We will also do this. So far, we have been outputting our Complex objects like this:
        cout << num1.GetReal() << " + " << num1.GetImag + << "i" << endl;
        
    because GetReal() and GetImag() return double values. There is a better C++ way though. We can overload << and >> so that they can handle stream objects by themselves. Then, we don't have to worry about whether we are writing/reading them to standard I/O devices, files, memory streams, socket streams or whatever. Once we've overloaded these two operators with stream objects, any streaming object can use our class.

    In this case, the operator overloads must be declared as global functions. They cannot be declared as class members, since the stream object occurs before the class object. First we deal with the output stream object.
          std::ostream& operator<<(std::ostream& os, const Complex& num) {
            os << num.GetReal();
    	if (num.GetImag() > 0)
    	  os << "+";
    	os << num.GetImag();
    	os << "i";
    	return os;
          }
        
    The function is declared to take a const reference to Complex, to assure the compiler that we are NOT going to tamper with the Complex object. We first output the real part of the number. Then we check whether the imaginary part is greater than 0 or not. If it is greater than 0, we output the '+' sign. Then we output the imaginary part. If the imaginary part is less than 0, the negative sign would have automatically been output first. Then, we output the character 'i' to the stream after the imaginary part. Finally we return the stream object. We have to return the stream object, since this allows us to chain multiple objects to be output on a single line (e.g.: cout << "value is " << num1 << endl;).

    Note that our example overloaded function is a bit crude because we didn't do any error checking to see if our stream object successfully processed our data or not. We're leaving it out to keep this example simple and easy to understand. Production code should always check os.good() or os.fail() to make sure that everything is kosher. The above overloaded function now allows us to do things like this:
          Complex num1(5, 3);
          // Write to standard output
          cout << "The value of num1 is: " << num1 << endl;
    
          // Write to a file
          ofstream file("complex.txt")
          file << "The value of num1 is: " << num1 << endl;
        
    As you can see from the above, overloading the ostream operator once makes our code reusable by any output stream object. In the above example, we wrote our object to the standard output and to a file. We could also write to a memory stream, a socket stream, a string stream, a serial port stream etc. without changing a single line of code in our overloaded function.

    Now we tackle overloading the >> operator, to read a Complex class variable from an input stream:
          std::istream& operator>>(std::istream& is, Complex& num){
            double real, imag;
    	char dummy;
    
    	is >> real;
    	is >> imag;
    	is >> dummy; // To read the 'i' from the istream
    	num.SetReal(real);
    	num.SetImag(imag);
    	return is;
          }
        
    Unlike the ostream overload, the istream overload takes a Complex& argument instead of a const Complex& argument. This is because it is going to alter the argument's value. The code simply reads two double values and a char value. Then, it assigns the two double values to the Complex argument variable and throws away the char value. The reason we are doing so is because the Complex number may be something like "12+34i". Now the first value read in will assign "12" to real. The next value read in will assign "+34" to imag. There is still the 'i' character left behind in the stream which we need to get rid of. Thus we read a char value which assigns 'i' to dummy. Finally we return the istream object reference, since this allows us to chain multiple objects to be read in a single line (e.g. cin >> num1 >> num2 >> num3).

    As before, our example code is a bit crude since it doesn't do any error checking to make sure that the stream object managed to read the data in successfully. Production code should check is.good() or is.fail() and handle the error accordingly. The new overloaded function allows us to write code like this:
          Complex num1, num2;
          // Read from standard input
          cin >> num1 >> num2;
          // Read from a file.
          ifstream file("complex.txt")
          file >> num1 >> num2; 
        
    As before, overloading the istream operator allows our code to be reused by any input stream operator. Thus, we can read our Complex objects from standard input, file, memory stream, socket stream, serial port stream etc., without altering our overloaded code.

    These two overloads demonstrate not only how to overload the Bitshift/Extraction operators, but also the awesome capabilities of C++ streams. By merely overloading the two operators for ostream and istream classes, we make our objects available to a variety of stream objects.

    Exercises

    1. Add error checking to our overloaded operators and throw exceptions if necessary.
    2. In our overloaded istream function, we're not checking if the value of dummy is 'i' or not. Does it make any difference what this value is? Justify your answer with an example.


    <<Previous: Relational Operators ^Up to Mayukh's World^ Next: Subscript Operator >>

    Copyright © 2004 Mayukh Bose. All rights reserved.
    This work may be freely reproduced provided this copyright notice is preserved.
    OPTIONAL: Please consider linking to this website (http://www.mayukhbose.com/) as well.