Is Make_Shared Really More Efficient Than New

Is make_shared really more efficient than new?

As infrastructure I was using llvm/clang 3.0 along with the llvm std c++ library within XCode4.

Well that appears to be your problem. The C++11 standard states the following requirements for make_shared<T> (and allocate_shared<T>), in section 20.7.2.2.6:

Requires: The expression ::new (pv) T(std::forward(args)...), where pv has type void* and points to storage suitable to hold an object of type T, shall be well formed. A shall be an allocator (17.6.3.5). The copy constructor and destructor of A shall not throw exceptions.

T is not required to be copy-constructable. Indeed, T isn't even required to be non-placement-new constructable. It is only required to be constructable in-place. This means that the only thing that make_shared<T> can do with T is new it in-place.

So the results you get are not consistent with the standard. LLVM's libc++ is broken in this regard. File a bug report.

For reference, here's what happened when I took your code into VC2010:

Create smart_ptr using make_shared...
Constructor make_shared
Create smart_ptr using make_shared: done.
Create smart_ptr using new...
Constructor new
Create smart_ptr using new: done.
Destructor
Destructor

I also ported it to Boost's original shared_ptr and make_shared, and I got the same thing as VC2010.

I'd suggest filing a bug report, as libc++'s behavior is broken.

std::shared_ptr initialization: make_sharedFoo() vs shared_ptrT(new Foo)

Both examples are rather more verbose than necessary:

std::shared_ptr<int> p(new int);  // or '=shared_ptr<int>(new int)' if you insist
auto p = std::make_shared<int>(); // or 'std::shared_ptr<int> p' if you insist

What's the difference?

The main difference is that the first requires two memory allocations: one for the managed object (new int), and one for the reference count. make_shared should allocate a single block of memory, and create both in that.

Which one should I prefer and why?

You should usually use make_shared as it's more efficient. As noted in another answer, it also avoids any possibility of a memory leak, since you never have a raw pointer to the managed object.

However, as noted in the comments, it has a potential disadvantage that the memory won't be released when the object is destroyed, if there are still weak pointers preventing the shared count from being deleted.


EDIT 2020/03/06:

Further recommendations come also from the official Microsoft documentation with associated examples. Keep the focus on the Example 1 snippet:

Whenever possible, use the make_shared function to create a shared_ptr
when the memory resource is created for the first time. make_shared is
exception-safe. It uses the same call to allocate the memory for the
control block and the resource, which reduces the construction
overhead. If you don't use make_shared, then you have to use an
explicit new expression to create the object before you pass it to the
shared_ptr constructor. The following example shows various ways to
declare and initialize a shared_ptr together with a new object.

new and make_shared for shared pointers

  1. The code referred to as the second variable is in fact this (taken from OP's code):

    auto ptr_res2(new Object("new"));

    This does not create a std::shared_ptr, it creates a pointer to Object.

  2. When creating a std::shared_ptr using its constructor that takes a naked pointer, you must pass a pointer to already allocated memory (e.g. allocated using new). This means that the memory for the object has already been allocated when creating the std::shared_ptr object itself. The std::shared_ptr needs to allocate memory for its own workings, like e.g. a reference counter. So there are 2 allocations: the one using new passed to the ctor of std::shared_ptr and the one required when constructing the std::shared_ptr itself.

    Object* ptr = new Object{"Foo"}; // Allocation 1 (object).
    std::shared_ptr<Object> p1{ptr}; // Allocation 2 (internal counters).

    The helper function std::make_shared uses only 1 allocation as you pass it the arguments needed to construct the object, not a pointer to the object itself. std::make_shared can then allocate memory once that holds both the object and the ref counter.

    auto p2 = std::make_shared<Object>{"Foo"} // Allocation 1 (object & counter).

When should I prefer `shared_ptr` to `make_shared`?

One of the situations is that std::make_shared does not support the specifying of the custom deleter.

Unlike the std::shared_ptr constructors, std::make_shared does not allow a custom deleter.

You can only do it with the constructor of std::shared_ptr, e.g.

std::shared_ptr<Foo> sh5(new Foo, [](auto p) {
std::cout << "Call delete from lambda...\n";
delete p;
});

Another issue is just as the linked post explained, std::make_shared performs only one allocation for both the control block and the object pointed to. That means after the object being destroyed, the memory it occupied might not be deallocated immediately. That might cause some memory usage issue.

How to create pointer with `make_shared`

You seem to be missing the namespace from your second example. Also you can construct your derived type in make_shared.

boost::shared_ptr<Exercise> americanExercise = boost::make_shared<AmericanExercise>(settlementDate, in.maturity); 

Data cache implications of using std::make_shared()

Maybe, but don't count on it.

For cache-friendliness, you want to use as little memory as possible, and you want operations that are close together in address to also be close together in time (that is, close enough that the second operation uses memory that is still in some level of cache from the effects of the first operation: the lower the level of cache the better).

If you use make_shared, then there might well be a slight saving in total memory use, which at least tends to be a win for the cache no matter what your memory usage pattern.

If you use make_shared, then the control block and the object referred to (referand) will be adjacent in memory.

If you don't use make_shared, and your objects are a different size from your control blocks, then with common memory allocators there's a reasonable chance that the objects will be clustered together in one place and the control blocks clustered together in a different place. If they are the same size (once rounded by the memory allocator in some implementation-specific way), then with common memory allocators there's a reasonable chance that they'll just alternate in memory for long runs unless shared_ptr does something to affect that.

Your memory access pattern will determine which of those layouts is better for the cache -- and of course the actual layout you get in the non-make_shared case might be something else again, depending on implementation details.

The fact that you have a vector is basically independent of all this, since the shared_ptr objects are separate from the control-blocks and the referands.

make_shared() reference counting in C++

A shared pointer contains two parts: The pointer to the "object" you have created, and a pointer to a special control block that contains the reference counter and possibly some other meta-data needed.

If you create your own std::shared_ptr object, these two memory block will be allocated separately. If you use std::make_shared then the function will only make a single allocation for both blocks of memory.



Related Topics



Leave a reply



Submit