C++ Experts Forum


The (B)Leading Edge: XDR_Stream Class, Part IV

Jack W. Reeves


Backtracking

This column is going to be short and to the point, I hope. First I have to address an error that reader Patrick Rabau spotted. In the FactoryCollection class that I described, there is a function find that takes a reference to a type_info object, such as returned by the typeid operator, and returns a string that represents a user-defined name for the type. Naturally, this is implemented internally with a standard library map. Since type_info objects cannot be copied directly, the map holds pointers to type_info objects as its key. Unfortunately, when I presented the code, I declared the map as follows:

std::map<std::type_info*, string>  TypeNameMap;

This uses the default comparison function of std::less<std::type_info*>. This in turn means that to function correctly, there can be only one instance of a type_info object for a given type. As Rabau pointed out, the Standard does not guarantee this. The resulting code has undefined behavior. This is another example of something that probably works on most platforms, but will sooner or later fail.

The correct declaration for the map provides a custom comparison operator that uses the type_info class' before member function to provide the ordering. It should look like this:

struct TypeInfoComp : std::binary_function<std::type_info*,
  std::type_info*, bool> {
  bool operator()(const std::type_info* lhs,
    const std::type_info* rhs) const
  { return lhs->before(*rhs); }
};

typedef std::map<std::type_info*, 
  std::string, TypeInfoComp>  TypeNameMap;

With this change, the map's find function will now base its equivalence test on the comparison provided by the type_info class itself, and not on the equivalence of two pointers.

Rabau also pointed out an error in the EBCDIC to ASCII translation in one of XDR_Stream's output functions. The correct code is now in the downloadable listing.

Error Handling

Now back to XDR_Stream. I mentioned at the end of the last column that I felt that XDR_Stream was not quite ready for production use. The reason is that its error handling was unacceptable. This was because an XDR_Streambuf is just a synonym for the specialization of a std::basic_streambuf<XDR_Char>. You may recall that it was the possibility of creating this specialization by creating an XDR_Char type that sparked the entire idea of the XDR_Stream class. Unfortunately, this specialization has a problem. The virtual functions of basic_streambuf that are expected to be overridden by a derived class to actually fetch characters from an underlying source, or put characters to an underlying sink, all return an int_type: more specifically, they return a XDR_Char_Traits::int_type. They are expected to return an eof() whenever an error is encountered reading the stream. Otherwise, the int_type is expected to contain the last read char_type value.

In order for this to work, an int_type has to be a type or a class that can represent all of the values of the corresponding character type, plus an eof value. The Standard notes that "If eof() can be held in char_type then some iostreams operations may give surprising results." As XDR_Char_Traits is currently defined, an int_type is just a long, almost the same as char_type itself. Furthermore, the currently defined value for eof() is -1. This means that anytime an XDR_Char is read with the value of -1, then it would appear that an error has been encountered on the stream. Since a value of -1 is a perfectly reasonable value for an XDR_Char that holds an integer value, this is not acceptable.

In order to get the XDR_Char type to work correctly, I had to change the XDR_Char_Traits type so that its int_type correctly meets the requirements of the Standard. To do this, I changed int_type to be:

typedef std::pair<char_type, bool> int_type;

The Boolean flag indicates whether the value is eof or not. I changed the XDR_Char_Traits function to_int_type(c) to return make_pair(c, false). The function to_char_type(x) returns x.first. The function eof() now returns make_pair(-1, true).

With these changes, it is possible to develop derived versions of XDR_Streambuf that can correctly differentiate between a returned int_type that contains a -1 value, and an error condition.

This was only half the battle, however. The Standard allows that a derived streambuf can throw an exception. The Standard requires that, if an exception occurs, the appropriate error bit (failbit or badbit) be set in the stream's state. Then, if the stream's exception mask for that bit is set, the exception should be propagated. This gets a little tricky. The first requirement obviously means that any exception thrown by a lower-level streambuf operation has to be caught. When the stream's state is set, however, if the stream's exception mask for that bit is set, then the basic_ios class will throw a basic_ios::failure exception. In order to propagate the original exception, the basic_ios::failure exception has to be caught and then the original exception rethrown.

On the other hand, if the stream's exception mask is not set, then the original exception is not rethrown.

All of these are requirements for the regular IOStream classes, but I figured that an XDR_Stream should follow the same pattern. This means that all formatting functions that directly call XDR_Streambuf functions have to be prepared for those lower-level functions to throw exceptions.

