Personal tools
2.11 Stream Storage for Private Use: iword, pword, and xalloc
Click on the banner to return to the user guide home page.
2.11 Stream Storage for Private Use: iword, pword, and xalloc
As we have seen in previous sections, a stream carries a number of data items: an error state, a format state, a locale, an exception mask, information about tied streams, and a stream buffer, to mention a few. Sometimes it is useful and necessary to store additional data in a stream.
2.11.1 An Example: Storing a Date Format String
Consider the inserter and extractor we defined for a date class in Section 2.7. The input and output operations were internationalized and relayed the task of date formatting and parsing to the stream's locale. Here, however, the rules for formatting and parsing were fixed, making them much more restricted than the features available in the standard C library, for example.
In the standard C library, you can specify format strings, similar to those for prinft() and scanf(), that describe the rules for parsing and formatting dates.[38] For example, the format string "%A, %B %d, %Y" stands for the rule that a date must consist of the name of the weekday, the name of the month, the day of the month, and the year--as in Friday, July 12, 1996.
Now imagine you want to improve the input and output operations for the date class by allowing specification of such format strings. How can you do this? Other format information is stored in the stream's format state; consequently, you may want to store the format string for dates somewhere in the stream as well. And indeed, you can.
Streams have an array for private use. An array element is of a union type that allows access as a long or as a pointer to void. The array is of unspecified size, and new memory is allocated as needed. In principle, you can think of it as infinitely long.
You can use this array to store in a stream whatever additional information you might need. In our example, we would want to store the format string.
The array can be accessed by two functions: iword() and pword(). Both functions take an index to an array element and return a reference to the respective element. The function iword() returns a reference to long; the function pword() allows access to the array element as a pointer to void.
Indices into the array are maintained by the xalloc() function, a static function in class ios_base that returns the next free index into the array.
2.11.2 Another Look at the Date Format String
We would like to store the format string for dates in the iostream storage through iword() and pword(). In this way, the input and output operations for date objects can access the format string for parsing and formatting. Format parameters are often set with manipulators (see Section 2.3.3.2), so we should add a manipulator that sets the date format string. It could be used like this:
date today; ofstream ostr; // _ ostr << setfmt("%D") << today;
Here is a suggested implementation for such a manipulator:
class setfmt : public smanip<const char*> { public: setfmt(const char* fmt) : smanip<const char*>(setfmt_,fmt) {} private: static const int datfmtIdx ; \\1 static ios_base& setfmt_(ios_base& str, const char* fmt) { str.pword(datfmtIdx) = (void*) fmt; \\2 return str; } template<class charT, class Traits> friend basic_ostream<charT, Traits> & \\3 operator << ( basic_ostream<charT, Traits >& os, const date& dat); }; const int setfmt::datfmtIdx = ios_base::xalloc(); \\4
The technique applied to implement the manipulator is described in detail in Example 2 of Section 2.8.2.3, so we won't repeat it here. But regarding this manipulator and the private use of iostream storage , there are other interesting details:
//1 | The manipulator class owns the index of the element in the iostream storage where we want to store the format string. It is initialized in \\4 by a call to xalloc(). |
//2 | The manipulator accesses the array pword() using the index datfmtIdx, and stores the pointer to the date format string. Note that the reference returned by pword() is only used for storing the pointer to the date format string. Generally, one should never store a reference returned by iword() or pword() in order to access the stored data through this reference later on. This is because these references can become invalid once the array is reallocated or copied. (See the Class Reference for more details.) |
//3 | The inserter for date objects needs to access the index into the array of pointers, so that it can read the format string and use it. Therefore, the inserter has to be declared as a friend. In principle, the extractor would have to be a friend, too; however, the standard C++ locale falls short of supporting the use of format strings like the ones used by the standard C function strptime(). Hence, the implementation of a date extractor that supports date format strings would be a lot more complicated than the implementation for the inserter, which can use the stream's locale. We have omitted the extractor for the sake of brevity. |
The inserter for date objects given below is almost identical to the one we described in Section 2.7.3:
template<class charT, class Traits> basic_ostream<charT, Traits> & operator << (basic_ostream<charT, Traits >& os, const date& dat) { ios_base::iostate err = 0; char* patt = 0; int len = 0; charT* fmt = 0; try { typename basic_ostream<charT, Traits>::sentry opfx(os); if(opfx) { patt = (char*) os.pword(setfmt.datfmtIdx); \\1 len = strlen(patt); fmt = new charT[len]; use_facet<ctype<charT> >(os.getloc()). widen(patt, patt+len, fmt); if (use_facet<time_put<charT ,ostreambuf_iterator<charT,Traits> > > (os.getloc()) .put(os,os,os.fill(),&dat.tm_date,fmt,fmt+len) \\2 .failed() ) err = ios_base::badbit; os.width(0); } } //try catch(...) { delete [] fmt; bool flag = FALSE; try { os.setstate(ios_base::failbit); } catch( ios_base::failure ) { flag= TRUE; } if ( flag ) throw; } delete [] fmt; if ( err ) os.setstate(err); return os; }
The only change from the previous inserter is that the format string here is read from the iostream storage (in statement //1) instead of being the fixed string "%x". The format string is then provided to the locale's time formatting facet (in statement //2).
2.11.3 Caveat
Note that the solution suggested here has a pitfall.
The manipulator takes the format specification and stores it. The inserter retrieves it and uses it. In such a situation, the question arises: Who owns the format string? In other words, who is responsible for creating and deleting it and hence controlling its lifetime? Neither the manipulator nor the inserter can own it because they share it.
We solved the problem by requiring the format specification to be created and deleted by the iostream user. Only a pointer to the format string is handed over to the manipulator, and only this pointer is stored through pword(). Also, we do not copy the format string because it would not be clear who--the manipulator or the inserter--is responsible for deleting the copy. Hence the iostream user has to monitor the format string's lifetime, and ensure that the format string is valid for as long as it is accessed by the inserter.
This introduces a subtle lifetime problem in cases where the date format is a variable instead of a constant: the stream might be a static stream and hence live longer that the date format variable. This is a problem you will always deal with when storing a pointer or reference to additional data instead of copying the data.
However, this subtle problem does not impose an undue burden on the user of our setfmt manipulator. If a static character sequence is provided, as in:
cout << setfmt("%A, %B %d, %Y") << today;
the setfmt manipulator can be used safely, even with static streams like cout.
©Copyright 1996, Rogue Wave Software, Inc.