6

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?

10
  • 3
    " since I have a LOT of derived classes, this would really slow down the program." Have you proved this? Because I highly doubt it. Commented May 23, 2012 at 15:17
  • What does this mean? Type array = { A, B, C };. Commented May 23, 2012 at 15:18
  • @JohnDibling: have never been too famous for their speed. When I have a switch with too many cases, I prefer to use an array of functions... Commented May 23, 2012 at 15:20
  • @Pubby: it's obviously wrong. It was just an example of what I would like to do... An array of class types. Commented May 23, 2012 at 15:22
  • 1
    Wait, so you want new array[0] to create an A nad new array[1] to create a B? Commented May 23, 2012 at 15:24

2 Answers 2

13

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.

Sign up to request clarification or add additional context in comments.

2 Comments

Well, this surely works, but it will instantiate a lot of functions make<A>, make<B>, etc... Isn't there any preprocessor trick that will work? :)
@AlfaOmega08: no preprocessor trick will work, sorry. As for generating different functions... yes it will, it is ineluctable since each class has its own constructor to call. I expanded the answer with a use of switch to help the compiler performing the necessary inlining; it should remove the code for the functions (no longer used) and the overhead of the function call.
5
typedef Base* BaseMaker();

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

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

Base* b = makers[2]();

1 Comment

You are a genius guy!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.