Polymorphism without "new"

A simple way to avoid dynamic allocation is to use static allocation, which is about as opposite from dynamic allocation as possible. It must be done carefully however, because even with a non-threaded program one can inadvertently get into a situation where two or more parts of the code each think that they “own” some statically allocated object. Worse, such essentially global variables (even when disguised as singletons, or in the code below as local statics) essentially serve as central hubs for spaghetti communication, where chaos-inducing information is freely propagated between places that you could never imagine, totally out of your control.

So, the static allocation scheme has some drawbacks… :-)

But let’s start there:

// Using static allocation.

#include <iostream>
using namespace std;

struct A { virtual void g() = 0; };

struct B : A { virtual void g() override { wcout << "A\n"; } };
struct C : A { virtual void g() override { wcout << "B\n"; } };

A& f( bool const x )
{
    static B    theB;
    static C    theC;

    if( x ) { theB = B(); return theB; } else { theC = C(); return theC; }
}

bool get_boolean() { return false; }

int main()
{
    bool const b = get_boolean();
    A& x = f( b ); 
    x.g();
}

To avoid the mistaken-ownership drawback of the static allocation scheme, you can provide the storage on the stack, using C++ automatic allocation (C++ automatic allocation is a stack by definition, a LIFO allocation scheme). But this means passing the storage down to the function. The function can then return a reference to the relevant object:

// Using automatic storage (the stack)

#include <iostream>
using namespace std;

struct A { virtual void g() = 0; };

struct B : A { virtual void g() override { wcout << "A\n"; } };
struct C : A { virtual void g() override { wcout << "B\n"; } };

A& f( bool const x, B& b, C& c )
{
    if( x ) { b = B(); return b; } else { c = C(); return c; }
}

bool get_boolean() { return false; }

int main()
{
    bool const b = get_boolean();
    B   objBStorage;
    C   objCStorage;
    A&  x   = f( b, objBStorage, objCStorage ); 
    x.g();
}

But even when we choose to ignore issues such as construction with side effects, and so on, i.e. when we blithely assume that classes B and C are designed to work well with such a scheme, the above wastes storage. If B and C instances are large, one may therefore consider using C++’s facilities for constructing objects in pre-existing storage, known as placement new. Due to memory alignment issues it’s a bit difficult to do correctly in C++03, but C++11 offers better support, as follows:

#include <iostream>
#include <memory>           // unique_ptr
#include <new>              // new
#include <type_traits>      // aligned_storage
using namespace std;

typedef unsigned char Byte;

struct A { virtual void g() = 0; };

struct B : A { virtual void g() override { wcout << "A\n"; } };
struct C : A { virtual void g() override { wcout << "B\n"; } };

A* f( bool const x, void* storage )
{
    return (x? static_cast<A*>( ::new( storage ) B() ) : ::new( storage ) C());
}

bool get_boolean() { return false; }

void destroyA( A* p ) { p->~A(); }

int main()
{
    enum{ enoughBytes = 
        (sizeof( B ) > sizeof( C ))? sizeof( B ) : sizeof( C ) };
    typedef aligned_storage< enoughBytes >::type StorageForBOrC;

    bool const b = get_boolean();
    StorageForBOrC storage;
    A* const pX = f( b, &storage );
    unique_ptr<A, void(*)(A*)> const cleanup( pX, destroyA );
    pX->g();
}

Now, which of the above would I choose?

Would I choose the severely restricted but simple and instant static allocation, or would I choose the memory-wasting automatic allocation, or perhaps … the optimized but somewhat complex in-place object construction?

The answer is, I would choose none of them!

Instead of focusing on micro-efficiency I would focus on clarity and correctness, and therefore simply take the performance hit of a dynamic allocation. For correctness I would use a smart pointer for the function result. If this turned out to really be slowing things down, I would perhaps consider using a dedicated small objects allocator.

In conclusion, don’t fret the small stuff! :-)


In function f objects B() or C() are both temporary, so you can only return them from f by value.

Maybe boost::variant is for you. Then you don't even need to have the method virtual or derive from a common base class.

Tags:

C++

C++11