Overloading the output operator

The following few paragraphs demonstrate when and how to overload the output operator. The aim of this article is to show you that overloading the output operator can come very handy in certain situations such as output chaining or output of an object that contains another object. To demonstrate the problem, we are going to use a class Skyscraper, which can be seen below:

#ifndef SKYSCRAPER_H
#define SKYSCRAPER_H
#include <string>
#include <iostream>
#include <stdexcept>

class Skyscraper
{
public:
    Skyscraper(const std::string& name, double height);
    double getHeight() const {return m_height;}
    std::string getName() const {return m_name;}
    void print() const;

private:
    std::string m_name;
    double m_height;
};

#endif // SKYSCRAPER_H
#include "skyscraper.h"

Skyscraper::Skyscraper(const std::string &name, double height):
    m_name(name), m_height(height){
    if(m_height < 0)
        throw std::invalid_argument(
                "Height must be non-negative.");
}

void Skyscraper::print() const {
    std::cout << this->m_name << " " << this->m_height << '\n';
}

How to write out parameters of an object

What the overloaded output operator ultimately does is write out parameters of an object. For the moment, you can think of the overloaded output operator as a special kind of print() method. You can see one print() method in the code above. A typical print() method for a Skyscraper would print out its two parameters, i.e. name and height.

void Skyscraper::print() const {
    std::cout << this->m_name << " " << this->m_height << '\n';
}

The function would be used in the following way:

#include <iostream>
#include "skyscraper.h"

int main()
{
    Skyscraper s1("Empire State", 381);
    Skyscraper s2("Petronas", 452);

    s1.print();
    s2.print();

    return 0;
}

Overloading the output operator

In a certain sense, overloading the output operator provides us with a functionality similar to the print() method. Look at the two declarations below:

void print() const;
std::ostream& operator<<(std::ostream& output, const Skyscraper &s);

Notice that unlike the print() method, the overloaded output operator returns a reference to the changed output stream. This enables to output the skyscrapers in a chain, as shown below:

std::cout << s1 << s2;

Another thing to notice is the constant reference to the Skyscraper in the parameter list. That is because we should not change the Skyscraper object while trying to output it. This is different from the input operator which needs a non-const reference to the Skyscraper as it populates a new Skyscraper object with data.

Now, compare the body of the two functions. Notice that the overloaded output operator takes a reference to the output stream as a parameter, populates it with the name and height and then returns the reference to the changed output stream.

void Skyscraper::print() const {
    std::cout << this->m_name << " " << this->m_height << '\n';
}
std::ostream& operator<<(std::ostream &output, const Skyscraper &s)
{
    output << s.getName() << " " << s.getHeight() << '\n';
    return output;
}

Note: The call

std::cout << s;

is equivalent to:

std::operator<<(std::cout, s);

The changes to the code can be seen below. Notice that I removed the print() function from the header and introduced the overloaded output operator.

#ifndef SKYSCRAPER_H
#define SKYSCRAPER_H
#include <string>
#include <iostream>
#include <stdexcept>

class Skyscraper
{
public:
    Skyscraper(const std::string& name, double height);
    double getHeight() const {return m_height;}
    std::string getName() const {return m_name;}

private:
    std::string m_name;
    double m_height;
};

std::ostream& operator<<(std::ostream &output, const Skyscraper &s);

#endif // SKYSCRAPER_H
#include "skyscraper.h"

Skyscraper::Skyscraper(const std::string &name, double height):
    m_name(name), m_height(height){
    if(m_height < 0)
        throw std::invalid_argument(
                "Height must be non-negative.");
}

std::ostream& operator<<(std::ostream &output, const Skyscraper &s)
{
    output << s.getName() << " " << s.getHeight() << '\n';
    return output;
}
#include <iostream>
#include "skyscraper.h"

int main()
{
    Skyscraper s1("Empire State", 381);
    Skyscraper s2("Petronas", 452);

    std::cout << s1 << s2;

    return 0;
}

Output operator as a friend

Output operator is not part of the Skyscraper class, but it should be able to access its members. C++ provides us with two ways of dealing with this problem:

1. If the class already contains the accessor methods (getName(), getHeight()), the overloaded output operator can use the accessor methods to output the object attributes. This is what we have used until now.

std::ostream& operator<<(std::ostream &output, const Skyscraper &s)
{
    output << s.getName() << " " << s.getHeight() << '\n';
    return output;
}

2. If the class does not contain accessor methods, we have to define the output operator a friend of the class Skyscraper. This allows the output operator to access the private members of the Skyscraper object directly (i.e. s.m_name, s.m_height).

std::ostream& operator<<(std::ostream &output, const Skyscraper &s)
{
    output << s.m_name << " " << s.m_height << '\n';
    return output;
}

The entire implementation can be seen below:

#ifndef SKYSCRAPER_H
#define SKYSCRAPER_H
#include <string>
#include <iostream>
#include <stdexcept>

class Skyscraper
{   
public:
    Skyscraper(const std::string& name, double height);
    friend std::ostream &operator<<(std::ostream &output,
                                    const Skyscraper &s);  

private:
    std::string m_name;
    double m_height;
};
#include "skyscraper.h"

Skyscraper::Skyscraper(const std::string &name, double height):
    m_name(name), m_height(height){
    if(m_height < 0)
        throw std::invalid_argument(
                "Height must be non-negative.");
}

std::ostream& operator<<(std::ostream &output, const Skyscraper &s)
{
    output << s.m_name << " " << s.m_height << '\n';
    return output;
}

Latest update: 15.04.2016
Created: 2014
© Walletfox.com, 2017