// create std::initializer_list
auto initList = { 10, 20 };
// create std::vector using std::initializer_list ctor
auto spv = std::make_shared>( initList);
For std::unique_ptr, these two scenarios (custom deleters and braced initializers) are the only ones where its makefunctions are problematic. For std::shared_ptrand its makefunctions, there are two more. Both are edge cases, but some developers live on the edge, and you may be one of them.
Some classes define their own versions of operator newand operator delete. The presence of these functions implies that the global memory allocation and deallocation routines for objects of these types are inappropriate. Often, class-specific routines are designed only to allocate and deallocate chunks of memory of precisely the size of objects of the class, e.g., operator newand operator deletefor class Widgetare often designed only to handle allocation and deallocation of chunks of memory of exactly size sizeof(Widget). Such routines are a poor fit for std::shared_ptr's support for custom allocation (via std::allocate_shared) and deallocation (via custom deleters), because the amount of memory that std::allocate_sharedrequests isn't the size of the dynamically allocated object, it's the size of that object plus the size of a control block. Consequently, using makefunctions to create objects of types with class-specific versions of operator newand operator deleteis typically a poor idea.
The size and speed advantages of std::make_sharedvis-a-vis direct use of newstem from std::shared_ptr's control block being placed in the same chunk of memory as the managed object. When that object's reference count goes to zero, the object is destroyed (i.e., its destructor is called). However, the memory it occupies can't be released until the control block has also been destroyed, because the same chunk of dynamically allocated memory contains both.
As I noted, the control block contains bookkeeping information beyond just the reference count itself. The reference count tracks how many std::shared_ptrs refer to the control block, but the control block contains a second reference count, one that tallies how many std::weak_ptrs refer to the control block. This second reference count is known as the weak count . [10] In practice, the value of the weak count isn't always equal to the number of std::weak_ptr s referring to the control block, because library implementers have found ways to slip additional information into the weak count that facilitate better code generation. For purposes of this Item, we'll ignore this and assume that the weak count's value is the number of std::weak_ptr s referring to the control block.
When a std::weak_ptrchecks to see if it has expired (see Item 19), it does so by examining the reference count (not the weak count) in the control block that it refers to. If the reference count is zero (i.e., if the pointed-to object has no std::shared_ptrs referring to it and has thus been destroyed), the std::weak_ptrhas expired. Otherwise, it hasn't.
As long as std::weak_ptrs refer to a control block (i.e., the weak count is greater than zero), that control block must continue to exist. And as long as a control block exists, the memory containing it must remain allocated. The memory allocated by a std::shared_ptr makefunction, then, can't be deallocated until the last std::shared_ptr and the last std::weak_ptrreferring to it have been destroyed.
If the object type is quite large and the time between destruction of the last std::shared_ptrand the last std::weak_ptris significant, a lag can occur between when an object is destroyed and when the memory it occupied is freed:
class ReallyBigType { … };
auto pBigObj = // create very large
std::make_shared(); // object via
// std::make_shared
… // create std::shared_ptrs and std::weak_ptrs to
// large object, use them to work with it
… // final std::shared_ptr to object destroyed here,
// but std::weak_ptrs to it remain
…// during this period, memory formerly occupied
// by large object remains allocated
… // final std::weak_ptr to object destroyed here;
// memory for control block and object is released
With a direct use of new, the memory for the ReallyBigTypeobject can be released as soon as the last std::shared_ptrto it is destroyed:
class ReallyBigType { ... }; // as before
std::shared_ptr pBigObj( newReallyBigType);
// create very large
// object via new
… // as before, create std::shared_ptrs and
// std::weak_ptrs to object, use them with it
… // final std::shared_ptr to object destroyed here,
// but std::weak_ptrs to it remain;
// memory for object is deallocated
…// during this period, only memory for the
// control block remains allocated
… // final std::weak_ptr to object destroyed here;
// memory for control block is released
Should you find yourself in a situation where use of std::make_sharedis impossible or inappropriate, you'll want to guard yourself against the kind of exception-safety problems we saw earlier. The best way to do that is to make sure that when you use newdirectly, you immediately pass the result to a smart pointer constructor in a statement that does nothing else . This prevents compilers from generating code that could emit an exception between the use of newand invocation of the constructor for the smart pointer that will manage the newed object.
As an example, consider a minor revision to the exception-unsafe call to the processWidgetfunction we examined earlier. This time, we'll specify a custom deleter:
void processWidget(std::shared_ptr spw, // as before
int priority);
void cusDel(Widget *ptr); // custom
// deleter
Here's the exception-unsafe call:
processWidget( // as before,
std::shared_ptr(new Widget, cusDel), // potential
computePriority() // resource
); // leak!
Recall: if computePriorityis called after “ new Widget” but before the std::shared_ptrconstructor, and if computePriorityyields an exception, the dynamically allocated Widgetwill be leaked.
Here the use of a custom deleter precludes use of std::make_shared, so the way to avoid the problem is to put the allocation of the Widgetand the construction of the std::shared_ptrinto their own statement, then call processWidgetwith the resulting std::shared_ptr. Here's the essence of the technique, though, as we'll see in a moment, we can tweak it to improve its performance:
Читать дальше