📄 reeves.htm
字号:
</NOBR></P>
<P><A NAME="dingp89"></A><A NAME="AUTO00079"></A><A NAME="goal4"></A><B>Goal IV. Avoid resource leaks.</B> The most obvious type of resource leak is a memory leak, but memory is not the only resource that can leak (I once had a program that tended to leak TCP/IP sockets). In one sense, a resource leak is just another example of a bad state. In this case though, the resource that is in the "bad" state is the one that has leaked, not the one that did the leaking. For this reason, I deal with it <NOBR>separately.<SCRIPT>create_link(89);</SCRIPT>
</NOBR></P>
<P><A NAME="dingp90"></A><A NAME="AUTO00080"></A>There are three different instances where exceptions can cause resource leaks: in a constructor, in a destructor, and in a function (whether a member of a class or not). Let us look at constructors <NOBR>first.<SCRIPT>create_link(90);</SCRIPT>
</NOBR></P>
<P><A NAME="dingp91"></A><A NAME="AUTO00081"></A>Constructors are a special case for the exception handling mechanism. When an exception propagates from a constructor, the partial object that has been constructed so far is destroyed. If necessary, any memory allocated from the free store for the object is also released. Note that the destructor for the object itself is not called — only destructors for any completely constructed subobjects. If, during the construction, a resource (such as memory) is obtained directly and not as part of a subobject whose destructor will release the resource, then a resource leak can occur (again, see <SCRIPT>sendmetoo(10,38223,'M');</SCRIPT> ONMOUSEOVER = "self.status = 'Item M10'; return true" ONMOUSEOUT = "self.status = self.defaultStatus">Item M10</A>). For <NOBR>example:<SCRIPT>create_link(91);</SCRIPT>
</NOBR></P>
<A NAME="AUTO00156"></A><UL><PRE>
class T1 { ... };
class T2 { ... };
class X {
T1* p1_;
T2* p2_;
public:
X();
~X();
};
X::X()
{
p1_ = new T1;
p2_ = new T2; // exception causes leak
}
</PRE>
</UL>
<P><A NAME="dingp92"></A><A NAME="AUTO00082"></A>If an exception is thrown by <CODE>T2()</CODE> during the initialization of <CODE>p2_</CODE>, then the <CODE>T2</CODE> object will be destroyed, and the memory obtained by new will be released, but not the pointer held in <CODE>p1_</CODE>. We have a memory <NOBR>leak.<SCRIPT>create_link(92);</SCRIPT>
</NOBR></P>
<P><A NAME="dingp93"></A><A NAME="AUTO00083"></A>There are a couple of ways this can be dealt with. We could use a try block to catch the exception and attempt to release the memory, but if we have several resources allocated this way, then the nested try blocks can get tedious and error prone. An alternative is to make sure the pointers are initialized to null, and then delete them all in the catch <NOBR>block:<SCRIPT>create_link(93);</SCRIPT>
</NOBR></P>
<A NAME="AUTO00157"></A><UL><PRE>
X::X() try :
p1_(null), p2_(null)
{
p1_ = new T1;
p2_ = new T2;
}
catch (...) {
delete p2_;
delete p1_; // reverse order
throw; // redundant
}
</PRE>
</UL>
<P><A NAME="dingp94"></A><A NAME="AUTO00084"></A>This example uses several features from the new Standard, so do not expect this to work on your compiler yet. Placing the <CODE>try</CODE> keyword immediately after the parameter list (or the exception specification if one exists), and the catch clauses after the function body, produces a <EM>function try block</EM>. A function try block associates a handler with the entire function body, including the <EM>constructor initializer list</EM>. In this example, the initializer list is not the concern, but you can see that the initializer list is within the scope of the <CODE>try</CODE>. With any pointer not set in the constructor body guaranteed to be null, the catch block can safely invoke delete on them. (The <CODE>throw</CODE> in the catch block is redundant. A catch clause of a function try block for a constructor or a destructor will automatically rethrow the exception when it finishes. As a matter of style, however, I always explicitly rethrow exceptions caught by a <CODE>catch</CODE> <CODE>(...)</CODE> clause (<A HREF="./REEVES.HTM#guide10" ONMOUSEOVER = "self.status = 'Guideline 10'; return true" ONMOUSEOUT = "self.status = self.defaultStatus">Guideline 10</A>).) All this leads <NOBR>to:<SCRIPT>create_link(94);</SCRIPT>
</NOBR></P>
<P><A NAME="dingp95"></A><A NAME="AUTO00085"></A><A NAME="guide6"></A><I>Guideline 6: If you have raw data pointers as members, initialize them to null in the initializer list of your constructor(s), then do necessary allocations in the constructor body where a <CODE>catch</CODE> block can deal with potential resource leaks.</I> This is one possible way to deal with a potential resource leak in a constructor. Another technique is to use the "resource acquisition is initialization" strategy. In this case, we make sure that every resource is associated with an object whose destructor will deallocate it. For dealing with memory allocated from the free store, the standard library template class <CODE>auto_ptr</CODE> (<A HREF="#list2" ONMOUSEOVER = "self.status = 'Listing 2'; return true" ONMOUSEOUT = "self.status = self.defaultStatus">Listing 2</A>) is available. Applied to our <NOBR>example:<SCRIPT>create_link(95);</SCRIPT>
</NOBR></P>
<A NAME="AUTO00158"></A><UL><PRE>
class X {
auto_ptr<T1> ap1_;
auto_ptr<T2> ap2_;
public:
X();
~X() {};
};
X::X() :
ap1_(new T1),
ap2_(new T2)
{}
</PRE>
</UL>
<P><A NAME="dingp96"></A><A NAME="AUTO00086"></A>Alternatively:<SCRIPT>create_link(96);</SCRIPT>
<A NAME="AUTO00159"></A><UL><PRE>
X::X()
{
ap1_.reset(new T1);
ap2_.reset(new T2);
}
</PRE>
</UL>
<P><A NAME="dingp97"></A><A NAME="AUTO00087"></A>Now, since <CODE>ap1_</CODE> and <CODE>ap2_</CODE> are both objects, if an exception occurs trying to initialize <CODE>ap2_</CODE>, then the stack unwind will destroy <CODE>ap1_</CODE>, which will call delete on the allocated pointer. In this case, our destructor is empty since the destructors of member objects are invoked <NOBR>automatically.<SCRIPT>create_link(97);</SCRIPT>
</NOBR></P>
<P><A NAME="dingp98"></A><A NAME="AUTO00088"></A>Besides having a destructor that will delete the resource, template <CODE>auto_ptr</CODE> also provides functionality for safely transferring ownership of a resource. We have made use of this capability several times already. In this case, we can use an "acquire then transfer ownership" strategy to give us the following version of <CODE>X</CODE>'s <NOBR>constructor:<SCRIPT>create_link(98);</SCRIPT>
</NOBR></P>
<A NAME="AUTO00160"></A><UL><PRE>
class X {
T1* p1_;
T2* p2_;
public:
X();
~X() {delete p1_; delete p2_;}
};
X::X() :
p1_(), p2_()
{
auto_ptr<T1> t1(new T1);
auto_ptr<T2> t2(new T2);
p1_ = t1.release();
p2_ = t2.release();
}
</PRE>
</UL>
<P><A NAME="dingp99"></A><A NAME="AUTO00089"></A>The <CODE>auto_ptr</CODE> objects are used to acquire the resources in a manner that guarantees they will be deleted if an exception occurs. When all resources have been successfully acquired, ownership is transferred to the class itself. This is just <A HREF="./REEVES.HTM#guide2" ONMOUSEOVER = "self.status = 'Guideline 2'; return true" ONMOUSEOUT = "self.status = self.defaultStatus">Guideline 2</A> applied to resource acquisition. I see this as a transitional strategy for constructors, however. In the long run, I suspect that the use of raw data pointers as class members will diminish in favor of <CODE>auto_ptr</CODE> style objects. This simplifies maintenance as well as the problems of coping with exceptions. If you worry about performance, keep in mind that all the operations of <CODE>auto_ptr</CODE> are inline functions (see <SCRIPT>sendmetoo(33,6729,'E');</SCRIPT> ONMOUSEOVER = "self.status = 'Item E33'; return true" ONMOUSEOUT = "self.status = self.defaultStatus">Item E33</A>). Most of these are one line functions that any decent compiler should have no trouble <NOBR>handling.<SCRIPT>create_link(99);</SCRIPT>
</NOBR></P>
<P><A NAME="dingp100"></A><A NAME="AUTO00090"></A>Resource allocations in ordinary functions can also cause resource leaks. Whereas a constructor is building an object and the goal is to make sure everything already acquired is released if an exception occurs, a function usually obtains a resource for internal use and releases it upon completion. If an exception happens after the resource is acquired but before it is released, then we have a leak. Under the discussion for <A HREF="./REEVES.HTM#guide2" ONMOUSEOVER = "self.status = 'Guideline 2'; return true" ONMOUSEOUT = "self.status = self.defaultStatus">Guideline 2</A>, we used an <CODE>auto_array_ptr</CODE> object in <CODE>Stack::push()</CODE> to manage the new buffer while it was being initialized. We can do the same thing in <CODE>Stack::operator=()</CODE>:<SCRIPT>create_link(100);</SCRIPT>
</P>
<A NAME="AUTO00161"></A><UL><PRE>
template<class T>
Stack<T>& Stack::operator=(const Stack<T>& rhs)
{
if (this == &rhs) return *this;
auto_array_ptr<T> new_buffer =
new T[rhs.nelems_]; //>x1
for (int i = 0; i < rhs.top_; i++)
new_buffer[i] = rhs.v_[i]; //>x2
v_ = new_buffer.reset(v_); // swap ownership
nelems_ = rhs.nelems_;
top_ = rhs.top_;
return *this;
}
</PRE>
</UL>
<P><A NAME="dingp101"></A><A NAME="AUTO00091"></A>If <CODE>v_</CODE> were an <CODE>auto_array_ptr</CODE> object instead of a raw pointer we would use the Standard template function <CODE>swap()</CODE> to perform the exchange of <NOBR>ownership:<SCRIPT>create_link(101);</SCRIPT>
</NOBR></P>
<A NAME="AUTO00162"></A><UL><PRE>
swap(v_, new_buffer);
</PRE>
</UL>
<P><A NAME="dingp102"></A><A NAME="AUTO00092"></A>Finally, we can have resource leaks in destructors. As a general rule, we do not want to throw (or propagate) exceptions from destructors (see <SCRIPT>sendmetoo(11,39749,'M');</SCRIPT> ONMOUSEOVER = "self.status = 'Item M11'; return true" ONMOUSEOUT = "self.status = self.defaultStatus">Item M11</A> and Herb Sutter's article on <A HREF="SU_FRAME.HTM" TARGET="_top" ONMOUSEOVER = "self.status = 'Exception-Safe Generic Containers'; return true" ONMOUSEOUT = "self.status = self.defaultStatus">"Exception-Safe Generic Containers"</A>). Nevertheless, we can not always prevent it, so let us take a look at a simple problem. Consider our first example of class <CODE>X</CODE> above. We have two pointers to two different types of objects. Everything has gone well, and now the des
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -