An object's control block is set up by the function creating the first std::shared_ptr
to the object. At least that's what's supposed to happen. In general, it's impossible for a function creating a std::shared_ptr
to an object to know whether some other std::shared_ptr
already points to that object, so the following rules for control block creation are used:
• std::make_shared
(see Item 21) always creates a control block. It manufactures a new object to point to, so there is certainly no control block for that object at the time std::make_shared
is called.
• A control block is created when a std::shared_ptr
is constructed from a unique-ownership pointer (i.e., a std::unique_ptr
or std::auto_ptr
). Unique-ownership pointers don't use control blocks, so there should be no control block for the pointed-to object. (As part of its construction, the std::shared_ptr
assumes ownership of the pointed-to object, so the unique-ownership pointer is set to null.)
• When a std::shared_ptr
constructor is called with a raw pointer, it creates a control block. If you wanted to create a std::shared_ptr
from an object that already had a control block, you'd presumably pass a std::shared_ptr
or a std::weak_ptr
(see Item 20) as a constructor argument, not a raw pointer. std::shared_ptr
constructors taking std::shared_ptr
s or std::weak_ptr
s as constructor arguments don't create new control blocks, because they can rely on the smart pointers passed to them to point to any necessary control blocks.
A consequence of these rules is that constructing more than one std::shared_ptr
from a single raw pointer gives you a complimentary ride on the particle accelerator of undefined behavior, because the pointed-to object will have multiple control blocks. Multiple control blocks means multiple reference counts, and multiple reference counts means the object will be destroyed multiple times (once for each reference count). That means that code like this is bad, bad, bad:
auto pw= new Widget; // pw is raw ptr
…
std::shared_ptr spw1( pw, loggingDel); // create control
// block for *pw
…
std::shared_ptr spw2( pw, loggingDel); // create 2nd
// control block
// for *pw!
The creation of the raw pointer pw
to a dynamically allocated object is bad, because it runs contrary to the advice behind this entire chapter: to prefer smart pointers to raw pointers. (If you've forgotten the motivation for that advice, refresh your memory on page 115.) But set that aside. The line creating pw
is a stylistic abomination, but at least it doesn't cause undefined program behavior.
Now, the constructor for spw1
is called with a raw pointer, so it creates a control block (and thereby a reference count) for what's pointed to. In this case, that's *pw
(i.e., the object pointed to by pw
). In and of itself, that's okay, but the constructor for spw2
is called with the same raw pointer, so it also creates a control block (hence a reference count) for *pw
. *pw
thus has two reference counts, each of which will eventually become zero, and that will ultimately lead to an attempt to destroy *pw
twice. The second destruction is responsible for the undefined behavior.
There are at least two lessons regarding std::shared_ptr
use here. First, try to avoid passing raw pointers to a std::shared_ptr
constructor. The usual alternative is to use std::make_shared
(see Item 21), but in the example above, we're using custom deleters, and that's not possible with std::make_shared
. Second, if you must pass a raw pointer to a std::shared_ptr
constructor, pass the result of new directly instead of going through a raw pointer variable. If the first part of the code above were rewritten like this,
std::shared_ptr spw1( new Widget, // direct use of new
loggingDel);
it'd be a lot less tempting to create a second std::shared_ptr
from the same raw pointer. Instead, the author of the code creating spw2
would naturally use spw1
as an initialization argument (i.e., would call the std::shared_ptr
copy constructor), and that would pose no problem whatsoever:
std::shared_ptr spw2( spw1); // spw2 uses same
// control block as spw1
An especially surprising way that using raw pointer variables as std::shared_ptr
constructor arguments can lead to multiple control blocks involves the this
pointer. Suppose our program uses std::shared_ptr
s to manage Widget
objects, and we have a data structure that keeps track of Widget
s that have been processed:
std::vector< std::shared_ptr> processedWidgets;
Further suppose that Widget
has a member function that does the processing:
class Widget {
public:
…
void process();
…
};
Here's a reasonable-looking approach for Widget::process
:
void Widget::process() {
… // process the Widget
processedWidgets.emplace_back( this); // add it to list of
} // processed Widgets;
// this is wrong!
The comment about this being wrong says it all — or at least most of it. (The part that's wrong is the passing of this, not the use of emplace_back
. If you're not familiar with emplace_back
, see Item 42.) This code will compile, but it's passing a raw pointer ( this
) to a container of std::shared_ptr
s. The std::shared_ptr
thus constructed will create a new control block for the pointed-to Widget(*this)
. That doesn't sound harmful until you realize that if there are std::shared_ptr
s outside the member function that already point to that Widget
, it's game, set, and match for undefined behavior.
The std::shared_ptr
API includes a facility for just this kind of situation. It has probably the oddest of all names in the Standard C++ Library: std::enable_shared_from_this
. That's a template for a base class you inherit from if you want a class managed by std::shared_ptr
s to be able to safely create a std::shared_ptr
from a this
pointer. In our example, Widget
would inherit from std::enable_shared_from_this
as follows:
class Widget : public std::enable_shared_from_this{
public:
…
void process();
…
};
As I said, std::enable_shared_from_this
is a base class template. Its type parameter is always the name of the class being derived, so Widget
inherits from std::enable_shared_from_this
. If the idea of a derived class inheriting from a base class templatized on the derived class makes your head hurt, try not to think about it. The code is completely legal, and the design pattern behind it is so well established, it has a standard name, albeit one that's almost as odd as std::enable_shared_from_this
. The name is The Curiously Recurring Template Pattern (CRTP) . If you'd like to learn more about it, unleash your search engine, because here we need to get back to std::enable_shared_from_this
.
Читать дальше