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

📄 mi10.htm

📁 一个非常适合初学者入门的有关c++的文档
💻 HTM
📖 第 1 页 / 共 2 页
字号:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN" "http://www.w3.org/TR/REC-html40/frameset.dtd">
<HTML LANG="EN">
<HEAD>
<TITLE>More Effective C++ | Item 10: Prevent resource leaks in constructors</TITLE>
<LINK REL=STYLESHEET HREF=../INTRO/ECMEC.CSS>

<SCRIPT LANGUAGE="Javascript" SRC="../JAVA/COOKIE.JS"></SCRIPT>
<SCRIPT LANGUAGE="Javascript">var imagemax = 0; setCurrentMax(0);</SCRIPT>
<SCRIPT LANGUAGE="Javascript" SRC="../JAVA/DINGBATS.JS"></SCRIPT>
<SCRIPT>
var dingbase = "MI10_DIR.HTM";
var dingtext = "Item M10, P";
if (self == top) {
 top.location.replace(dingbase + this.location.hash);
}
</SCRIPT>

</HEAD>
<BODY BGCOLOR="#FFFFFF" TEXT="#000000" ONLOAD="setResize()">
<!-- SectionName = "M10: Prevent resource leaks in constructors" -->
<A NAME="38223"></A>
<DIV ALIGN="CENTER"><FONT SIZE="-1">Back to <A HREF="./MI9_FR.HTM" TARGET="_top">Item 9: Use destructors to prevent resource leaks</A> &nbsp;&nbsp;<BR>&nbsp;&nbsp;Continue to <A HREF="./MI11_FR.HTM" TARGET="_top">Item 11: Prevent exceptions from leaving destructors</A></FONT></DIV>


<P><A NAME="dingp1"></A><font ID="mititle">Item 10: &nbsp;Prevent resource leaks in constructors.</font><SCRIPT>create_link(1);</SCRIPT>
</P>

<A NAME="72121"></A><A NAME="38800"></A>

<P><A NAME="dingp2"></A>
Imagine you're developing software for a multimedia address book. Such an address book might hold, in addition to the usual textual information of a person's name, address, and phone numbers, a picture of the person and the sound of their voice (possibly giving the proper pronunciation of their <NOBR>name).<SCRIPT>create_link(2);</SCRIPT>
</NOBR></P><A NAME="69713"></A>
<P><A NAME="dingp3"></A>
To implement the book, you might come up with a design like <NOBR>this:<SCRIPT>create_link(3);</SCRIPT>
</NOBR></P>
<A NAME="38913"></A>
<UL><PRE>class Image {                        // for image data
public:
  Image(const string&amp; imageDataFileName);
  ...
};
<A NAME="70436"></A>
class AudioClip {                    // for audio data
public:
  AudioClip(const string&amp; audioDataFileName);
  ...
};
<A NAME="69762"></A>
class PhoneNumber {         ... };   // for holding phone numbers
<A NAME="69763"></A>
<A NAME="p51"></A>
class BookEntry {                    // for each entry in the
public:                              // address book
<A NAME="69747"></A>
BookEntry(const string&amp; name,
          const string&amp; address = "",
          const string&amp; imageFileName = "",
          const string&amp; audioClipFileName = "");
~BookEntry();
<A NAME="69879"></A>
// phone numbers are added via this function
void addPhoneNumber(const PhoneNumber&amp; number);
...
<A NAME="57457"></A>
private:
  string theName;                 // person's name
  string theAddress;              // their address
  list&lt;PhoneNumber&gt; thePhones;    // their phone numbers
  Image *theImage;                // their image
  AudioClip *theAudioClip;        // an audio clip from them
};
</PRE>
</UL><A NAME="38821"></A>
<P><A NAME="dingp4"></A>
Each <CODE>BookEntry</CODE> must have name data, so you require that as a constructor argument (see <a href="./MI3_FR.HTM#84818" TARGET="_top">Item 3</A>), but the other fields &#151; the person's address and the names of files containing image and audio data &#151; are optional. Note the use of the <CODE>list</CODE> class to hold the person's phone numbers. This is one of several container classes that are part of the standard C++ library (see <A HREF="../EC/EI49_FR.HTM#8392" TARGET="_top">Item E49</A> and <A HREF="./MI35_FR.HTM#5473" TARGET="_top">Item 35</A>).<SCRIPT>create_link(4);</SCRIPT>
</P><A NAME="38808"></A>
<P><A NAME="dingp5"></A>
A straightforward way to write the <CODE>BookEntry</CODE> constructor and destructor is as <NOBR>follows:<SCRIPT>create_link(5);</SCRIPT>
</NOBR></P>
<A NAME="38837"></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(0), theAudioClip(0)
{
  if (imageFileName != "") {
    theImage = new Image(imageFileName);
  }
<A NAME="69938"></A>
if (audioClipFileName != "") {
theAudioClip = new AudioClip(audioClipFileName);
  }
}
<A NAME="38845"></A>
BookEntry::~BookEntry()
{
  delete theImage;
  delete theAudioClip;
}
</PRE>
</UL><A NAME="38835"></A>
<A NAME="p52"></A><P><A NAME="dingp6"></A>
The constructor initializes the pointers <CODE>theImage</CODE> and <CODE>theAudioClip</CODE> to null, then makes them point to real objects if the corresponding arguments are non-empty strings. The destructor deletes both pointers, thus ensuring that a <CODE>BookEntry</CODE> object doesn't give rise to a resource leak. Because C++ guarantees it's safe to delete null pointers, <CODE>BookEntry</CODE>'s destructor need not check to see if the pointers actually point to something before deleting <NOBR>them.<SCRIPT>create_link(6);</SCRIPT>
</NOBR></P><A NAME="38864"></A>
<P><A NAME="dingp7"></A>
Everything looks fine here, and under normal conditions everything is fine, but under abnormal conditions &#151; under <I>exceptional</I> conditions &#151; things are not fine at <NOBR>all.<SCRIPT>create_link(7);</SCRIPT>
</NOBR></P><A NAME="69973"></A>
<P><A NAME="dingp8"></A>
Consider what will happen if an exception is thrown during execution of this part of the <CODE>BookEntry</CODE> <NOBR>constructor:<SCRIPT>create_link(8);</SCRIPT>
</NOBR></P>

