0

In C++, I have several classes inheriting from an abstract super class. Subclasses have a static attribute sharing the same name type but of course have different values. My question is what is the best approach to implement this and what are the pros and cons for each implementation.

PS: There are some related discussions like here but most don't explain why approach (which works on my machine) 1 below should not be used. Besides, the subclass methods in approach 2 are not static and wwe will have to get an instance to invoke them.

Apporach 1: uinitialized const static in superclass

class Abstract {
  public:
    const static int type;
};

class C1: public Abstract {
  public:
    const static int type = 1;
};

class C2 : public Abstract {
  public:
    const static int type = 2;
};

Approach 2: using virtual functions instead of variables

class Abstract {
  public:
    virtual int get_type() = 0;
};

class C1: public Abstract {
  public:
    int get_type() {return 1;}
};

class C2 : public Abstract {
  public:
    int get_type() {return 2;}
};

Other approaches that I'm not aware of...

EDIT:

As some answers/comments mentioned below, I'm trying to identify actual type at runtime. However I cannot really think of a nicer design.

To make it concrete, let's say Abstract=EduInst for educational institution, C1=Univ, C2=College, etc. I have a std::map<Key, EduInst*> storing all institutions, which are generated at runtime depending on user input. At times I need to operate only on Univs or Colleges. What is a good way to implement this?

Community
  • 1
  • 1
wlnirvana
  • 1,811
  • 20
  • 36

2 Answers2

2

First the warnings:

Inheritance describes an "is kind of" relationship with the base class. It only makes sense when you are storing different kinds of objects in the same container, when those kinds of object absolutely share the same interface, and when no special handling is required on any one of the derived classes (i.e. when you, the object's consumer don't need to know its type).

Furthermore, if you only have a few kinds of the same thing, and those kinds of thing can possibly be known at compile time, then it's probably a mistake to use inheritance.

If your inherited object must identify its actual type to a client, this is further evidence that inheritance is the wrong solution - since now you're going to use code to find code, which is a bad idea (it's not testable, and it's liable to go wrong when your program is in a state that you didn't anticipate).

Furthermore, if you have objects that are dissimilar but need to be stored in the same container, then boost::variant is probably the solution you're looking for. You would then use boost::static_visitor to perform operations on the object in the variant. The advantage of this is that is absolutely type-safe and the compiler won't allow you to forget to handle a type.

Having said all that...

approach 1 won't work because if you have the type of the derived class already, you'll always get the base class' type.

approach 2 will work but it's horrid and an indication of a broken design.

Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • See edit above. It turns out that my objects are similar and thus I want to save them in the same container. However I still need to use code to find code unless I store the `type` explicitly or use something like `isinstanceof`. – wlnirvana Dec 07 '15 at 21:43
  • After your edit I am more convinced than before that virtual inheritance is the wrong solution. However if you must check the type of an object, use typeid(). – Richard Hodges Dec 07 '15 at 21:55
  • What is potentially a better design for this problem? `Univ`, `College` do have a lot in common like `enroll()`, `tuition`, etc. – wlnirvana Dec 07 '15 at 21:59
  • Which part of the program requires that the consumer (caller) knows what the type of the object is? I.e. Where are the parts of the program where logic is different depending on the object type? – Richard Hodges Dec 07 '15 at 23:11
  • For example suppose `Univ` and `HighSchool` both implemente `raiseTuition()`, but the interactive user (in this case playing the role of a government) decides only raise tuition for `Univ`. – wlnirvana Dec 07 '15 at 23:33
  • in this case, the `type` of learning institution is part of an attribute. This attribute is being used as part of an index during a search. In that case, it can just go in the base class as a data member. The method to retrieve it does not need to be polymorphic at all (see molbdnilo's answer) – Richard Hodges Dec 07 '15 at 23:39
  • I understand the method works. It's just a bit wired because intuitively this apprarently is an attribute shared by all instances of same (sub)class and never changes, thus is semantically `const static`. Besides, implementing it as a normal member in base class feels "redundant" because we should be able to infer the runtime type of an object, otherwise dynamic binding won't work. However I admit that using `typeid` is rare in c++. I guess the relatively better solution is member variable in base class. Thank you very much for discussion! – wlnirvana Dec 07 '15 at 23:49
  • 20 years ago, OO was seen as the final solution to every programming problem. These days we're a little wiser :) – Richard Hodges Dec 08 '15 at 00:43
1

You can't have "virtual" static members.

Approach one is for finding out the "type" attribute of a class, the second for finding out the "type" attribute of an instance.
To put it differently: to use the first, you need to know the class; to use the second, you need to know the instance.
It's impossible to write code where you know neither.

Note that approach one can give you unexpected results if you use type inside a function in a base class.
For instance,

class Abstract
{
public:
    int get_type() const { return type; }
};

// ...
C1 c;
std::cout << c.get_type(); 

will output 0, since that is the value of Abstract::type.

A variation of the second approach lets you avoid the virtual function if you sacrifice the space of another member:

class Abstract {
  public:
    // ...
    int get_type() const { return type; }
  protected:
    Abstract(int t) : type(t) {}
  private:
    int type;
};

class C1: public Abstract {
  public:
    C1() : Abstract(1) {}
};

class C2 : public Abstract {
  public:
    C2() : Abstract(2) {}
};
molbdnilo
  • 64,751
  • 3
  • 43
  • 82