8

I'm trying to use unique_ptr with a custom deleter for SDL_Surface type. This is only an example using int type, but I hope you get the idea.

#include <iostream>
#include <functional>
#include <memory>

typedef int SDL_Surface;


SDL_Surface * CreateSurface()
{
    SDL_Surface * p = new SDL_Surface;
    return p;
}

void FreeSurface(SDL_Surface *p)
{
    delete p;
}

int main() {
    std::unique_ptr<SDL_Surface, std::function< void (SDL_Surface *) > > uptr_1; 

    //how to assign a value to uptr_1 and the deleter? 

    return 0;
}

Is uptr_1 correctly declared and initialized to nullptr? If so, how can I assign the pointer and the deleter function?

And how can I encapsulate this: std::unique_ptr< SDL_Surface, std::function< void (SDL_Surface *) > > with the deleter to not always write that line on every SDL_Surface I want, another typedef?

I'm just starting to learn C++11 features and this is a hard one for me.

Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
FrameBuffer
  • 757
  • 1
  • 7
  • 26
  • `std::unique_ptr > uptr_1(CreateSurface(), &::FreeSurface);` – Giulio Franco Feb 19 '15 at 15:00
  • 5
    `std::function` is a poor choice for a deleter in the general case, because its constructors can throw, but `unique_ptr`'s deleter's constructor must not throw. However, if you are only using it with a plain function pointer, it's safe. – T.C. Feb 19 '15 at 15:10
  • Related - https://stackoverflow.com/q/24251747/241631 – Praetorian Feb 19 '15 at 17:42

3 Answers3

11

You can initialise the unique_ptr with a pointer and deleter, or use = normally if re-assigning later:

std::unique_ptr<SDL_Surface, std::function<void (SDL_Surface *)>> uptr_1(CreateSurface(), &FreeSurface);

uptr_1 = std::unique_ptr<SDL_Surface, std::function<void (SDL_Surface *)>>(CreateSurface(), &FreeSurface);

Refer to suitable docs for details.

To shorten the long type, you can indeed use a type alias (typedef or using):

typedef std::unique_ptr<SDL_Surface, void (*)(SDL_Surface*)> Surface_ptr;

//or

using Surface_ptr = std::unique_ptr<SDL_Surface, void (*)(SDL_Surface*)>;

Notice I've actually used void (*)(SDL_Surface*) for the deleter type. If you know you'll always pass an actual function (or stateless lambda) in, there's no reason to drag in std::function, which has some overhead due to type erasure.

Also, you can shorten it even further by creating a default-constructible functor for the deleter:

struct FreeSurface_Functor
{
  void operator() (SDL_Surface *s) const
  {
    FreeSurface(s);
  }
};

That way, you can make the type of your pointer std::unique_ptr<SDL_Surface, FreeSurface_Functor> (possibly aliased) and you don't have to provide the deleter; it will be default-constructed:

std::unique_ptr<SDL_Surface, FreeSurface_Functor> uptr_1(CreateSurface());
T.C.
  • 133,968
  • 17
  • 288
  • 421
Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
  • have I to check in the custom deleter if the pointer is nullptr ? Or std::unique_ptr will not call the default deleter if the pointer is nullptr? Thanks for the answer. – FrameBuffer Feb 19 '15 at 15:50
  • 1
    @FrameBuffer You don't have to check. `unique_ptr` is guaranteed to do the check for you, and only call the deleter if non-null. You can find stuff like that in [the docs I've linked](http://en.cppreference.com/w/cpp/memory/unique_ptr/~unique_ptr). – Angew is no longer proud of SO Feb 19 '15 at 15:51
  • 5
    You might point out that the technique of defining a stateless deleter class type is preferred because quality `unique_ptr` implementations will optimize away their storage with the Empty Base Optimization. – Casey Feb 19 '15 at 15:59
2

I would go with decltype:

std::unique_ptr<SDL_Surface, decltype(&FreeSurface)> uptr_1(
          CreateSurface(),
          FreeSurface
);
lvella
  • 12,754
  • 11
  • 54
  • 106
1

Is uptr_1 correctly declared and initialized to nullptr

Yes, a default constructed unique_ptr will refer to null.

if so, how can I assign the pointer and the deleter function?

You should be constructing the unique_ptr with arguments

 std::unique_ptr<SDL_Surface, std::function< void (SDL_Surface *) > > uptr_1{CreateSurface(), FreeSurface};

Alternatively, after the default construction you could use move assignment with a temporary

uptr_1 = std::unique_ptr<SDL_Surface, std::function< void (SDL_Surface *) > >{CreateSurface(), FreeSurface};

As you've suggested yourself, a type alias can help

using SDL_Uptr = std::unique_ptr<SDL_Surface, std::function< void (SDL_Surface *)>>;
SDL_Uptr  uptr_1;
uptr_1 = SDL_Uptr{CreateSurface(), FreeSurface};

An intermediate function could help simplify this if it becomes repetitive (which it probably will if you make a lot of them).

std::unique_ptr<SDL_Surface, void (*)(SDL_Surface *)>
make_sdl_ptr() {
    return std::unique_ptr<SDL_Surface, void (*)(SDL_Surface *)>{CreateSurface(), FreeSurface};
}

You could then call this with auto uptr = make_sdl_ptr();

Angew's answer with a DefaultConstructible deleter calling your function is also a really nice solution.

Ryan Haining
  • 35,360
  • 15
  • 114
  • 174