304
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.

ckpepper02
  • 3,109
  • 5
  • 26
  • 42
Anup Buchke
  • 4,300
  • 5
  • 19
  • 34

8 Answers8

377

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.

mpark
  • 6,479
  • 2
  • 13
  • 17
  • 61
    It's a good idea to mention the small corner-case downside of `make_shared` as well: since 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. – Casey Jan 03 '14 at 21:01
  • @mpark so if i call `reset()` on the `shared_ptr` then even the control block is reset, in case of make_shared? is this also the case in direct creation? – Koushik Shetty Jan 05 '14 at 15:26
  • @Koushik Calling `reset()` will decrement the `shared_ptr` count in the control block in both cases. Is that what you meant by "control block is reset"? – mpark Jan 05 '14 at 15:32
  • 15
    Another, more stylistic, point is: If you use `make_shared` and `make_unique` consistently, you won't have owning raw pointers an can treat every occurrence of `new` as a code smell. – Philipp Jan 05 '14 at 22:04
  • @mpark no what I meant was, if there is only one owner to the pointer then will the control block be deleted when I do `reset()`, when using `make_shared()`?. – Koushik Shetty Jan 06 '14 at 05:25
  • 6
    If there is only one `shared_ptr`, and no `weak_ptr`s, calling `reset()` on the `shared_ptr` instance will delete the control block. But this is regardless or whether `make_shared` was used. Using `make_shared` makes a difference because it could prolong the life-time of __the memory allocated for the managed object__. When the `shared_ptr` count hits 0, the destructor for the managed object gets called regardless of `make_shared`, but freeing its memory can only be done if `make_shared` was __not__ used. Hope this makes it more clear. – mpark Jan 06 '14 at 18:42
  • Forgot to tag you, @Koushik – mpark Jan 06 '14 at 18:52
  • 4
    It would also be worth mentioning that make_shared can take advantage of the "We Know Where You Live" optimization that permits the control block to be a pointer smaller. (For details, see [Stephan T. Lavavej's GN2012 presentation](http://channel9.msdn.com/Events/GoingNative/GoingNative-2012/STL11-Magic-Secrets) at about minute 12.) make_shared thus not only avoids an allocation, it also allocates less total memory. – KnowItAllWannabe Jan 28 '14 at 06:31
  • How can you see the fact that both "std::make_shared performs a single heap-allocation accounting for the space necessary for both the control block and the data"? How can this be done? Could someone please guide me? – Hanna Khalil Dec 11 '16 at 18:49
  • @HannaKhalil you should be able to write a simple program and run `valgrind` to observe the number of allocations. – mpark Dec 17 '16 at 06:19
  • @mpark I was asking implementation wise. I can't think how one can programmatically unify these 2 allocations to a single allocation and looking at the implementation is not helping – Hanna Khalil Dec 17 '16 at 21:44
  • 1
    @HannaKhalil: Is this perhaps the realm of what you're looking for...? http://melpon.org/wandbox/permlink/b5EpsiSxDeEz8lGH – mpark Dec 20 '16 at 05:17
  • @Philipp 1) Doesn't that exclude using `reset()` 2) How do you assign a pointer to an array (no `std::array`) to std::unique_ptr without using `new`? – Matthias Aug 11 '17 at 08:28
  • So is the upshot of this that using make_shared undermines the concept of weak_ptrs to some degree? Inasmuch as the memory will essentially leak if a weak_ptr is hanging around, even if the main target object has been destructed by the removal of the final shared_ptr. – Kaitain Dec 08 '17 at 17:41
  • @mpark, this is an interesting point. I wonder if the shared_ptr created by make_share would purge both the control block and the pointee, and then create a new control block that is reset. Seems this is probably generally preferred since we would expect that the most common scenario would have sizeof(pointee) >> sizeof(control block) – thang May 21 '18 at 00:47
  • @Casey Do `weak_ptr` has its own control block, or it simply refer to `share_ptr`'s control block? Your comment seems to indicate the latter, just want to confirm. :D – Rick Jul 12 '18 at 03:36
  • @Rick The control block corresponds to an allocation. All `shared_ptr`s and `weak_ptr`s that denote that same allocation share the same control block. Which incidentally makes [`owner_less`](https://en.cppreference.com/w/cpp/memory/owner_less) work. – Casey Jul 13 '18 at 04:43
  • 1
    I think that your "std::shared_ptr manages two entities: ...." is a better explanation of the ownership and memory than the explanation at cppreference. – WillC Nov 16 '18 at 01:29
  • This `Update I: Exception-Safety` part is invalid. Compiler can't partially evaluate arguments of `F`. If argument is evaluated then it must be complete evaluation. So shown order of evaluation is impossible and this example and explanation is invalid. AFAIK current implementation of `shared_ptr` do not have problem with exception safety when instantiate `shared_ptr` (some old version had this issue when allocating object was successful, but allocating control block failed). – Marek R Aug 30 '19 at 15:03
  • Thanks @MarekR, I've updated the answer to point out that the exception-safety aspect of it is indeed obsolete since C++17. Did I understand you correctly? – mpark Aug 30 '19 at 19:44
  • @HannaKhalil `char * p = malloc(sizeof(T) + sizeof(control)); T * t = new(p) T(args...); control * c = new(p + sizeof(T)) control(1, 0);`. i.e. [placement-new](https://en.cppreference.com/w/cpp/language/new) – Caleth Nov 14 '19 at 13:22
  • I think single heap allocation(`make_shared`) is safer from `weak_ptr` perspective comparing to calling `shared_ptr` constructor directly using `new`. Using `new` leaves a small (or large?) window where `weak_ptr` will hold the lock on control block while the actual data block on heap has been freed. – Vishal Sahu Mar 30 '20 at 02:50
28

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.

Mike Seymour
  • 235,407
  • 25
  • 414
  • 617
  • 2
    I understood your first point correctly. Can you please elaborate or give some links on the second point about exception safety? – Anup Buchke Jan 03 '14 at 03:16
24

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){}
};
Dr_Sam
  • 1,688
  • 14
  • 24
  • I ran into this exact problem and decided to use `new`, otherwise I would have used `make_shared`. Here is a related question about that: https://stackoverflow.com/questions/8147027/how-do-i-call-stdmake-shared-on-a-class-with-only-protected-or-private-const. – jigglypuff Nov 09 '18 at 03:37
6

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.

Simon Ferquel
  • 356
  • 1
  • 3
6

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

icebeat
  • 81
  • 1
  • 3
4

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.
boop_the_snoot
  • 2,713
  • 3
  • 21
  • 40
James
  • 41
  • 5
1

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...

0

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.

orlando
  • 9
  • 1
  • 4
    That test is somewhat pointless. Was the test done in release configuration with optimisations turned out? Also all of your items are freed immediately so it is not realistic. – Phil1970 Jul 08 '17 at 12:51