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.
8 Answers
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:
new Lhs("foo"))new Rhs("bar"))std::shared_ptr<Lhs>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"));