Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Determining largest derived class at compile time

Let's say I have a class Base, which has N children, Derived0, Derived1, ... ,DerivedN. I'm trying to create a pool allocator for objects inheriting from Base, and the implementation relies on knowing the size of the largest child of Base, since the pools much each be large enough to contain one of those objects. Here's a simple example with trivial classes and N = 2. In reality, N may be larger and Base's children may not be simple POD classes.

class Base {
public:
    virtual ~Base() = 0;
};

class Derived0 : public Base {
    int a;
};

class Derived1 : public Base {
    int a, b;
};

class Derived2 : public Base {
    int a, b, c;
};

So let's say that I want to create a buffer large enough to store 10 children. I've been banging my head on this for a while, and this is all I can come up with:

static const size_t NUMBER_OF_POOLS = 10;

static const size_t LARGEST_CHILD_SIZE =
    sizeof(Derived0) > sizeof(Derived1) ?
        sizeof(Derived0) > sizeof(Derived2) ?
            sizeof(Derived0) :
            sizeof(Derived2) :
        sizeof(Derived1) > sizeof(Derived2) ?
            sizeof(Derived1) :
            sizeof(Derived2);

char buffer[NUMBER_OF_POOLS * LARGEST_CHILD_SIZE];

This works, but it's pretty easy to see how this gets pretty cluttered as N starts to grow. Is there any scalable way to implement this, where you don't need to manually build a nested "unary tree" (for lack of a better term) which grows into a giant mess as N increases?

Here are the constraints/environment I'm working with:

  • The size of the largest child must be known at compile time.
  • C++03 compatibility is necessary. I'm interested in any suggestions (C++11 welcome!) in the interest of curiosity, but I'll ultimately be building this on a compiler without C++11 support. I understand that C++11 added support for unions with non-POD members, so I was thinking that you might be able to clean up the interface a bit using that.
  • This will only be built using GCC, so GCC extensions are technically an option.

Any help/suggestions are appreciated.

like image 505
Matt K Avatar asked Apr 14 '15 22:04

Matt K


3 Answers

You could consider using a boost::variant and take its size:

    sizeof (boost::variant< Derived0, Derived1, Derived2 > )  

This will return the size of the largest element.

By the way, it could also simplify your buffer management, by making it a buffer of this variant, and indexing directly the right element. The good news is that boost takes then care of alignment requirements:

    typedef boost::variant< Derived0, Derived1, Derived2 > DerivedVar; 
    DerivedVar buffer[NUMBER_OF_POOLS];  
like image 189
Christophe Avatar answered Sep 28 '22 10:09

Christophe


Works only for POD classes:

union AllDerived {
    Derived0 _0;
    Derived1 _1;
    Derived2 _2;
};

static const size_t LARGEST_CHILD_SIZE = sizeof(AllDerived);
static const size_t NUMBER_OF_POOLS = 10;
char buffer[NUMBER_OF_POOLS * LARGEST_CHILD_SIZE];

And this solution works not only for POD:

template <int Value1, int Value2>
struct static_max {
    static const int value = (Value1 < Value2) ? Value2 : Value1 ;
};

template<typename T, typename U>
struct TypeList {
    typedef T Head;
    typedef U Tail;
};

class NullType {};

template
<
    typename T1  = NullType, typename T2  = NullType, typename T3  = NullType,
    typename T4  = NullType, typename T5  = NullType, typename T6  = NullType,
    typename T7  = NullType, typename T8  = NullType, typename T9  = NullType,
    typename T10 = NullType, typename T11 = NullType, typename T12 = NullType,
    typename T13 = NullType, typename T14 = NullType, typename T15 = NullType,
    typename T16 = NullType, typename T17 = NullType, typename T18 = NullType
>
struct MakeTypelist
{
private:
    typedef typename MakeTypelist
    <
        T2 , T3 , T4 ,
        T5 , T6 , T7 ,
        T8 , T9 , T10,
        T11, T12, T13,
        T14, T15, T16,
        T17, T18
    >
    ::Result TailResult;

public:
    typedef TypeList<T1, TailResult> Result;
};

template<>
struct MakeTypelist<>
{
    typedef NullType Result;
};

template<typename TList>
struct MaxTypeSize;

template <>
struct MaxTypeSize<NullType> {
    enum { value=0 };
};

template<typename T, typename U>
struct MaxTypeSize<TypeList<T,U>> {
    enum { value = static_max<sizeof(T), MaxTypeSize<U>::value>::value };
};

typedef MakeTypelist<Derived0, Derived1, Derived2>::Result AllTypes;
static const size_t LARGEST_CHILD_SIZE = MaxTypeSize<AllTypes>::value;
static const size_t NUMBER_OF_POOLS = 10;
char buffer[NUMBER_OF_POOLS * LARGEST_CHILD_SIZE];

Here we use type list and compile-type max function. Type list implementation you can find in Loki library. Compile-time function MaxTypeSize calculate maximum type size in list.

like image 23
gomons Avatar answered Sep 28 '22 09:09

gomons


Modify your code by introducing a helper macro, resulting better code format. When a new derived class is added, just insert a STATIC_MAX(sizeof(... line and add a ). This works in C++03.

#define STATIC_MAX(a, b)  ((a) > (b) ? (a) : (b))

static const size_t LARGEST_CHILD_SIZE =
    STATIC_MAX(sizeof(Derived0),
    STATIC_MAX(sizeof(Derived1),
    STATIC_MAX(sizeof(Derived2),
               0)));

And if C++1x compiler is an option, you can use "variadic template" and "constexpr function" to get a more simple solution. When a new derived class is added, just insert the class name as template parameter in the last line.

template <typename T>
static constexpr T static_max(T a, T b) {
    return a < b ? b : a;
}

template <typename T, typename... Ts>
static constexpr T static_max(T a, Ts... bs) {
    return static_max(a, static_max(bs...));
}

template <typename... Ts>
constexpr size_t max_sizeof() {
    return static_max(sizeof(Ts)...);
};

static constexpr size_t LARGEST_CHILD_SIZE =
    max_sizeof<Derived0, Derived1, Derived2>();
like image 40
Quotation Avatar answered Sep 28 '22 10:09

Quotation