Search code examples
c++arraystypesabstract-factory

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?


Solution

  • 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.