C++ provides several ways to perform input and output operations, including the use of streams. Streams are objects that can receive or provide data. We’re already familiar with the std::cout
stream, which allows us send data to the terminal.
But, the use of streams goes far beyond this. They are the underlying system we use to communicate with files, network sockets, hardware devices, and more.
In C++, there are three types of streams:
All three types of streams provide a flexible interface that enables us to work with data in a variety of formats, including binary, text, and even custom formats.
Streams can be used in different ways, depending on the needs of the program. For example, we can use the stream insertion operator <<
to write data to an output stream, or the stream extraction operator >>
to read data from an input stream.
We can also use stream manipulators to control the formatting of the data being read or written. Additionally, streams provide error-handling mechanisms to help ensure that data is processed correctly and that the program can handle unexpected input or output conditions.
In the following sections, we start our exploration of streams by looking at output streams.
Our C++ streams can be either buffered or unbuffered. An unbuffered stream transfers data one byte at a time. This means that each byte is sent or received as soon as it is ready.
In contrast, a buffered stream uses an intermediate buffer to collect a larger block of data. When the buffer is full, the data is sent to the destination. When the buffer is not full, we can still instruct it to send what it has. This is referred to as flushing the buffer.
While unbuffered streams are more responsive, many systems, such as file systems, are not designed to receive a constant stream of individual bytes. They perform much better with fewer, larger transfers, so streams that interact with these systems benefit from being buffered.
Most output streams are buffered, and depending on the system, this can include std::cout
. In an environment where our std::cout
output is being buffered, we may not have noticed it.
This is because a buffer is automatically flushed when the object is destructed, which happens automatically when it goes out of scope, or when our program ends.
By delaying the destruction, we may be able to see the buffering:
#include <iostream>
#include <thread>
int main() {
using namespace std::chrono_literals;
std::cout << "Hello world!";
std::this_thread::sleep_for(5s);
std::cout << "\nDone!";
}
Even though we stream "Hello world!" before we put our thread to sleep, on some terminals, we will not see it until after the 5 seconds have passed. This is because it is being inserted into an intermediate buffer.
Once std::cout
is destroyed at the end of main()
, its destructor will flush the buffer, causing us to see all of our output at once.
Hello world!
Done!
When working with streams, we can flush their buffer at any time. There are a few ways of doing this. We can call the flush()
method on the stream:
#include <iostream>
#include <thread>
int main() {
using namespace std::chrono_literals;
std::cout << "Hello world!";
std::cout.flush();
std::this_thread::sleep_for(5s);
std::cout << "\nDone!";
}
Hello World!
Done
We can insert a std::flush
into the stream:
#include <iostream>
#include <thread>
int main() {
using namespace std::chrono_literals;
std::cout << "Hello world!" << std::flush;
std::this_thread::sleep_for(5s);
std::cout << "\nDone!";
}
Hello World!
Done
In addition to inserting a line break, the std::endl
symbol also flushes the buffer:
#include <iostream>
#include <thread>
int main() {
using namespace std::chrono_literals;
std::cout << "Hello world!" << std::endl;
std::this_thread::sleep_for(5s);
std::cout << "Done!";
}
Hello World!
Done
std::ostream
and std::cout
Output streams have a type of std::ostream
.
Throughout this lesson, we’ll use the familiar std::cout
, but it is only one example of an output stream. There are many more, and we can create our own. The methods and concepts we talk about here apply to all output streams.
Standard library output streams are defined within the <ostream>
header. Typically, we include <iostream>
, which contains both input and output streams.
std::cout
is an output stream that writes to the standard output. This is sometimes also referred to as stdout, the terminal, or the console.
We send items to output streams using the <<
operator, with the stream on the left and the object to send on the right. Many built-in objects, such as numeric types, strings, and pointers are compatible with the <<
 operator.
#include <iostream>
int main() {
std::cout << "Hello World";
}
Hello World
We can also support the <<
operator from within our custom types, which we’ll cover later.
The <<
operator returns a reference to the stream, so we can chain <<
 operations:
