Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create an array of classes types?

I have a single class "Base", and a few tens of classes derived from Base. I would like to have a method that creates me the right class by an index. Like this:

class Base
{
};

class A : public Base
{
}

class B : public Base
{
}

class C : public Base
{
}

Type array = { A, B, C };

and then I could do new array[i];

How could this be achieved with C++(0x)? Usually I would use an the Abstract Factory Pattern. But since I have a LOT of derived classes, this would really slow down the program.

Since the derived classes will be used only once I also taught to use this:

Base *array = { new A, new B, new C };

But this would lead to huge memory consumption, not counting that not every class will always be used.

Any suggestion?

like image 814
Francesco Boffa Avatar asked May 23 '12 15:05

Francesco Boffa


2 Answers

You cannot use an array of classes, but you can use an array of pointers to functions.

typedef std::unique_ptr<Base> (*Creator)();

template <typename T>
std::unique_ptr<Base> make() { return new T{}; }

Creator const array[] = { make<A>, make<B>, make<C> };

int main() {
    std::unique_ptr<Base> b = array[1]();

    b->foo();
}

For those worried by the cost of creating so many template functions, here is an example:

#include <stdio.h>

struct Base { virtual void foo() const = 0; };

struct A: Base { void foo() const { printf("A"); } };
struct B: Base { void foo() const { printf("B"); } };
struct C: Base { void foo() const { printf("C"); } };


typedef Base* (*Creator)();

template <typename T>
static Base* make() { return new T{}; }

static Creator const array[] = { make<A>, make<B>, make<C> };

Base* select_array(int i) {
    return array[i]();
}

Base* select_switch(int i) {
    switch(i) {
    case 0: return make<A>();
    case 1: return make<B>();
    case 2: return make<C>();
    default: return 0;
    }
}

LLVM/Clang generates the following output:

define %struct.Base* @select_array(int)(i32 %i) uwtable {
  %1 = sext i32 %i to i64
  %2 = getelementptr inbounds [3 x %struct.Base* ()*]* @array, i64 0, i64 %1
  %3 = load %struct.Base* ()** %2, align 8, !tbaa !0
  %4 = tail call %struct.Base* %3()
  ret %struct.Base* %4
}

define noalias %struct.Base* @select_switch(int)(i32 %i) uwtable {
  switch i32 %i, label %13 [
    i32 0, label %1
    i32 1, label %5
    i32 2, label %9
  ]

; <label>:1                                       ; preds = %0
  %2 = tail call noalias i8* @operator new(unsigned long)(i64 8)
  %3 = bitcast i8* %2 to i32 (...)***
  store i32 (...)** bitcast (i8** getelementptr inbounds ([3 x i8*]* @vtable for A, i64 0, i64 2) to i32 (...)**), i32 (...)*** %3, align 8
  %4 = bitcast i8* %2 to %struct.Base*
  br label %13

; <label>:5                                       ; preds = %0
  %6 = tail call noalias i8* @operator new(unsigned long)(i64 8)
  %7 = bitcast i8* %6 to i32 (...)***
  store i32 (...)** bitcast (i8** getelementptr inbounds ([3 x i8*]* @vtable for B, i64 0, i64 2) to i32 (...)**), i32 (...)*** %7, align 8
  %8 = bitcast i8* %6 to %struct.Base*
  br label %13

; <label>:9                                       ; preds = %0
  %10 = tail call noalias i8* @operator new(unsigned long)(i64 8)
  %11 = bitcast i8* %10 to i32 (...)***
  store i32 (...)** bitcast (i8** getelementptr inbounds ([3 x i8*]* @vtable for C, i64 0, i64 2) to i32 (...)**), i32 (...)*** %11, align 8
  %12 = bitcast i8* %10 to %struct.Base*
  br label %13

; <label>:13                                      ; preds = %9, %5, %1, %0
  %.0 = phi %struct.Base* [ %12, %9 ], [ %8, %5 ], [ %4, %1 ], [ null, %0 ]
  ret %struct.Base* %.0
}

Unfortunately, it is not quite intelligent enough to automatically inline the functions with a regular array code (known issue with the LLVM optimizer, I don't know if gcc does better)... but using switch it is indeed possible.

like image 135
Matthieu M. Avatar answered Sep 18 '22 12:09

Matthieu M.


typedef Base* BaseMaker();

template <class X> Base* make() {
  return new X;
}

BaseMaker* makers[] = { make<A>, make<B>, make<C> };

Base* b = makers[2]();
like image 21
n. 1.8e9-where's-my-share m. Avatar answered Sep 19 '22 12:09

n. 1.8e9-where's-my-share m.