<A NAME="69974"></A>
<UL><PRE>if (audioClipFileName != "") {
  theAudioClip = new AudioClip(audioClipFileName);
}
</PRE>
</UL><A NAME="69978"></A>

<P><A NAME="dingp9"></A>
An exception might arise because <CODE>operator</CODE> <CODE>new</CODE> (see <A HREF="./MI8_FR.HTM#33985" TARGET="_top">Item&nbsp;8</A>) is unable to allocate enough memory for an <CODE>AudioClip</CODE> object. One might also arise because the <CODE>AudioClip</CODE> constructor itself throws an exception. Regardless of the cause of the exception, if one is thrown within the <CODE>BookEntry</CODE> constructor, it will be propagated to the site where the <CODE>BookEntry</CODE> object is being <NOBR>created.<SCRIPT>create_link(9);</SCRIPT>
</NOBR></P><A NAME="38894"></A>
<P><A NAME="dingp10"></A>
Now, if an exception is thrown during creation of the object <CODE>theAudioClip</CODE> is supposed to point to (thus transferring control out of the <CODE>BookEntry</CODE> constructor), who deletes the object that <CODE>theImage</CODE> already points to? The obvious answer is that <CODE>BookEntry</CODE>'s destructor does, but the obvious answer is wrong. <CODE>BookEntry</CODE>'s destructor will never be called. <NOBR>Never.<SCRIPT>create_link(10);</SCRIPT>
</NOBR></P><A NAME="70035"></A>
<P><A NAME="dingp11"></A>
C++ destroys only <I>fully constructed</I> objects, and an object isn't fully constructed until its constructor has run to completion. So if a <CODE>BookEntry</CODE> object <CODE>b</CODE> is created as a local <NOBR>object,<SCRIPT>create_link(11);</SCRIPT>
</NOBR></P>

