In the previous article, we introduced the Resource Acquisition Is Initialization (RAII) principle as the pillar of Resource management, and described auto_ptr and tr1 :: the performance of shared_ptr in applying this principle to heap-based resources. Not all resources are heap-based. However, for such resources, smart pointers like auto_ptr and tr1: shared_ptr are generally not as appropriate as resource handlers. In this case, you may need to create your own resource management class based on your own needs.
For example, assume that you use the lock and unlock functions provided by the c api to manipulate Mutex-type Mutex objects:
void lock(Mutex *pm); // lock mutex pointed to by pm
void unlock(Mutex *pm); // unlock the mutex
To ensure that you never forget to unlock a Mutex lock, you want to create a class to manage the lock. The RAII principle specifies the basic structure of such a class. The constructor obtains the resource and releases it through the Destructor:
class Lock {
public:
explicit Lock(Mutex *pm)
: mutexPtr(pm)
{ lock(mutexPtr); } // acquire resource
~Lock() { unlock(mutexPtr); } // release resource
private:
Mutex *mutexPtr;
};
The customer follows the RAII style convention to use Lock:
Mutex m; // define the mutex you need to use
...
{ // create block to define critical section
Lock ml(&m); // lock the mutex
... // perform critical section operations
} // automatically unlock mutex at end
// of block
This is fine, but what should happen if a Lock object is copied?
Lock ml1(&m); // lock m
Lock ml2(ml1); // copy ml1 to ml2-what should
// happen here?
This is an example of a more general problem. Every RAII author is faced with the following problem: what should happen when a RAII object is copied? In most cases, you can select one of the following possibilities:
Copy prohibited. In many cases, it makes no sense to allow RAII to be copied. This is probably true for classes like Lock, because the "copy" of the basic elements of synchronization has little significance. When copying a file is meaningless to a RAII class, you should disable it. Item 6 explains how to do this. Declare that the copy operation is private. For Lock, it may look like this:
class Lock: private Uncopyable { // prohibit copying - see
public: // Item 6
... // as before
};
The reference count of the underlying resources. Sometimes what people need is to keep a resource until the last object that uses it is destroyed. In this case, copying a RAII object should increase the number of objects that reference this resource. This is the meaning of "copy" When tr1: shared_ptr is used.
Generally, the RAII class only needs to contain a tr1: shared_ptr data member to copy the reference count. For example, if Lock uses the reference count, it may change the mutexPtr type from Mutex * To tr1: shared_ptr <Mutex>. Unfortunately, the default behavior of tr1: shared_ptr is to delete it when the reference count of what it points to is changed to 0, but this is not what we want. After we use Mutex, we want to unlock it rather than delete it.
Fortunately, tr1: shared_ptr allows a "deleter" Specification-a function or function object called when the reference count turns to 0. (This function is not available in auto_ptr. auto_ptr always deletes its pointer .) Deleter is the second optional parameter of the constructor of tr1: shared_ptr, so the code looks like this:
class Lock {
public:
explicit Lock(Mutex *pm) // init shared_ptr with the Mutex
: mutexPtr(pm, unlock) // to point to and the unlock func
{ // as the deleter
lock(mutexPtr.get()); // see Item 15 for info on "get"
}
private:
std::tr1::shared_ptr<Mutex> mutexPtr; // use shared_ptr
}; // instead of raw pointer
In this example, note how the Lock class does not declare a destructor. That's because it is no longer needed. Item 5 explains the destructor of A Class (whether generated by the compiler or defined by the user) that automatically calls the non-static (non-static) data member of the class. In this example, mutexPtr is used. However, when the reference count of the mutex changes to 0, the destructor of mutexPtr will automatically call the deleter of tr1: shared_ptr -- here is the unlock. (Most people who have read the source code of this class realize that you need to add a comment to indicate that you have not forgotten the structure, but only rely on the default behavior generated by the compiler .)
Copy the underlying resources. Sometimes, as you want, you can have multiple copies of a resource. The only premise is that you need a resource management class to ensure that after you use it, each copy is released. In this case, copying a resource management object also copies its hidden resources. That is to say, copying a resource management class requires a "Deep copy ".
Some standard string types are implemented by the heap memory pointer, which stores the string character. Such a String object contains a pointer to the heap memory. When a string object is copied, this copy should consist of the pointer and the memory it points. Such strings is represented by deep copy.
Transfer the ownership of the underlying resources. In some special cases, you may want to ensure that only one RAII object references an unprocessed resource. When this RAII object is copied, the ownership of the resource is transferred from the Copied object to the Copied object. As described in the previous article, this is the meaning of "copy" When auto_ptr is used.
Copying functions (copy constructor and copy assignment operator) may be generated by the compiler, so unless the version generated by the compiler is exactly what you want, you should write them yourself. In some cases, you also need to support the generic versions of these functions.
Things to Remember
· Copying a raii object must copy the resources it manages. Therefore, the copying behavior of the resource determines the copying behavior of the RAII object.
· Normal RAII copying behaviors do not accept copying and reference counting, but other behaviors are possible.