Rogue Wave banner
Previous fileTop of DocumentContentsNext file

11.5 More Improved Extractors and Inserters

Insertion and extraction still do not fit seamlessly into the iostream framework. The inserters and extractors for built-in types can be controlled through formatting flags that our operators thus far ignore. Our operators don't observe a field width while inserting, or skip whitespaces while extracting, and so on.

They don't care about error indication either. So what if the extracted date is February 31? So what if the insertion fails because the underlying buffer can't access the external device for some obscure reason? So what if a facet throws an exception? We should certainly set some state bits in the respective stream's state and throw or rethrow exceptions, if the exception mask says so.

However, the more general question here is: What are inserters and extractors supposed to do? Some recommendations follow.

Regarding format flags, inserters and extractors should:

Regarding state bits, inserters and extractors should:

Regarding the exception mask, inserters and extractors should:

Regarding locales, inserters and extractors should:

Regarding the stream buffer:


NOTE: Do not call the stream's input or output functions after creating a sentry object in your inserter or extractor. Use the stream buffer's functions instead.

11.5.1 Applying the Recommendations to the Example

Let us now go back and apply the recommendations to the extractor and inserter for class date in the example we have been constructing. Here is an improved version of the extractor:

//1The variable err will keep track of errors as they occur. In this example, it is handed over to the time_get facet, which will set the respective state bits.
//2All operations inside an extractor or inserter should be inside a try-block, so that the respective error states could be set correctly before the exception is actually thrown.
//3Here we define the sentry object that does all the preliminary work, like skipping leading white spaces.
//4We check whether the preliminaries were done successfully. Class sentry has a conversion to bool that allows this kind of check.
//5This is the call to the time parsing facet of the stream's locale, as in the primitive version of the extractor.
//6Let's assume our date class allows us to check whether the date is semantically valid, e.g., it would detect wrong dates like February 30. Extracting an invalid date should be treated as a failure, so we set the failbit.
//7Note that in this case it is not advisable to set the failbit through the stream's setstate() function, because setstate() also raises exceptions if they are switched on in the stream's exception mask. We don't want to throw an exception at this point, so we add the failbit to the state variable err.
//8Here we catch all exceptions that might have been thrown so far. The intent is to set the stream's error state before the exception terminates the extractor, and to rethrow the original exception.
//9Now we eventually set the stream's error state through its setstate() function. This call might throw an ios_base::failure exception according to the stream's exception mask.
//10We catch this exception because we want the original exception thrown rather than the ios_base::failure in all cases.
//11We rethrow the original exception. If there was no exception raised so far, we set the stream's error state through its setstate() function.

The inserter is implemented using the same pattern:

The inserter and the extractor have only a few minor differences:

//1We prefer to use the other put() function of the locale's time_put facet. It is more flexible and allows us to specify a sequence of format specifiers instead of just one. We declare a character array that contains the sequence of format specifiers and widen it to wide characters, if necessary.
//2Here we provide the format specifiers to the time_put facet's put() function.
//3The put() function returns an iterator pointing immediately after the last character produced. We check the success of the previous output by calling the iterators failed() function.
//4If the output failed then the stream is presumably broken, and we set badbit.
//5Here we reset the field width, because the facet's put() function uses the stream's format settings and adjusts the output according to the respective field width. The rule is that the field width shall be reset after each usage.

11.5.2 An Afterthought

Why is it seemingly so complicated to implement an inserter or extractor? Why doesn't the first simple approach suffice?

First, it is not really as complicated as it seems if you stick to the patterns: we give these patterns in the next section. Second, the simple extractors and inserters in our first approach do suffice in many cases, when the user-defined type consists mostly of data members of built-in types, and runtime efficiency is not a great concern.

However, whenever you care about the runtime efficiency of your input and output operations, it is advisable to access the stream buffer directly. In such cases, you will be using fast low-level services and hence will have to add format control, error handling, etc., because low-level services do not handle this for you. In our example, we aimed at optimal performance; the extractor and inserter for locale-dependent parsing and formatting of dates are very efficient because the facets directly access the stream buffer. In all these cases, you should follow the patterns we are about to give.


Previous fileTop of DocumentContentsNext file

©Copyright 1998, Rogue Wave Software, Inc.
Send mail to report errors or comment on the documentation.


OEM Release, June 1998