Moments of Inertia by Rachel Crawford

About     Archive     Categories     Tags     Feed     Projects     Magewinds     Warcry Card Creator    

Custom Deleters: A Real Example

I’ve written in the past about standard-library smart pointers and how they can make management of memory allocated from the heap much easier. How useful are they when working with objects allocated from elsewhere, such as objects created by a library we use?

Box2D is a popular 2D rigid-body physics engine in which the b2World is the top-level object that represents the physics ‘world’. Rigid bodies are represented by instances of b2Body and are created like so:

// world is a b2World*
// bodyDef is a b2BodyDef that describes the properties of the new body
b2Body* body = world->CreateBody(&bodyDef);

The b2World owns the b2Body and the memory it is created from, and when the world is destroyed it destroys all the bodies it contains. To remove an individual body and free its memory we call b2World::DestroyBody(b2Body*).

This creates a small challenge for us when we build our game code on top. Let’s say we have an Entity class, which has a b2Body* member.

class Entity {
  b2Body* body;
};

In Entity’s destructor we may want to call DestroyBody, so that when an Entity goes out of scope, it tells the b2World to remove its body from the world:

Entity::~Entity() {
  if (body) body->GetWorld()->DestroyBody(body);
}

These are unique ownership semantics (just about), so we could make body a std::unique_ptr rather than a raw pointer. Let’s try it!

std::unique_ptr<b2Body> bodyPtr = world->CreateBody(&bodyDef);

Unfortunately, this is no good. By default, std::unique_ptr’s destructor calls delete on its internal pointer, which isn’t what we want. The b2World is who really owns the memory, so deleting it out from underneath it would probably cause undefined behaviour down the line, if not immediately. The memory which the b2World allocates bodies etc. from may be a pool block-allocated from the heap. It might not even come from the heap, it may be on the stack. Either way, calling delete on a bit of it would not go down well.

So we need to call b2World::DestroyBody instead of delete.

Custom Deleters

We’re in luck: std::unique_ptr has more than one template parameter.

template<class T, class Deleter = std::default_delete<T>>
class unique_ptr;

The first is the type to which it points, and the second allows the user to define a custom deleter. A custom deleter must be a type with an operator that takes a pointer to T. The custom deleter lets us define what unique_ptr’s destructor does if its internal pointer is non-null.

Here is a pointless example of one that just does the same as std::default_delete<b2Body>:

struct b2BodyDeleter {
  void operator()(b2Body* body) const {
    delete body;
  }
};

using std::unique_ptr<b2Body, b2BodyDeleter> BodyPtr;

BodyPtr = world->CreateBody(&bodyDef);

Obviously this isn’t what we need because we need to call b2World::DestroyBody.

Gotcha

A quick aside, and a warning – you might be wondering why we can’t just use a regular ol’ free function for this. Here’s why.

void b2BodyDeleter(b2Body* body) {
  delete body;
}

using BodyPtr bp = std::unique_ptr<b2Body, b2BodyDeleter>;

BodyPtr doesn’t compile because b2BodyDeleter isn’t a type - it’s a function. Using decltype and an ampersand, we can proceed:

using BodyPtr = std::unique_ptr<int, decltype(&b2BodyDeleter)>;

decltype(&b2BodyDeleter) gives us the type of a pointer to the b2BodyDeleter function (which I’m not going to write, because ugh). However, when we try to instantiate BodyPtr we hit a snag – the class doesn’t have a default constructor anymore, and must be constructed with a pointer to b2BodyDeleter (or any function with a matching signature, actually – so much for safety!).

BodyPtr body; // Doesn't compile!
BodyPtr body(nullptr, &b2BodyDeleter); // Does compile!
BodyPtr body(world->CreateBody(&bodyDef), &b2BodyDeleter); // Does compile!

Unfortunately, BodyPtr is no longer a zero-cost abstraction as it now consists of both a pointer to a b2Body and a function pointer.

static_assert(sizeof(BodyPtr) == sizeof(b2Body*)); // Fails!

If we instead use a functor like our original b2BodyDeleter then the empty base class optimization allows unique_ptr’s size to equal the size of a raw pointer. When using a function pointer our smart pointer becomes stateful, meaning it carries additional stuff along with its underlying raw pointer. This is a bit of a gotcha, and arguably things shouldn’t have to be this way (i.e. functions in C++ should be first-class types), but writing a functor or a lambda to avoid this isn’t too much bother.

b2BodyDeleter

So finally here’s our real b2BodyDeleter:

struct b2BodyDeleter {
  void operator()(b2Body* body) const {
    body->GetWorld()->DestroyBody(body);
  }
};

using BodyPtr = std::unique_ptr<b2Body, b2BodyDeleter>;

static_assert(sizeof(BodyPtr) == sizeof(b2Body*));

BodyPtr body = world->CreateBody(&bodyDef);

We had a choice here. Instead of accessing the b2World using b2Body::GetWorld we could store a World* (or World&) in b2BodyDeleter, setting it in the constructor. That doesn’t really bring any advantages that I can think of, and I can think of two disadvantages. For one, it would force us to specify an extra argument every time we instantiate a BodyPtr. For another, it’d make the deleter stateful and increase its size. No good!

Now we can swap Entity’s raw b2Body pointer for a BodyPtr, and we never have to worry about manually calling b2World::DestroyBody for the Entity’s body again. The body will be removed from the world when the Entity goes out of scope. We get all the other benefits of unique_ptr, too.

There are limitations to doing things this way: an Entity now must go out of scope before the b2World does, otherwise b2Body->GetWorld will return a dangling pointer. We can no longer destroy the b2World before all the Entity instances who are using it unless we call release on each Entity’s BodyPtr before its destructor is called.

This example may seem like a bit of a strawman to you, but in my homebrew game engine switching from using raw b2Body pointers to smart ones has been a pleasant improvement. It fits into my RAII-based approach to just about everything.

Further Reading

Fluent C++ did a series about smart pointers recently, with a few posts dedicated to custom deleters:

comments powered by Disqus