2

During C++03 we did not have unique_ptr, so I had used the tr1::shared_ptr instead.

Now, in C++11, in such cases, I am replacing calls like this:

tr1::shared_ptr<X> o(new X); 

with

std::unique_ptr<X> o(new X);

Unfortunately, for some reason, I cannot replace cases containg a deleter, where the deleter is a function:

void special_delete(Y *p) { ... }

tr1::shared_ptr<Y> o(new Y(), special_delete); 


std::unique_ptr<Y> o(new Y(), special_delete);  // does not compile
std::unique_ptr<Y, std::function<void(Y*)> > o(new Y(), special_delete);  // this compiles

Why does this happen? Is there a homogeneous way I can replace all shared_ptr constructors with unique_ptr constructors ?

I have created this, but I am not really happy about it..

template <class Y>
using unique_ptr_with_deleter = std::unique_ptr<Y, std::function<void(Y*)> >;

unique_ptr_with_deleter<Y> o(new Y(), special_delete);  // this compiles
Grim Fandango
  • 2,296
  • 1
  • 19
  • 27
  • 2
    Related: [Why does unique_ptr take two template parameters when shared_ptr only takes one?](https://stackoverflow.com/questions/21355037/why-does-unique-ptr-take-two-template-parameters-when-shared-ptr-only-takes-one) – cpplearner Jul 04 '18 at 20:12
  • @JesperJuhl: Except precisely *not* in the OP's case. – Kerrek SB Jul 04 '18 at 20:16

2 Answers2

4

The shared_ptr template type-erases the deleter. There's only one single type shared_ptr<T> for any kind of deleter you want. It's a very heavy-weight (and expensive) tool.

By contrast, the unique_ptr makes the deleter part of the type, and so it has no dynamic call overhead. A unique_ptr<T, D> is very nearly as cheap as a raw pointer.

Note that you can construct a shared_ptr<T> from a unique_ptr<T, D> rvalue for any deleter type D.


Random commentary on your solutions: neither solution is great. Using the function pointer as the deleter needlessly makes the deleter a dynamic part of your unique_ptr state. The std::function case is even worse, using an entire type-erasing virtual dispatch mechanism for the deletion -- keeping in mind that you know statically how to delete!

The better solution is to make the deleter part of the type, not part of the value, by writing your own type:

struct YDeleter { void operator()(Y* p) { special_delete(p); } };

std::unique_ptr<Y, YDeleter> p(new Y);   // or presumaby some special factory

That way, the entire deletion logic is known at compile time and can be inlined as much as possible, without additional function call indirection.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • And possibly something like: `template struct Deleter { template void operator()(T* p) {f(p);} };` and then `using YDeleter = Deleter<&special_delete>;`. – Jarod42 Jul 04 '18 at 23:19
  • Isn't this answer pretty much what the OP had in the first place? (Which I myself saw nothing wrong with). – Paul Sanders Jul 05 '18 at 07:21
  • @PaulSanders: How so? My deleter is stateless, the OP's is not. – Kerrek SB Jul 05 '18 at 21:29
  • Oh right, I see it now, thanks. I myself did it the same way as you a while back, don"t know why I forgot that. – Paul Sanders Jul 05 '18 at 21:40
2

You should supply deleter type

std::unique_ptr<Y, decltype(&special_delete)> o{new Y{}, &special_delete};

Note that replacing tr1::shared_ptr<X> o(new X); with std::unique_ptr<X> o(new X); is not a good idea and is not supposed to work in general. std::unique_ptr is more like a replacement for auto_ptr.

user7860670
  • 35,849
  • 4
  • 58
  • 84
  • Yes, but why don't I have to do this instantiation in std::shared_ptr ? – Grim Fandango Jul 04 '18 at 20:05
  • 1
    @GrimFandango Because they handle deleters differently. For `unique_ptr` deleter is a part of the type while `shared_ptr` embeds deleter into implicitly created shared state so deleter type is not part of the type. For `shared_ptr` different shared states might be instantiated based on deleter supplied into constructor. – user7860670 Jul 04 '18 at 20:10
  • I like the use of `decltype` here - very neat. – Paul Sanders Jul 06 '18 at 07:14
  • _`shared_ptr` embeds deleter into implicitly created shared state_ right, I understand that now, but why does it do it differently, there seems to be no obvious reason? I guess it's just history, `shared_ptr` dating back to C++03 and all. I think we could say they got it wrong back then. – Paul Sanders Jul 06 '18 at 07:18