An object's control block is set up by the function creating the first std::shared_ptrto the object. At least that's what's supposed to happen. In general, it's impossible for a function creating a std::shared_ptrto an object to know whether some other std::shared_ptralready 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_sharedis 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_ptrassumes 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_ptrfrom an object that already had a control block, you'd presumably pass a std::shared_ptror a std::weak_ptr(see Item 20) as a constructor argument, not a raw pointer. std::shared_ptrconstructors taking std::shared_ptrs or std::weak_ptrs 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_ptrfrom 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 pwto 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 pwis a stylistic abomination, but at least it doesn't cause undefined program behavior.
Now, the constructor for spw1is 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 spw2is called with the same raw pointer, so it also creates a control block (hence a reference count) for *pw. *pwthus has two reference counts, each of which will eventually become zero, and that will ultimately lead to an attempt to destroy *pwtwice. The second destruction is responsible for the undefined behavior.
There are at least two lessons regarding std::shared_ptruse here. First, try to avoid passing raw pointers to a std::shared_ptrconstructor. 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_ptrconstructor, 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_ptrfrom the same raw pointer. Instead, the author of the code creating spw2would naturally use spw1as an initialization argument (i.e., would call the std::shared_ptrcopy 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_ptrconstructor arguments can lead to multiple control blocks involves the thispointer. Suppose our program uses std::shared_ptrs to manage Widgetobjects, and we have a data structure that keeps track of Widgets that have been processed:
std::vector< std::shared_ptr> processedWidgets;
Further suppose that Widgethas 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_ptrs. The std::shared_ptrthus 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_ptrs outside the member function that already point to that Widget, it's game, set, and match for undefined behavior.
The std::shared_ptrAPI 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_ptrs to be able to safely create a std::shared_ptrfrom a thispointer. In our example, Widgetwould inherit from std::enable_shared_from_thisas follows:
class Widget : public std::enable_shared_from_this{
public:
…
void process();
…
};
As I said, std::enable_shared_from_thisis a base class template. Its type parameter is always the name of the class being derived, so Widgetinherits 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.
Читать дальше