HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppCritical

Difference in make_shared and normal shared_ptr in C++

Submitted by: @import:stackoverflow-api··
0
Viewed 0 times
anddifferencemake_sharedshared_ptrnormal

Problem

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


I am not able to understand why make_shared is more efficient than directly using shared_ptr.

I need a step by step explanation of the sequence of objects created and operations done by both, so that I understand how make_shared is efficient. I have given one example above for reference.

Solution

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, const std::shared_ptr &rhs) { /* ... */ }

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


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

  • new Lhs("foo"))



  • new Rhs("bar"))



  • std::shared_ptr



  • std::shared_ptr



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(new Lhs("foo"));
auto rhs = std::shared_ptr(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("foo"), std::make_shared("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.

Code Snippets

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")));
auto lhs = std::shared_ptr<Lhs>(new Lhs("foo"));
auto rhs = std::shared_ptr<Rhs>(new Rhs("bar"));
F(lhs, rhs);
F(std::make_shared<Lhs>("foo"), std::make_shared<Rhs>("bar"));

Context

Stack Overflow Q#20895648, score: 489

Revisions (0)

No revisions yet.