iostream vs. iostream.h

Published October 19, 2005 by SiCrane
Do you see issues with this article? Let us know.
Advertisement

Introduction

A frequent piece of advice is often given to new C++ programmers is to use <iostream> instead of <iostream.h> or <fstream> instead of <fstream.h>. This is often given with only the explanation that the .h forms are deprecated without explaining what the difference is and why, in fact, using the extensionless version is superior.

Background

The original iostream library was written to challenge the claim that a terse, type safe I/O system needed special language support. [1] It was developed at Bell Labs by Bjarne Stroustrup and shipped with the original C++ compiler, CFront and described in the first edition of Stroustrup's The C++ Programming Language. This version of the iostream library lived in the headers iostream.h, fstream.h and so on. Now CFront, its library and The C++ Programming Langauge formed the de facto standard for C++ for some time. [2] Hence when other compiler vendors implemented C++, they copied the iostream library (amongst others).

Sometime later, the C++ language entered the standardization process. This process focused both on the core language and the standard library, of which the iostream library was to be part. The version of the iostream library that the Standards Committee produced was quite a bit different from the CFront implementation. For one thing the standard library version was heavily templated. Other functional differences included the change in several enumerations that controlled I/O. Essentially, while some of the old code would work with the new library, a lot still didn't.

To ease transition, the C++ Standards Committee declared that code including the standard C++ headers would use include directives that lack an extension. [3] This allowed compiler vendors to ship the old style C++ library headers with the .h extension and the new style headers without. From the period following this decision by the Standards Committee to about 2002 this was the common decision by many compiler vendors. This was often coupled with deprecation warnings when the old headers were included.

However, many compilers in recent years have taken a different approach. Instead they either leave out the old style headers completely, so that code depending on iostream.h simply won't compile, or they have the old style header include the new style header and use using directives to pull the declarations from the std namespace into the root namespace. Hence, if a given piece of code using iostream.h is compiled on a randomly chosen, recent compiler, the results are unpredictable. It may fail to compile due to simple lack of the header. It may fail to compile because the compiler tries to use the new style library, but the code relies on functionality only present in the old iostream library. Or it may get built against the old style library.

The Advantages of New Over Old

There are several reasons why new code should be written using the extensionless version of the header files instead of the .h forms. The first is the unpredictability of such code when compiled on modern compilers. As previously mentioned, the result of using the .h headers is implementation specific. And as time goes by, the chance that a given compiler will have the old style library available decreases.

Even given a compiler that has the CFront iostream library available, there are concrete benefits to using the standard version. For one thing, the standard version has an interface that is exception aware. This includes both defining exceptions that the iostream library may throw, but also reducing the number of ways that the iostream library may be interfaced in exception unsafe ways. For example, elimination of two stage construction of iostream objects reduces the possibility of leaking resources.

The new style iostreams also integrate better with the rest of the Standard C++ Library. The best example of this is using istreambuf_iterators to load an entire file into a container.

Also, the standard C++ library has better localization support. This includes the use of locales with stream objects to handle things such as determining if a "." or a "," is used as a decimal seperator. It also includes wide character support to handle a larger range of local characters.

Migration Hints

A large amount of sample code and tutorials are still available that uses the CFront iostream library. This is often old code that simply was never updated to take advantage of the standard. [4] Given the task of modifying code that uses the old style iostream library to the standard version, the most common problems are as follows:

  1. The standard iostream library lives in the std namespace. This can be fixed by either the application of an using declaration or prefixing the relevant identifiers with std::. [5]
  2. The nocreate flag doesn't exist in the standard library version of the iostream library. However, a std::ifstream object will not open a file if it doesn't exist. So one way to bypass this restriction is to first attempt to open the file with a std::ifstream object. If the std::ifstream object fails to open the file, abort the operation. If it succeeds, close the file, and reopen the file with the desired file class. Similar techniques can be done with a std::fstream or std::filebuf object in place of the std::ifstream object with the right combination of flags. [6]
  3. The noreplace flag doesn't exist in the standard library version of the iostream library either. To get the functionality of the noreplace flag you need to reverse the logic of the nocreate flag: open the file first with a std::ifstream (or std::filebuf or std::fstream object with the appropriate flags) and if it suceeds than abort the operation. Otherwise reopn the file with the desired end file class.
  4. Manual forward declarations of iostream objects may not compile depending on the compiler. The easiest solution is to replace such forward declarations with the inclusion of the iosfwd header.
  5. The CFront istream and ostsream classes had protected default constructors. Code extending the CFront iostream library to create custom stream classes would derived from istream or ostream and call the default constructor, followed by a call to init(). The standard library replaces this two stage construction method with a single argument constructor that accepts a streambuf object. Also the standard iostream objects will no longer take ownership of the streambuf assigned in the constructor. The consequence is that the derived class will need to manage the lifetime of the streambuf.

