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.

Free, Unlimited Access
3D Character Concept Art
Ryan McCombe
Ryan McCombe
Updated

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 are an abstraction that allows us to treat a single source as both an input and output stream

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.

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!";
}
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

Stream Manipulators

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 as 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 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 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

Ignoring Output Manipulators

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.

The put() Method

The 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

The write() Method

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

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

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

Output Streams with Custom Types

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)

Summary

In this lesson, we've explored C++ output streams, covering everything from basic concepts to advanced techniques for customizing output and handling errors.

Main Points Covered

  • Introduction to streams in C++, focusing on output streams for writing data to destinations.
  • Distinction between buffered and unbuffered streams, and the significance of flushing buffers.
  • The use of the std::cout stream for standard output and how to manipulate data formatting with stream manipulators.
  • Explanation of output stream manipulators such as std::oct, std::dec, std::hex, std::boolalpha, std::noboolalpha, std::setprecision(), std::setw(), and std::setfill().
  • Overview of the put() and write() methods as alternatives to the << operator for unformatted output.
  • Techniques for error handling in output streams, including checking stream states and using std::cerr for error messages.
  • Methods for enabling custom types to work with output streams by overloading the << operator.
  • Introduction to output stream exceptions for automated error management.

Was this lesson useful?

Next Lesson

String Streams

A detailed guide to C++ String Streams using std::stringstream. Covers basic use cases, stream position seeking, and open modes.
3D Character Concept Art
Ryan McCombe
Ryan McCombe
Updated
A computer programmer
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
Next Lesson

String Streams

A detailed guide to C++ String Streams using std::stringstream. Covers basic use cases, stream position seeking, and open modes.
3D Character Concept Art
Contact|Privacy Policy|Terms of Use
Copyright © 2024 - All Rights Reserved