Difference in make_shared and normal shared_ptr in C++
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"));
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_ptr
s keep the control block alive?
There must be a way for weak_ptr
s to determine if the managed object is still valid (eg. for lock
). They do this by checking the number of shared_ptr
s 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_ptr
s or weak_ptr
s 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_ptr
s alive, and free the memory for the control block (maybe later) when there are no weak_ptr
s alive.
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.
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
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 toObject
.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 usingnew
). This means that the memory for the object has already been allocated when creating thestd::shared_ptr
object itself. Thestd::shared_ptr
needs to allocate memory for its own workings, like e.g. a reference counter. So there are 2 allocations: the one usingnew
passed to the ctor ofstd::shared_ptr
and the one required when constructing thestd::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).
Does shared_ptr a = make_shared() create a copy of the shared_ptr before constructor is run?
There are two things to avoid the copy:
- 1 is the compiler's RVO (return value optimization);
- 2 is the move constructor/assignment.
for code auto foo = std::make_shared<Foo>();
RVO will create the object directly on the stack. and even we disable the RVO by -fno-elide-constructors
, the move constructor will try used as the returned object from make_shared
is a temporary one.
Below is a simple test code. (this code only show the concept but not for a real-world shared_ptr implementation)
#include <iostream>
template <typename T>
struct my_shared_ptr
{
T *t_{nullptr};
my_shared_ptr(T *t): t_(t) {
std::cout << "constructor" << std::endl;
};
my_shared_ptr(const my_shared_ptr<T>&) {
std::cout << "copy" << std::endl;
}
my_shared_ptr<T>& operator=(const my_shared_ptr<T>&) {
std::cout << "copy" << std::endl;
return *this;
}
#ifndef NO_MOVE
my_shared_ptr(my_shared_ptr<T>&&) {
std::cout << "move" << std::endl;
}
my_shared_ptr<T>& operator=(my_shared_ptr<T>&&) {
std::cout << "move" << std::endl;
return *this;
}
#endif
};
template <typename T>
my_shared_ptr<T>
my_make_shared() {
return my_shared_ptr<T>(new T);
}
struct Foo {};
int main()
{
auto foo = my_make_shared<Foo>();
return 0;
}
Condition 1, compile with c++11 shows:
$ g++ a.cc -std=c++11 ; ./a.out
constructor
Condition 2, compile with c++11/disable RVO shows:
$ g++ a.cc -std=c++11 -fno-elide-constructors ; ./a.out
constructor
move
move
Condition 3, compile with c++11/disable RVO/no move shows:
$ g++ a.cc -std=c++11 -fno-elide-constructors -DNO_MOVE ; ./a.out
constructor
copy
copy
What happens when using make_shared
The first case does not perform a double allocation, it performs two allocations, one for the managed object and one for the control block of the shared_ptr
.
For the second case, cppreference has a good explanation for why std::make_shared usually only performs one memory allocation it says (emphasis mine going forward):
This function typically allocates memory for the T object and for the
shared_ptr's control block with a single memory allocation (it is a
non-binding requirement in the Standard). In contrast, the declaration
std::shared_ptr p(new T(Args...)) performs at least two memory
allocations, which may incur unnecessary overhead.
and from std::shared_ptr section it says:
When shared_ptr is created by calling std::make_shared or
std::allocate_shared, the memory for both the control block and the
managed object is created with a single allocation. The managed object
is constructed in-place in a data member of the control block. When
shared_ptr is created via one of the shared_ptr constructors, the
managed object and the control block must be allocated separately. In
this case, the control block stores a pointer to the managed object.
This make_shared
description is consistent with the C++11 draft standard which says in section 20.7.2.2.6
shared_ptr creation
template<class T, class... Args> shared_ptr<T> make_shared(Args&&... args);
template<class T, class A, class... Args>
shared_ptr<T> allocate_shared(const A& a, Args&&... args);
[...]
Remarks: Implementations should perform no more than one memory
allocation. [ Note: This provides efficiency equivalent to an
intrusive smart pointer. —end note ][ Note: These functions will typically allocate more memory than
sizeof(T) to allow for internal bookkeeping structures such as the
reference counts. —end note ]
Herb Sutter has a more detailed explanation of the advantages of using make_shared
in GotW #89 Solution: Smart Pointers and points out some advantages:
- It reduces allocation overhead
- It improves locality.
- Avoids an explicit new.
- Avoids an exception safety issue.
Be aware that when using std::weak_ptr using make_shared has some disadvantages.
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.
Are there any downsides with using make_shared to create a shared_ptr
I know of at least two.
- You must be in control of the allocation. Not a big one really, but some older api's like to return pointers that you must delete.
- No custom deleter. I don't know why this isn't supported, but it isn't. That means your shared pointers have to use a vanilla deleter.
Pretty weak points. so try to always use make_shared.
Related Topics
Programmatically Find the Number of Cores on a Machine
What Happens to Global and Static Variables in a Shared Library When It Is Dynamically Linked
C/C++ With Gcc: Statically Add Resource Files to Executable/Library
Linking Static Libraries to Other Static Libraries
How to Call C++ Function from C
How to Access Private Members from Outside the Class Without Using Friends
C99 Stdint.H Header and Ms Visual Studio
Why Does C++ Disallow Anonymous Structs
Rules For C++ String Literals Escape Character
Testing Pointers For Validity (C/C++)
Why Does Omission of "#Include ≪String≫" Only Sometimes Cause Compilation Failures
Why Copy Constructor Is Not Called in This Case
How to Achieve the Theoretical Maximum of 4 Flops Per Cycle