22 Jan 2024 • C++ tricks: NonRAIIDynamicArray

In Cocaine Diesel our DynamicArray class takes an Allocator in its constructor. One our of allocators is just malloc plus std::map< void *, AllocInfo > for leak checking, which we initialise statically. So it didn't take us long to run into the standard static initialisation order problem when we wanted to use file-scoped arrays.

At first we added a special constructor that put the array into a non-RAII mode, a trick I learned at Umbra:

enum NoInit { NO_INIT };

class DynamicArray {
public:
    DynamicArray( NoInit ) { ... }
};

To make this work you need to add explicit init/shutdown methods, and also not run the normal destructor because static destructors have the same problem. It works but it always seemed kind of clunky to me.

Another problem we ran into was that returning arrays from functions was always kind of annoying. Flipping the RVO/malloc coin is not good enough, so we had to write such functions like f( DynamicArray< int > * results ) { ... } which is also annoying.

So I split DynamicArray into NonRAIIDynamicArray and DynamicArray. By itself that would honestly be a nice trick and worthy of a post already, but C++ also lets you change the visibility of inherited members which makes the implementation very concise. So you can make DynamicArray derive from NonRAIIDynamicArray, hide the explicit init/shutdown methods, and add a constructor/destructor like so:

template< typename T >
class DynamicArray : public NonRAIIDynamicArray< T > {
    using NonRAIIDynamicArray< T >::init;
    using NonRAIIDynamicArray< T >::shutdown;

public:
    NONCOPYABLE( DynamicArray );

    DynamicArray( Allocator * a, size_t initial_capacity = 0 ) {
        init( a, initial_capacity );
    }

    ~DynamicArray() {
        shutdown();
    }
};

With this static dynamic arrays work with no magic, and you can write functions that return n results pretty much how you would in any scripting language:

Span< int > Square( Allocator * a, Span< const int > xs ) {
    NonRAIIDynamicArray< int > squares( a );
    for( int x : xs ) {
        squares.add( x * x );
    }
    return squares.span();
}

Cleanup is easy too with defer, or non-existent if you use an auto-freeing temp allocator.