Assume that we work with a model library for investments (such as stocks and bonds), and various Investment forms are derived from a root-class investor:
class Investment { ... }; // root class of hierarchy of
// investment types
Let us further assume that this library uses a factory function to provide us with a specific method for the Investment object:
Investment* createInvestment(); // return ptr to dynamically allocated
// object in the Investment hierarchy;
// the caller must delete it
// (parameters omitted for simplicity)
Note that when the objects returned by the createInvestment function are no longer used, the caller of createInvestment deletes the objects. Therefore, consider writing a function f to fulfill the following responsibilities:
void f()
{
Investment *pInv = createInvestment(); // call factory function
... // use pInv
delete pInv; // release object
}
This seems okay, but there are several situations where f will fail to delete the investment object it obtained from createInvestment. There may be a return statement in advance somewhere in the "..." section of this function. If such a return statement is executed, the control process will no longer be able to reach the delete statement. A similar situation may also occur if createInvestment is used and deleted in a loop, which exits early with a continue or goto statement. Also, some statements in "..." may throw an exception. In this case, the control process will not reach that delete. No matter how the delete is skipped, what we leak is not only to accommodate the memory of the investment object, but also to include any resources held by that object.
Of course, careful programming can prevent these errors, but considering that the Code may change over time. To maintain the software, some may add a return or continue statement without fully grasping the impact of other parts of the function's resource management policy. What's more, part of f's "..." may call a function that never throws exceptions, but it suddenly does this after it is "Improved. The delete statement that depends on f can always reach it is unreliable.
To ensure that the resources returned by createInvestment are always released, we need to put those resources into a class. The destructor of this class will automatically release the resources when the control process leaves f. In fact, this is only half of the concept introduced in this article: to put resources inside an object, we can rely on C ++ to automatically call the destructor to ensure that resources are released. (We will introduce the other half of this article later .)
Many resources are dynamically allocated to the stack and used in a separate block or function, and should be released when the control flow leaves the block or function. The auto_ptr of the standard library is tailored to this situation. Auto_ptr is an object similar to a pointer (a smart pointer). Its destructor automatically calls delete on what it points. The following describes how to use auto_ptr to prevent f's potential resource leakage:
void f()
{
std::auto_ptr<Investment> pInv(createInvestment()); // call factory
// function
... // use pInv as
// before
} // automatically
// delete pInv via
// auto_ptr’s dtor
This simple example demonstrates two important aspects of using object to manage resources:
After obtaining the resource, you should hand it over to the resource management object immediately. As shown above, the resources returned by createInvestment are used to initialize the auto_ptr to be used to manage it. In fact, because it Is so common to get a Resource and initialize a Resource management object in the same statement, the concept of using an object to manage resources Is often referred to as Resource Acquisition Is Initialization (RAII ). Sometimes the acquired resources are assigned to resource management objects rather than initializing them. However, both methods immediately hand over the obtained resources to resource management objects.
Resource management objects use their destructor to ensure that resources are released. Because when an object is destroyed (for example, when an object leaves its activity range), The Destructor is automatically called, no matter how the control process leaves a block, all resources are correctly released. If resources are released, an exception is thrown, and the issue becomes tricky. However, I will discuss these issues later, so don't worry about them.
Because when an auto_ptr is destroyed, it will automatically delete what it points to, so it is very important not to point more than one auto_ptr to the same object. If this happens, the object will be deleted more than once, and your program will enter the undefined behavior through shortcuts. To prevent this problem, auto_ptrs has an unusual feature: copying them (by copying constructors or copying assignment operators) is to leave them blank, the copied pointer is assumed to be the unique ownership of the resource.
std::auto_ptr<Investment> // pInv1 points to the
pInv1(createInvestment()); // object returned from
// createInvestment
std::auto_ptr<Investment> pInv2(pInv1); // pInv2 now points to the
// object; pInv1 is now null
pInv1 = pInv2; // now pInv1 points to the
// object, and pInv2 is null
This strange copy behavior increases the potential demand, that is, resources managed through auto_ptrs must never point to them more than one auto_ptr, this means that auto_ptrs is not the best way to manage all resources dynamically allocated. For example, the STL container requires that its content be "normal", so the auto_ptrs container is not allowed.
Compared with auto_ptrs, another optional solution is reference-counting smart pointer (RCSP ). A rcsp is a smart pointer that can continuously track how many objects point to a specific resource and delete it when there is no longer anything pointing to that resource. In this case, RCSP provides behavior similar to garbage collection ). Unlike garbage collection, RCSP cannot break through circular references in any case (for example, two objects without other users direct to each other ).
TR1: shared_ptr of tr1 is an RCSP, so you can write f as follows:
void f()
{
...
std::tr1::shared_ptr<Investment>
pInv(createInvestment()); // call factory function
... // use pInv as before
} // automatically delete
// pInv via shared_ptr’s dtor
The code here looks almost the same as the code using auto_ptr, but copying shared_ptrs is more natural:
void f()
{
...
std::tr1::shared_ptr<Investment> // pInv1 points to the
pInv1(createInvestment()); // object returned from
// createInvestment
std::tr1::shared_ptr<Investment> // both
pInv1 and pInv2 now
pInv2(pInv1); // point to the object
pInv1 = pInv2; // ditto - nothing has
// changed
...
} // pInv1 and pInv2 are
// destroyed, and the
// object they point to is
// automatically deleted
Because copying tr1: shared_ptrs works "as expected", they can be used in STL containers and other environments that are incompatible with the unorthodox copy behavior of auto_ptr.
Do not make a mistake. This article is not about auto_ptr, tr1: shared_ptr or any other kind of smart pointer. It is about the importance of using objects to manage resources. About auto_ptr and tr1: shared_ptr are just examples of objects that do these tasks. (For more information about tr1: shared_ptr, see Item 14,18 and 54 .)
About auto_ptr and tr1: shared_ptr both use delete in their destructor, instead of delete []. This means that the idea of applying about auto_ptr or tr1: shared_ptr to an array for dynamic allocation is awesome. However, sadly, you can compile it:
std::auto_ptr<std::string> // bad idea! the wrong
aps(new std::string[10]); // delete form will be used
std::tr1::shared_ptr<int> spi(new int[1024]); // same problem
You may be surprised to find that there is no such thing as auto_ptr or tr1: shared_ptr that can be used to dynamically allocate arrays in C ++, even in tr1. That's because vector and string can almost always replace dynamic array allocation. If you still think there are better classes like auto_ptr and tr1: shared_ptr that can be used for arrays, you can check Boost. There, you will be happy to find the boost: scoped_array and boost: shared_array classes that provide the behavior you are looking. This Item's guidance on using objects to manage resources indirectly shows that if you manually release resources (for example, using delete instead of using resource management), you are looking for trouble. Pre-fabricated resource management classes such as auto_ptr and tr1: shared_ptr usually make the suggestions in this article easy, but sometimes you use a resource, these pre-processed classes cannot do things as you wish. In this case, you need to carefully create your own resource management class. That is not terrible, but it contains some nuances that require your careful consideration.
As a final comment, I must point out that the form of the unprocessed pointer returned by createInvestment is the request for resource leakage, because it is too easy for the caller to call delete on the pointer they get back. (Even if they use an auto_ptr or tr1: shared_ptr to complete the delete operation, they still have to remember to store the returned values of createInvestment to smart pointer objects .) To solve this problem, you need to change the createInvestment interface.
Things to Remember
· To prevent resource leakage, use RAII objects to obtain resources in the RAII object constructor and release them in the destructor.
· Two common RAII types are tr1: shared_ptr and auto_ptr. Tr1: shared_ptr is usually a better choice, because its copy behavior is intuitive. Copy one