📄 inp_5394.htm
字号:
<HTML><HEAD><TITLE>2.7 Input/Output of User-Defined Types</TITLE></HEAD><BODY><A HREF="ug2.htm"><IMG SRC="images/banner.gif"></A><BR><A HREF="inm_4073.htm"><IMG SRC="images/prev.gif"></A><A HREF="booktoc2.htm"><IMG SRC="images/toc.gif"></A><A HREF="man_6665.htm"><IMG SRC="images/next.gif"></A><BR><STRONG>Click on the banner to return to the user guide home page.</STRONG><H2>2.7 Input/Output of User-Defined Types</H2><P>One of the major advantages of the iostreams facility is extensibility. Just as you have inserters and extractors for almost all types defined by the C++ language and library, you can implement extractors and inserters for all your own user-defined types. To avoid surprises, the input and output of user-defined types should follow the same conventions used for insertion and extraction of built-in types. In this section, you will find guidelines for building a typical extractor and inserter for a user-defined type.</P><A NAME="2.7.1"><H3>2.7.1 An Example Using a User-Defined Type</H3></A><P>Let us work through a complete example with the following date class as the user-defined type:</P><PRE>class date {public: date(int d, int m, int y); date(const tm& t); date(); // more constructors and useful member functionsprivate: tm tm_date;};</PRE><P>This class has private data members of type <SAMP>tm</SAMP>, which is the time structure defined in the C library (in header file <SAMP><ctime></SAMP>). </P><A NAME="2.7.2"><H3>2.7.2 A Simple Extractor and Inserter for the Example</H3></A><P>Following the read and write conventions of iostreams, we would insert and extract the date object like this:</P><PRE>date birthday(2,6,1952);cout << birthday << '\n';</PRE><P>or </P><PRE>date aDate;cout << '\n' << "Please, enter a date (day month year)" << '\n';cin >> aDate;cout << aDate << '\n';</PRE><P>For the next step, we would implement shift operators as inserters and extractors for <SAMP>date</SAMP> objects. Here is an extractor for class <SAMP>date</SAMP>:</P><PRE>template<class charT, class Traits>basic_istream<charT, Traits>& //1operator>> (basic_istream<charT,Traits>& is, //2 date& dat) //3{ is >> dat.tm_date.tm_mday; //4 is >> dat.tm_date.tm_mon; is >> dat.tm_date.tm_year; return is; //5} </PRE><TABLE CELLPADDING="3"><TR VALIGN="top"><TD>//1</TD><TD>The returned value for extractors (and inserters) is a reference to the stream, so that several extractions can be done in one expression.</TD></TR><TR VALIGN="top"><TD>//2</TD><TD>The first parameter usually is the stream from which the data shall be extracted.</TD></TR><TR VALIGN="top"><TD>//3</TD><TD>The second parameter is a reference, or alternatively a pointer, to an object of the user-defined type.</TD></TR><TR VALIGN="top"><TD>//4</TD><TD>In order to allow access to private data of the date class, the extractor must be declared as a friend function in class <SAMP>date</SAMP>.</TD></TR><TR VALIGN="top"><TD>//5</TD><TD>The return value is the stream from which the data was extracted.</TD></TR></TABLE><P>As the extractor accesses private data members of class date, it must be declared as a friend function to class date. The same holds for the inserter. Here's a more complete version of class <SAMP>date</SAMP>:</P><PRE>class date {public: date(int d, int m, int y); date(tm t); date(); // more constructors and useful member functionsprivate: tm tm_date;template<class charT, Traits>friend basic_istream<charT, Traits> &operator >> (basic_istream<charT, Traits >& is, date& dat); template<class charT, Traits>friend basic_ostream<charT, Traits> &operator << (basic_ostream<charT, Traits >& os, const date& dat);};</PRE><P>The inserter can be built analogously, as shown in the following code. The only difference is that you would hand over a <I>constant</I> reference to a date object, because the inserter is not supposed to modify the object it prints.</P><PRE>template<class charT, class Traits>basic_ostream<charT, Traits>&operator << (basic_ostream<charT, Traits >& os, const date& dat){ os << dat.tm_date.tm_mon << '-'; os << dat.tm_date.tm_mday << '-'; os << dat.tm_date.tm_year ; return os;}</PRE><A NAME="2.7.3"><H3>2.7.3 Improved Extractors and Inserters</H3></A><P>The format of dates depends on local and cultural conventions. Naturally, we want our extractor and inserter to parse and format the date according to such conventions. To add this functionality to them, we use the time facet contained in the respective stream's locale as follows:</P><PRE>template<class charT, class Traits>basic_istream<charT, Traits>&operator >> (basic_istream<charT, Traits >& is, date& dat){ ios_base::iostate err = 0; use_facet<time_get<charT,Traits> >(is.getloc()) //1 .get_date(is, istreambuf_iterator<charT,Traits>() //2 ,is, err, &dat.tm_date); //3 return is;}</PRE><TABLE CELLPADDING="3"><TR VALIGN="top"><TD>//1</TD><TD>Use the <SAMP>time_get</SAMP> facet of the input stream's locale to handle parsing of dates according to cultural conventions defined by the locale. The locale in question is obtained through the stream's <SAMP>getloc()</SAMP> function. Its <SAMP>time_get</SAMP> facet is accessed through a call to the global <SAMP>use_facet<..>()</SAMP> function. The type argument to the <SAMP>use_facet</SAMP> function template is the facet type. (See the chapter on internationalization for more details on locales and facets.).</TD></TR><TR VALIGN="top"><TD>//2</TD><TD>The facet's member function <SAMP>get_date()</SAMP> is called. It takes a number of arguments, including:<BR><BR><I>A range of input iterators. </I>For the sake of performance and efficiency, facets directly operate on a stream's buffer. They access the stream buffer through stream buffer iterators. (See the section on stream buffer iterators in the <I>Standard C++ Library User's Guide</I>.) Following the philosophy of iterators in the Standard C++ Library, we must provide a range of iterators. The range extends from the iterator pointing to the first character to be accessed, to the character past the last character to be accessed (the <I>past-the-end-position</I>). <BR><BR>The beginning of the input sequence is provided as a reference to the stream. The <SAMP>istreambuf_iterator</SAMP> class has a constructor taking a reference to an input stream. Therefore, the reference to the stream is automatically converted into an <SAMP>istreambuf_iterator</SAMP> that points to the current position in the stream. As end of the input sequence, an end-of-stream iterator is provided. It is created by the default constructor of class <SAMP>istreambuf_iterator</SAMP>. With these two stream buffer iterators, the input is parsed from the current position in the input stream until a date or an invalid character is found, or the end of the input stream is reached. </TD></TR><TR VALIGN="top"><TD>//3</TD><TD>The other parameters are:<BR><BR><I>Formatting flags. </I>A reference to the <SAMP>ios_base</SAMP> part of the stream is provided here, so that the facet can use the stream's formatting information through the stream's members <SAMP>flags()</SAMP>, <SAMP>precision()</SAMP>, and <SAMP>width()</SAMP>.<BR><I>An iostream state</I>. It is used for reporting errors while parsing the date.<BR><BR><I>A pointer to a time object</I>. It has to be a pointer to an object of type <SAMP>tm</SAMP>, which is the time structure defined by the C library. Our date class maintains such a time structure, so we hand over a pointer to the respective data member <SAMP>tm_date</SAMP>.</TD></TR></TABLE><P>The inserter is built analogously:</P><PRE>template<class charT, class Traits>basic_ostream<charT, Traits>& operator << (basic_ostream<charT, Traits >& os, const date& dat){ use_facet <time_put<charT,ostreambuf_iterator<charT,Traits> > > //1 (os.getloc()) .put(os,os,os.fill(),&dat.tm_date,'x'); //2 return os;}</PRE><TABLE CELLPADDING="3"><TR VALIGN="top"><TD>//1</TD><TD>Here we use the <SAMP>time_put</SAMP> facet of the stream's locale to handle formatting of dates.</TD></TR><TR VALIGN="top"><TD>//2</TD><TD>The facet's <SAMP>put()</SAMP> function takes the following arguments:<BR><BR><I>An output iterator</I>. We use the automatic conversion from a reference to an output stream to an <SAMP>ostreambuf_iterator</SAMP>. This way the output will be inserted into the output stream, starting at the current write position.<BR><BR><I>The formatting flags</I>. Again we provide a reference to the <SAMP>ios_base</SAMP> part of the stream to be used by the facet for retrieving the stream's formatting information.<BR><BR><I>The fill character</I>. We would use the stream's fill character here. Naturally, we could use any other fill character; however, the stream's settings are normally preferred.<BR><BR><I>A pointer to a time structure</I>. This structure will be filled with the result of the parsing.<BR><BR><I>A format specifier</I>. This can be a character, like <SAMP>'x'</SAMP> in our example here, or alternatively, a character sequence containing format specifiers, each consisting of a <SAMP>%</SAMP> followed by a character. An example of such a format specifier string is <SAMP>"%A, %B %d, %Y"</SAMP>. It has the same effect as the format specifiers for the <SAMP>strftime()</SAMP> function in the C library; it produces a date like: <SAMP>Tuesday, June 11, 1996</SAMP>. We don't use a format specifier string here, but simply the character <SAMP>'x'</SAMP>, which specifies that the locale's appropriate date representation shall be used.</TD></TR></TABLE><P>Note how these versions of the inserter and extractor differ from previous simple versions: we no longer rely on existing inserters and extractors for built-in types, as we did when we used <SAMP>operator<<(int)</SAMP> to insert the <SAMP>date</SAMP> object's data members individually. Instead, we use a low-level service like the <SAMP>time</SAMP> facet's <SAMP>get_date()</SAMP> service. The consequence is that we give away all the functionality that high-level services like the inserters and extractors already provide, such as format control, error handling, etc.</P><P>The same happens if you decide to access the stream's buffer directly, perhaps for optimizing your program's runtime efficiency. The stream buffer's services, too, are low-level services that leave to you the tasks of format control, error handling, etc.</P><P>In the following sections, we will explain how you can improve and complete your inserter or extractor if it directly uses low-level components like locales or stream buffers.</P><A NAME="2.7.4"><H3>2.7.4 More Improved Extractors and Inserters</H3></A><P>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.</P><P>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.</P><P>However, the more general question here is: what are inserters and extractors supposed to do? Some recommendations follow.</P><UL><LI><P><B><I>Regarding </I></B><B>format flags</B><B><I>, inserters and extractors should:</I></B></P><UL><LI><P>Create a sentry object right at the beginning of every inserter and extractor. In its constructor and destructor, the sentry performs certain standard tasks, like skipping white characters, flushing tied streams, etc. See the <I>Class Reference</I> for a detailed explanation.</P></LI><LI><P>Reset the width after each usage.</P></LI></UL></LI><LI><P><B><I>Regarding </I></B><B>state bits</B><B><I>, inserters and extractors should:</I></B></P><UL><LI><P>Set <SAMP>badbit</SAMP> for all problems with the stream buffer.</P></LI><LI><P>Set <SAMP>failbit</SAMP> if the formatting or parsing itself fails.</P></LI><LI><P>Set <SAMP>eofbit</SAMP> when the end of the input sequence is reached.</P></LI></UL></LI><LI><P><B><I>Regarding the </I></B><B>exception mask</B><B><I>, inserters and extractors should:</I></B></P><UL><LI><P>Use the <SAMP>setstate()</SAMP> function for setting the stream's error state. It automatically throws the <SAMP>ios_base::failure</SAMP> exception according to the exceptions switch in the stream's exception mask.</P></LI><LI><P>Catch exceptions thrown during the parsing or formatting, set <SAMP>failbit </SAMP>or <SAMP>badbit</SAMP>, and rethrow the <I>original</I> exception.</P></LI></UL></LI><LI><P><B><I>Regarding </I></B><B>locales</B><B><I>, inserters and extractors should:</I></B></P><UL><LI><P>Use the stream's locale, not the stream buffer's locale. The stream buffer's locale is supposed to be used solely for code conversion. Hence, imbuing a stream with a new locale will only affect the stream's locale and never the stream buffer's locale.</LISTFN></LI></UL></LI><LI><P><B><I>Regarding the </I></B><B>stream buffer</B><B><I>:</I></B></P><UL><LI><P>If you use a sentry object in your extractor or inserter, you should not call any functions from the formatting layer. This would cause a dead-lock in a multithreading situation, since the sentry object locks the stream through the stream's <I>mutex</I> (= mutual exclusive lock). A nested call to one of the stream's member functions would again create a sentry object, which would wait for the same mutually exclusive lock and, voil_, you have deadlock. Use the stream buffer's functions instead. They do not use the stream's mutex, and are more efficient anyway.</P></LI></UL></LI></UL><HR><STRONG><P><I>Please note:</I> 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.</P></STRONG><HR><A NAME="2.7.4.1"><H4>2.7.4.1 Applying the Recommendations to the Example</H4></A><P>Let us now go back and apply the recommendations to the extractor and inserter for class <SAMP>date</SAMP> in the example we have been constructing. Here is an improved version of the extractor:</P><PRE>template<class charT, class Traits>basic_istream<charT, Traits>& operator >> (basic_istream<charT, Traits >& is, date& dat){ ios_base::iostate err = 0; //1
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -