⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 mc3.htm

📁 高效c++编程
💻 HTM
📖 第 1 页 / 共 5 页
字号:
               ? new AudioClip(audioClipFileName)
               : 0)
{}
</PRE>
</UL><A NAME="39258"></A><p><A NAME="dingp50"></A>
but this leads to the problem we originally wanted to eliminate: if an exception is thrown during initialization of <CODE>theAudioClip</CODE>, the object pointed to by <CODE>theImage</CODE> is never destroyed. Furthermore, we can't solve the problem by adding <CODE>try</CODE> and <CODE>catch</CODE> blocks to the constructor, because <CODE>try</CODE> and <CODE>catch</CODE> are statements, and member initialization lists allow only expressions. (That's why we had to use the <CODE>?:</CODE> syntax instead of the <CODE>if</CODE>-<CODE>then</CODE>-<CODE>else</CODE> syntax in the initialization of <CODE>theImage</CODE> and <CODE>theAudioClip</CODE>.)<SCRIPT>create_link(50);</SCRIPT>
</p>
<A NAME="39317"></A>
<P><A NAME="dingp51"></A>
Nevertheless, the only way to perform cleanup chores before exceptions propagate out of a constructor is to catch those exceptions, so if we can't put <CODE>try</CODE> and <CODE>catch</CODE> in a member initialization list, we'll have to put them somewhere else. One possibility is inside private member functions that return pointers with which <CODE>theImage</CODE> and <CODE>theAudioClip</CODE> should be <NOBR>initialized:<SCRIPT>create_link(51);</SCRIPT>
</NOBR></P>
<A NAME="70261"></A>
<UL><PRE>class BookEntry {
public:
  ...                     // as above
<A NAME="70262"></A>
private:
  ...                     // data members as above
<A NAME="70265"></A>
Image * initImage(const string&amp; imageFileName);
  AudioClip * initAudioClip(const string&amp;
                            audioClipFileName);
};
<A NAME="44136"></A>
BookEntry::BookEntry(const string&amp; name,
                     const string&amp; address,
                     const string&amp; imageFileName,
                     const string&amp; audioClipFileName)
: theName(name), theAddress(address),
  theImage(initImage(imageFileName)),
  theAudioClip(initAudioClip(audioClipFileName))
{}
<A NAME="39348"></A>
<A NAME="p57"></A>// theImage is initialized first, so there is no need to
// worry about a resource leak if this initialization
// fails. This function therefore handles no exceptions
Image * BookEntry::initImage(const string&amp; imageFileName)
{
  if (imageFileName != "") return new Image(imageFileName);
  else return 0;
}
<A NAME="39349"></A>
// theAudioClip is initialized second, so it must make
// sure theImage's resources are released if an exception
// is thrown during initialization of theAudioClip. That's
// why this function uses try...catch.
AudioClip * BookEntry::initAudioClip(const string&amp;
                                     audioClipFileName)
{
  try {
    if (audioClipFileName != "") {
      return new AudioClip(audioClipFileName);
    }
    else return 0;
  }
  catch (...)   {
    delete theImage;
    throw;
  }
}
</PRE>
</UL><A NAME="39296"></A>
<P><A NAME="dingp52"></A>
This is perfectly kosher, and it even solves the problem we've been laboring to overcome. The drawback is that code that conceptually belongs in a constructor is now dispersed across several functions, and that's a maintenance <NOBR>headache.<SCRIPT>create_link(52);</SCRIPT>
</NOBR></P><A NAME="69830"></A>
<P><A NAME="dingp53"></A>
A better solution is to adopt the advice of <A HREF="#5292" onMouseOver = "self.status = 'Link to MEC++ Item 9'; return true" onMouseOut = "self.status = self.defaultStatus">Item 9</A> and treat the objects pointed to by <CODE>theImage</CODE> and <CODE>theAudioClip</CODE> as resources to be managed by local objects. This solution takes advantage of the facts that both <CODE>theImage</CODE> and <CODE>theAudioClip</CODE> are pointers to dynamically allocated objects and that those objects should be deleted when the pointers themselves go away. This is precisely the set of conditions for which the <CODE>auto_ptr</CODE> classes (see <A HREF="#5292" onMouseOver = "self.status = 'Link to MEC++ Item 9'; return true" onMouseOut = "self.status = self.defaultStatus">Item 9</A>) were designed. We can therefore change the raw pointer types of <CODE>theImage</CODE> and <CODE>theAudioClip</CODE> to their <CODE>auto_ptr</CODE> <NOBR>equivalents:<SCRIPT>create_link(53);</SCRIPT>
</NOBR></p>
<A NAME="70365"></A>
<UL><PRE>class BookEntry {
public:
  ...                                      // as above
<A NAME="70366"></A>
private:
  ...
  const auto_ptr&lt;Image&gt; theImage;          // these are now
  const auto_ptr&lt;AudioClip&gt; theAudioClip;  // auto_ptr objects
};
</PRE>
</UL><P><A NAME="dingp54"></A><A NAME="44030"></A>
<A NAME="p58"></A><P><A NAME="dingp54"></A>
Doing this makes <CODE>BookEntry</CODE>'s constructor leak-safe in the presence of exceptions, and it lets us initialize <CODE>theImage</CODE> and <CODE>theAudioClip</CODE> using the member initialization <NOBR>list:<SCRIPT>create_link(54);</SCRIPT>
</NOBR></P><A NAME="70386"></A>
<UL><PRE>
BookEntry::BookEntry(const string&amp; name,
                     const string&amp; address,
                     const string&amp; imageFileName,
                     const string&amp; audioClipFileName)