#include <iostream>
int main() {
std::string Greeting{"Hello"};
std::cout << Greeting << " World";
}
Hello World
Output streams correctly handle escape characters, such as \n
to insert a line break
#include <iostream>
int main() {
std::cout << "Hello\nWorld";
}
Hello
World
One way we can modify the behavior of our streams is by inserting manipulators into them. We insert manipulators into streams using the <<
operator. For example, to make our output stream display numbers in hexadecimal, we would do this:
#include <iostream>
int main() {
std::cout << std::hex;
std::cout << 255;
}
ff
Most manipulators modify the behavior of the stream for the remainder of its life, or until we insert a manipulator that overrides it. For example, we can revert our stream to treating numbers as decimal by streaming std::dec
to it:
#include <iostream>
int main() {
std::cout << std::hex;
std::cout << "Hex: " << 255;
std::cout << "\nStill Hex: " << 123;
std::cout << std::dec;
std::cout << "\n\nNow Decimal: " << 255;
std::cout << "\nStill Decimal: " << 123;
}
Hex: ff
Still Hex: 7b
Now Decimal: 255
Still Decimal: 123
The standard library comes with a range of manipulators. The most common ones are listed below
std::oct
, std::dec
, and std::hex
These manipulators change the numeric base of our stream to octal (base 8), decimal (base 10), or hexadecimal (base 16) respectively. Decimal is the default.
#include <iostream>
int main() {
std::cout << std::oct << 255 << '\n';
std::cout << std::dec << 255 << '\n';
std::cout << std::hex << 255 << '\n';
}
377
255
ff
These can also be used as standalone functions, where they accept the stream they should apply to as an argument:
#include <iostream>
int main() {
std::oct(std::cout);
std::cout << 255 << '\n';
std::dec(std::cout);
std::cout << 255 << '\n';
std::hex(std::cout);
std::cout << 255 << '\n';
}
377
255
ff
std::setbase()
From <iomanip>
we can use the std::setbase()
manipulator, passing an integer.
Currently, only 8
, 10
, or 16
are used, so this is an alternative to std::oct
, std::dec
, and std::hex
from the previous section. The default base is 10
.
#include <iomanip>
#include <iostream>
int main() {
std::cout << std::setbase(8) << 255 << '\n';
std::cout << std::setbase(10) << 255 << '\n';
std::cout << std::setbase(16) << 255 << '\n';
}
377
255
ff
std::boolalpha
and std::noboolalpha
We may have noticed when we stream a boolean to the terminal, true
appears as 1
, and false
appears as 0
.
We can modify this behavior by streaming std::boolalpha
or std::noboolalpha
. The default is std::noboolalpha
.
#include <iostream>
int main() {
std::cout << std::boolalpha << true << ' '
<< false << '\n';
std::cout << std::noboolalpha << true << ' '
<< false << '\n';
}
true false
1 0
These can also be used as standalone functions, where they accept an argument for the stream they should apply to:
#include <iostream>
int main() {
std::boolalpha(std::cout);
std::cout << true << ' ' << false << '\n';
std::noboolalpha(std::cout);
std::cout << true << ' ' << false << '\n';
}
true false
1 0
std::setprecision()
This is available within <iomanip>
and is used to set the precision with which floating point numbers are displayed.
The default is usually 6
, but we can reset to the default dynamically by passing -1
as the precision argument.
#include <iomanip>
#include <iostream>
int main() {
using std::cout, std::setprecision;
float pi{3.141592};
cout << setprecision(1) << pi << '\n';
cout << setprecision(2) << pi << '\n';
cout << setprecision(3) << pi << '\n';
cout << setprecision(-1) << pi;
}
3
3.1
3.14
3.14159
The precision()
function is also available as an output stream method, allowing us to implement the behavior using a different syntax:
#include <iostream>
int main() {
std::cout.precision(3);
std::cout << 3.1415;
}
3.14
std::setw()
The std::setw()
manipulator sets the minimum width of content that is inserted into the stream. Unlike previous manipulators, std::setw()
will only apply to the next input.
If the input is shorter than the minimum width, it will be "filled" by adding additional characters to the beginning until it reaches the minimum length. This is commonly called left padding.
By default, the padding will be done using space characters:
#include <iomanip>
#include <iostream>
int main() {
std::cout << "Using std::setw(6):\n";
std::cout << std::setw(6) << 1 << '\n';
std::cout << std::setw(6) << 12 << '\n';
std::cout << std::setw(6) << 123 << '\n';
std::cout << std::setw(6) << 1234 << '\n';
std::cout << std::setw(6) << 12345 << '\n';
}
Using std::setw(6):
1
12
123
1234
12345
std::setfill()
We can change the character used for left padding by passing it to std::setfill()
:
#include <iomanip>
#include <iostream>
int main() {
std::cout << std::setfill('0');
std::cout << std::setw(6) << 1 << '\n';
std::cout << std::setw(6) << 12 << '\n';
std::cout << std::setw(6) << 123 << '\n';
std::cout << std::setw(6) << 1234 << '\n';
std::cout << std::setw(6) << 12345 << '\n';
}
000001
000012
000123
001234
012345
Output streams have two alternatives to the <<
operator: the put()
and write()
 methods.
The main difference between these methods and the <<
operator is that they ignore formatting. That is, they ignore any output manipulators like setw()
that are active in the stream.
put()
MethodThe put
method streams an individual character to the stream.
#include <iomanip>
#include <iostream>
int main() {
std::cout.put('A');
std::cout.put('\n');
std::cout << std::setw(5);
std::cout << "B\n";
// This setw will be ignored
std::cout << std::setw(10);
std::cout.put('C');
}
A
B
C
write()
MethodThe write()
method inserts a c-style string (char*
) into the stream. It accepts a second argument, representing how many characters to stream.
#include <iomanip>
#include <iostream>
int main() {
std::cout.write("Hello\n", 6);
std::cout << std::setw(10);
std::cout << "Hello\n";
// setw will be ignored
std::cout << std::setw(10);
std::cout.write("Hello\n", 3);
}
Hello
Hello
Hel
The second argument should be less than or equal to the length of the string. If we want to stream the entire string, we may not know its length if it is a variable.
In that case, we can use the strlen()
function on a C-style string, or a method like size()
if our string originates from a std::string
:
#include <iostream>
int main() {
const char* Hello{"Hello"};
std::cout.write(Hello, strlen(Hello));
std::cout.put(' ');
std::string World{"World"};
std::cout.write(World.c_str(), World.size());
}
Hello World
std::cerr
With the basic use of std::cout
we’ve been doing so far, it is quite unlikely for anything to go wrong. However, as we work on more advanced projects, stream errors will become more likely, and will be something we need to know how to detect and repair.
Two main types of errors can occur when working with output streams:
When an operation triggers one of these issues, bit flags within an internal state of the stream object will be set.
The first category of errors will have std::ios::failbit
toggled to true, whilst the second category will use std::ios::badbit
.
We can check for either of these states by calling methods on our stream:
good()
returns true
if neither failbit
nor badbit
is setfail()
returns true
if either failbit
or badbit
is setbad()
returns true
if badbit
is setThe stream itself also has a boolean conversion operator, so we can, for example, use std::cout
as a boolean.
if (std::cout) // ...
If std::cout
is truthy, it is equivalent to std::cout.good()
being true.
std::ios::eof
Streams have an additional bit flag: std::ios::eof
. The presence of this flag modifies the behavior of the boolean functions we described above.
However, this flag does not apply to output streams. It becomes relevant when working with input streams, which we cover later.
The following code shows various examples where we call these functions to check the state of our stream.
We also use std::cerr
if our steam is broken. std::cerr
works in much the same way as std::cout
but is intended for logging errors. It is a distinct stream, which means it can be used as normal even when std::cout
is broken.
#include <iostream>
int main() {
if (std::cout) {
std::cout << "Everything is fine\n";
}
if (std::cout.good()) {
std::cout << "Everything is fine\n";
}
if (std::cout.fail()) {
std::cerr << "Something went wrong\n";
}
if (std::cout.bad()) {
std::cerr << "cout is broken\n";
}
}
Everything is fine
Everything is fine
setstate()
and clear()
We can manipulate the error state of our streams using setstate()
to set a bit flag, and clear()
to revert the stream to its original, good state:
#include <iostream>
int main() {
std::cout.setstate(std::ios::failbit);
if (!std::cout) {
std::cerr << "Something is wrong\n";
}
std::cout.clear();
if (std::cout) {
std::cout << "We're all good now";
}
}
Something is wrong
We're all good now
Rather than checking our streams for errors, we can instead ask them to throw an exception when an error occurs. We do this using the output stream's exceptions()
method, passing a bit set for the states for which we want an exception thrown:
std::cout.exceptions(std::ios::failbit |
std::ios::badbit);
Now, when an error occurs, an exception with a type of std::ios::failure
will be thrown, which we can catch.
Below, we simulate an error using setstate()
:
#include <iostream>
int main() {
std::cout.exceptions(std::ios::failbit |
std::ios::badbit);
try {
std::cout.setstate(std::ios::failbit);
} catch (const std::ios::failure& e) {
std::cerr
<< "Something went wrong with cout:\n"
<< e.what();
}
}
Something went wrong with cout:
ios_base::failbit set: iostream stream error
Often, we’ll want to make our custom types compatible with output streams. The typical way we do this is by providing an overload for the <<
operator for ostream
objects, where the stream will be the first parameter, and our object will be the second.
To make the <<
operation chainable, as it is with other types, we need to ensure our function returns the ostream
reference. It looks like this:
std::ostream& operator<<(
std::ostream& Stream,
const MyType& MyObject) {
Stream << "(Information about MyObject)";
return Stream;
}
Below, we show a full example of overriding the <<
operator for a custom Player
 type.
#include <iostream>
class Player {
public:
std::string Name{"Roderick"};
std::string Class{"Barbarian"};
int Level{5};
};
std::ostream& operator<<(std::ostream& Stream,
const Player& P) {
Stream << P.Name << " (Level " << P.Level
<< " " << P.Class << ")";
return Stream;
}
We can now stream Player
objects to an output stream, and get the desired result:
#include <iostream>
class Player {/*...*/}
std::ostream& operator<<() {/*...*/}
int main() {
Player PlayerOne;
std::cout << PlayerOne;
}
Roderick (Level 5 Barbarian)
In this lesson, we've explored C++ output streams, covering everything from basic concepts to advanced techniques for customizing output and handling errors.
std::cout
stream for standard output and how to manipulate data formatting with stream manipulators.std::oct
, std::dec
, std::hex
, std::boolalpha
, std::noboolalpha
, std::setprecision()
, std::setw()
, and std::setfill()
.put()
and write()
methods as alternatives to the <<
operator for unformatted output.std::cerr
for error messages.<<
operator.A detailed overview of C++ Output Streams, from basics and key functions to error handling and custom types.
Comprehensive course covering advanced concepts, and how to use them on large-scale projects.