📄 cre_2288.htm
字号:
<HTML><HEAD><TITLE>2.12 Creating New Stream Classes by Derivation</TITLE></HEAD><BODY><A HREF="ug2.htm"><IMG SRC="images/banner.gif"></A><BR><A HREF="str_5412.htm"><IMG SRC="images/prev.gif"></A><A HREF="booktoc2.htm"><IMG SRC="images/toc.gif"></A><A HREF="def_8655.htm"><IMG SRC="images/next.gif"></A><BR><STRONG>Click on the banner to return to the user guide home page.</STRONG><H2>2.12 Creating New Stream Classes by Derivation</H2><P>Sometimes it is useful to derive a stream type from the standard iostreams. This is the case when you want to add data members or functions, or modify the behavior of a stream's I/O operations.</P><P>In <A HREF="str_5412.htm">Section 2.11</A>, we learned that additional data can be added to a stream object by using <SAMP>xalloc()</SAMP>, <SAMP>iword()</SAMP>, and <SAMP>pword()</SAMP>. However, this solution has a certain weakness in that only a pointer to the additional data can be stored and someone else has to worry about the actual memory.</P><P>This weakness can be overcome by deriving a new stream type that stores the additional data as a data member. Let's consider again the example of the <SAMP>date</SAMP> inserter and the <SAMP>setfmt</SAMP> manipulator from <A HREF="str_5412.htm">Section 2.11</A>. Here let's derive a new stream that has an additional data member for storing the format string together with a corresponding member function for setting the date format specification.<A HREF="endnote2.htm#fn41">[41]</A> Again, we will confine the example to the inserter of the <SAMP>date</SAMP> object and omit the extractor. Instead of inserting into an output stream, as we did before, we will now use a new type of stream called <SAMP>odatstream</SAMP>:</P><PRE>date today;odatstream ostr(cout);// _ostr << setfmt("%D") << today;</PRE><P>In the next sections, we will explore how we can implement such a derived stream type.</P><A NAME="2.12.1"><H3>2.12.1 Choosing a Base Class</H3></A><P>The first question is: Which of the standard stream classes shall be the base class? The answer fully depends on the kind of addition or modification you want to make. In our case, we want to add formatting information, which depends on the stream's character type since the format string is a sequence of tiny characters. As we will see later on, the format string must be expanded into a sequence of the stream's character type for use with the stream's locale. Consequently, a good choice for a base class is class <SAMP>basic_iostream <charT,Traits></SAMP>, and since we want the format string to impact only output operations, the best choice is class <SAMP>basic_ostream <charT,Traits></SAMP>. </P><P>In general, you choose a base class by looking at the kind of addition or modification you want to make and comparing it with the characteristics of the stream classes.</P><UL><LI><P>Choose <SAMP>ios_base</SAMP> if you add information and services that do not depend on the stream's character type.</P></LI><LI><P>Choose <SAMP>basic_ios<charT, Traits></SAMP> if the added information does depend on the character type, or requires other information not available in <SAMP>ios_base</SAMP>, such as the stream buffer.</P></LI><LI><P>Derive from the stream classes <SAMP>basic_istream <charT,Traits></SAMP>, <SAMP>basic_ostream <charT,Traits></SAMP>, or <SAMP>basic_iostream <charT, Traits></SAMP> if you want to add or change the input and output operations.</P></LI><LI><P>Derive from the stream classes <SAMP>basic_(i/o)fstream <charT,Traits></SAMP>, or <SAMP>basic_(i/o)stringstream <charT, Traits, Allocator></SAMP> if you want to add or modify behavior that is file- or string-related, such as the way a file is opened. </P></LI></UL><P>Derivations from <SAMP>basic_istream <charT,Traits></SAMP>, <SAMP>basic_ostream <charT,Traits></SAMP>, or <SAMP>basic_iostream <charT, Traits></SAMP> are the most common cases, because you typically want to modify or add input and output operations. </P><P>If you derive from <SAMP>ios_base</SAMP> or <SAMP>basic_ios<charT, Traits>,</SAMP> you do not inherit any input and output operations; you do this if you really want to reimplement all of them or intend to implement a completely different kind of input or output operation, such as unformatted binary input and output.</P><P>Derivations from file or string streams such as <SAMP>basic_(i/o)fstream <charT,Traits></SAMP> or <SAMP>basic_(i/o)stringstream <charT, Traits, Allocator></SAMP> are equally rare, because they make sense only if file- or string-related data or services must be added or modified.</P><HR><STRONG><P>Choose <SAMP>basic_istream <charT,Traits></SAMP>, <SAMP>basic_ostream <charT,Traits></SAMP>, or <SAMP>basic_iostream <charT, Traits></SAMP> as a base class when deriving new stream classes, unless you have good reason not to do so.</P></STRONG><HR><A NAME="2.12.2"><H3>2.12.2 Construction and Initialization</H3></A><P>All standard stream classes have class <SAMP>basic_ios<charT,Traits></SAMP> as a virtual base class. In C++, a virtual base class is initialized by its most derived class; i.e., our new <SAMP>odatstream</SAMP> class is responsible for initialization of its base class <SAMP>basic_ios<charT,Traits></SAMP>. Now class <SAMP>basic_ios<charT,Traits></SAMP> has only one public constructor, which takes a pointer to a stream buffer. This is because class <SAMP>basic_ios<charT,Traits></SAMP> contains a pointer to the stream buffer, which has to be initialized when a <SAMP>basic_ios</SAMP> object is constructed. Consequently, we have to figure out how to provide a stream buffer to our base class. Let's consider two options:</P><UL><LI><P>Derivation from file stream or string stream classes; i.e., class <SAMP>(i/o)fstream<></SAMP> or class <SAMP>(i/o)stringstream<></SAMP>, and </P></LI><LI><P>Derivation from the stream classes <SAMP>basic_(i/o)stream<></SAMP>. </P></LI></UL><A NAME="2.12.2.1"><H4>2.12.2.1 Derivation from File Stream or String Stream Classes Like <SAMP>(i/o)fstream<></SAMP> or <SAMP>(i/o)stringstream<></SAMP></H4></A><P>The file and string stream classes contain a stream buffer data member and already monitor their virtual base class's initialization by providing the pointer to their own stream buffer. If we derive from one of these classes, we will not provide another stream buffer pointer because it would be overwritten by the file or string stream's constructor anyway. (Remember that virtual base classes are constructed before non-virtual base classes regardless of where they appear in the hierarchy.) Consider:</P><PRE>template <class charT, class Traits=char_traits<charT> >class MyOfstream : public basic_ofstream<charT,Traits> { public: MyOfstream(const char* name) : basic_ios<charT,Traits>(_<I>streambufptr_</I>) , basic_ofstream<charT,Traits>(name) {} // . . .};</PRE><P>The order of construction would be:</P><PRE>basic_ios(basic_streambuf<charT,Traits>*)basic_ofstream(const char*)basic_ostream(basic_streambuf<charT,Traits>*)ios_base()</PRE><P>In other words, the constructor of <SAMP>basic_ofstream</SAMP> overwrites the stream buffer pointer set by the constructor of <SAMP>basic_ios</SAMP>.</P><P>To avoid this dilemma, class <SAMP>basic_ios<charT,Traits></SAMP> has a protected default constructor in addition to its public constructor. This default constructor, which requires a stream buffer pointer, doesn't do anything. Instead, there is a protected initialization function <SAMP>basic_ios<charT,Traits>::init()</SAMP> that can be called by any class derived from <SAMP>basic_ios<charT,Traits></SAMP>. With this function, initialization of the <SAMP>basic_ios<></SAMP> base class is handled by the stream class that actually provides the stream buffer--in our example, <SAMP>basic_ofstream<charT,Traits></SAMP>. It will call the protected <SAMP>init()</SAMP> function:</P><PRE>template <class charT, class Traits=char_traits<charT> >class MyOfstream : public basic_ofstream<charT,Traits> { public: MyOfstream(const char* name) : basic_ofstream<charT,Traits>(name) {} // . . .};</PRE><P>The order of construction and initialization is:</P><PRE>basic_ios()basic_ofstream(const char*)basic_ostream()which calls: basic_ios<charT,Traits>::init(basic_streambuf<charT,Traits>*)ios_base()</PRE><A NAME="2.12.2.2"><H4>2.12.2.2 Derivation from the Stream Classes <SAMP>basic_(i/o)stream<></SAMP></H4><P>The scheme for deriving from the stream classes is slightly different in that you must always provide a pointer to a stream buffer. This is because the stream classes do not contain a stream buffer, as the file or string stream classes do. For example, a class derived from an output stream could look like this:</P><PRE>template <class charT, class Traits=char_traits<charT> >class MyOstream : public basic_ostream<charT,Traits> { public: MyOstream(basic_streambuf<charT,Traits>* sb) : basic_ostream<charT,Traits>(sb) {} // . . .};</PRE><P>There are several ways to provide the stream buffer required for constructing such a stream:</P><UL><LI><P><B>Create the stream buffer independently, before the stream is created.</B> Here is a simple example in which a file buffer is created as a separate object and used by the derived stream:</P><PRE> basic_filebuf<char> strbuf; strbuf.open("/tmp/xxx"); MyOstream<char> mostr(&strbuf); mostr << "Hello world\n";</PRE></LI><LI><P><B>Take the stream buffer from another stream.</B> In the example below, the stream buffer is "borrowed" from the standard error stream <SAMP>cerr</SAMP>:</P><PRE> MyOstream<char,char_traits<char> > mostr(cerr.rdbuf()); mostr << "Hello world\n";</PRE><P>Remember that the stream buffer is now shared between <SAMP>mostr</SAMP> and <SAMP>cerr</SAMP> (see <A HREF="str_0182.htm#2.9.2">Section 2.9.2</A> for details).</P></LI><LI><P><B>Contain the stream buffer in the derived stream, either as a data member or inherited</B>. This option is typically preferred when a new stream buffer type is used along with the new stream type.</P></LI></UL><A NAME="2.12.3"><H3>2.12.3 The Example</H3></A><P>Let's return now to our example, in which we are creating a new stream class by derivation.</P><A NAME="2.12.3.1"><H4>2.12.3.1 The Derived Stream Class</H4></A><P>Let us derive a new stream type <SAMP>odatstream</SAMP> that has an additional data member <SAMP>fmt_</SAMP> for storing a date format string, together with a corresponding member function <SAMP>fmt()</SAMP> for setting the date format specification. </P><PRE>template <class charT, class Traits=char_traits<charT> >class odatstream : public basic_ostream <charT,Traits>{lpublic: odatstream(basic_ostream<charT,Traits>& ostr,const char* fmt = "%x") \\1 : basic_ostream<charT,Traits>(ostr.rdbuf()) { fmt_=new charT[strlen(fmt)]; use_facet<ctype<charT> >(ostr.getloc()). widen(fmt, fmt+strlen(fmt), fmt_); \\2 } basic_ostream<charT,Traits>& fmt(const char* f) \\3 { delete[] fmt_; fmt_=new charT[strlen(f)]; use_facet<ctype<charT> >(os.getloc()). widen(f, f+strlen(f), fmt_); return *this; } charT const* fmt() const \\4 { charT * p = new charT[Traits::length(fmt_)]; Traits::copy(p,fmt_,Traits::length(fmt_)); return p; } ~odatstream() \\5 { delete[] fmt_; } private: charT* fmt_; \\6 template <class charT, class Traits> \\7 friend basic_ostream<charT, Traits> & operator << (basic_ostream<charT, Traits >& os, const date& dat);};</PRE><TABLE CELLPADDING="3"><TR VALIGN="top"><TD>//1</TD><TD>A date output stream borrows the stream buffer of an already existing output stream, so that the two streams will share the stream buffer.<BR><BR>The constructor also takes an optional argument, the date format string. This is always a sequence of tiny characters.</TD></TR><TR VALIGN="top"><TD>//2</TD><TD>The format string is widened or translated into the stream's character type <SAMP>charT</SAMP>. This is because the format string will be provided to the time facet of the stream's locale, which expects an array of characters of type <SAMP>charT</SAMP>.</TD></TR><TR VALIGN="top"><TD>//3</TD><TD>This version of function <SAMP>fmt()</SAMP> allows you to set the format string. </TD></TR><TR VALIGN="top"><TD>//4</TD><TD>This version of function <SAMP>fmt()</SAMP> returns the current format string setting.</TD></TR><TR VALIGN="top"><TD>//5</TD><TD>The date stream class needs a destructor that deletes the format string.</TD></TR><TR VALIGN="top"><TD>//6</TD><TD>A pointer to the date format specification is stored as a private data member <SAMP>fmt_</SAMP>.</TD></TR><TR VALIGN="top"><TD>//7</TD><TD>The inserter for dates will have to access the date format specification. For this reason, we make it a friend of class <SAMP>odatstream</SAMP>.</TD></TR></TABLE><A NAME="2.12.3.2"><H4>2.12.3.2 The Date Inserter</H4></A><P>We would like to be able to insert <SAMP>date</SAMP> objects into all kinds of output streams. And, whenever the output stream is a date output stream of type <SAMP>odatstream</SAMP>, we would like to take advantage of its ability to carry additional information for formatting date output. How can this be achieved?</P><P>It would be ideal if the inserter for <SAMP>date</SAMP> objects were a virtual member function of all output stream classes that we could implement differently for different types of output streams. For example, when a date object is inserted into an <SAMP>odatstream,</SAMP> the formatting would use the available date formatting string; when inserted into an arbitrary output stream, default formatting would be performed. Unfortunately, we cannot modify the existing output stream classes, since they are part of a library you will not want to modify.</P><P>This kind of problem is typically solved using dynamic casts. Since the stream classes have a virtual destructor, inherited from class <SAMP>basic_ios</SAMP>, we can use dynamic casts to achieve the desired virtual behavior.<A HREF="endnote2.htm#fn42">[42]</A> </P><P>Here is the implementation of the date inserter, which is similar to the one in <A HREF="inp_5394.htm#2.7.2">Section 2.7.2</A>. The differences are shaded:</P><PRE>template<class charT, class Traits>basic_ostream<charT, Traits> &operator << (basic_ostream<charT, Traits >& os, const date& dat){ ios_base::iostate err = 0; try { typename basic_ostream<charT, Traits>::sentry opfx(os); if(opfx) { charT* fmt;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -