GotW #48

Home Blog Talks Books & Articles Training & Consulting

On the
blog
RSS feed November 4: Other Concurrency Sessions at PDC
November 3
: PDC'09: Tutorial & Panel
October 26: Hoare on Testing
October 23
: Deprecating export Considered for ISO C++0x

This is the original GotW problem and solution substantially as posted to Usenet. See the book More Exceptional C++ (Addison-Wesley, 2002) for the most current solution to this GotW issue. The solutions in the book have been revised and expanded since their initial appearance in GotW. The book versions also incorporate corrections, new material, and conformance to the final ANSI/ISO C++ standard.

Switching Streams
Difficulty: 2 / 10

What's the best way to switch between different stream sources and targets, including the standard console streams and files?

Problem

JG Question

1. What are the types of std::cin and std::cout?

Guru Question

2. Write an ECHO program that simply echoes its input and that can be invoked equivalently in two ways:

    ECHO <infile >outfile

    ECHO infile outfile

In most popular command-line environments, the first command assumes that the program takes input from std::cin and sends output to std::cout. The second command tells the program to take its input from the file named "infile" and to produce output in the file named "outfile". The program should be able to support all of the above input/output options.

Solution

1. What are the types of std::cin and std::cout?

Short answer is that cin is:

  std::basic_istream<char, std::char_traits<char> >

and cout is:

  std::basic_ostream<char, std::char_traits<char> >

Longer answer: std::cin and std::cout have type std::istream and std::ostream, respectively. In turn, those are typdef'd as std::basic_istream<char> and std::basic_ostream<char>. Finally, after accounting for the default template arguments, we get the above.

Note: If you are using an older implementation of the iostreams subsystem, you might still see intermediate classes like istream_with_assign. Those classes do not appear in the final standard, and your implementation should eliminate them soon as it catches up, if it hasn't already.

2. Write an ECHO program that simply echoes its input and that can be invoked equivalently in two ways:

    ECHO <infile >outfile

    ECHO infile outfile

In most popular command-line environments, the first command assumes that the program takes input from std::cin and sends output to std::cout. The second command tells the program to take its input from the file named "infile" and to produce output in the file named "outfile". The program should be able to support all of the above input/output options.

Method 0: The Tersest Solution

The tersest solution is a single statement:

  #include <fstream>
  #include <iostream>
  using namespace std;

  int main( int argc, char* argv[] ) {
    (argc>2
       ? ofstream(argv[2], ios::out | ios::binary)
       : cout)
    <<
    (argc>1
       ? ifstream(argv[1], ios::in | ios::binary)
       : cin)
    .rdbuf();
  }

This works because basic_ios provides a convenient rdbuf() member function that returns the streambuf used inside a given stream object. Pretty much everything in the iostreams subsystem is derived from the basic_ios class template. In particular, that includes cin, cout, and the ifstream and ofstream templates.

More Flexible Solutions

Method 0 has two major drawbacks: First, the terseness is borderline, and extreme terseness is not suitable for production code. From the GotW coding standards:

- programming style:

- prefer readability:

- avoid writing terse (brief but difficult to understand and maintain) code; eschew obfuscation (Sutter97b)

Second, although Method 0 answers the immediate question, it's only good for when you want to copy the input verbatim. That may be enough today, but what if tomorrow you need to do other processing on the input, like converting it to upper case or calculating a total or removing every third character? That may well be a reasonable thing to want to do in the future, so it would be better right now to encapsulate the processing work in a separate function that can use the right kind of input or output object polymorphically:

  #include <fstream>
  #include <iostream>
  using namespace std;

  int main( int argc, char* argv[] ) {
    Process(
      (argc>1
         ? ifstream(argv[1], ios::in  | ios::binary)
         : cin ),
      (argc>2
         ? ofstream(argv[2], ios::out | ios::binary)
         : cout)
      );
  }

But how do we implement Process()? In C++, there are two useful ways to express polymorphism:

Method 1: Templates (Compile-Time Polymorphism)

The first way is to use compile-time polymorphism using templates, which merely requires the passed objects to have a suitable interface (such as a member function named rdbuf):

  template<class In, class Out>
  void Process( In& in, Out& out ) {
    // ... do something more sophisticated,
    //     or just plain "out << in.rdbuf();"...
  }

Method 2: Virtual Functions (Run-Time Polymorphism)

The second way is to use run-time polymorphism, which makes use of the fact that there is a common base class with a suitable interface:

  //  Method 2(a): First attempt, sort of okay.
  //
  void Process( basic_istream<char>& in,
                basic_ostream<char>& out ) {
    // ... do something more sophisticated,
    //     or just plain "out << in.rdbuf();"...
  }

(The parameters are not of type basic_ios<char>& because that wouldn't permit the use of operator<<.)

Of course, this depends on the input and output streams being derived from basic_istream<char> and basic_ostream<char>. That happens to be good enough for our example, but not all streams are based on plain chars or even on char_traits<char>. For example, wide character streams are based on wchar_t, and GotW #29 showed the usefulness of user-defined traits with different behaviour (ci_char_traits, for case insensitivity).

Even Method 2 ought to use templates, and let the compiler deduce the arguments appropriately:

  //  Method 2(b): Better solution.
  //
  template<class C = char, class T = char_traits<C> >
  void Process( basic_istream<C,T>& in,
                basic_ostream<C,T>& out ) {
    // ... do something more sophisticated,
    //     or just plain "out << in.rdbuf();"...
  }

Sound Engineering Principles

All of these answers are "right" as far as they go, but in this situation I personally tend to prefer Method 1. This is because of two valuable guidelines:

Guideline: Prefer extensibility.

Avoid writing code that only solves the immediate problem. Writing an extensible solution is almost always far better (just don't go overboard).

Balanced judgment is one hallmark of the experienced programmer. In particular, experienced programmers understand how to strike the right balance between writing special-purpose code that solves only the immediate problem (shortsighted, hard to extend) and writing a gradiose general framework to solve what should be a simple problem (rabid overdesign).

Method 1 is only slightly more complex than Method 0, but that extra complexity buys you better extensibility. It is at once both simpler and more flexible than Method 2; it is more adaptable to new situations because it avoids being hardwired to work with the iostreams hierarchy only.

So, prefer extensibility. Note that this is NOT an open license to go overboard and overdesign what ought to be a simple system. It is, however, encouragement to do more than just solve the immediate problem, when a little thought lets you discover that the problem you're solving is a special case of a more general problem. This is especially true because designing for extensibility often implicitly means designing for encapsulation:

Guideline: Prefer encapsulation. Separate concerns.

As far as possible, one piece of code -- function, or class -- should know about and be responsible for one thing.

Arguably best of all, Method 1 exhibits good separation of concerns: The code that knows about the possible differences in input/output sources and sinks is separated from the code that knows how to actually do the work. This is a second hallmark of sound engineering.

Copyright 2009 Herb Sutter