[c++] Difference in make_shared and normal shared_ptr in C++

std::shared_ptr<Object> p1 = std::make_shared<Object>("foo");
std::shared_ptr<Object> p2(new Object("foo"));

Many google and stackoverflow posts are there on this, but I am not able to understand why make_shared is more efficient than directly using shared_ptr.

Can someone explain me step by step sequence of objects created and operations done by both so that I will be able to understand how make_shared is efficient. I have given one example above for reference.

This question is related to c++ c++11 shared-ptr

The answer is


The difference is that std::make_shared performs one heap-allocation, whereas calling the std::shared_ptr constructor performs two.

Where do the heap-allocations happen?

std::shared_ptr manages two entities:

  • the control block (stores meta data such as ref-counts, type-erased deleter, etc)
  • the object being managed

std::make_shared performs a single heap-allocation accounting for the space necessary for both the control block and the data. In the other case, new Obj("foo") invokes a heap-allocation for the managed data and the std::shared_ptr constructor performs another one for the control block.

For further information, check out the implementation notes at cppreference.

Update I: Exception-Safety

NOTE (2019/08/30): This is not a problem since C++17, due to the changes in the evaluation order of function arguments. Specifically, each argument to a function is required to fully execute before evaluation of other arguments.

Since the OP seem to be wondering about the exception-safety side of things, I've updated my answer.

Consider this example,

void F(const std::shared_ptr<Lhs> &lhs, const std::shared_ptr<Rhs> &rhs) { /* ... */ }

F(std::shared_ptr<Lhs>(new Lhs("foo")),
  std::shared_ptr<Rhs>(new Rhs("bar")));

Because C++ allows arbitrary order of evaluation of subexpressions, one possible ordering is:

  1. new Lhs("foo"))
  2. new Rhs("bar"))
  3. std::shared_ptr<Lhs>
  4. std::shared_ptr<Rhs>

Now, suppose we get an exception thrown at step 2 (e.g., out of memory exception, Rhs constructor threw some exception). We then lose memory allocated at step 1, since nothing will have had a chance to clean it up. The core of the problem here is that the raw pointer didn't get passed to the std::shared_ptr constructor immediately.

One way to fix this is to do them on separate lines so that this arbitary ordering cannot occur.

auto lhs = std::shared_ptr<Lhs>(new Lhs("foo"));
auto rhs = std::shared_ptr<Rhs>(new Rhs("bar"));
F(lhs, rhs);

The preferred way to solve this of course is to use std::make_shared instead.

F(std::make_shared<Lhs>("foo"), std::make_shared<Rhs>("bar"));

Update II: Disadvantage of std::make_shared

Quoting Casey's comments:

Since there there's only one allocation, the pointee's memory cannot be deallocated until the control block is no longer in use. A weak_ptr can keep the control block alive indefinitely.

Why do instances of weak_ptrs keep the control block alive?

There must be a way for weak_ptrs to determine if the managed object is still valid (eg. for lock). They do this by checking the number of shared_ptrs that own the managed object, which is stored in the control block. The result is that the control blocks are alive until the shared_ptr count and the weak_ptr count both hit 0.

Back to std::make_shared

Since std::make_shared makes a single heap-allocation for both the control block and the managed object, there is no way to free the memory for control block and the managed object independently. We must wait until we can free both the control block and the managed object, which happens to be until there are no shared_ptrs or weak_ptrs alive.

Suppose we instead performed two heap-allocations for the control block and the managed object via new and shared_ptr constructor. Then we free the memory for the managed object (maybe earlier) when there are no shared_ptrs alive, and free the memory for the control block (maybe later) when there are no weak_ptrs alive.


I think the exception safety part of mr mpark's answer is still a valid concern. when creating a shared_ptr like this: shared_ptr< T >(new T), the new T may succeed, while the shared_ptr's allocation of control block may fail. in this scenario, the newly allocated T will leak, since the shared_ptr has no way of knowing that it was created in-place and it is safe to delete it. or am I missing something? I don't think the stricter rules on function parameter evaluation help in any way here...


If you need special memory alignment on the object controlled by shared_ptr, you cannot rely on make_shared, but I think it's the only one good reason about not using it.


Shared_ptr: Performs two heap allocation

  1. Control block(reference count)
  2. Object being managed

Make_shared: Performs only one heap allocation

  1. Control block and object data.

About efficiency and concernig time spent on allocation, I made this simple test below, I created many instances through these two ways (one at a time):

for (int k = 0 ; k < 30000000; ++k)
{
    // took more time than using new
    std::shared_ptr<int> foo = std::make_shared<int> (10);

    // was faster than using make_shared
    std::shared_ptr<int> foo2 = std::shared_ptr<int>(new int(10));
}

The thing is, using make_shared took the double time compared with using new. So, using new there are two heap allocations instead of one using make_shared. Maybe this is a stupid test but doesn't it show that using make_shared takes more time than using new? Of course, I'm talking about time used only.


The shared pointer manages both the object itself, and a small object containing the reference count and other housekeeping data. make_shared can allocate a single block of memory to hold both of these; constructing a shared pointer from a pointer to an already-allocated object will need to allocate a second block to store the reference count.

As well as this efficiency, using make_shared means that you don't need to deal with new and raw pointers at all, giving better exception safety - there is no possibility of throwing an exception after allocating the object but before assigning it to the smart pointer.


I see one problem with std::make_shared, it doesn't support private/protected constructors


There is another case where the two possibilities differ, on top of those already mentioned: if you need to call a non-public constructor (protected or private), make_shared might not be able to access it, while the variant with the new works fine.

class A
{
public:

    A(): val(0){}

    std::shared_ptr<A> createNext(){ return std::make_shared<A>(val+1); }
    // Invalid because make_shared needs to call A(int) **internally**

    std::shared_ptr<A> createNext(){ return std::shared_ptr<A>(new A(val+1)); }
    // Works fine because A(int) is called explicitly

private:

    int val;

    A(int v): val(v){}
};

Examples related to c++

Method Call Chaining; returning a pointer vs a reference? How can I tell if an algorithm is efficient? Difference between opening a file in binary vs text How can compare-and-swap be used for a wait-free mutual exclusion for any shared data structure? Install Qt on Ubuntu #include errors detected in vscode Cannot open include file: 'stdio.h' - Visual Studio Community 2017 - C++ Error How to fix the error "Windows SDK version 8.1" was not found? Visual Studio 2017 errors on standard headers How do I check if a Key is pressed on C++

Examples related to c++11

Remove from the beginning of std::vector Converting std::__cxx11::string to std::string What exactly is std::atomic? C++ How do I convert a std::chrono::time_point to long and back Passing capturing lambda as function pointer undefined reference to 'std::cout' Is it possible to use std::string in a constexpr? How does #include <bits/stdc++.h> work in C++? error::make_unique is not a member of ‘std’ no match for ‘operator<<’ in ‘std::operator

Examples related to shared-ptr

Difference in make_shared and normal shared_ptr in C++ When is std::weak_ptr useful? Error: expected type-specifier before 'ClassName' Differences between unique_ptr and shared_ptr Example to use shared_ptr? Should we pass a shared_ptr by reference or by value? Where is shared_ptr? When to use virtual destructors?