: theName(name), theAddress(address),
  theImage(imageFileName != ""
           ? new Image(imageFileName)
           : 0),
  theAudioClip(audioClipFileName != ""
               ? new AudioClip(audioClipFileName)
               : 0)
{}
</PRE>
</UL><A NAME="44041"></A>
<P><A NAME="dingp55"></A>
In this design, if an exception is thrown during initialization of <CODE>theAudioClip</CODE>, <CODE>theImage</CODE> is already a fully constructed object, so it will automatically be destroyed, just like <CODE>theName</CODE>, <CODE>theAddress</CODE>, and <CODE>thePhones</CODE>. Furthermore, because <CODE>theImage</CODE> and <CODE>theAudioClip</CODE> are now objects, they'll be destroyed automatically when the <CODE>BookEntry</CODE> object containing them is. Hence there's no need to manually delete what they point to. That simplifies <CODE>BookEntry</CODE>'s destructor <NOBR>considerably:<SCRIPT>create_link(55);</SCRIPT>
</NOBR></p>

<A NAME="44025"></A>
<UL><PRE>BookEntry::~BookEntry()
{}                                      // nothing to do!
</PRE>
</UL><A NAME="70404"></A>

<P><A NAME="dingp56"></A>
This means you could eliminate <CODE>BookEntry</CODE>'s destructor <NOBR>entirely.<SCRIPT>create_link(56);</SCRIPT>
</NOBR></P><A NAME="43984"></A>
<P><A NAME="dingp57"></A>
It all adds up to this: if you replace pointer class members with their corresponding <CODE>auto_ptr</CODE> objects, you fortify your constructors against resource leaks in the presence of exceptions, you eliminate the need to manually deallocate resources in destructors, and you allow <CODE>const</CODE> member pointers to be handled in the same graceful fashion as non-<CODE>const</CODE> <NOBR>pointers.<SCRIPT>create_link(57);</SCRIPT>
</NOBR></P><A NAME="49081"></A>
<P><A NAME="dingp58"></A>
Dealing with the possibility of exceptions during construction can be tricky, but <CODE>auto_ptr</CODE> (and <CODE>auto_ptr</CODE>-like classes) can eliminate most of the drudgery. Their use leaves behind code that's not only easy to understand, it's robust in the face of exceptions, <NOBR>too.<SCRIPT>create_link(58);</SCRIPT>
</NOBR></P>

<!-- SectionName="M11: Prevent exceptions from leaving destructors" -->

<A NAME="39749"></A>
<DIV ALIGN="CENTER"><FONT SIZE="-1">Back to <A HREF="./MC3.HTM#38223">Item 10: Prevent resource leaks in constructors</A> &nbsp;&nbsp;<BR>&nbsp;&nbsp;Continue to <A HREF="./MC3.HTM#76790">Item 12: Understand how throwing an exception differs from passing a parameter or calling a virtual function</A></FONT></DIV>
<P><A NAME="dingp59"></A><font ID="mititle">Item 11: &nbsp;Prevent exceptions from leaving destructors.</font><SCRIPT>create_link(59);</SCRIPT>
</P>

