1

Hello I have a global array of let's say

array = ["A", "B", "C", "D", "E"]

I want to design a parent class with 5 children classes like this

Parent, Child1, Child2, Child3, Child4, Child5

The Parent class will have a method name getID();

How can i design such that this behavior occur

Child1.getID() is array[0] gives "A"
Child2.getID() is array[1] gives "B"
Child3.getID() is array[2] gives "C"
Child4.getID() is array[3] gives "D"
Child5.getID() is array[4] gives "E"

Is this possible?

The reason is that I don't want to keep copy and pasting getID() for 5 children. It would be nice to just write getID() in parent class.

Zanko
  • 4,298
  • 4
  • 31
  • 54
  • This looks like a textbook case for [virtual functions](https://stackoverflow.com/questions/2391679/why-do-we-need-virtual-functions-in-c). – Ken Y-N Jun 17 '16 at 02:28
  • is Child 1 always map to 0? – ggrr Jun 17 '16 at 02:28
  • Thank you but in that case I still have to rewrite getID() for each child right?? – Zanko Jun 17 '16 at 02:29
  • @amuse yes Child1 is always map to 0 – Zanko Jun 17 '16 at 02:30
  • do you want to do it without modifying child class? – ggrr Jun 17 '16 at 02:33
  • @amuse yes my end goal is to not write anything in child class. – Zanko Jun 17 '16 at 02:34
  • then would it required to modify the parent class when a new child class appear? seems it is not quite a good design – ggrr Jun 17 '16 at 02:35
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/114897/discussion-between-zanko-and-amuse). – Zanko Jun 17 '16 at 02:36
  • The array declaration `array = ["A", "B", "C", "D", "E"]` looks very much like Python. It's not C++. It's unclear what you want, but it's not more difficult to create an array of strings in C++ than in Python. – Cheers and hth. - Alf Jun 17 '16 at 02:55
  • @Cheersandhth.-Alf that was part of the explanation and not the actual code :) I am trying to figure out the right design. – Zanko Jun 17 '16 at 03:02
  • This is a pretty nasty design smell to me. What does your `getID()` give you that you can't get from `typeid`? What do you intend to use `getID()` for? Why do you prefer repetition in the parent class to repetition in the child classes? If you just want to avoid repeating `getID()` in each child there are other options. – Michael Anderson Jun 17 '16 at 04:16
  • @MichaelAnderson hi getID() is just a sample function. As of now I have about 20 functions+, like getHP(), getMP(), getEXP() etc.. As you can see different entity ought to have different HP. All these info is stored in a array of structure. I just need to point to the correct structure when I call getHP(); – Zanko Jun 17 '16 at 04:20
  • Hint: you want to read about the **open/closed** principle. And beyond that: you want to read about "doing good OO design" in general. Because your question implies that what you **want** will lead you to very suboptimal solutions. – GhostCat Jun 17 '16 at 06:58

3 Answers3

0

No, not like that. A virtual method in a subclass that overrides virtual methods from its multiple superclasses has no indication of which superclass's virtual method was invoked.

An alternative design is to have each superclass's getid() method be non-virtual, and be a wrapper that invokes an abstract getMyId(), passing it a single parameter, that's unique to each superclass.

The parent subclass implements the abstract getMyId() method, and uses the parameter that the superclass passed, that indicates which superclass invoked the abstract method.

Very simple, and straightforward.

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
  • "The parent subclass implements the abstract getMyId() method, and uses the parameter that the superclass passed," I am abit confused in this case is it a typo? Should it be "subclass passed"? – Zanko Jun 17 '16 at 02:33
0

One way to do this, assuming that I understand the question correctly, is to look up the string based on the type id of the class, e.g.,

#include <typeinfo>         // Necessary for using `typeid`.
#include <typeindex>        // std::type_index
#include <map>              // Consider using `std::unordered_map` instead.

struct Parent
{
    auto id() const -> char const*;
    virtual ~Parent() {}    // Need something virtual for `typeid`.
};

struct Child1: Parent {};
struct Child2: Parent {};
struct Child3: Parent {};
struct Child4: Parent {};
struct Child5: Parent {};

auto Parent::id() const
    -> char const*
{
    using namespace std;
    static const map<type_index, char const*> ids =
    {
        {typeid( Child1 ), "A"},
        {typeid( Child2 ), "B"},
        {typeid( Child3 ), "C"},
        {typeid( Child4 ), "D"},
        {typeid( Child5 ), "E"}
    };

    return ids.at( typeid(*this) );
}

#include <iostream>
using namespace std;
auto main()
    -> int
{ cout << Child1().id() << Child3().id() << "\n"; }
Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
  • Hello thank you so much! After browsing around i found a similar question on http://stackoverflow.com/questions/12796580/static-variable-for-each-derived-class. Do you think this example allow the base class to use variable defined in its subclass? – Zanko Jun 17 '16 at 03:39
  • @Zanko: Just as it's possible to associate a type id with a constant string, it's possible to associate it with a static modifiable object. But centralizing things is a known anti-pattern. Better to try to distribute functionality and (not the least) responsibilities and knowledge in some way, so that you somehow get small bundles of internally strongly connected functionality, knowledge and responsibility, which cooperate via only the barest minimum of knowledge about each other. Think of your classes & objects as a team of persons. One person micro-managing the others = ungood. Coop = good. – Cheers and hth. - Alf Jun 17 '16 at 03:43
0

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 {}
Michael Anderson
  • 70,661
  • 7
  • 134
  • 187