<<
Operator<<
operator, so our custom types can stream information directly to the console using std::cout
So far, we've been using a simple terminal prompt to view the output of our program. Later in this course, we'll switch to rendering graphics using SDL.
But, there are some useful tricks we have that can help us make the most of our terminal for building out quick programs.
<<
OperatorWe've seen how we can stream numbers and strings to the terminal using the <<
operator:
std::cout << "Hello World " << 42;
We'd like to be able to do something similar with our custom objects. For example:
#include <iostream>
class Character {/*...*/}
int main() {
Character Enemy;
std::cout << Enemy;
}
To do this, we need to overload the <<
operator to be usable with std::cout
as the left operand, and Character
as the right operand.
Inspecting that in our IDE, or checking the documentation, would reveal it to be a std::ostream
, which is an output stream.
Because std::cout
is the left operand, the std::ostream
reference needs to be the first argument for our overload, whilst the Character&
is the second argument. So, our overload would look like this:
void operator<< (
std::ostream& Stream,
const Character& Enemy
) {
Stream << "Hello World";
}
With that, our code will now compile, with "Hello World" being streamed to the terminal:
#include <iostream>
class Character {/*...*/}
void operator<<(std::ostream&, const Character&)
int main() {
Character Enemy;
std::cout << Enemy;
}
Hello World
<<
as a Member FunctionIt’s tempting to define the <<
overload as a member function on the Character
class, to keep everything encapsulated. However, as a member function, it would need to be defined by the type of the left operand. In this case, that is the std::ostream
class, not our Character
class.
The std::ostream
class is not our code, so extending it is quite difficult. As such, we define overloads as free functions instead. In a large project, these functions can be defined within the same files we use for the right operand’s type - eg Character.h
- so our code is still kept organized.
<<
OperatorsWe want to make two updates to make our overload more useful. Firstly, we want to be able to chain <<
operators, just like we can when streaming other types. For example, we want to be able to do things like this:
std::cout << MyCharacter << '\n';
To enable this, we need to ensure our overload function returns a reference to the stream we're writing to. So, we update the return type of our overload, and return
the stream that was used as the left operand:
std::ostream& operator<<(
std::ostream& Stream,
const Character& Enemy
) {
Stream << "Hello World";
return Stream;
}
With that, we can now chain additional <<
operators after streaming a Character
object:
#include <iostream>
class Character {/*...*/}
void operator<<(std::ostream&, const Character&)
int main() {
Character Enemy;
std::cout << Enemy << "!!!";
}
Hello World!!!
Secondly, we want our Character
output to generate some meaningful output, so we can stream it rather than "Hello World".
Representing the state of objects as a simple string is sometimes referred to as serialization. We’ll see more of this in the next course, as it unlocks features like saving files to the user’s hard drive and communicating over the internet, enabling programs like multiplayer games.
For now, we can just create a simple function that generates a string that stores some info about our object, in this format:
[Level] Name (Health / MaxHealth)
To accomplish this, we could add a public function that uses the std::format
techniques we covered in the previous lesson:
class Character {
public:
std::string Serialize() const {
return std::format(
"[Level {}] {} ({} / {})",
Level, Name, Health, MaxHealth);
}
private:
std::string Name { "Goblin Warrior" };
int Level { 15 };
int MaxHealth { 150 };
int Health { 90 };
};
Finally, let's update our overload to use this function:
std::ostream& operator<<(
std::ostream& Stream,
const Character& Enemy
) {
Stream << Enemy.Serialize();
return Stream;
}
With that, everything is in place. Developers can now easily stream information about our objects to std::cout
like any other type:
#include <iostream>
class Character {/*...*/}
void operator<<(std::ostream&, const Character&)
int main() {
Character Enemy;
std::cout << Enemy;
}
[Level 15] Goblin Warrior (90 / 150)
In this lesson, we explored how to overload the <<
operator in C++, enabling us to stream custom objects directly to the console using std::cout
. Through practical examples, we demonstrated chaining operators and serializing objects for more effective output. Key topics included:
<<
operator for user-defined types.std::cout
by using std::ostream&
and a reference to our custom type as parameters.<<
operators by returning a reference to the stream.Serialize
method in our class.So far, our programs have been restricted to outputting a vertical stream of text. In the next lesson, we’ll begin to introduce the concept of an application loop. This involves our program clearing the screen and regenerating new output on each iteration, which is the basis for much richer interactions.
<<
OperatorLearn how to overload the <<
operator, so our custom types can stream information directly to the console using std::cout
Become a software engineer with C++. Starting from the basics, we guide you step by step along the way