man_6665.htm
来自「C++标准库 C++标准库 C++标准库 C++标准库」· HTM 代码 · 共 438 行 · 第 1/2 页
HTM
438 行
<HTML><HEAD><TITLE>2.8 Manipulators</TITLE></HEAD><BODY><A HREF="ug2.htm"><IMG SRC="images/banner.gif"></A><BR><A HREF="inp_5394.htm"><IMG SRC="images/prev.gif"></A><A HREF="booktoc2.htm"><IMG SRC="images/toc.gif"></A><A HREF="str_0182.htm"><IMG SRC="images/next.gif"></A><BR><STRONG>Click on the banner to return to the user guide home page.</STRONG><H2>2.8 Manipulators</H2><P>We have seen examples of manipulators in <A HREF="for_5394.htm#2.3.3.2">Section 2.3.3.2</A>. There we learned that:</P><OL><LI><P>Manipulators are objects that can be inserted into or extracted from a stream, and</P></LI><LI><P>Such insertions and extractions have specific desirable side effects.</P></LI></OL><P>As a recap, here is a typical example of two manipulators:</P><PRE>cout << setw(10) << 10.55 << endl;</PRE><P>The inserted objects <SAMP>setw(10)</SAMP> and <SAMP>endl</SAMP> are the manipulators. As a side effect, the manipulator <SAMP>setw(10)</SAMP> sets the stream's field width to <SAMP>10</SAMP>. Similarly, the manipulator <SAMP>endl</SAMP> inserts the end of line character and flushes the output. </P><P>As we have mentioned previously, extensibility is a major advantage of iostreams. We've seen in the previous section how you can implement inserters and extractors for user-defined types that behave like the built-in input and output operations. Additionally, you can add user-defined manipulators that fit seamlessly into the iostreams framework. In this section, we will see how to do this.</P><P>First of all, to be extracted or inserted, a manipulator must be an object of a type that we call <SAMP>manipT</SAMP>, for which overloaded versions of the shift operators exist. (Associated with the manipulator type <SAMP>manipT,</SAMP> there is usually a function that we will call <SAMP>fmanipT()</SAMP>that we will explain in detail later.) Here's the pattern for the manipulator extractor:</P><PRE>template <class charT, class Traits>basic istream<charT,Traits>&operator>> (basic istream<charT,Traits>& istr ,const manipT& manip){ return fmanipT(istr, _); }</PRE><P>With this extractor defined, you can extract a manipulator <SAMP>Manip</SAMP>, which is an object of type <SAMP>manipT</SAMP>, by simply saying: </P><PRE>cin >> Manip;</PRE><P>This results in a call to the <SAMP>operator>>()</SAMP> sketched out above. The manipulator inserter is analogous.</P><P>A manipulator's side effect is often created by calling an associated function <IMG SRC="images/inline5.gif"> that takes a stream and returns the stream. There are several ways to associate the manipulator type <SAMP>manipT</SAMP> to the function <IMG SRC="images/inline5.gif"> that we will explore in the subsequent sections. The iostream framework does not specify a way to implement manipulators, but there is a base class called <SAMP>smanip</SAMP> that you can use for deriving your own manipulators. We will explain this technique along with other useful approaches. </P><P>It will turn out that there is a major difference between manipulators with parameters like <SAMP>width(10)</SAMP> and manipulators without parameters like <SAMP>endl</SAMP>. Let's start with the simpler case of manipulators without parameters.</P></LI></OL><A NAME="2.8.1"><H3>2.8.1 Manipulators without Parameters</H3></A><P>Manipulators that do not have any parameters, like <SAMP>endl</SAMP>, are the simplest form of manipulator. The manipulator type <SAMP>manipT</SAMP> is a function pointer type, the manipulator <SAMP>Manip</SAMP> is the function pointer, and the associated function <IMG SRC="images/inline5.gif"> is the function pointed to.</P><P>In iostreams, the following function pointer types serve as manipulators:</P><PRE>(1) ios_base& (*pf)(ios_base&)(2) basic_ios<charT,Traits>& (*pf)(basic_ios<charT,Traits>)(3) basic_istream<charT,Traits>& (*pf)(basic_istream<charT,Traits>)(4) basic_ostream<charT,Traits>& (*pf)(basic_ostream<charT,Traits>)</PRE><P>The signature of a manipulator function is not limited to the examples above. If you have created your own stream types, you will certainly want to use additional signatures as manipulators.</P><P>For the four manipulator types listed above, the stream classes already offer the required overloaded inserters and member functions. For input streams, extractors take the following form:</P><PRE>template<class charT, class traits>basic_istream<charT, traits>&basic_istream<charT, traits>::operator>>(basic_istream<charT,traits>& (*pf)(<B><I>input_stream_type</I></B>&) ){ return (*pf)(*this);..}</PRE><P>where <B><I>input_stream_type</I></B> is one of the function pointer types (1)-(3).</P><P>Similarly, for output streams we have:</P><PRE>template<class charT, class traits>basic_ostream<charT, traits>&basic_ostream<charT, traits>::operator<<(basic_ostream<charT, traits>& (*pf)(<B><I>output_stream_type</I></B>& )){ return (*pf)(*this); }</PRE><P>where <B><I>output_stream_type</I></B> is one of the function pointer types (1), (2), or (4).</P><A NAME="2.8.1.1"><H4>2.8.1.1 Examples of Manipulators without Parameters</H4></A><P>Let's look at the manipulator <SAMP>endl</SAMP> as an example of a manipulator without parameters. The manipulator <SAMP>endl</SAMP>, which can be applied solely to output streams, is a pointer to the following function of type (4):</P><PRE>template<class charT, class traits>inline basic_ostream<charT, traits>&endl(basic_ostream<charT, traits>& os){ os.put( os.widen('\n') ); os.flush(); return os;}</PRE><P>Hence an expression like: </P><PRE>cout << endl; </PRE><P>results in a call to the inserter:</P><P><SAMP>ostream& ostream::operator<< (ostream& (*pf)(ostream&))</SAMP> </P><P>with<SAMP> endl</SAMP> as the actual argument for <SAMP>pf</SAMP>. In other words, <SAMP>cout << endl;</SAMP> is equal to <SAMP>cout.operator<<(endl);</SAMP></P><P>Here is another manipulator, <SAMP>boolalpha</SAMP>, that can be applied to input <I>and</I> output streams. The manipulator <SAMP>boolalpha</SAMP> is a pointer to a function of type <SAMP>(1)</SAMP>:</P><PRE>ios_base& boolalpha(ios_base& strm){ strm.setf(ios_base::boolalpha); return strm;} </PRE><HR><STRONG><P><I>Summary:</I> Every function that takes a reference to an <SAMP>ios_base</SAMP>, a <SAMP>basic_ios</SAMP>, a <SAMP>basic_ostream</SAMP>, or a <SAMP>basic_istream</SAMP>, and returns a reference to the same stream, can be used as a parameter-less manipulator.</P></STRONG><HR><A NAME="2.8.1.2"><H4>2.8.1.2 A Remark on the Manipulator endl</H4></A><P>The manipulator<SAMP> endl</SAMP> is often used for inserting the end-of-line character into a stream. However, <SAMP>endl</SAMP> does additionally flush the output stream, as you can see from the implementation of <SAMP>endl</SAMP> shown above. Flushing a stream, a time-consuming operation that decreases performance, is unnecessary in most common situations. In the standard example:</P><PRE>cout << "Hello world" << endl;</PRE><P>flushing is not necessary because the standard output stream <SAMP>cout</SAMP> is tied to the standard input stream <SAMP>cin</SAMP>, so input and output to the standard streams are synchronized anyway. Since no flush is required, the intent is probably to insert the end-of-line character. If you consider typing <SAMP>'\n'</SAMP> more trouble than typing <SAMP>endl</SAMP>, you can easily add a simple manipulator <SAMP>nl</SAMP> that inserts the end-of-line character, but refrains from flushing the stream. </P><A NAME="2.8.2"><H3>2.8.2 Manipulators with Parameters</H3></A><P>Manipulators with parameters are more complex than those without because there are additional issues to consider. Before we explore these issues in detail and examine various techniques for implementing manipulators with parameters, let's take a look at one particular technique, the one that is used to implement standard manipulators such as <SAMP>setprecision(),</SAMP> <SAMP>setw()</SAMP>, etc.</P><A NAME="2.8.2.1"><H4>2.8.2.1 The Standard Manipulators</H4></A><P>Rogue Wave's implementation of the standard iostreams uses a certain technique for implementing most standard manipulators with parameters: the manipulator type <SAMP>manipT</SAMP> is a function pointer type; the manipulator object is the function pointed to; and the associated function <IMG SRC="images/inline5.gif"> is a global function. </P><P>The C++ standard defines the manipulator type as <SAMP>smanip</SAMP>. The type itself is implementation-defined; all you know is that it is returned by some of the standard manipulators. In Rogue Wave's implementation, <SAMP>smanip</SAMP> is a class template:</P><PRE>template<class T>class smanip { public: smanip(ios_base& (*pf)(ios_base&, T), T manarg);};</PRE><P>A standard manipulator like <SAMP>setprecision()</SAMP> can be implemented as a global function returning an object of type <SAMP>smanip<T></SAMP>:</P><PRE>inline smanip<int> setprecision(int n){ return smanip<int>(sprec, n); }</PRE><P>The associated function <IMG SRC="images/inline5.gif"> is the global function<SAMP> sprec</SAMP>:</P><PRE>inline ios_base& sprec(ios_base& str, int n){ str.precision(n); return str;}</PRE><A NAME="2.8.2.2"><H4>2.8.2.2 The Principle of Manipulators with Parameters</H4></A><P>The previous section gave an example of a technique for implementing a manipulator with one parameter: the technique used to implement the standard manipulators in iostreams. However, this is not the only way to implement a manipulator with parameters. In this section, we examine other techniques. Although all explanations are in terms of manipulators with one parameter, it is easy to extend the techniques to manipulators with several parameters.</P><P>Let's start right at the beginning: what is a manipulator with a parameter?</P><P>A manipulator with a parameter is an object that can be inserted into or extracted from a stream in an expression like:</P><PRE>cout << Manip(x);cin >> Manip(x);</PRE><P><SAMP>Manip(x)</SAMP> must be an object of type <SAMP>manipT</SAMP>, for which the shift operators are overloaded. Here's an example of the corresponding inserter:</P><PRE>template <class charT, class Traits>basic ostream<charT,Traits>&operator<< (basic ostream<charT,Traits>& ostr ,const manipT& manip){ // call the associated function <IMG SRC="images/inline5.gif">, e.g<SAMP>.</SAMP> (*manip.<IMG SRC="images/inline5.gif"> )(ostr,manip.argf); return os;}</PRE><P>With this inserter defined, the expression <SAMP>cout << Manip(x);</SAMP> is equal to a call to the shift operator sketched above; i.e., <SAMP>operator<<(cout, Manip(x));</SAMP></P><P>Assuming that a side effect is created by an associated function <IMG SRC="images/inline5.gif">, the manipulator must call the associated function with its respective argument(s). Hence it must store the associated function together with its argument(s) for a call from inside the shift operator. </P><P>The associated function <IMG SRC="images/inline5.gif"> can be a static or a global function, or a member function of type <SAMP>manipT</SAMP>, for example.</P><P>In the inserter above, we've assumed that the associated function <IMG SRC="images/inline5.gif"> is a static or a global function, and that it takes exactly one argument. Generally, the manipulator type <SAMP>manipT</SAMP> might look like this:</P><PRE>template <class FctPtr, class Arg1, class Arg2, _>class manipT{ public: manipT(FctPtr, Arg1, Arg2, _); private: FctPtr fp_; Arg1 arg1_; Arg2 arg2_;};</PRE><P>Note that this is only a suggested manipulator, however. In principle, you can define the manipulator type in any way that makes the associated side effect function and its arguments available for a call from inside the respective shift operators for the manipulator type. We will see other examples of such manipulator types later in this section; for instance, a manipulator type called <SAMP>smanip</SAMP> defined by the C++ standard. It is an implementation-defined function type returned by the standard manipulators. See the <I>Class Reference </I>for details.</P><P>Returning now to the example above, the manipulator object provided as an argument to the overloaded shift operator is obtained by <SAMP>Manip(x)</SAMP>, which has three possible solutions:</P><OL><LI><P><SAMP>Manip(x)</SAMP> is a function call. In this case, <SAMP>Manip</SAMP> would be the name of a function that takes an argument of type <SAMP>x</SAMP> and returns a manipulator object of type <SAMP>manipT</SAMP>; i.e., <SAMP>Manip</SAMP> is a function with the following signature: </P><PRE>manipT Manip (X x);</PRE></LI><LI><P><SAMP>Manip(x)</SAMP> is a constructor call. In this case, <SAMP>Manip</SAMP> would be the name of a class with a constructor that takes an argument of type <SAMP>X</SAMP> and constructs a manipulator object of type <SAMP>Manip</SAMP>; i.e., <SAMP>Manip</SAMP> and <SAMP>manipT</SAMP> would be identical:</P><PRE>class Manip { public: Manip(X x);};</PRE></LI><LI><P><SAMP>Manip(x)</SAMP> is a call to a function object. In this case, <SAMP>Manip</SAMP> would be an object of a class <SAMP>M</SAMP>, which defines a function call operator that takes an argument of type <SAMP>x</SAMP> and returns a manipulator object of type <SAMP>manipT</SAMP>:</P><PRE>class M { public: manipT operator()(X x);} Manip;</PRE></LI></OL><P>Solutions (1) and (2) are semantically different from solution (3). In solution (1), <SAMP>Manip</SAMP> is a function and therefore need not be created by the user. In solution (2), <SAMP>Manip</SAMP> is a class name and an unnamed temporary object serves as manipulator object. In solution (3), however, the manipulator object <SAMP>Manip</SAMP> must be explicitly created by the user. Hence the user has to write:</P><PRE>manipT Manip;cout << Manip(x);</PRE><P>which is somewhat inconvenient because it forces the user to know an additional name, the manipulator type <SAMP>manipT</SAMP>, and to create the manipulator object <SAMP>Manip</SAMP>.<A HREF="endnote2.htm#fn31">[31]</A> For these reasons, solution (3) is useful if the manipulator has <I>state</I>; i.e., if it stores additional data like a manipulator, let call it <SAMP>lineno</SAMP>, which provides the next line number each time it is inserted.</P><P>The problem with solution (2) is that several current compilers cannot handle unnamed objects.</P><P>For any of the three solutions just discussed, there is also a choice of associated functions. The associated function <IMG SRC="images/inline5.gif"> can be either:</P><UL><P>a) A static or a global function;</P><P>b) A static member function;</P><P>c) A virtual member function.</P></UL><P>Among these choices, (b), i.e. use of a static member function, is the preferable in an object-oriented program because it permits encapsulation of the manipulator together with its associated function. This is particularly recommended if the manipulator has <I>state</I>, as in solution (3), where the manipulator is a function object, and the associated function has to access the manipulator's state. </P><P>Using ( c), i.e. a virtual member function, introduces the overhead of a virtual function call each time the manipulator is inserted or extracted. It is useful if the manipulator has state, and the state needs to be modified by the associated manipulator function. A static member function would only be able to access the manipulator's static data; a non-static member function, however, can access the object-specific data.</P><A NAME="2.8.2.3"><H4>2.8.2.3 Examples of Manipulators with Parameters</H4></A><P>In this section, let's look at some examples of manipulators with parameters. The examples here are arbitrary combinations of solutions (1) to (3) for the manipulator type, with (a) to (c) for the associated function. We also use the standard manipulator <SAMP>setprecision()</SAMP>to demonstrate the various techniques.</P><P><B>Example 1: Function Pointer and Global Function.</B> This example combines (1) and (c), and so:</P><UL><LI><P><SAMP>manipT</SAMP> is a function pointer type;</P></LI><LI><P>The manipulator object is the function pointed to; and </P></LI><LI><P>The associated function <IMG SRC="images/inline5.gif"> is a global function. </P></LI></UL><P>Rogue Wave's implementation of the standard iostreams uses this technique for implementing most standard manipulators with parameters. See <A HREF="man_6665.htm#2.8.2.1">Section 2.8.2.1</A> for reference.</P><P><B>Example 2: Unnamed Object and Static Member Function.</B> This example combines (2) and (b), and thus:</P><UL><LI><P>The manipulator object <SAMP>Manip</SAMP> is an unnamed object; </P></LI><LI><P>The manipulator type <SAMP>manipT</SAMP> is a class; and</P></LI><LI><P>The associated function <IMG SRC="images/inline5.gif"> is a static member function.</P></UL><P>The manipulator type <SAMP>manipT</SAMP> can be derived from the manipulator type <SAMP>smanip</SAMP> defined by iostreams. Here is an alternative implementation of a manipulator like <SAMP>setprecision()</SAMP>:</P><PRE>class setprecision : public smanip<int> { public: setprecision(int n) : smanip<int>(sprec_, n) { } private: static ios_base& sprec_(ios_base& str, int n) { str.precision(n); return str; }};</PRE><P><B>Example 3: Unnamed Object and Virtual Member Function.</B> This example (2) and (c), and therefore:</P>
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?