std::enable_shared_from_this
defines a member function that creates a std::shared_ptr
to the current object, but it does it without duplicating control blocks. The member function is shared_from_this
, and you use it in member functions whenever you want a std::shared_ptr
that points to the same object as the this pointer. Here's a safe implementation of Widget::process
:
void Widget::process() {
// as before, process the Widget
…
// add std::shared_ptr to current object to processedWidgets
processedWidgets.emplace_back( shared_from_this());
}
Internally, shared_from_this
looks up the control block for the current object, and it creates a new std::shared_ptr
that refers to that control block. The design relies on the current object having an associated control block. For that to be the case, there must be an existing std::shared_ptr
(e.g., one outside the member function calling shared_from_this
) that points to the current object. If no such std::shared_ptr
exists (i.e., if the current object has no associated control block), behavior is undefined, although shared_from_this
typically throws an exception.
To prevent clients from calling member functions that invoke shared_from_this
before a std::shared_ptr
points to the object, classes inheriting from std::enable_shared_from_this
often declare their constructors private
and have clients create objects by calling factory functions that return std::shared_ptr
s. Widget
, for example, could look like this:
class Widget: public std::enable_shared_from_this {
public:
// factory function that perfect-forwards args
// to a private ctor
template
static std::shared_ptr create(Ts&&... params);
…
void process(); // as before
…
private:
… // ctors
};
By now, you may only dimly recall that our discussion of control blocks was motivated by a desire to understand the costs associated with std::shared_ptr
s. Now that we understand how to avoid creating too many control blocks, let's return to the original topic.
A control block is typically only a few words in size, although custom deleters and allocators may make it larger. The usual control block implementation is more sophisticated than you might expect. It makes use of inheritance, and there's even a virtual function. (It's used to ensure that the pointed-to object is properly destroyed.) That means that using std::shared_ptr
s also incurs the cost of the machinery for the virtual function used by the control block.
Having read about dynamically allocated control blocks, arbitrarily large deleters and allocators, virtual function machinery, and atomic reference count manipulations, your enthusiasm for std::shared_ptr
s may have waned somewhat. That's fine.
They're not the best solution to every resource management problem. But for the functionality they provide, std::shared_ptr
s exact a very reasonable cost. Under typical conditions, where the default deleter and default allocator are used and where the std::shared_ptr
is created by std::make_shared
, the control block is only about three words in size, and its allocation is essentially free. (It's incorporated into the memory allocation for the object being pointed to. For details, see Item 21.) Dereferencing a std::shared_ptr
is no more expensive than dereferencing a raw pointer. Performing an operation requiring a reference count manipulation (e.g., copy construction or copy assignment, destruction) entails one or two atomic operations, but these operations typically map to individual machine instructions, so although they may be expensive compared to non-atomic instructions, they're still just single instructions. The virtual function machinery in the control block is generally used only once per object managed by std::shared_ptr
s: when the object is destroyed.
In exchange for these rather modest costs, you get automatic lifetime management of dynamically allocated resources. Most of the time, using std::shared_ptr
is vastly preferable to trying to manage the lifetime of an object with shared ownership by hand. If you find yourself doubting whether you can afford use of std::shared_ptr
, reconsider whether you really need shared ownership. If exclusive ownership will do or even may do, std::unique_ptr
is a better choice. Its performance profile is close to that for raw pointers, and “upgrading” from std::unique_ptr
to std::shared_ptr
is easy, because a std::shared_ptr
can be created from a std::unique_ptr
.
The reverse is not true. Once you've turned lifetime management of a resource over to a std::shared_ptr
, there's no changing your mind. Even if the reference count is one, you can't reclaim ownership of the resource in order to, say, have a std::unique_ptr
manage it. The ownership contract between a resource and the std::shared_ptr
s that point to it is of the 'til-death-do-us-part variety. No divorce, no annulment, no dispensations.
Something else std::shared_ptr
s can't do is work with arrays. In yet another difference from std::unique_ptr
, std::shared_ptr
has an API that's designed only for pointers to single objects. There's no std::shared_ptr
. From time to time, “clever” programmers stumble on the idea of using a std::shared_ptr
to point to an array, specifying a custom deleter to perform an array delete (i.e., delete []
). This can be made to compile, but it's a horrible idea. For one thing, std::shared_ptr
offers no operator[]
, so indexing into the array requires awkward expressions based on pointer arithmetic. For another, std::shared_ptr
supports derived-to-base pointer conversions that make sense for single objects, but that open holes in the type system when applied to arrays. (For this reason, the std::unique_ptr
API prohibits such conversions.) Most importantly, given the variety of C++11 alternatives to built-in arrays (e.g., std::array
, std::vector
, std::string
), declaring a smart pointer to a dumb array is almost always a sign of bad design.
Things to Remember
• std::shared_ptr
s offer convenience approaching that of garbage collection for the shared lifetime management of arbitrary resources.
• Compared to std::unique_ptr
, std::shared_ptr
objects are typically twice as big, incur overhead for control blocks, and require atomic reference count manipulations.
• Default resource destruction is via delete
, but custom deleters are supported. The type of the deleter has no effect on the type of the std::shared_ptr
.
• Avoid creating std::shared_ptr
s from variables of raw pointer type.
Item 20: Use std::weak_ptr
for std::shared_ptr
-like pointers that can dangle.
Paradoxically, it can be convenient to have a smart pointer that acts like a std::shared_ptr
(see Item 19), but that doesn't participate in the shared ownership of the pointed-to resource. In other words, a pointer like std::shared_ptr
that doesn't affect an object's reference count. This kind of smart pointer has to contend with a problem unknown to std::shared_ptr
s: the possibility that what it points to has been destroyed. A truly smart pointer would deal with this problem by tracking when it dangles , i.e., when the object it is supposed to point to no longer exists. That's precisely the kind of smart pointer std::weak_ptr
is.
Читать дальше