Suppose we are working with a library for modeling investments, there is a root class for different types of investments.
class Investment{...};
And we can get a specific Investment object through a factory function.
Investment * createInvestment();
As for the caller of createInvestment(), it is responsible to delete the object when it is done with it.
Consider, a function f written to fulfill this obligation:
void f()
{
Investment *pInv = createInvestment();
...
delete pInv;
}
However, f could fail to delete the investments object for some reasons.
There might be a "return" in the
"..." part of the function.
There is a loop in the "..." part of the function, and it executes "continue" or "break", so it cannot execute "delete".
some statement inside "..." throw an exception..
In order to make sure the resource return by createInvestment
is always released, we need to put the resource inside an object whose destructor will automatically release the resource when control leaves f
.
At most time, resources are dynamically allocated on the heap and used within a single block or function.The standard library's auto_ptr
is tailor-made for this kind of situation.auto_ptr
is a pointer-like object whose destructor automatically calls delete
on what is points to. So we can modify the procedure:
void f()
{
std::auto_ptr<investment> pInv(createInvestment());
...
}
And it shows two critical aspects of using objects to manage resources:
- Resources are acquired and immediately turned over to resource-managing objects.
The idea of using objects to manage resources is often called Resource Acquisition is Initialization(RAII)(资源取得时机便是初始化时机),because it is so common to acquire a resource and initialize a resource-managing object in the same statement.
- Resource-managing objects use their destructors to ensure that resources are released.
Because destructors are called automatically when an object is destroyed(e.g. when an object goes out of scope), resources are correctly released.
Since an auto_ptr
automatically deletes what it points to when the auto_ptr
is destroyed, it is important that there never be more than one auto_ptr
pointing to an object.Because if the object is deleted more than once, it would put the program on the fast track to undefined behavior.In order to prevent this error, auto_ptr
has an unusual characteristic : copying them sets them to null, and the copying pointer assumes sole ownership of the resource!
std::auto_ptr<Investment> pInv1(createInvestment());
std::auto_ptr<Investment> pInv2(pInv1); // pInv2 points to the object ,pInv1 is null
pInv1 = pInv2; // pInv1 points to the object , pInv2 is null
It ensures that there would never has two auto_ptr
points to the same object.
About the shared_ptr
, it is a kind of reference-counting smart pointer(RCSP). An RCSP is a smart pointer that keeps track of how many objects point to a particular resource and automatically deletes the resource when nobody is pointing to it any longer. However, RCSP cannot break cycles of references(e.g., two unused objects that point to each other).
void f()
{
std::tr1::shared_ptr<Investment> pInv1(createInvestment());
std::tr1::shared_ptr<Investment> pInv2(pInv1); // both pInv1 and pInv2 point to the object
pInv1 = pInv2; // nothing has changed
...
}
auto_ptr
and shared_ptr
are good examples of using objects to manage resources.
P.S : Both auto_ptr
and shared_ptr
use delete
in their destructors, not delete []
. So it is bad idea to use them with allocated arrays.
Conclusion
- To prevent resource leaks , use RAII objects that acquire resources in their constructors and release them in their destructors.
- Two commonly useful RAII classes are
tr1::shared_ptr
andauto_ptr
.tr1::shared_ptr
is usually the better choice, because its behavior when copied is intuitive.Copy anauto_ptr
sets it to null.