TITLE: switching streams (Newsgroups: comp.lang.c++.moderated, 30 Dec 98) AUTHOR: Herb Sutter > ------------------------------------------------------------------- > Guru of the Week problems and solutions are posted regularly on > news:comp.lang.c++.moderated. For past problems and solutions > see the GotW archive at www.peerdirect.com. (c) 1998 H.P.Sutter > News archives may keep copies of this article. > ------------------------------------------------------------------- > >_______________________________________________________ > >GotW #48: Switching Streams > >Difficulty: 2 / 10 >_______________________________________________________ > > >Congratulations to Dietmar Kuehl, the Guru of the Week! >This one comes as no surprise; Dietmar is one of the >newsgroup's iostreams gurus. > >Honorable mention: Martijn Mulder independently >proposed the shortest working solution (see Method 0, >below). Mulder's solution was the tersest of all >proposed solutions, but its terseness is also a >disadvantage and it didn't take into account opening >the files in binary mode. > > >>JG Question >>----------- >> >>1. What are the types of std::cin and std::cout? > >Short answer: > > cin: std::basic_istream > > cout: std::basic_ostream > > >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 and >std::basic_ostream. 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. > > >>Guru Question >>------------- >> >>2. Write an ECHO program that simply echoes its input >> and that can be invoked equivalently in two ways: >> >> ECHO outfile >> >> ECHO infile outfile > >Method 0: The Tersest Solution >------------------------------ > >The tersest solution is a single statement: > > #include > #include > 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 PeerDirect 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 > #include > 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 > 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& in, > basic_ostream& out ) { > // ... do something more sophisticated, > // or just plain "out << in.rdbuf();"... > } > >(The parameters are not of type basic_ios& >because that wouldn't permit the use of operator<<.) > >Of course, this depends on the input and output streams >being derived from basic_istream and >basic_ostream. That happens to be good enough >for our example, but not all streams are based on plain >chars or even on char_traits. 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 > > void Process( basic_istream& in, > basic_ostream& 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. 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.