chap02.htm.kbk

来自「c++设计思想」· KBK 代码 · 共 1,047 行 · 第 1/5 页

KBK
1,047
字号
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">This highlights what could be seen as a
flaw in the STL: there&#8217;s no facility in any of the STL containers to
automatically <B>delete</B> the pointers they contain, so you must do it by
hand. It&#8217;s as if the assumption of the STL designers was that containers
of pointers weren&#8217;t an interesting problem, although I assert that it is
one of the more common things you&#8217;ll want to do.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Automatically deleting a pointer turns
out to be a rather aggressive thing to do because of the <I>multiple
membership</I> problem. If a container holds a pointer to an object, it&#8217;s
not unlikely that pointer could also be in another container. A pointer to an
<B>Aluminum</B> object in a list of <B>Trash</B> pointers could also reside in a
list of <B>Aluminum</B> pointers. If that happens, which list is responsible for
cleaning up that object &#8211; that is, which list &#8220;owns&#8221; the
object?</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">This question is virtually eliminated if
the object rather than a pointer resides in the list. Then it seems clear that
when the list is destroyed, the objects it contains must also be destroyed.
Here, the STL shines, as you can see when creating a container of <B>string</B>
objects. The following example stores each incoming line as a <B>string</B> in a
<B>vector&lt;string&gt;</B>:</FONT><BR></P></DIV>

<BLOCKQUOTE><FONT SIZE = "+1"><PRE><font color=#009900>//: C07:StringVector.cpp</font>
<font color=#009900>// A vector of strings</font>
<font color=#009900>//{L} ../TestSuite/Test</font>
#include <font color=#004488>"..</font><font color=#004488>/require.h"</font>
#include &lt;string&gt;
#include &lt;vector&gt;
#include &lt;fstream&gt;
#include &lt;iostream&gt;
#include &lt;iterator&gt;
#include &lt;sstream&gt;
<font color=#0000ff>using</font> <font color=#0000ff>namespace</font> std;

<font color=#0000ff>int</font> main(<font color=#0000ff>int</font> argc, <font color=#0000ff>char</font>* argv[]) {
  <font color=#0000ff>char</font>* fname = <font color=#004488>"StringVector.cpp"</font>;
  <font color=#0000ff>if</font>(argc &gt; 1) fname = argv[1];
  ifstream in(fname);
  assure(in, fname);
  vector&lt;string&gt; strings;
  string line;
  <font color=#0000ff>while</font>(getline(in, line))
    strings.push_back(line);
  <font color=#009900>// Do something to the strings...</font>
  <font color=#0000ff>int</font> i = 1;
  vector&lt;string&gt;::iterator w;
  <font color=#0000ff>for</font>(w = strings.begin();
      w != strings.end(); w++) {
    ostringstream ss;
    ss &lt;&lt; i++;
    *w = ss.str() + <font color=#004488>": "</font> + *w;
  }
  <font color=#009900>// Now send them out:</font>
  copy(strings.begin(), strings.end(),
    ostream_iterator&lt;string&gt;(cout, <font color=#004488>"\n"</font>));
  <font color=#009900>// Since they aren't pointers, string </font>
  <font color=#009900>// objects clean themselves up! </font>
} <font color=#009900>///:~</font></PRE></FONT></BLOCKQUOTE>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Once the <B>vector&lt;string&gt;
</B>called <B>strings</B> is created, each line in the file is read into a
<B>string</B> and put in the <B>vector</B>:</FONT><BR></P></DIV>

<BLOCKQUOTE><FONT SIZE = "+1"><PRE>  <font color=#0000ff>while</font>(getline(in, line))
    strings.push_back(line);</PRE></FONT></BLOCKQUOTE>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">The operation that&#8217;s being
performed on this file is to add line numbers. A <B>stringstream</B> provides
easy conversion from an <B>int</B> to a <B>string</B> of characters representing
that <B>int</B>.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Assembling <B>string</B> objects is quite
easy, since <B>operator+</B> is overloaded. Sensibly enough, the iterator
<B>w</B> can be dereferenced to produce a string that can be used as both an
rvalue <I>and</I> an lvalue:</FONT><BR></P></DIV>

<BLOCKQUOTE><FONT SIZE = "+1"><PRE>*w = ss.str() + <font color=#004488>": "</font> + *w;</PRE></FONT></BLOCKQUOTE>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">The fact that you can assign back into
the container via the iterator may seem a bit surprising at first, but
it&#8217;s a tribute to the careful design of the STL.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Because the <B>vector&lt;string&gt;</B>
contains the objects themselves, a number of interesting things take place.
First, no cleanup is necessary. Even if you were to put addresses of the
<B>string</B> objects as pointers into <I>other</I> containers, it&#8217;s clear
that <B>strings</B> is the &#8220;master list&#8221; and maintains ownership of
the objects.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Second, you are effectively using dynamic
object creation, and yet you never use <B>new</B> or <B>delete</B>! That&#8217;s
because, somehow, it&#8217;s all taken care of for you by the <B>vector</B>
(this is non-trivial. You can try to figure it out by looking at the header
files for the STL &#8211; all the code is there &#8211; but it&#8217;s quite an
exercise). Thus your coding is significantly cleaned up.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">The limitation of holding objects instead
of pointers inside containers is quite severe: you can&#8217;t upcast from
derived types, thus you can&#8217;t use polymorphism. The problem with upcasting
objects by value is that they get sliced and converted until their type is
completely changed into the base type, and there&#8217;s no remnant of the
derived type left. It&#8217;s pretty safe to say that you <I>never</I> want to
do this.</FONT><A NAME="_Toc519042015"></A><BR></P></DIV>
<A NAME="Heading194"></A><FONT FACE = "Verdana, Tahoma, Arial, Helvetica, Sans"><H2 ALIGN="LEFT">
Inheriting from STL containers</H2></FONT>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">The power of instantly creating a
sequence of elements is amazing, and it makes you realize how much time
you&#8217;ve spent (or rather, wasted) in the past solving this particular
problem. For example, many utility programs involve reading a file into memory,
modifying the file and writing it back out to disk. One might as well take the
functionality in <B>StringVector.cpp</B> and package it into a class for later
reuse.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Now the question is: do you create a
member object of type <B>vector</B>, or do you inherit? A general guideline is
to always prefer composition (member objects) over inheritance, but with the STL
this is often not true, because there are so many existing algorithms that work
with the STL types that you may want your new type to <I>be</I> an STL type. So
the list of <B>string</B>s<B> </B>should also <I>be</I> a <B>vector</B>, thus
inheritance is desired.</FONT><BR></P></DIV>

<BLOCKQUOTE><FONT SIZE = "+1"><PRE><font color=#009900>//: C07:FileEditor.h</font>
<font color=#009900>// File editor tool</font>
#ifndef FILEEDITOR_H
#define FILEEDITOR_H
#include &lt;string&gt;
#include &lt;vector&gt;
#include &lt;iostream&gt;

<font color=#0000ff>class</font> FileEditor : 
<font color=#0000ff>public</font> std::vector&lt;std::string&gt; {
<font color=#0000ff>public</font>:
  <font color=#0000ff>void</font> open(<font color=#0000ff>char</font>* filename);
  FileEditor(<font color=#0000ff>char</font>* filename) {
    open(filename);
  }
  FileEditor() {};
  <font color=#0000ff>void</font> write(std::ostream&amp; out = std::cout);
};
#endif <font color=#009900>// FILEEDITOR_H ///:~</font></PRE></FONT></BLOCKQUOTE>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Note the careful avoidance of a global
<B>using namespace std</B> statement here, to prevent the opening of the
<B>std</B> namespace to every file that includes this header.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">The constructor opens the file and reads
it into the <B>FileEditor</B>, and <B>write(&#160;)</B> puts the <B>vector</B>
of <B>string</B> onto any <B>ostream</B>. Notice in <B>write(&#160;) </B>that
you can have a default argument for a reference.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">The implementation is quite
simple:</FONT><BR></P></DIV>

<BLOCKQUOTE><FONT SIZE = "+1"><PRE><font color=#009900>//: C07:FileEditor.cpp {O}</font>
#include <font color=#004488>"FileEditor.h"</font>
#include <font color=#004488>"..</font><font color=#004488>/require.h"</font>
#include &lt;fstream&gt;
<font color=#0000ff>using</font> <font color=#0000ff>namespace</font> std;

<font color=#0000ff>void</font> FileEditor::open(<font color=#0000ff>char</font>* filename) {
  ifstream in(filename);
  assure(in, filename);
  string line;
  <font color=#0000ff>while</font>(getline(in, line))
    push_back(line);
}

<font color=#009900>// Could also use copy() here:</font>
<font color=#0000ff>void</font> FileEditor::write(ostream&amp; out) {
  <font color=#0000ff>for</font>(iterator w = begin();  w != end(); w++)
    out &lt;&lt; *w &lt;&lt; endl;
} <font color=#009900>///:~</font></PRE></FONT></BLOCKQUOTE>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">The functions from
<B>StringVector.cpp</B> are simply repackaged. Often this is the way classes
evolve &#8211; you start by creating a program to solve a particular
application, then discover some commonly-used functionality within the program
that can be turned into a class.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">The line numbering program can now be
rewritten using <B>FileEditor</B>:</FONT><BR></P></DIV>