Exceptions

There are exceptions to every rule, and that includes the use of the Standard iostream library instead of the CFront library. The largest category deals with legacy code. For the most part, the new style and the old style iostream library cannot peacefully coexist in the same executable. So if there is an object code only library that needs to be used that incorporates the CFront iostream library, then new code may in turn be forced to use the old style headers.

Even if the source code is available for a legacy component, there simply may not be enough time available to a project to convert to the Standard iostream library and do proper testing. This can be more of a business decision than a software engineering decision.

References

The Design and Evolution of C++

The Annotated C++ Reference Manual

The C++ Standard: Incorporating Technical Corrigendum No. 1

(The two amazon.com links should include the gamedev ref IDs; someone might want to check to make sure it's done properly.)

Appendix 1: A Simple Migration Example

For an example of migrating code that uses the CFront iostream library to the new style iostream library, here's the traditional Hello World example:

#include <iostream.h>

int main(int, char **) {
  cout << "Hello World!" << endl;

  return 0;
}

For this program all that needs to be done is to change the name of the header and put std:: in front of the things referenced from iostream.

#include <iostream>

int main(int, char **) {
  std::cout << "Hello World!" << std::endl;

  return 0;
}

This can also be done with using directives or declarations.

#include <iostream>

int main(int, char **) {
  using namespace std;

  cout << "Hello World!" << endl;

  return 0;
}
#include <iostream>

int main(int, char **) {
  using std::cout;
  using std::endl;

  cout << "Hello World!" << endl;

  return 0;
}

Appendix 2: A More Realistic Migration Example

A real example of porting code comes from NeHe's OpenGL tutorials, Lesson 31. In particular the function MilkshapeModel::loadModelData() uses the old style ifstream class to read in the data. The relevant portion is:

#include <fstream.h>

bool MilkshapeModel::loadModelData( const char *filename )
{
	ifstream inputFile( filename, ios::in | ios::binary | ios::nocreate );
	if ( inputFile.fail())
		return false;	// "Couldn't open the model file."

	inputFile.seekg( 0, ios::end );
	long fileSize = inputFile.tellg();
	inputFile.seekg( 0, ios::beg );

	byte *pBuffer = new byte[fileSize];
	inputFile.read( pBuffer, fileSize );
	inputFile.close();

	// and so on

Here the fstream.h header needs to be changed to fstream. Then to minimize changes a using namespace std is inserted into the function. Finally the ios::nocreate flag is removed from the ifstream constructor, since ios::nocreate does not exist in the standard C++ library, and std::ifstream would not create a new file anyways.

#include <fstream>

bool MilkshapeModel::loadModelData( const char *filename )
{
	using namespace std;

	ifstream inputFile( filename, ios::in | ios::binary );
	if ( inputFile.fail())
		return false;	// "Couldn't open the model file."

	inputFile.seekg( 0, ios::end );
	long fileSize = inputFile.tellg();
	inputFile.seekg( 0, ios::beg );

	byte *pBuffer = new byte[fileSize];
	inputFile.read( pBuffer, fileSize );
	inputFile.close();

	// and so on

As a bonus, using the standard stream library allows easy integration with standard containers. In this case, you can automatically fill a std::vector with the contents of the file without doing the seekg() calls and manually managing the lifetime of the buffer.

#include <fstream>
#include <vector>
#include <iterator>

bool MilkshapeModel::loadModelData( const char *filename )
{
	using namespace std;

	ifstream inputFile( filename, ios::in | ios::binary );
	if ( inputFile.fail())
		return false;	// "Couldn't open the model file."

	std::istreambuf_iterator<char> start(inputFile);
	std::istreambuf_iterator<char> end;
	std::vector<char> buffer(start, end);

	inputFile.close();

	// and so on

Appendix 3: A Complex Migration Example

For a more advanced example of code migration, I've taken source code from a logging system in an application, and reduced it to a minimal example of the relevant parts. This application would create a new log object every time the program was run, associated with a log file. The first time it was run it would use "log000000.txt" then the next time "log000001.txt" and so on. The log object would dump time stamped information into the log file (though the time stamping has been removed from this example). So the example source files look like this:

logger.h

#ifndef LOGGER_H
#define LOGGER_H

// including iostream.h makes build time too long
//   forward declaration instead
// #include <iostream.h>

class ostream;

class Logger {
  public:
    Logger(ostream &amp;amp; log) : log_(log) {}

    void log(const char * message);
  private:
    ostream &amp;amp; log_;
};

#endif

dostuff.h

#ifndef DOSTUFF_H
#define DOSTUFF_H

void do_stuff(Logger &amp;amp; log);

#endif

logger.cpp

#include <logger.h>
#include <iostream.h>

void Logger::log(const char * message) {
   log_ << message << endl;
}

dostuff.cpp

#include "dostuff.h"

void do_stuff(Logger &amp;amp; log) {
  log.log("Doing stuff");
  log.log("Stopped doing stuff");
}

main.cpp:

#include <iostream.h>
#include <fstream.h>
#include <stdio.h>
#include "logger.h"
#include "dostuff.h"

class MyOStream : public ostream {
  public:
    MyOStream() : ostream() {
      filebuf * buf = new filebuf();
      int i = 0;
      char buffer[256] = "log000000.txt";
      while (!buf->open(buffer, ios::out | ios::noreplace)) {
        sprintf(buffer, "log%06d.txt", ++i);
      }

      ios::init(buf);
      ios::delbuf(1);
    }
};

int main(int, char **) {
  MyOStream os;
  if (os.good()) {
    Logger log(os);
    do_stuff(log);
  }

  return 0;
}

Of these files, two need no modification: dostuff.h and dostuff.cpp since they are clients of the logging functionality and don't depend on the iostream library directly. So let's tackle the logger.h first.

The logger.h file has an interesting comment regarding forward declarations. At some point during the development process of this project, build times started to become a problem. One culprit was the logger header file, which pulled in the iostream library even though it didn't actually need the type definitions. So clients of the logging facility like the dostuff.cpp file, which did not depend on iostreams directly, were still paying full cost for the iostream compilation. One approach would have been to use ostream.h instead of iostream.h, but for even faster compilation, the entire header was reduced to just a forward declaration.

The first impulse someone porting this code might have is to replace the forward declaration with class std::ostream; Unfortunately that is not valid syntax for a forward declaration. The next impulse might be to replace it with:

namespace std {
  class ostream;
}

This doesn't work either because in the Standard C++ Library, ostream is not actually a class; it's a typedef for std::basic_ostream<char, std::char_traits<char> >. Another option is to simply scrap the forward declaration for including the iostream header (or ostream) header, but that would defeat the purpose of the forward declaration in the first place. Actually, it would probably be even worse since template definitions tend to be much longer and more difficult to process than the non-template versions. Fortunately, the Standard Library supplies a header, iosfwd, that provides forward declarations for all the iostream classes. Once that piece in place it's only a matter of inserting a couple of std::'s in the code.

#ifndef LOGGER_H
#define LOGGER_H

// only need the forward declarations
#include <iosfwd>

class Logger {
  public:
    Logger(std::ostream &amp;amp; log) : log_(log) {}

    void log(const char * message);
  private:
    std::ostream &amp;amp; log_;
};

#endif

Modifying logger.cpp turns out to be trivial. All that needs to be done is inserting a single std:: into the code and changing the header.

#include <logger.h>
#include <iostream>

void Logger::log(const char * message) {
   log_ << message << std::endl;
}

Migrating the main.cpp code is a bit more involved. The first thing to notice is that the main() function is fine, nothing needs to be done here since it doesn't directly use any of the iostream types. The second thing to notice is that code creates a new ostreamclass. So the question is: is this legal with the Standard iostream library? A quick perusal of section 27 in the C++ Standard shows that the iostream classes have virtual in all the right places, so the std::ostream class can be legitimately extended through inheritance.

Now to see what the code actually does. [7] First the constructor creates a new filebuf object. Then it tries going through each file name till it can open a new file that doesn't already exist. Finally it initializes the ostream with the filebuf, and tells the ostream to delete the filebuf when it's done with it.

The first problem with porting this code is that std::ostream doesn't have a default constructor. The only constructor available to std::ostream is one that accepts a std::streambuf pointer. A naïve approach would be to try to do something like:

MyOStream() : std::ostream(new std::filebuf) {}

One problem with this is that std::ostream's constructor may throw an exception. In that case the filebuf will be leaked. It's even worse if the filebuf was constructed with an actual file name instead of with the default constructor. In that case a file handle may be leaked. A related problem is that there is no equivalent of the delbuf() function in the standard library; a std::ostream will not take ownership of the streambuf that it is passed. Similarly, making the filebuf a member variable is also problematic since base classes are initialized before member variables, and that would result in passing the address of an unconstructed object to the ostream. One actual solution depends on multiple inheritance: [8]

class MyOStreamBase {
  protected:
    std::filebuf buf_;
};

class MyOstream : private MyOStreamBase, public std::ostream {
  public:
    MyOStream() : std::ostream(&amp;amp;buf;) {}
};

Since base subobjects are initialized in order of the declaration of inheritance, the MyOStreamBase subobject will be initialized before the ostream subject, this means that the filebuf will be constructed and passing its pointer to the ostream constructor should work fine. This also eliminates the need for the ostream object to manage the lifetime of the filebuf object.

Finally the meat of the constructor: the original version kept opening successive files with noreplace until one succeeded. std::filebuf doesn't recognize the noreplace flag, so the logic needs to be changed. Instead the loop will try opening successive file names in the in mode until one fails, then tries to reopen it with the out flag.

class MyOStream : private MyOStreamBase, public std::ostream {
  public:
    MyOStream() : std::ostream(&amp;amp;buf;_) {
      int i = 0;
      std::stringstream sstr("log000000.txt");
      sstr << std::setfill('0');
      while (buf_.open(sstr.str().c_str(), ios_base::in)) { // does not
        // need std:: in front of ios_base because of Koenig lookup
        buf_.close();
        sstr.str("");
        sstr << "log" << std::setw(6) << ++i << ".txt";
      }
      buf_.open(sstr.str().c_str(), ios_base::out);
    }
};

[1] This claim was made in the Ada Rationale in 1979 by Jean Ichbiah.

[2] Dependence on The C++ Programming Language and CFront was one of the motivating factor behind standardization of the language. Compiler vendors were often presented with an ambiguity in The C++ Programming Language or later The Annotated C++ Reference Manual. The usual tie breaker was to do what CFront did; however, CFront's behavior in corner cases was often to dump core.

[3] The verbage here is strange since the standard does not dictate how the headers needed to be stored. A compiler is free to keep its iostream header on disk as iostream, iostream.h, iostream.hpp, iostream.hxx or even this_is_not_iostream.h as long as #include <iostream> pulls in the standard version of the header.

[4] This also includes an unfortunate amount of relatively new code written by novice programmers that didn't know any better. In general I would be highly suspicious of the quality of any sample source code or tutorial that uses the old style iostream library if it was written since the year 2002.

[5] As usual, declaring using namespace inside a header file is not advised.

[6] One consequence of this is that ifstreams opened with the nocreate flag are trivial to migrate: just remove the nocreate flag. (Code like this does appear in the wild).

[7] Yes, this is code that I wrote. In my defense, the class did more than just have a funny constructor, it was ten years ago and I've learned since then.

[8] This is known as the base from member idiom. Creating a streambuf for a derived iostream class is the canonical example for this idiom, though it has uses in other places.

Cancel Save
0 Likes 0 Comments

Comments

Nobody has left a comment. You can be the first!
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!

A frequent piece of advice is often given to new C++ programmers is to use instead of or instead of . This is often given with only the explanation that the .h forms are deprecated without explaining what the difference is and why, in fact, using the extensionless version is superior.

Advertisement
Advertisement

Other Tutorials by SiCrane

SiCrane has not posted any other tutorials. Encourage them to write more!
Advertisement