How to pass deleter to make_shared?
Asked Answered
M

5

58

Since C++11, because of several reasons, developers tend to use smart pointer classes for dynamic lifetime objects. And with those new smart pointer classes, standards, even suggest to not use operators like new instead they suggest to use make_shared or make_unique to avoid some error prone.

If we like to use a smart pointer class, like shared_ptr, we can construct one like,

shared_ptr<int> p(new int(12));

Also we would like to pass a custom deleter to smart pointer classes,

shared_ptr<int> p(new int(12), deleter);

On the other hand, if we like to use make_shared to allocate, for ex. int, instead of use new and shared_ptr constructor, like on the first expression above, we can use

auto ip = make_shared<int>(12);

But what if we like to also pass a custom deleter to make_shared, is there a right way to do that? Seems like compilers, at least gcc, gives an error to,

auto ip = make_shared<int>(12, deleter);
Manaker answered 12/12, 2015 at 18:23 Comment(1)
Write your own make_shared() that supports this, it is doable.Justis
L
83

As other have said, make_shared cannot be used with a custom deleter. But I want to explain why.

Custom deleters exist because you allocated the pointer in some special way, and therefore you need to be able to deallocate it in a correspondingly special way. Well, make_shared allocates the pointer with new. Objects allocated with new should be deallocated with delete. Which the standard deleter dutifully does.

In short, if you can live with the default allocation behavior, you can live with the default deallocation behavior too. And if you can't live with the default allocation behavior, you should use allocate_shared, which uses the provided allocator to both allocate and deallocate the storage.

Also, make_shared is allowed to (and almost certainly will) allocate the memory for T and the control block for the shared_ptr within the same allocation. This is something that your deleter can't really know about or deal with. Whereas allocate_shared is capable of handling it, since the allocator you provide can do allocation and deallocation duties.

Leafstalk answered 12/12, 2015 at 18:53 Comment(12)
Upvoted, I've never thought about that, but it makes perfectly sense. Good hint.Farthermost
+1 but custom deleters aren't just for controlling deallocation, the pointer owned by a shared_ptr hasn't necessarily been "allocated" at all. You might want to use fclose to close a FILE*, for example. Using allocate_shared allows you to control the memory allocation, but not use arbitrary resources. You still have to construct and destruct an object, not obtain "a resource" from a function and free it with another (deleter) function. Even if lwg 2070 gets fixed that still doesn't allow custom deleters with make/allocate_shared.Hf
The bottom line is that because make/allocate_shared don't allow you to change the "obtain a resource" step it doesn't make sense for them to support custom ways to "free the resource" either. They are used for simple create/destroy an object uses of shared_ptr.Hf
Or put another way: consider an incomplete type, X, and functions get() and put(X*). You can use this with shared_ptr<X>(get(), &put) but there's no way to use make_shared or allocate_shared because they both depend on sizeof(X) which is not known.Hf
@JonathanWakely: But in those cases, you created the object being pointed to. So you could never use make_shared or allocate_shared, since the primary purpose of both is for them to create the object.Leafstalk
That should be the answer :) Discussing allocation and allocate_shared is an unnecessary diversion. The point is that make_shared creates the object so you should also trust it to "uncreate" it.Hf
@JonathanWakely: So I should say, "In short, if you can live with the default allocation behavior, you can live with the default deallocation behavior too?" My point with allocate_shared is that it can be used in many cases where you want an explicit deleter. Not necessarily all, but many.Leafstalk
As I said, there's more to "uncreate" than deallocation, there's also a destructor, but often when you need a custom deleter it doesn't necessarily involve any destructor, or any deallocation.Hf
"Well, make_shared allocates the pointer with new. Objects allocated with new should be deallocated with delete. Which the standard deleter dutifully does." There's no guarantee make_shared uses new, it could allocate it with std::allocator (as my implementation does), or malloc. Or a custom allocator, or something else. You don't know, and so your deleter wouldn't know what to do. make_shared creates the object, somehow, so you should trust it to uncreate it.Hf
I agree, accept for when it comes to threads, which need to be joined before deletedMoralize
@JonathanWakely eel.is/c++draft/util.smartptr.shared#create-7.4 standard requires that make_shared to be using ::new, what you were referring to might be something like allocate_sharedMalacostracan
@AlexRoddick that says that the object is constructed using placement new into a memory location pv, "where pv has type void* and points to storage suitable to hold an object of type U." It doesn't say how that storage is allocated. I stand by my statement: the standard doesn't require the storage to be allocated using new.Hf
F
13

