Search

13.4 — Overloading the I/O operators

For classes that have multiple member variables, printing each of the individual variables on the screen can get tiresome fast. For example, consider the following class:

If you wanted to print an instance of this class to the screen, you’d have to do something like this:

Of course, it makes more sense to do this as a reusable function. And in previous examples, you’ve seen us create print() functions that work like this:

While this is much better, it still has some downsides. Because print() returns void, it can’t be called in the middle of an output statement. Instead, you have to do this:

It would be much easier if you could simply type:

and get the same result. There would be no breaking up output across multiple statements, and no having to remember what you named the print function.

Fortunately, by overloading the << operator, you can!

Overloading operator<<

Overloading operator<< is similar to overloading operator+ (they are both binary operators), except that the parameter types are different.

Consider the expression std::cout << point. If the operator is <<, what are the operands? The left operand is the std::cout object, and the right operand is your Point class object. std::cout is actually an object of type std::ostream. Therefore, our overloaded function will look like this:

Implementation of operator<< for our Point class is fairly straightforward -- because C++ already knows how to output doubles using operator<<, and our members are all doubles, we can simply use operator<< to output the member variables of our Point. Here is the above Point class with the overloaded operator<<.

This is pretty straightforward -- note how similar our output line is to the line in the print() function we wrote previously. The most notable difference is that std::cout has become parameter out (which will be a reference to std::cout when the function is called).

The trickiest part here is the return type. With the arithmetic operators, we calculated and returned a single answer by value (because we were creating and returning a new result). However, if you try to return std::ostream by value, you’ll get a compiler error. This happens because std::ostream specifically disallows being copied.

In this case, we return the left hand parameter as a reference. This not only prevents a copy of std::ostream from being made, it also allows us to “chain” output commands together, such as std::cout << point << std::endl;

You might have initially thought that since operator<< doesn’t return a value to the caller, we should define the function as returning void. But consider what would happen if our operator<< returned void. When the compiler evaluates std::cout << point << std::endl;, due to the precedence/associativity rules, it evaluates this expression as (std::cout << point) << std::endl;. std::cout << point would call our void-returning overloaded operator<< function, which returns void. Then the partially evaluated expression becomes: void << std::endl;, which makes no sense!

By returning the out parameter as the return type instead, (std::cout << point) returns std::cout. Then our partially evaluated expression becomes: std::cout << std::endl;, which then gets evaluated itself!

Any time we want our overloaded binary operators to be chainable in such a manner, the left operand should be returned (by reference). Returning the left-hand parameter by reference is okay in this case -- since the left-hand parameter was passed in by the calling function, it must still exist when the called function returns. Therefore, we don’t have to worry about referencing something that will go out of scope and get destroyed when the operator returns.

Just to prove it works, consider the following example, which uses the Point class with the overloaded operator<< we wrote above:

This produces the following result:

Point(2, 3.5, 4) Point(6, 7.5, 8)

Overloading operator>>

It is also possible to overload the input operator. This is done in a manner analogous to overloading the output operator. The key thing you need to know is that std::cin is an object of type std::istream. Here’s our Point class with an overloaded operator>>:

Here’s a sample program using both the overloaded operator<< and operator>>:

Assuming the user enters 3.0 4.5 7.26 as input, the program produces the following result:

You entered: Point(3, 4.5, 7.26)

Conclusion

Overloading operator<< and operator>> make it extremely easy to output your class to screen and accept user input from the console.

Quiz time

Take the Fraction class we wrote in the previous quiz (listed below) and add an overloaded operator<< and operator>> to it.

The following program should compile:

And produce the result:

Enter fraction 1: 2/3
Enter fraction 2: 3/8
2/3 * 3/8 is 1/4

Here’s the Fraction class:

Show Solution


13.5 -- Overloading operators using member functions
Index
13.3 -- Overloading operators using normal functions

294 comments to 13.4 — Overloading the I/O operators

  • Waldo Lemmer

    - `type &identifier` except Q#1
    - Section "Overloading operator<<", code block 2, lines 29 and 30 use direct initialization
    - You don't normally print '\n' before getting input, but you do in main() at section "Overloading operator>>".

    How do I know whether to use () or {} to initialize class objects?

    • Alex

      Use {}, except for the cases where you need to use (). For classes with a constructor that takes a std::initializer_list parameter, a single element inside {} will favor the std::initializer_list constructor over other constructors.

      Therefore, for containers that can be initialized with a list of elements, we typically use {} initialization when we want to initialize the class with a list of elements, and () when we want to call a non-std::initializer_list constructor.

      Most often you see this with classes like std::vector:

  • Manuel

    I get error: ‘std::string Digi::ZigbeeDevice::m_IEEE_Addr’ is private within this context and I have:

    ZigbeeDevice.h
    ```
    namespace Digi
    {

    class ZigbeeDevice
    {
    public:
        ZigbeeDevice() = delete;
        ZigbeeDevice(std::string ieee_addr);

        ~ZigbeeDevice();

        friend std::ostream& operator<<(std::ostream &out, const ZigbeeDevice &zigbeeDevice);

    private:
        std::string m_IEEE_Addr;
        std::string m_ParentGatewayId;
        int         m_NetAddr;
        int         m_NodeType;
        int         m_ParentAddr;
        int         m_ProfileId;
        int         m_DeviceType;
        bool        m_Status;
        std::string m_UpdateTime;
    };

    } /* namespace Digi */
    ```

    And

    ZigbeeDevice.cpp

    ```
    Digi::ZigbeeDevice::ZigbeeDevice(std::string ieee_addr) :
    m_IEEE_Addr{ieee_addr}, m_ParentGatewayId{""}, m_NetAddr{0}, m_NodeType{0},
    m_ParentAddr{0}, m_ProfileId{0}, m_DeviceType{0}, m_Status{0}, m_UpdateTime{""}
    {

    }

    Digi::ZigbeeDevice::~ZigbeeDevice()
    {
        // TODO Auto-generated destructor stub
    }

    std::ostream& operator<<(std::ostream &out, const Digi::ZigbeeDevice &zigbeeDevice)
    {
        out << "xpExtAddr:" << zigbeeDevice.m_IEEE_Addr << std::endl;

        return out;
    }

    ```

    Why is this?

  • happyLOL

    Hi Alex,

    I've read a chapter in which you went in-depth and explained how the operator<< works for multiple expression chaining
    e.g:

    whereby

    is returned after

    Do you remember the chapter number?

    EDIT:
    God, I'm so sorry for spamming the comment section, I've found it after I used the google search, for anyone keen to read the in-depth chaining writeup by Alex, here is the link:

    https://www.learncpp.com/cpp-tutorial/the-hidden-this-pointer/

    under the section:
    "Chaining member functions"

    P.S. Alex, can you add the chaining explanation into your site index please? It helped newbies like me

  • J34NP3T3R

    Question

    if i do it like this

    am i still returning ostream as reference or am i returning a copy of anonymous ?

  • Question 1

  • yeokaiwei

    1. Quiz
    This quiz was interesting.

    Your code can work very early on but it just won't match the output that was set out in the requirements ie the '/'

    It looks so easy until you look at the solution and go "OH!".

    The "input.ignore(std::numeric_limits<std::streamsize>::max(), '/');" is really good because it's just a small thing but the solution is quite complicated.

    I hope this is acceptable.

Leave a Comment

Put all code inside code tags: [code]your code here[/code]