7

Is it possible to use a custom deleter after creating a std::shared_ptr without using new?

My problem is that object creation is handled by a factory class and its constructors & destructors are protected, which gives a compile error, and I don't want to use new because of its drawbacks.

To elaborate: I prefer to create shared pointers like this, which doesn't let you set a custom deleter (I think):

auto sp1 = make_shared<Song>(L"The Beatles", L"Im Happy Just to Dance With You");

Or I can create them like this, which does let met set a deleter through an argument:

auto sp2(new Song, MyDeleterFunc);

But the second one uses new, which AFAIK isn't as efficient as the top sort of allocation.

Maybe this is clearer: is it possible to get the benefits of make_shared<> as well as a custom deleter? Would that mean having to write an allocator?

Kristian D'Amato
  • 3,996
  • 9
  • 45
  • 69
  • 1
    Could you add some (pseudo-)code to explain what you need or otherwise elaborate? (I suspect an XY-Problem) – Daniel Frey Oct 25 '13 at 21:35
  • i don't understand your problem, ofc you are free to initialize shared_ptrs with your values, it doesn't have to be something returned by the new operator – Pavel Beliy Oct 25 '13 at 21:37
  • possible duplicate of [Using custom deleter with std::shared\_ptr](http://stackoverflow.com/questions/12340810/using-custom-deleter-with-stdshared-ptr) – Zac Howland Oct 25 '13 at 21:41
  • The deleter is not part of the *type*, only of the *object*. So... no. – Kerrek SB Oct 25 '13 at 21:42
  • Care to elaborate, please? I'm new to smart pointers. – Kristian D'Amato Oct 25 '13 at 21:44
  • There are very few cases where a destructor has to be protected, especially when using shared pointers. Resist your urge to protect your class from downright malicious code like `delete sptr.get()`. – Fozi Oct 25 '13 at 21:56
  • Mmm... that urge is very difficult to control once you've `protected`ized your constructors, for me at least! – Kristian D'Amato Oct 25 '13 at 22:00

2 Answers2

8

No, there is no form of std::make_shared that takes a custom deleter.

If you need to return a shared_ptr with a custom deleter then you'll have to take the performance hit.

Think about it: If you use make_shared then it will allocate a larger memory region that can store the reference count and your object together, and the placement new will be called. The shared_ptr returned from make_shared already has a custom deleter, one that calls your object's destructor explicitly and then frees the larger memory block.

Fozi
  • 4,973
  • 1
  • 32
  • 56
4

You have to use new in your case as the design of std::make_shared to avoid an additional allocation can only work if std::make_shared can use it's own (internal) custom deleter to free to combined memory of the object and the shared_count.

You have to accept that with your own custom deleter you can not optimize away an allocation, but you should still use a std::make_shared-like wrapper to encapsulate the new for reasons of safe usage. This helps to avoid memory leaks in cases where the constructors of the object throw an exception and someone uses

template<typename T>
void f(const T&, const T&);

f( std::shared_ptr<X>(new X), std::shared_ptr<X>(new X) ); // possible leak

instead of

std::shared_ptr<X> make_X() { return std::shared_ptr<X>(new X); }
f( make_X(), make_X() ); // no leak
Daniel Frey
  • 55,810
  • 13
  • 122
  • 180
  • Thanks. Can you expand on why the second is better than the first? I don't see why there's a potential for leaks only in the first one. – Kristian D'Amato Oct 25 '13 at 22:09
  • 2
    In the second case each call to `make_X` makes sure that each pointer returned from `new` is immediately stored in a `std::shared_ptr` and so even in case of an exception, there is a `shared_ptr` which will delete the instance returned from `new`. – Daniel Frey Oct 25 '13 at 22:13
  • Is there anything reasonable? That will do the same as make_shared and will work with non-public destructors? IMHO, requirement to have public destructor defeats whole purpose of having shared pointers. – uuu777 Jan 17 '15 at 21:37
  • What is wrong with this approach: `make_shared>`, where `storage_with_deleter` contains a `std::aligned_storage_t t;` and a `D d;`, on construction placement-news into the `t`, and on `~storage_with_deleter` invokes `d(static_cast(&t))`, then use aliasing constructor to turn it into a `shared_ptr`. – Yakk - Adam Nevraumont Sep 02 '15 at 14:46
  • @Yakk It might work, but since one point of `make_shared` is to be more efficient, returning another instance of `shared_ptr` through the aliasing constructor will prevent elision or other optimization (depending on context) and requiring an additional atomic ref-counter add and delete. In the end you'd have to benchmark it to see if it is really worth the additional complexity. – Daniel Frey Sep 02 '15 at 15:06
  • @DanielFrey Good point: the aliasing constructor takes by `const&` instead of by value (or rvalue ref). That looks like a flaw, as it forces the atomic increment and decrement. However, I doubt two atomic operations will out-cost a single extra memory allocation plus all the later cache misses. I think writing `make_shared_with_deleter` should work, but I'm not sure what syntax to use... Using forwarding tuples is ... awkward. – Yakk - Adam Nevraumont Sep 02 '15 at 15:13