<BLOCKQUOTE><FONT SIZE = "+1"><PRE><font color=#009900>//: C07:FEditTest.cpp</font>
<font color=#009900>//{L} FileEditor ../TestSuite/Test</font>
<font color=#009900>// Test the FileEditor tool</font>
#include <font color=#004488>"FileEditor.h"</font>
#include <font color=#004488>"..</font><font color=#004488>/require.h"</font>
#include &lt;sstream&gt;
<font color=#0000ff>using</font> <font color=#0000ff>namespace</font> std;

<font color=#0000ff>int</font> main(<font color=#0000ff>int</font> argc, <font color=#0000ff>char</font>* argv[]) {
  FileEditor file;
  <font color=#0000ff>if</font>(argc &gt; 1) {
    file.open(argv[1]);
  } <font color=#0000ff>else</font> {
    file.open(<font color=#004488>"FEditTest.cpp"</font>);
  }
  <font color=#009900>// Do something to the lines...</font>
  <font color=#0000ff>int</font> i = 1;
  FileEditor::iterator w = file.begin();
  <font color=#0000ff>while</font>(w != file.end()) {
    ostringstream ss;
    ss &lt;&lt; i++;
    *w = ss.str() + <font color=#004488>": "</font> + *w;
    w++;
  }
  <font color=#009900>// Now send them to cout:</font>
  file.write();
} <font color=#009900>///:~</font></PRE></FONT></BLOCKQUOTE><DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Now the operation of reading
the file is in the constructor:</FONT><BR></P></DIV>

<BLOCKQUOTE><FONT SIZE = "+1"><PRE>FileEditor file(argv[1]);</PRE></FONT></BLOCKQUOTE>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">(or in the <B>open( ) </B>method) and
writing happens in the single line (which defaults to sending the output to
<B>cout</B>):</FONT><BR></P></DIV>

<BLOCKQUOTE><FONT SIZE = "+1"><PRE>file.write();</PRE></FONT></BLOCKQUOTE>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">The bulk of the program is involved with
actually modifying the file in
memory.</FONT><A NAME="_Toc519042016"></A><BR></P></DIV>
<A NAME="Heading195"></A><FONT FACE = "Verdana, Tahoma, Arial, Helvetica, Sans"><H2 ALIGN="LEFT">
A plethora of iterators</H2></FONT>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">As mentioned earlier, the iterator is the
abstraction that allows a piece of code to be <I>generic</I>, and to work with

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?