<A NAME="70036"></A>
<UL><PRE>void testBookEntryClass()
{
  BookEntry b("Addison-Wesley Publishing Company",
              "One Jacob Way, Reading, MA 01867");
<A NAME="38917"></A>
...
<A NAME="38918"></A>
}
</PRE>
</UL><A NAME="38895"></A><P><A NAME="dingp12"></A>and an exception is thrown during construction of <CODE>b</CODE>, <CODE>b</CODE>'s destructor will not be called. Furthermore, if you try to take matters into your own hands by allocating <CODE>b</CODE> on the heap and then calling <CODE>delete</CODE> if an exception is <NOBR>thrown,<SCRIPT>create_link(12);</SCRIPT>
</NOBR></P>
<A NAME="38923"></A>
<UL><PRE><A NAME="p53"></A>void testBookEntryClass()
{
  BookEntry *pb = 0;
<A NAME="70472"></A>
  try {
    pb = new BookEntry("Addison-Wesley Publishing Company",
                       "One Jacob Way, Reading, MA 01867");
    ...
  }
  catch (...) {                // catch all exceptions
<A NAME="70015"></A>
     delete pb;                // delete pb when an
                               // exception is thrown
<A NAME="70016"></A>
     throw;                    // propagate exception to
  }                            // caller
<A NAME="38943"></A>
  delete pb;                   // delete pb normally
}
</PRE>
</UL><A NAME="89738"></A>
<P><A NAME="dingp13"></A>you'll find that the <CODE>Image</CODE> object allocated inside <CODE>BookEntry</CODE>'s constructor is still lost, because no assignment is made to <CODE>pb</CODE> unless the <CODE>new</CODE> operation succeeds. If <CODE>BookEntry</CODE>'s constructor throws an exception, <CODE>pb</CODE> will be the null pointer, so deleting it in the <CODE>catch</CODE> block does nothing except make you feel better about yourself. Using the smart pointer class <CODE>auto_ptr&lt;BookEntry&gt;</CODE> (see <a href="./MI9_FR.HTM#5292" TARGET="_top">Item&nbsp;9</A>) instead of a raw <CODE>BookEntry*</CODE> won't do you any good either, because the assignment to <CODE>pb</CODE> still won't be made unless the <CODE>new</CODE> operation <NOBR>succeeds.<SCRIPT>create_link(13);</SCRIPT>
</NOBR></P>
<A NAME="38974"></A>
<P><A NAME="dingp14"></A>
There is a reason why C++ refuses to call destructors for objects that haven't been fully constructed, and it's not simply to make your life more difficult. It's because it would, in many cases, be a nonsensical thing &#151; possibly a harmful thing &#151; to do. If a destructor were invoked on an object that wasn't fully constructed, how would the destructor know what to do? The only way it could know would be if bits had been added to each object indicating how much of the constructor had been executed. Then the destructor could check the bits and (maybe) figure out what actions to take. Such bookkeeping would slow down constructors, and it would make each object larger, too. C++ avoids this overhead, but the price you pay is that partially constructed objects aren't automatically destroyed. (For an example of a similar trade-off involving efficiency and program behavior, turn to <a href="../EC/EI13_FR.HTM#2117" TARGET="_top">Item E13</A>.)<SCRIPT>create_link(14);</SCRIPT>
</P><A NAME="70098"></A>
<P><A NAME="dingp15"></A>
Because C++ won't clean up after objects that throw exceptions during construction, you must design your constructors so that they clean up after themselves. Often, this involves simply catching all possible exceptions, executing some cleanup code, then rethrowing the exception so it continues to propagate. This strategy can be incorporated into the <CODE>BookEntry</CODE> constructor like <NOBR>this:<SCRIPT>create_link(15);</SCRIPT>
</NOBR></P>
<A NAME="70094"></A>
<UL><PRE><A NAME="p54"></A>
BookEntry::BookEntry(const string&amp; name,
          	     const string&amp; address,
          	     const string&amp; imageFileName,
	      	     const string&amp; audioClipFileName)
: theName(name), theAddress(address),
  theImage(0), theAudioClip(0)
{
  try {                            // this try block is new
    if (imageFileName != "") {
      theImage = new Image(imageFileName);
    }
<A NAME="70041"></A>
  if (audioClipFileName != "") {
      theAudioClip = new AudioClip(audioClipFileName);
    }
  }
  catch (...) {                      // catch any exception
<A NAME="70109"></A>

    delete theImage;                 // perform necessary
    delete theAudioClip;             // cleanup actions
<A NAME="70063"></A>

    throw;                           // propagate the exception
  }
}

⌨️ 快捷键说明

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