As from the documentation, make_shared accepts a list of arguments with which an instance of T will be constructed.
Moreover, the documentation says that:

This function is typically used to replace the construction std::shared_ptr(new T(args...)) of a shared pointer from the raw pointer returned by a call to new.

Because of that, you can deduce that you can't set a custom deleter.
To do that, you have to create the shared_ptr for yourself by means of the right constructor.
As an example of a constructor from the proposed list, you can use:

template< class Y, class Deleter > 
shared_ptr( Y* ptr, Deleter d );

Thus, the code will be something like:

auto ptr = std::shared_ptr(new MyClass{arg1, arg2}, myDeleter);

Instead of:

auto ptr = std::make_shared<MyClass>(arg1, arg2);
Farthermost answered 12/12, 2015 at 18:38 Comment(0)
H
7

It is unspecified how make_shared obtains the memory for the object (it could use operator new or malloc or some kind of allocator) so there is no way a custom deleter could know how to do the right thing. make_shared creates the object, so you have to also rely on it to destroy the object correctly and do the appropriate clean up, whatever that is.

Also we would like to pass a custom deleter to smart pointer classes,

shared_ptr<int> p(new int(12), deleter);

I don't think this is a very realistic example. A custom deleter is typically used when the resource was obtained in some special way. If you just created it with new like this then why do you need a custom deleter anyway?

If you just want some code to be run on destruction then put it in a destructor! That way you can also still use it with make_shared e.g.

struct RunSomethingOnDestruction {
  RunSomethingOnDestruction(int n) : i(n) { }
  ~RunSomethingOnDestruction() { /* something */ }
  int i;
};

auto px = std::make_shared<RunSomethingOnDestruction>(12);
std:shared_ptr<int> p(px, px->i);

This gives you a shared_ptr<int> that is created by make_shared (so you get the memory optimisations done by make_shared) that will run some custom code on destruction.

Hf answered 13/12, 2015 at 0:58 Comment(2)
and if you can't alter the destructor of the thing being pointed to?Logogriph
Then use a custom deleter, and don't use make_shared.Hf
T
4

You can't. make_shared<T> forwards the provided arguments to the constructor of type T. It is used for the simple case when you want the default deleter.

Teratology answered 12/12, 2015 at 18:32 Comment(0)
I
0

If you use a custom deleter you can't use make_unique or make_shared functions when you create a smart pointer objects . Since we need to provide our custom deleter these functions do not support that .

Don't use make_unique or make_shared if you need a custom deleter or adopting a raw pointer from elsewhere.

The idea is if you need a specialized way to delete your object you probably need a specialized way to create them too .

Let say we a class Test

#include <iostream>    
using namespace std;    
class Test
{
private : 
    int data; 
    public : 
    Test() :data{0}
    {
        cout << "Test constructor (" << data << ")" << endl;
    }
    Test(int d) : data{ d }
    {
        cout << "Test constructor (" << data << ")" << endl; 
    }
    int get_data() const { return data; }
    ~Test()
    { 
        cout << "Test Destructor (" << data << ')' << endl; 
    }
};

// main function. 
int main()
{
   // It's fine if you use  make_shared and custom deleter like this
   std::shared_ptr<Test> ptr(new Test{1000},
            [](Test *ptr)
            {
                cout << "some Code that you want to execute "; 
                delete ptr;
            });
         return 0;
}

But if you use make_shared function you will get a compiler error

std::shared_ptr<Test> ptr = make_shared<Test>(1000,
            [](Test *ptr){
               cout << "some Code that you want to execute "; 
               delete ptr;
            });

Basically make_shared function is a wrapper for new and delete and if you want a custom deleter you have to provide you own new and delete

Instrumentation answered 20/12, 2018 at 15:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.