Thursday, November 10, 2005

Subversion in C++?

So a while back one of the Subversion committers mentioned that it might make sense to write some (as yet theoretical) future version of Subversion in a dialect C++ instead of C.

You see, Subversion is VERY object oriented, at least for C code, and we jump through a LOT of hoops as a result of the C language. For example, there's about a million places in the source tree where we pass around void pointers as a way of storing context for some callback. In C++ we could pass objects that carry their own context around with them, instantly cutting in half the number of arguments we need to keep track of.

Anyway, I was thinking about this on the drive home from work today, and I started to wonder what would be required to make that really possible. The first thing that jumped to mind was making APR interoperate with C++. Virtually all resource management in Subversion is done via APR pools, and we're kind of used to that by now, so switching to a non-pool based world would be kind of weird. But APR pools deal in void pointers, low level raw memory C stuff, and setting up higher level cleanups is a pretty manual process.

Now it's possible to take the raw memory you got from a pool and turn it into an instance of a C++ object, via something called placement new, but it's kind of a pain in the ass, and even then you have to manually call the destructor when you're done with the object, which is kind of contrary to the point of allocating things out of a memory pool...

So the question is, how do we allocate a C++ object from a pool, but automatically register a cleanup that takes care of calling the object's destructor when the pool is cleared or destroyed?

It took a little doing, but I managed to come up with something I like. It looks like this:

int
main (int argc, char *argv[])
{
apr::initialize();

apr::pool p;

for (int i = 0; i < 10; ++i)
{
p.clear();

Foo *f = p.allocate();
}

return EXIT_SUCCESS;
}

Now to make that work, you do have to jump through one little hoop. The Foo class needs to have a static 'cleanup' function defined on it, which calls the object's destructor. That function will be used as the pool cleanup callback, so you don't have to worry about cleaning it up yourself. To simplify the process of writing this function, there's a helper macro that pounds it out for you, so the Foo class looks like this:

struct Foo
{
Foo()
{
std::printf("in constructor\n");
}

~Foo()
{
std::printf("in destructor\n");
}

APR_DECLARE_CLEANUP(Foo);
};

That really doesn't seem like such a horrible price to pay for the convenience of being able to work with pools the way we've come to expect, right?

Anyway, I doubt this will ever really be used for anything, but if you want to check out the code I wrote to make this work, you can grab it here.