<A NAME="72127"></A><A NAME="38227"></A>
<P><A NAME="dingp60"></A>
There are two situations in which a destructor is called. The first is when an object is destroyed under "normal" conditions, e.g., when it goes out of scope or is explicitly <CODE>delete</CODE>d. The second is when an object is destroyed by the exception-handling mechanism during the stack-unwinding part of exception <NOBR>propagation.<SCRIPT>create_link(60);</SCRIPT>
</NOBR></P><A NAME="38249"></A>
<A NAME="p59"></A><P><A NAME="dingp61"></A>
That being the case, an exception may or may not be active when a destructor is invoked. Regrettably, there is no way to distinguish between these conditions from inside a destructor.<A HREF="#79380" onMouseOver = "self.status = 'Link to Footnote 4'; return true" onMouseOut = "self.status = self.defaultStatus"><sup>4</sup></A> As a result, you must write your destructors under the conservative assumption that an exception <I>is</I> active, because if control leaves a destructor due to an exception while another exception is active, C++ calls the <CODE>terminate</CODE> function. That function does just what its name suggests: it terminates execution of your program. Furthermore, it terminates it <I>immediately</I>; not even local objects are <NOBR>destroyed.<SCRIPT>create_link(61);</SCRIPT>
</NOBR></P><A NAME="38335"></A>
<P><A NAME="dingp62"></A>
As an example, consider a <CODE>Session</CODE> class for monitoring on-line computer sessions, i.e., things that happen from the time you log in through the time you log out. Each <CODE>Session</CODE> object notes the date and time of its creation and <NOBR>destruction:<SCRIPT>create_link(62);</SCRIPT>
</NOBR></p>

<A NAME="38352"></A>
<UL><PRE>class Session {
public:
  Session();
  ~Session();
  ...
<A NAME="57502"></A>
private:
  static void logCreation(Session *objAddr);
  static void logDestruction(Session *objAddr);
};
</PRE>
</UL><A NAME="38354"></A>

<P><A NAME="dingp63"></A>
The functions <CODE>logCreation</CODE> and <CODE>logDestruction</CODE> are used to record object creations and destructions, respectively. We might therefore expect that we could code <CODE>Session</CODE>'s destructor like <NOBR>this:<SCRIPT>create_link(63);</SCRIPT>
</NOBR></p>

<A NAME="38365"></A>
<UL><PRE>Session::~Session()
{
  logDestruction(this);
}
</PRE>
</UL><A NAME="42224"></A>

<P><A NAME="dingp64"></A>
This looks fine, but consider what would happen if <CODE>logDestruction</CODE> throws an exception. The exception would not be caught in <CODE>Session</CODE>'s destructor, so it would be propagated to the caller of that destructor. But if the destructor was itself being called because some other exception had been thrown, the <CODE>terminate</CODE> function would automatically be invoked, and that would stop your program dead in its <NOBR>tracks.<SCRIPT>create_link(64);</SCRIPT>
</NOBR></P><A NAME="42234"></A>
<P><A NAME="dingp65"></A>
In many cases, this is not what you'll want to have happen. It may be unfortunate that the <CODE>Session</CODE> object's destruction can't be logged, it might even be a major inconvenience, but is it really so horrific a pros<A NAME="p60"></A>pect that the program can't continue running? If not, you'll have to prevent the exception thrown by <CODE>logDestruction</CODE> from propagating out of <CODE>Session</CODE>'s destructor. The only way to do that is by using <CODE>try</CODE> and <CODE>catch</CODE> blocks. A naive attempt might look like <NOBR>this,<SCRIPT>create_link(65);</SCRIPT>
</NOBR></p>
<A NAME="42258"></A>
<UL><PRE>Session::~Session()
{
  try {
    logDestruction(this);
  }
  catch (...) {
    cerr  &lt;&lt; "Unable to log destruction of Session object "
          &lt;&lt; "at address "
          &lt;&lt; this
          &lt;&lt; ".\n";
  }
}

⌨️ 快捷键说明

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