Are there any downsides with using make_shared to create a shared_ptr
Asked Answered
D

5

52

Are there any downsides with using make_shared<T>() instead of using shared_ptr<T>(new T).

Boost documentation states

There have been repeated requests from users for a factory function that creates an object of a given type and returns a shared_ptr to it. Besides convenience and style, such a function is also exception safe and considerably faster because it can use a single allocation for both the object and its corresponding control block, eliminating a significant portion of shared_ptr's construction overhead. This eliminates one of the major efficiency complaints about shared_ptr.

Donaldson answered 27/1, 2010 at 14:31 Comment(6)
One might wonder, what are the other major efficiency complaints about shared_ptr?Merryman
Exception safety is a pretty strong asset of std::make_shared. Try to use it whenever possible.Perron
@ViktorSehr Mutex locks on the reference counter when a shared_ptr is copied mainly :)Should
@Drax: Ah, so theyre thread safe? Didnt know thatMerryman
@Should Dont mind the questionmark btw, I can google :)Merryman
@ViktorSehr cppreference says: All member functions (including copy constructor and copy assignment) can be called by multiple threads on different instances of shared_ptr without additional synchronisation even if these instances are copies and share ownership of the same object. Which means that the shared data has some kind of synchronisation. (AFAIK implementations usually use atomic operations to increment the counter which make them less performance eaters)Should
J
26

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.

Joyann answered 27/1, 2010 at 14:40 Comment(9)
There's no custom deleter because only make_shared itself knows how to delete the object.Horst
The custom deleter is used to do some interesting tricks not limited to just deleting, which is why deft_code mentioned it.Heterophony
If you could add a custom deleter, then you should have the ability for a custom allocator as well; let's say that it would complicate the interface too much.Kurdistan
@ipapadop: I don't follow -- the allocation has already happened by that point and doesn't need to happen again, so there's no need to keep an allocator function around. (Either you've passed an already-allocated pointer to the shared_ptr ctor, or you've asked make_shared() to do the allocation for you.)Superannuate
@MikeSeymour: From shared_ptr's point of view, yes, it needs to have a custom deleter furnished by make_shared(), but it would be possible and perhaps useful to provide the caller of make_shared() with the ability to run a custom "sub-deleter" that gets run from inside make_shared()'s own custom deleter.Superannuate
What I'm saying is that it would look something like this: template<class T, class Allocator, class Deleter, class... Args> shared_ptr<T> make_shared2(Allocator a, Deleter d, Args... args); Yes, it could be provided, but it's not very pretty.Kurdistan
@ipapadop: It makes sense now, thanks. (You can still overload operator new as a workaround, but I realise that doesn't give you per-allocation control).Superannuate
@Kurdistan sounds like you want std::allocate_shared .Selfabuse
@PedroLamarão yes, but you have to write a class conforming to the Allocator concept, which is a few lines of code. See for example the shared_ptr constructor that takes both a Deleter and an Allocator (en.cppreference.com/w/cpp/memory/shared_ptr/shared_ptr).Kurdistan
C
39

In addition to the points presented by @deft_code, an even weaker one:

  • If you use weak_ptrs that live after all the shared_ptrs to a given object have died, then this object's memory will live in memory along with the control block until the last weak_ptr dies. In other words the object is destroyed but not deallocated until the last weak_ptr is destroyed.
Crat answered 27/1, 2010 at 15:41 Comment(3)
That's also the case if you don't use make_shared. The only difference is that the control block will be in a separately allocated lump of memory.Horst
@Mike: surely not - normally, if there are no shared_ptrs to the object, only weak_ptrs, then the object is deleted immediately. I don't know off-hand whether the control block remains, I'll take your word for it. With make_shared, the control block and the object co-habit a single allocation, so if the memory for one of them remains then both do (although I'd guess the object is destructed, just the memory not freed?).Shaunta
Er, yeah. Sorry. I must have had my other brain in when I wrote that.Horst
J
26

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.

Joyann answered 27/1, 2010 at 14:40 Comment(9)
There's no custom deleter because only make_shared itself knows how to delete the object.Horst
The custom deleter is used to do some interesting tricks not limited to just deleting, which is why deft_code mentioned it.Heterophony
If you could add a custom deleter, then you should have the ability for a custom allocator as well; let's say that it would complicate the interface too much.Kurdistan
@ipapadop: I don't follow -- the allocation has already happened by that point and doesn't need to happen again, so there's no need to keep an allocator function around. (Either you've passed an already-allocated pointer to the shared_ptr ctor, or you've asked make_shared() to do the allocation for you.)Superannuate
@MikeSeymour: From shared_ptr's point of view, yes, it needs to have a custom deleter furnished by make_shared(), but it would be possible and perhaps useful to provide the caller of make_shared() with the ability to run a custom "sub-deleter" that gets run from inside make_shared()'s own custom deleter.Superannuate
What I'm saying is that it would look something like this: template<class T, class Allocator, class Deleter, class... Args> shared_ptr<T> make_shared2(Allocator a, Deleter d, Args... args); Yes, it could be provided, but it's not very pretty.Kurdistan
@ipapadop: It makes sense now, thanks. (You can still overload operator new as a workaround, but I realise that doesn't give you per-allocation control).Superannuate
@Kurdistan sounds like you want std::allocate_shared .Selfabuse
@PedroLamarão yes, but you have to write a class conforming to the Allocator concept, which is a few lines of code. See for example the shared_ptr constructor that takes both a Deleter and an Allocator (en.cppreference.com/w/cpp/memory/shared_ptr/shared_ptr).Kurdistan
M
14

From http://www.codesynthesis.com/~boris/blog/2010/05/24/smart-pointers-in-boost-tr1-cxx-x0/

The other drawback of the make_shared() implementation is the increase in the object code size. Due to the way this optimization is implemented, an additional virtual table as well as a set of virtual functions will be instantiated for each object type that you use with make_shared().

Merryman answered 10/5, 2011 at 21:43 Comment(1)
Is this expected to persist going forward as compilers progress?Heterophony
T
8

Additionally, make_shared is not compatible with the factory pattern. This is because the call to make_shared within your factory function calls the library code, which in turn calls new, which it doesn't have access to, since it cannot call the class's private constructor(s) (constructor(s) should be private, if you follow the factory pattern correctly).

Typeface answered 4/3, 2013 at 13:50 Comment(0)
S
7

With make shared you can not specify how allocation and deallocation of the held object will be done.

When that is desired, use std::allocate_shared<T> instead:

std::vector<std::shared_ptr<std::string>> avec; 
std::allocator<std::string> aAllocator;
avec.push_back(std::allocate_shared<std::string>(aAllocator,"hi there!"));

Note that the vector does not need to be informed about the allocator!

For making a custom allocator, have a look here https://mcmap.net/q/354084/-why-doesn-39-t-this-c-stl-allocator-allocate

Sipe answered 4/11, 2012 at 9:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.