Fork me on GitHub

SDL and Modern C++: Images

Resource Acquisition Is Instantiation

A common term you might come across when learning C++ is Resource Acquisition Is Instantiation or RAII for short. The philosophy behind this is that a class should manage acquiring and releasing resources on its own. By resources I generally mean memory allocated on the heap, file handles, and such. An instance of a class is responsible for managing the resource assigned to it.

I found a neat use for this while working on my game. As is par for C-based APIs, SDL returns a pointer to a surface data-structure it allocates on the heap. It is up to the programmer to remember to free this data-structure when they're done with it. For simple programs this is easy but can get complicated quickly when you're doing things like procedural generation and instantiating game entities all over the place. So in order to reduce that complexity in my code I created a class that manages the surface data-structure as a resource.

Smart Pointers

As I've been working on my game I ran into something called, smart pointers. They're included in C++11 and in Boost if you're on a compiler that doesn't support C++11 yet. If at all possible I avoid the use of plain, old pointers in my C++ code because these babies have very clear semantics. More importantly for my Image class they will ensure that the proper destructor is called on my resource!

Take a moment to let that sink in...

You don't have to manually remember to call SDL_FreeSurface (or the equivalent in your library of choice).

The Image Class

The Image class is simple. I have a single member that is an instance of a shared_ptr. The shared pointer class has a member pointer that points to a structure called a control block. The control block structure keeps a reference count. The shared pointer's copy constructor and assignment operators increment the reference count in the control block. The destructor decrements the reference count. The last shared pointer to the resource to get deleted calls the deleter member method on the resource which defaults to the standard delete function.

Generally shared_ptr should be avoided unless you really need it. The additional reference count and pointers don't come for free. However in a game where you have many entities that might actually use the same image it can be quite useful. This Image class can be copied amongst as many game entities as I require and the surface will always be freed once the final reference to the surface is destroyed.

#include <memory>

class Image {
    std::shared_ptr<SDL_Surface> surface;

public:
    Image();
    Image(SDL_Surface* surface);
    Image(const Image& other);
    Image& operator=(const Image& other);
    ~Image();

    bool draw(SDL_Surface* dest, int x, int y);
    bool draw(SDL_Surface* dest, int x, int y, int x2, int y2, int
    w, int h);
};

Technically we don't even need to specify a destructor on this class. It's not actually managing any resources on its own! However I like to put my destructors in anyway just out of habit.

Image::Image()
{
    surface = NULL;
}

Image::Image(SDL_Surface* s)
: surface(s, SDL_FreeSurface)
{
}

Image::Image(const Image& other)
: surface(other.surface.get(), SDL_FreeSurface)
{
}

Image& Image::operator=(const Image& other)
{
    surface = other.surface;

    return *this;
}

Image::~Image()
{
}

This are just the constructors. I don't mind initializing objects to NULL to make life easier. You just have to be careful to check the state of your pointer members in your methods. If you don't include a default constructor then of course you will have to explicitly initialize any Image members in a class' initialization list.

Speaking of initialization lists you should note them on the second constructor and the copy constructor. This is the only place I know of where you can pass arguments to a shared pointer's constructor. The second argument to this constructor is a function that will be passed a singled argument of T type passed into the template. If omitted then the shared pointer would use the default delete function. Instead we pass it SDL_FreeSurface which will be called on our surface pointer when the last reference to it is removed.

The first benefit of this is that we can have as many game entities as we like use the same image in memory. The best thing about using a shared pointer is that we don't have to manually check to make sure nobody is using the surface anymore. It's all handled for us.

And just to demonstrate how you reference the pointer managed by the shared pointer here are the implementations of my draw function.

bool Image::draw(SDL_Surface* dest, int x, int y)
{
    if ((dest == NULL) || (surface == NULL)) {
        return false;
    }
    SDL_Rect pos;
    pos.x = x;
    pos.y = y;

    SDL_BlitSurface(&*surface,
                    NULL,
                    dest,
                    &pos);
    return true;
}

bool Image::draw(SDL_Surface* dest, int x, int y, int x2, int y2,
int w, int h)
{
    if ((dest == NULL) || (surface == NULL)) {
        return false;
    }
    SDL_Rect pos;
    pos.x = x;
    pos.y = y;

    SDL_Rect src;
    src.x = x2;
    src.y = y2;
    src.w = w;
    src.h = h;

    SDL_BlitSurface(&*surface,
                    &src,
                    dest,
                    &pos);
    return true;
}

And there you have it. Assuming you have a main function with SDL initialized you can use this class like so:

Image img1(SDL_LoadBMP("/path/to/sprite.png"));
Image img2 = img1;

img1.draw(10, 10);
img2.draw(100, 200, 32, 32, 32, 32);

delete img1;

img2.draw(64, 64);

A trivial example but between the two objects there is only one SDL_Surface object in memory. When we assign img1 to img2 we're calling Image::operator= with img1 as the parameter. This copies the shared pointer to the surface in img1 into img2's surface member which increases the reference count in the control block for this resource to 2. When we delete img1 we don't lose the surface when img2 tries to access it in the final call to Image::draw because the reference count is still 1. Only when we go out of scope and img2 is destroyed will the reference count be less than one and SDL_FreeSurface will be called on the pointer.

I still check for NULL in the draw functions for two reasons. The first is because I don't know what's on the other side of SDL_Surface* dest. And I also don't know whether this instance was called with the default constructor or not.

In either case I chose to return false here so that the caller can do something about it if they wish but I don't consider it an error. You may want a different behavior. This is just a trivial example after all.

Conclusion

RAII is a useful pattern for encapsulating resources given to you by C APIs. Using smart pointers will allow you to ensure those resources are managed effectively without having to manage them manually in your classes. Without the shared pointer the Image class would have to implement its own reference counting scheme if it wanted to have a copy constructor and copy assignment operator. Or else you would have to get all puritan and say that each game entity is responsible for loading a copy of it image in memory... even if it's the same image used by a hundred other entities in the scene -- which we all know is wasteful.

So read up on smart pointers and think about modifying your classes to use them.