The code for the oXDR_Stream::put(char_type) function that will handle the above circumstances is shown below.

oXDR_Stream& 
oXDR_Stream::put(char_type c)
{
  try {
    int_type x = rdbuf()->sputc(c);
    if (x == traits_type::eof()) setstate(badbit);
  } catch (...) {
    if (not good()) throw;
    bool rethrow = false;
    try {
        setstate(badbit);
    } catch (...) {
        rethrow = true;
    }
    if (rethrow) throw;
  }
  return *this;
}

In the first catch(...) clause, the current state of the stream is first checked. If it is not good(), then that means the exception was thrown (probably) by the basic_ios class when setstate(failbit) was called. That exception is just rethrown. If the stream is still marked as good(), then the exception was thrown by a lower-level routine. In this case, the operation calls setstate(badbit) inside a try/catch block. If the basic_ios class throws an exception at this point, it must be caught and just treated as a signal that the original exception is to be rethrown. If no exception results when setstate(badbit) is called, then that means the exception mask is clear, so the original exception is not rethrown. This code is the way it is because I could not see any way to set the stream state without causing an exception to be thrown if the stream's exception mask was also set. Perhaps an alternative approach would be to save the exception mask before the call to setstate(badbit), restore it afterward, and then check it to see if the original exception should be rethrown. Since exceptions are presumably rare events, even for IOStreams, I do not really object to the nested try blocks.

You will note that this function sets failbit if an eof() is encountered, but sets badbit if a lower-level exception occurs. This reflects my feeling that exceptions represent unrecoverable failures, and so they leave the stream in a bad state. In my example XIOStream class that is included in the downloadable code, the stream buffer returns an ordinary eof() if the underlying buffer is empty when an attempt is made to read an XDR_Char. On the other hand, if the underlying buffer can only return a portion of an XDR_Char, I consider this an exceptional condition and throw a basic_ios::failure exception.

With these modifications added to all necessary formatting functions, I finally believe that XDR_Stream is ready for real world use.

At this point, it seems appropriate to look back at where we have been, and why I wanted to get here in the first place. My original intent in describing XDR_Stream in such detail was to show a good example of the extensibility of the Standard IOStreams library. I think I have demonstrated that. Yes, there is a lot of code here. Yes, it was not trivial to come up with some of it. On the other hand, it would have been considerably more work to come up with all the functionality inherent in the basic_streambuf and basic_ios templates.

Along the way, a number of subtle, but important points have been raised. The need to create a custom cytpe<XDR_Char> facet and install it in the global locale just so the basic_ios::init function would work correctly completely caught me by surprise. Getting the actual type for XDR_Char correct so that it meets the Standard requirement for a POD type and, at the same time, works around some bugs in different library implementations also took a couple of tries to get right. Finally, getting the error handling correct required some careful review of the requirements for char_traits, as well as for the functionality expected of IOStreams in general.

Obviously, I could have just shown the finished code and not described my thinking and the changes I discovered that I had to make as I went along. I felt it was worthwhile to walk through the whole process is some detail for several reasons. First, I wanted to give a solid real-world example of how the design of a class library proceeds. I really see too much software that has very little reusable middle-layer abstractions in it.

Second, I wanted to point out some of the things that can go wrong and that designers should be aware of. Undefined behavior resulting from not completely adhering to the requirements of the Standard is something you must constantly be aware of. Sometimes libraries can be buggy. And finally, in spite of best efforts, you can sometimes just plain overlook something, but this no different than any other aspect of software development. Therefore, I hope that instead of scaring you off, I have maybe gone a little ways toward convincing you that deriving your own IOStream is not a totally impossible task that must be left to a few dedicated programmers that do nothing else.

And that is my real point. Reuse comes in many forms. Sometimes you can reuse something as-is. Other times you have to do some tailoring. Maybe that means creating a derived class. Sometimes it means taking an existing framework and creating a new piece of your own that fits into the framework. There are a lot of advantages for doing so, even if the process is more work than just creating a derived class. The C++ Standard has a lot of extensibility of this type. It occurs in the STL, and it occurs in the IOStreams library. You just have to be willing to take advantage of it.

In the next installment, I hope to show a true real-world use for a very special type of XDR_Stream.

Jack W. Reeves is an engineer and consultant specializing in object-oriented software design and implementation. His background includes Space Shuttle simulators, military CCCI systems, medical imaging systems, financial data systems, and numerous middleware and low-level libraries. He currently is living and working in Europe and can be contacted via jack_reeves@bleading-edge.com.