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 lets 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 stream 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.
Whilst 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!";
}
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!";
}
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!";
}
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 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.
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:
std::string Greeting {"Hello"};
std::cout << Greeting << " World";
Hello World
Output streams correctly handle escape characters, such as \n
to insert a line break
std::cout << "Hello\nWorld";
Hello
World
One way we can modify the behavior of our streams is by inserting manipulators into them. Most manipulators modify the behavior of the stream for the remainder of its life.
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 a parameter for the stream they should apply to:
std::oct(std::cout);
std::dec(std::cout);
std::hex(std::cout);
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 value 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 a parameter for the stream they should apply to:
std::boolalpha(std::cout);
std::noboolalpha(std::cout);
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;
float pi{3.141592};
cout << std::setprecision(1) << pi << '\n';
cout << std::setprecision(2) << pi << '\n';
cout << std::setprecision(3) << pi << '\n';
cout << std::setprecision(-1) << pi;
}
3
3.1
3.14
3.14159
precision
is also available as an output stream method, allowing us to specify it in a different way:
#include <iostream>
int main() {
std::cout.precision(3);
std::cout << 3.1415;
}
3.14
std::setw()
and std::setfill()
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, but we can change the character used for padding by passing it to std::setfill()
:
#include <iomanip>
#include <iostream>
int main() {
using std::cout;
cout << std::setfill('0');
cout << std::setw(6) << 1 << '\n';
cout << std::setw(6) << 12 << '\n';
cout << std::setw(6) << 123 << '\n';
cout << std::setw(6) << 1234 << '\n';
cout << std::setw(6) << 12345 << '\n';
}
000001
000012
000123
001234
012345
put
and write
methodsOutput 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()
The put
method streams an individual character to the stream.
#include <iomanip>
#include <iostream>
int main() {
std::cout << std::setw(5);
std::cout << 'A' << std::endl;
std::cout << std::setw(10);
std::cout.put('A');
}
A
A
write()
The 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 << std::setw(10);
std::cout << "Hello" << std::endl;
std::cout << std::setw(10);
std::cout.write("Hello", 3);
}
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 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 happen 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 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 return 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 Character
 type.
#include <iostream>
class Character {
public:
std::string Name{"Roderick"};
std::string Class{"Barbarian"};
int Level{5};
};
std::ostream& operator<<(std::ostream& Stream,
const Character& C) {
Stream << C.Name << " (Level " << C.Level
<< " " << C.Class << ")";
return Stream;
}
We can now stream Character
objects to an output stream, and get the desired result:
#include <iostream>
int main() {
Character Player;
std::cout << Player;
}
Roderick (Level 5 Barbarian)
Comprehensive course covering advanced concepts, and how to use them on large-scale projects.