std::shared_ptr
is the C++11 way of binding these worlds together. An object accessed via std::shared_ptr
s has its lifetime managed by those pointers through shared ownership . No specific std::shared_ptr
owns the object. Instead, all std::shared_ptr
s pointing to it collaborate to ensure its destruction at the point where it's no longer needed. When the last std::shared_ptr
pointing to an object stops pointing there (e.g., because the std::shared_ptr
is destroyed or made to point to a different object), that std::shared_ptr
destroys the object it points to. As with garbage collection, clients need not concern themselves with managing the lifetime of pointed-to objects, but as with destructors, the timing of the objects' destruction is deterministic.
A std::shared_ptr
can tell whether it's the last one pointing to a resource by consulting the resource's reference count , a value associated with the resource that keeps track of how many std::shared_ptr
s point to it. std::shared_ptr
constructors increment this count (usually — see below), std::shared_ptr
destructors decrement it, and copy assignment operators do both. (If sp1
and sp2
are std::shared_ptr
s to different objects, the assignment “ sp1 = sp2;
” modifies sp1
such that it points to the object pointed to by sp2
. The net effect of the assignment is that the reference count for the object originally pointed to by sp1
is decremented, while that for the object pointed to by sp2
is incremented.) If a std::shared_ptr
sees a reference count of zero after performing a decrement, no more std::shared_ptr
s point to the resource, so the std::shared_ptr
destroys it.
The existence of the reference count has performance implications:
• std::shared_ptr
s are twice the size of a raw pointer, because they internally contain a raw pointer to the resource as well as a raw pointer to the resource's reference count. [8] This implementation is not required by the Standard, but every Standard Library implementation I'm familiar with employs it.
• Memory for the reference count must be dynamically allocated. Conceptually, the reference count is associated with the object being pointed to, but pointed-to objects know nothing about this. They thus have no place to store a reference count. (A pleasant implication is that any object — even those of built-in types — may be managed by std::shared_ptr
s.) Item 21explains that the cost of the dynamic allocation is avoided when the std::shared_ptr
is created by std::make_shared
, but there are situations where std::make_shared
can't be used. Either way, the reference count is stored as dynamically allocated data.
• Increments and decrements of the reference count must be atomic, because there can be simultaneous readers and writers in different threads. For example, a std::shared_ptr
pointing to a resource in one thread could be executing its destructor (hence decrementing the reference count for the resource it points to), while, in a different thread, a std::shared_ptr
to the same object could be copied (and therefore incrementing the same reference count). Atomic operations are typically slower than non-atomic operations, so even though reference counts are usually only a word in size, you should assume that reading and writing them is comparatively costly.
Did I pique your curiosity when I wrote that std::shared_ptr
constructors only “usually” increment the reference count for the object they point to? Creating a std::shared_ptr
pointing to an object always yields one more std::shared_ptr
pointing to that object, so why mustn't we always increment the reference count?
Move construction, that's why. Move-constructing a std::shared_ptr
from another std::shared_ptr
sets the source std::shared_ptr
to null, and that means that the old std::shared_ptr
stops pointing to the resource at the moment the new std::shared_ptr
starts. As a result, no reference count manipulation is required. Moving std::shared_ptr
s is therefore faster than copying them: copying requires incrementing the reference count, but moving doesn't. This is as true for assignment as for construction, so move construction is faster than copy construction, and move assignment is faster than copy assignment.
Like std::unique_ptr
(see Item 18), std::shared_ptr
uses delete
as its default resource-destruction mechanism, but it also supports custom deleters. The design of this support differs from that for std::unique_ptr
, however. For std::unique_ptr
, the type of the deleter is part of the type of the smart pointer. For std::shared_ptr
, it's not:
auto loggingDel = [](Widget *pw) // custom deleter
{ // (as in Item 18)
makeLogEntry(pw);
delete pw;
};
std::unique_ptr< // deleter type is
Widget, decltype(loggingDel) // part of ptr type
> upw(new Widget, loggingDel);
std::shared_ptr // deleter type is not
spw(new Widget, loggingDel); // part of ptr type
The std::shared_ptr
design is more flexible. Consider two std::shared_ptr
s, each with a custom deleter of a different type (e.g., because the custom deleters are specified via lambda expressions):
auto customDeleter1 = [](Widget *pw) { … }; // custom deleters,
auto customDeleter2 = [](Widget *pw) { … }; // each with a
// different type
std::shared_ptr pw1(new Widget, customDeleter1);
std::shared_ptr pw2(new Widget, customDeleter2);
Because pw1
and pw2
have the same type, they can be placed in a container of objects of that type:
std::vector> vpw{ pw1, pw2 };
They could also be assigned to one another, and they could each be passed to a function taking a parameter of type std::shared_ptr
. None of these things can be done with std::unique_ptr
s that differ in the types of their custom deleters, because the type of the custom deleter would affect the type of the std::unique_ptr
.
In another difference from std::unique_ptr
, specifying a custom deleter doesn't change the size of a std::shared_ptr
object. Regardless of deleter, a std::shared_ptr
object is two pointers in size. That's great news, but it should make you vaguely uneasy. Custom deleters can be function objects, and function objects can contain arbitrary amounts of data. That means they can be arbitrarily large. How can a std::shared_ptr
refer to a deleter of arbitrary size without using any more memory?
It can't. It may have to use more memory. However, that memory isn't part of the std::shared_ptr
object. It's on the heap or, if the creator of the std::shared_ptr
took advantage of std::shared_ptr
support for custom allocators, it's wherever the memory managed by the allocator is located. I remarked earlier that a std::shared_ptr
object contains a pointer to the reference count for the object it points to. That's true, but it's a bit misleading, because the reference count is part of a larger data structure known as the control block . There's a control block for each object managed by std::shared_ptr
s. The control block contains, in addition to the reference count, a copy of the custom deleter, if one has been specified. If a custom allocator was specified, the control block contains a copy of that, too. The control block may also contain additional data, including, as Item 21explains, a secondary reference count known as the weak count, but we'll ignore such data in this Item. We can envision the memory associated with a std::shared_ptr
object as looking like this:
Читать дальше