User's Guide

3.10 Exceptions

The Standard Iostreams library
The Boost Iostreams library
Exception Safety
Acknowledgments

The Standard Iostreams library

The public member functions of std::basic_streambuf which perform i/o — sgetn(), sputn(), pubsync(), etc. — are implemented using the protected virtual interface of std::basic_streambufunderflow(), overflow(), sync(), etc. Most of these protected virtual functions are allowed to signal failure either by throwing an exception or by returning an error indicator (often traits_type::eof()). All exceptions thrown are propogated by the public member functions, so users of raw stream buffers must in principle be prepared to handle exceptions. However, the stream buffers which ship with most standard library implementations do not throw exceptions.

Standard streams, by contrast, throw no exceptions by default: users must set the exception mask to request that exceptional conditions be reported using exceptions. If ios_base::badbit is set in a stream's exception mask, all exceptions thrown by the underlying stream buffer will be caught and rethrown.

With streams, therefore, the user has a choice whether to enable exceptions; with stream buffers, the implementor has a choice, but the user has none.

The Boost Iostreams library

Policy

The fundamental stream buffer template in the Boost Iostreams library, stream_buffer, implements the protected virtual memeber functions of std::basic_streambuf by delegating to the i/o functions read, write, put, get, etc., which in turn delegate to member functions of Filters and Devices. These member functions throw exceptions to report errors. The exceptions are propagated by the protected virtual members of stream_buffer and by the public members of std::basic_streambuf.

It is recommended that all exceptions thrown by user-defined Filters and Devices derive from std::ios_base::failure.[1]

Rationale

An alternative formulation would have been to require Filter and Device member functions with return type std::streamsize to return -1 to indicate errors, and to modify the specification of functions returning void so that they return a bool indicating success. The decision to rely on exceptions to report errors was made to allow implementors a means to convey aditional information about the cause of errors, and to free them from having to remember which return values are designated as error indicators.

A third possibility would have been to follow the example of std::basic_streambuf and allow member functions of Filters and Devices to signal errors either by throwing exceptions or by returning designated error indicators. This was rejected because it would have complicated the specifications of the various Filter and Device concepts and made the internals of stream_buffer more difficult to understand and maintain.[2]

Exception Safety

The Iostreams library aims to provide to following guarantees if an exception is thrown during an i/o operation:
  1. Resources are freed by calling destructors or close, if appropriate.
  2. The invariants specified by the C++ standard library for streams and stream buffers are maintained. Attempting to perform additional i/o is allowed but may cause further exceptions.
  3. The invariants specified by the Boost Iostreams library for streams and stream buffers are maintained. Specifically,
    1. A stream_buffer or stream can be closed and reopened to perform additional i/o with a new instance of the underlying Filter or Device type.
    2. A filtering_streambuf's or filtering_stream's chain of Filters and Devices is in a consistent state. If the current Device is removed from the chain and a new one added the user may perform i/o as if the the stream or stream buffer were newly constructed.

Conditions 2. and 3. rely on the specification of the Filter and Device concepts. Note that there is no guarantee that a stream's or stream buffer's character sequences are in a consistent state, i.e., that data has not been corrupted, and no way in general to determine the state of these sequences after an exception.

These conditions amount to what is known as the basic guarantee of exception safety (see [Abrahams1].)

Acknowledgments

Thanks to Angelika Langer and John Torjo for discussion of exceptions.


[1]The C++ standard describes std::ios_base::failure as "the base class for the types of all objects thrown as exceptions, by functions in the Iostreams library, to report errors detected during stream buffer operations." ([ISO], section 27.4.2.1.1.) There is a difference of opinion among commentators whether this means that all exceptions thrown by stream buffer member functions must be of type failure.

A leading text on C++ iostreams interprets the language in the standard to mean that exceptions "may be of any type" but that "if the exception is rasied due to an error situation discovered by any of the IOStreams operations, it is of the type failure" ([Langer], p. 36, emphasis added). As an example, the authors state that a std::bad_alloc generated during buffer allocation need not be caught and rethrown as a failure.

Another leading text, however, takes the view that all exceptions must derive from failure. ([Josuttis1], p. 602.)

The Iostreams library takes a neutral position on this matter: all exceptions thrown internally by the library are of type failure or a derived class, but exceptions thrown by user-defined types are propogated as-is.

[2]As Herb Sutter writes, "it is always poor design to allow an operation to report the same error in two different ways....It complicates the interface and the semantics. And it makes the caller's life harder because the caller must be able to handle both flavors of error reporting." ([Sutter], p. 130.)