- Modern C++:Efficient and Scalable Application Development
- Richard Grimes Marius Bancila
- 821字
- 2021-06-10 18:28:17
Sharing ownership
There are occasions when you will need to share a pointer: you may create several objects and pass a pointer to a single object to each of them so they can call this object. Ordinarily, when an object has a pointer to another object, that pointer represents a resource that should be destroyed during the destruction of the containing object. If a pointer is shared, it means that when one of the objects deletes the pointer, the pointers in all of the other objects will be invalid (this is called a dangling pointer because it no longer points to an object). You need a mechanism where several objects can hold a pointer that will remain valid until all the objects using that pointer have indicated they will no longer need to use it.
C++11 provides this facility with the shared_ptr class. This class maintains a reference count on the resource, and each copy of the shared_ptr for that resource will increment the reference count. When one instance of shared_ptr for that resource is destroyed, it will decrement the reference count. The reference count is shared, so it means that a non-zero value signifies that at least one shared_ptr exists accessing the resource. When the last shared_ptr object decrements the reference count to zero, it is safe to release the resource.
This means that the reference count must be managed in an atomic way to handle multithreaded code.
Since the reference count is shared, it means that each shared_ptr object holds a pointer to a shared buffer called the control block, and this means it holds the raw pointer and a pointer to the control block, and so each shared_ptr object will hold more data than a unique_ptr. The control block is used for more than just the reference count.
A shared_ptr object can be created to use a custom deleter (passed as a constructor parameter), and the deleter is stored in the control block. This is important because it means that the custom deleter is not part of the type of the smart pointer, so several shared_ptr objects wrapping the same resource type but using different deleters are still the same type and can be put in a container for that type.
You can create a shared_ptr object from another shared_ptr object, and this will initialize the new object with the raw pointer and the pointer to the control block, and increment the reference count.
point* p = new point(1.0, 1.0);
shared_ptr<point> sp1(p); // Important, do not use p after this!
shared_ptr<point> sp2(sp1);
p = nullptr;
sp2->x = 2.0;
sp1->y = 2.0;
sp1.reset(); // get rid of one shared pointer
Here, the first shared pointer is created using a raw pointer. This is not the recommended way to use shared_ptr. The second shared pointer is created using the first smart pointer, so now there are two shared pointers to the same resource (p is assigned to nullptr to prevent its further use). After this, either sp1 or sp2 can be used to access the same resource. At the end of this code, one shared pointer is reset to nullptr; this means that sp1 no longer has a reference count on the resource, and you cannot use it to access the resource. However, you can still use sp2 to access the resource until it goes out of scope, or you call reset.
In this code, the smart pointers were created from a separate raw pointer. Since the shared pointers now have taken over the lifetime management of the resource it is important to no longer use the raw pointer, and in this case it is assigned to nullptr. It is better to avoid the use of raw pointers, and the Standard Library enables this with a function called make_shared, which can be used like this:
shared_ptr<point> sp1 = make_shared<point>(1.0,1.0);
The function will create the specified object using a call to new, and since it takes a variable number of parameters, you can use it to call any constructor on the wrapped class.
You can create a shared_ptr object from a unique_ptr object, which means that the pointer is moved to the new object and the reference counting control block created. Since the resource will now be shared, it means that there is no longer exclusive ownership on the resource, so the pointer in the unique_ptr object will be made a nullptr. This means that you can have a factory function that returns a pointer to an object wrapped in a unique_ptr object, and the calling code can determine if it will use a unique_ptr object to get exclusive access to the resource or a shared_ptr object to share it.
There is little point in using shared_ptr for arrays of objects; there are much better ways to store collections of objects (vector or array). In any case, there is an indexing operator ([]) and the default deleter calls delete, not delete[].