C++ make_shared


shared_ptr<string> p1 = make_shared<string>(10, '9');  
 
shared_ptr<string> p2 = make_shared<string>("hello");  
 
shared_ptr<string> p3 = make_shared<string>(); 
Copy the code

Use make_shared initialization whenever possible

Smart Pointers were introduced in C++11, along with a template function STD ::make_shared that returns a specified type of STD ::shared_ptr. How does this benefit us compared to the STD ::shared_ptr constructor?

The advantages of make_shared initialization

1. Improve performance

Shared_ptr needs to maintain reference count information: strong references, which record how many living Shared_ptrs are currently holding the object. Shared objects are destroyed (and possibly freed) when the last strong reference leaves. Weak reference, which records how many weak_ptrs are currently observing the object. When the last weak reference leaves, the shared internal information control block is destroyed and freed (the shared object is also freed if it has not already been freed). If you allocate an object by using the original new expression and then pass it to shared_ptr (i.e. using shared_ptr’s constructor), the shared_ptr implementation has no choice but to allocate control blocks separately:

If make_shared was selected, the situation would look like this:

One feature of STD ::make_shared (as opposed to using new directly) is efficiency. Using STD ::make_shared allows the compiler to produce smaller, faster code that uses cleaner data structures. Consider the following code that uses new directly:

std::shared_ptr<Widget> spw(new Widget);
Copy the code

This code obviously needs to allocate memory, but it actually allocates it twice. Each STD ::shared_ptr points to a control block that contains, among other things, a reference count for the object being pointed to. The memory for this control block is allocated in the STD ::shared_ptr constructor. So using new directly requires a chunk of memory to be allocated to the Widget and another chunk to the control block.

If STD ::make_shared is used instead

auto spw = std::make_shared<Widget>();
Copy the code

One allocation is enough. This is because STD ::make_shared requires a separate memory block to hold both Widget objects and control blocks. This optimization reduces the static size of the program because the code contains only one memory allocation call, and it speeds up the execution of the code because memory is allocated only once. In addition, using STD ::make_shared eliminates some of the information that control blocks need to log, potentially reducing the program’s total memory footprint.

The same efficiency analysis of STD ::make_shared can be applied to STD ::allocate_shared, so the performance benefits of STD ::make_shared can be extended to this function.

2, abnormal security

We use computePriority() when calling processWidget and use new instead of STD ::make_shared:

ProcessWidget (STD ::shared_ptr<Widget>(new Widget), // Potential resource leaks computePriority());Copy the code

As the comment indicates, the above code causes the Widget created by new to leak. So how exactly was it leaked? Both the calling code and the called function use STD ::shared_ptr, and STD ::shared_ptr is designed to prevent resource leaks. When the last STD ::shared_ptr pointing here disappears, they automatically destroy the resource they point to. If everyone is using STD ::shared_ptr everywhere, how does this code cause the resource to leak?

The compiler translates the source code into the object code. At runtime, the parameters of the function must be evaluated before the function is called, so when processWidget is called, the following must happen before processWidget can start executing:

The expression “new Widget” must be valued, that is, a Widget must be created on the heap. The STD ::shared_ptr constructor (responsible for managing Pointers created by new) must be executed. ComputePriority must run. The compiler does not have to produce code in this order. But the “new Widget” must be executed before the STD ::shared_ptr constructor is called, because the structure of new is used as an argument to the constructor, but computePriority may be executed before (or after, or oddly, in between) both calls. That is, the compiler might produce code in this order:

Execute "New Widget". Perform computePriority. Execute the STD ::shared_ptr constructor.Copy the code

If such code is generated and computePriority raises an exception at run time, the Widget dynamically allocated in step 1 is leaked because it is never stored in the STD :: shareD_ptr that manages it only in step 3.

Using STD ::make_shared can avoid such problems. The calling code will look like this:

ProcessWidget (STD ::make_shared<Widget>(), // No resource leaks computePriority());Copy the code

At runtime, regardless of which STD ::make_shared or computePriority is called first. If STD ::make_shared is called first, the original pointer to the dynamically allocated Widget can be safely stored in the returned STD ::shared_ptr before the computePriority call. If an exception is raised after computePriority, the destructor of STD :: shareD_ptr will find that the widgets it holds need to be destroyed. And if computePriority is called first and an exception is raised, STD ::make_shared is not called, so dynamically allocated widgets are not a concern here.

In fact, the same reason would be used if STD ::unique_ptr and STD ::make_shared were replaced with STD ::shared_ptr and STD ::make_shared. Therefore, using STD ::make_unique instead of new is just as important as writing exception-safe code using STD ::make_shared.

disadvantages

Make_shared cannot be used when constructors are protected or private

While make_shared is good, there are some problems. For example, if the object I want to create doesn’t have a public constructor, make_shared can’t be used. There are a few tricks we can use to solve this problem. How do I call :: STD ::make_shared on a class with only protected or private constructors?

Object memory may not be reclaimed in time

Make_shared allocates memory only once, which looks good. Reduced memory allocation overhead. The problem is that Weak_ptr keeps the control block (strong reference, and weak reference information) alive, and therefore keeps the object’s allocated memory along with weak_Ptr, which is released only when the last Weak_Ptr leaves scope. The memory that can be released when strong references are reduced to 0 is now released when strong references are reduced to 0, which unexpectedly delays the memory release time. This is a concern for memory-demanding scenarios.