46

I'm trying to work out how to use std::shared_ptr with a custom deleter. Specifically, I'm using it with SDL_Surface as:

std::shared_ptr<SDL_Surface>(SDL_LoadBMP(....),SDL_FreeSurface);

which compiles and runs fine. However, I would like to try out my own deleter and cannot work out how to do so. The documentation for SDL_FreeSurface is found here:

http://sdl.beuc.net/sdl.wiki/SDL_FreeSurface

in which I find the SDL_FreeSurface is declared as:

void SDL_FreeSurface(SDL_Surface* surface);

As a test, and going by that information, I tried the following function:

void DeleteSurface(SDL_Surface* surface)
{
    std::cout << "Deleting surface\n";
    SDL_FreeSurface(surface);
}

However, compiling with g++ gives me the following error:

error: no matching function for call to 'std::shared_ptr<SDL_Surface>::shared_ptr(SDL_Surface*, <unresolved overloaded function type>)'

I have looked at the gnu documentation for the gcc std::shared_ptr implementation but cannot make much sense of it. What am I doing wrong?

EDIT: I've since narrowed down the problem, but will leave the original question above. What I had was a Game class which, if I strip it down to a basic implementation, was something like:

class Game {
    public:
        /* various functions */
    private:
        void DeleteSurface(SDL_Surface* surface);
        bool CacheImages();
        std::vector<std::shared_ptr<SDL_Surface> > mCachedImages;

        /* various member variables and other functions */
}

with the implementation of DeleteSurface as above, and the implementation of CacheImages() as:

bool CacheImages()
{
    mCachedImages.push_back(std::shared_ptr<SDL_Surface>(SDL_LoadBMP(...),DeleteSurface);
    return true;
}

which game me the error I listed above. However, if I move the DeleteSurface() function outside the Game class without otherwise altering it, the code compiles. What is it about including the DeleteSurface function in the Game class that is causing problems?

Jason C
  • 38,729
  • 14
  • 126
  • 182
Wheels2050
  • 879
  • 2
  • 9
  • 17

2 Answers2

63
std::shared_ptr<SDL_Surface>(SDL_LoadBMP(....), [=](SDL_Surface* surface)
{
    std::cout << "Deleting surface\n";
    SDL_FreeSurface(surface);
});

or

void DeleteSurface(SDL_Surface* surface)
{
    std::cout << "Deleting surface\n";
    SDL_FreeSurface(surface);
}

std::shared_ptr<SDL_Surface>(SDL_LoadBMP(....), DeleteSurface);

EDIT:

Seeing your updated question, DeleteSurface should be a non-member function, otherwise you need to use std::bind or std::mem_fn or some other member function pointer adapter.

ronag
  • 49,529
  • 25
  • 126
  • 221
  • Your second example is what I already had, which won't compile. – Wheels2050 Sep 09 '12 at 16:40
  • @Wheels2050: It compiles just fine, you are leaving something out from your example in that case. – ronag Sep 09 '12 at 16:58
  • Thanks for that. I still don't understand why the member function won't work, but you've shown me what to investigate. – Wheels2050 Sep 09 '12 at 17:18
  • 1
    @Wheels2050: non-static member functions need to be called on an instance of `Game`, which will be the object pointer to by `this` inside the member function. `shared_ptr` has no instance of `Game` when it calls the deleter function. – Steve Jessop Sep 09 '12 at 17:26
  • OK, thanks Steve - that's pretty obvious now you've explained it! I appreciate the clarification. – Wheels2050 Sep 09 '12 at 17:30
  • Note that it could be defined in the class, only you need to make the function static. – Alexis Wilke Oct 20 '14 at 07:39
  • 12
    Nothing's actually being captured here, is it? There probably shouldn't be an `=` in your lambda capture group; I found it confusing. – Kyle Strand Nov 14 '15 at 15:45
10

This code provides an example of a shared pointer construction with the deleter as an object method. It display the std::bind instruction to use.

The example is a simple object recycler. When the last reference to the object is destroyed, the object is returned to the free object pool inside the recycler.

The recyler can be easily changed into an object cache by adding a key to the get() and add() methods and by storing the objects in a std::map.

class ObjRecycler
{
private:
    std::vector<Obj*> freeObjPool;
public:
    ~ObjRecycler()
    {
        for (auto o: freeObjPool)
            delete o;
    }

    void add(Obj *o)
    {
        if (o)
            freeObjPool.push_back(o);
    }

    std::shared_ptr<Obj> get()
    {
        Obj* o;
        if (freeObjPool.empty())
            o = new Obj();
        else
        {
            o = freeObjPool.back();
            freeObjPool.pop_back();
        }
        return std::shared_ptr<Obj>(o, 
             std::bind(&ObjRecycler::add, this, std::placeholders::_1));
    }
}
chmike
  • 20,922
  • 21
  • 83
  • 106
  • 3
    Just wanted to say that this doesn't work as intended. When you call `get()`, you'll **always** get a shared pointer with a reference count of `1`. What's worse, multiple shared pointers can then point to the same memory. If one goes out of scope, you'll get segfaults. What you need is not an `std::vector`, but an `std::vector>`. – rwols Sep 25 '16 at 08:29
  • 1
    you don't want a `std::vector>` as that won't preserve the lifetime of the buffer you want to reuse, you want a `std::vector>` instead. you can impliticly create a `std::shared_ptr` from your pre-existing `std::unique_ptr` and then the custom deleter can then take the raw now-unowned pointer and place it into a new `std::unique_ptr` and put that into your `std::vector`. – Sam Kellett Nov 12 '17 at 15:33