Overloading the << operator
The following few paragraphs demonstrate when and how to overload the << operator, i.e. the stream insertion operator. The aim of this article is to show you that overloading the stream insertion operator can come very handy in certain situations such as output chaining or output of an object that contains another object. To demonstrate the application, 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); auto name() const {return m_name;} auto height() const {return m_height;} 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 << operator ultimately does is write out parameters of an object. For the moment, you can think of the overloaded << 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() { auto s1 = Skyscraper {"Empire State", 381}; auto s2 = Skyscraper {"Petronas", 452}; s1.print(); s2.print(); }
Overloading the stream insertion operator
In a certain sense, overloading the output operator provides us with 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;
Additionaly, notice the constant reference to the Skyscraper object in the argument list. The const reference is there so that we don't 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 an argument, 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.name() << " " << s.height() << '\n'; return output; }
Note that the calls below are equivalent:
std::cout << s; 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 overloaded the output operator instead.
#ifndef SKYSCRAPER_H #define SKYSCRAPER_H #include <string> #include <iostream> #include <stdexcept> class Skyscraper { public: Skyscraper(const std::string& name, double height); auto name() const {return m_name;} auto height() const {return m_height;} 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.name() << " " << s.height() << '\n'; return output; }
#include <iostream> #include "skyscraper.h" int main() { auto s1 = Skyscraper {"Empire State", 381}; auto s2 = Skyscraper {"Petronas", 452}; std::cout << s1 << s2; }
Output operator as a friend
Although the insertion stream operator isn't part of the Skyscraper interface, it should be able to access its member variables in order to provide output. C++ provides us with two ways of dealing with this problem:
1. If the class already contains the accessor methods, i.e. name(), height(), the overloaded insertion stream 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.name() << " " << s.height() << '\n'; return output; }
2. If the class doesn't contain accessor methods, we have to define the insertion stream operator as a friend of the class Skyscraper. This allows the << 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; }
Tagged: C++