Developing cross-platform C++ projects that don’t require the heap

Cross-platform abstraction is generally done with inheritance. I’ve developed a cleaner method that ditches inheritance but may turn some “purists” off.

The typical (and obvious) method to implement cross-platform objects is to put your platform specific objects in a platform specific directory and make them inherit from a common interface.

The project file can then selectively include those directories based on the current platform.

For example:

/include/Mutex.h

/include/win32/Win32Mutex.h

/include/linux32/Linux32Mutex.h

Mutex.h

class Mutex

{

public:

Mutex();

virtual ~Mutex();

virtual void aquire() = 0; // no implementation

virtual void release() = 0;

static Mutex* createMutex(); // function definition with no implementation

// no variables as we don’t know what we need

};

Win32Mutex.h

#include “Mutex.h”

class Win32Mutex :

public Mutex

{

public:

Win32Mutex();

virtual ~Win32Mutex();

virtual void aquire(); // we implement these here

virtual void release();

private:

HANDLE m_kHandle; // we add the variables we need

};

Win32Mutex.cpp

#include “Win32Mutex.h”

// we provide our win32 implementation of createMutex here

Mutex* Mutex::createMutex()

{

return new Win32Mutex(); // heap allocation!

}

<snip>

This works quite well. But there’s one problem. We have to allocate this object on the heap! We must always refer to this object as a pointer to support polymorphism.

MultithreadedClass.h

class MultithreadedClass

{

private:

Mutex* m_pMutex;

public:

MultithreadedClass() :

m_pMutex( 0 )

{

m_pMutex = createMutex(); // on the heap!

};

<snip>

};

 

This works. But we have an ugly hierarchy of inheritance that we would like to destroy. We also have to refer to ALL mutices as pointers on the heap.

Abstracting the heap usage

We can remove the need for objects to use the createMutex() functions by doing the following:

Mutex.h

class Mutex

{

public:

// This class provides a base for our platform specific mutex function calls

class MutexData

{

public:

MutexData();

virtual ~MutexData();

virtual void aquire() = 0;

virtual void release() = 0;

};

private:

MutexData* m_pMutexData;

static MutexData* createMutexData(); // no implementation

public:

Mutex()

{

m_pMutexData = createMutexData();

};

void aquire()

{

// no need for pure virtual functions!

m_pMutexData->aquire();

};

<snip>

};

Win32Mutex.h

#include “Mutex.h”

class Win32MutexData :

public Mutex::MutexData

{

public:

Win32MutexData();

virtual void aquire(); // these functions are implemented fully

virtual void release();

private:

HANDLE m_kHandle;

};

Win32Mutex.cpp

#include “Win32Mutex.h”

MutexData* Mutex::createMutexData()

{

return new Win32MutexData();

}

<snip>

MultithreadedClass.h

#include “Mutex.h”

class MultithreadedClass

{

private:

Mutex m_kMutex;

public:

MultithreadedClass():

m_kMutex() // on the stack!

{

};

<snip>

};

With this implementation, we’ve hidden the inheritance from the user so they can simply use a normal Mutex object on the stack.

But the mutex itself still uses the heap, we’ve just abstracted it from the user.

Using the heap is ok, but if we don’t have to then we shouldn’t!

Heap allocation / deallocation outside of application initialisation and shutdown will lead to memory fragmentation. If we create and destroy a lot of “Mutex” objects we will slowly fragment our memory due to its mutex being on the heap!

The solution

My solution to cross-platform abstraction, is to go back to the past. Forget everything you’ve learnt.

Remember, we’re creating libraries for other programmers to use, or just for ourself. We should minimise the chance of programmer error, but we can’t, and shouldn’t, cater to malicious programmers (read: idiots).

So let’s just ditch inheritance, let’s ditch the heap, and just expect our programmers to be nice.

There is no need for base classes! We have our project files, they know what platform we’re developing for. There is no need for our code to support the task of project files.

First, provide a new include and source directory that consists of the platform name

/include/

/include/Win32

/include/Linux32

/src

/src/Win32

/src/Linux32

Under these directories, create the same path you would for the base object.

/include/Win32/ProjectName/MyModule

And put our object headers under there

/include/Win32/ProjectName/MyModule/Mutex.h

/src/Win32/Mutex.cpp

We now set our Win32 project to include

/include/Win32

and to exclude any non-platform source directories

exclude /src/Linux32

We can now implement our Mutex class!

class Mutex

{

public:

Mutex();

~Mutex(); // no inheritance so not virtual

void aquire();

void release();

private:

HANDLE m_kHandle;

}

MyApp.cpp

// the project file abstracts the platform issues so we can just include like this!

#include “ProjectName/MyModule/Mutex.h”

void main( int argc, char** argv )

{

Mutex kMutex; // no heap usage!

kMutex.aquire(); // no pointer calls!

}

What have we done here?! The Mutex class has win32 code in it! That’s right!

Because it resides in “<project name>/include/Win32” it will only be used on Win32 platforms, so we can do whatever we want with it!

Also note, there are no virtual methods, so we don’t get the overhead of virtual function calls!

And the object now resides on the stack!

To get consistent usage across all platforms, we just have to make a “gentleman’s agreement” that all Mutex classes will implement certain methods with the same signature. This should be documented in the files where it is done and in the project documentation.

The same method can be used to provide cross-platform typedefs for various types like thread IDs.

Isn’t that bad design?

No. We would normally use the same method to typedef primitives across all platforms, so why not non-primitive types.

Maybe if you’ve had inheritance and clear hierarchies pummeled into your brain. But we shouldn’t code for idiots. We should code APIs that are targeted at good programmers. And if a programmer can avoid using the heap, and the API calls are the same, then I doubt they’ll care how it’s implemented.

So now you’ve had a peek into how I develop clean, cross-platform C++ code.

Just remember, we should target our code at peer programmers, not malicious idiots. Make your code reflect that.

Advertisements

3 Responses to “Developing cross-platform C++ projects that don’t require the heap”

  1. […] This post was mentioned on Twitter by twistedpairdev, twistedpairdev. twistedpairdev said: Developing cross-platform C++ projects that don't require the heap: http://t.co/CSaQ5nr […]

  2. Jurgen Says:

    Nice Article,

    This is how we do it as well for developing cross platform libraries for Games. We have header and source files for different platforms, Nintendo WII, Xbox 360, PS3 etc.. and only one main user/public header file. We do this mostly for objects that do not require extension. For objects that are designed to have different implementation we make a pure virtual interface class.

    Best
    Jurgen

    • The code above is to avoid pure virtual functions when defining different implementations.
      Using these leads to heap usage due to the need to ‘new’ the objects as the base class is not able to be instantiated.

      For some projects it’s not a big issue, but when you’re allocating and de-allocating a lot of objects, and there’s no method for running a compaction pass over the heap (read: GC supported languages) then eventually your memory will fragment and possibly lead to crashes.

      With objects on the stack, you have a more deterministic memory usage model. Heap allocation leads to non-determinism. And there’s nothing worse than non-deterministic behavior when you’re debugging!

      With that in mind, each project has its own requirements and goals. Not all projects need gold plate and polish. Something I really need to learn. I tend to over-engineer projects instead of just getting them done (albeit, done well).

      Cheers for the comment =)
      Adam

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: