C++ Output Streams

A detailed overview of C++ Output Streams, from basics and key functions, to error handling and custom types.
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

as.jpg
Ryan McCombe
Ryan McCombe
Posted

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:

  • Output streams are used to write data to a destination
  • Input streams are used to read data from a source
  • Bidirectional streams is an abstraction that allows us to treat a single source as both an input and output stream

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.

Buffered vs Unbuffered 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!

Flushing Buffers

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

Output Manipulators

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

Output Stream put and write methods

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()

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

Output Error Handling and 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:

  • An operation can fail - this is generally considered a recoverable error because the stream itself is still intact
  • The stream can become corrupted - this is generally considered unrecoverable

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 set
  • fail() returns true if either failbit or badbit is set
  • bad() returns true if badbit is set

The 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.

End of File Flags - 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

Output Stream Exceptions

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

Using Output Streams with Custom Objects

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)

Was this lesson useful?

Ryan McCombe
Ryan McCombe
Posted
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

7a.jpg
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Free, unlimited access!

This course includes:

  • 106 Lessons
  • 550+ Code Samples
  • 96% Positive Reviews
  • Regularly Updated
  • Help and FAQ
Next Lesson

C++ String Streams

A detailed guide to C++ String Streams using the standard library stringstream. Covers basic use cases, stream position seeking and open modes.
04a.jpg
Contact|Privacy Policy|Terms of Use
Copyright © 2023 - All Rights Reserved