I'm going to assume that the aim is to avoid repetition of the body of getID()
in each child class. But I also think moving the array into the parent class is "not ideal".
There are many options you can use to avoid the boilerplate code.
Which one works best for you depends heavily on the real use case for the classes.
Use only one child class and a factory method in parent
Push the common code into a base class and add a factory method to the parent class (You could even merge the parent class and base class if appropriate).
struct ChildBase : public Base {
explicit ChildBase(const char* name) : name(name) {};
const char * name;
const char * getID() { return name; }
}
struct Base {
ChildBase getChild1() { return ChildBase("A"); }
ChildBase getChild2() { return ChildBase("B"); }
}
or even
struct Base {
const char* ids = { "A", "B", "C", ...};
ChildBase getChild(int i) { return ChildBase(ids[i]); }
}
This second form easily generalizes to loading the configuration at run time.
Use a base class
Push the common code into a base class.
Usually this would look like:
struct ChildBase : public Base {
ChildBase(const char* name) : name(name) {};
const char * name;
const char * getID() { return name; }
}
struct Child1 : public ChildBase {
Child1() : ChildBase("A") {};
}
struct Child2 : public ChildBase {
Child2() : ChildBase("B") {};
}
but this doesn't save you much since you still need to customize the constructor. You can reduce the cruft using a templated base class instead
template<const char* ID>
struct ChildBase() {
const char* getID() { return ID; }
};
struct Child1 : public ChildBase<"A">();
struct Child2 : public ChildBase<"B">();
Macros
Yes, macros are evil. But they can solve a problem like this pretty easily
#define DefineChild(CLASS, ID) \
struct CLASS : public Base { \
const char * getID() { \
return ID; \
} \
}
DefineChild(Child1, "A");
DefineChild(Child2, "B");
Code Generation
Generate a base class for each of the child classes in your build script. (Or even each of the child classes directly if they're as empty as you say.)
Definition File - this is how you configure what gets generated. You'll need to write a generation script, but that can usually be pretty trivial.
Child1 "A"
Child2 "B"
Child3 "C"
...
Generated code - The output of your generation script
// Generated code - do not edit
struct Child1Base : public Base {
const char* getID() { return "A"; }
}
...
Application Code - Here you can customize the behaviour of
the generated code if needed.
struct Child1 : public Child1Base {}
struct Child2 : public Child2Base {}
struct Child3 : public Child3Base {}
struct Child4 : public Child4Base {}