5

Without RTTI and with virtual functions.

I encountered 2 different common solutions to provide type of the object:

with virtual method call and keep id inside the method:

class Base {
 public:
 virtual ~Base();
 virtual int getType() const =0;
};
class Derived : public Base {
 public:
 virtual int getType() const { return DerivedID; }
};

with inline method call and keep id in the base class:

class Base {
 int id_;
 public:
 virtual ~Base();
 inline int getType() const { return id_; }
};

class Derived : public Base {
 public:
  Derived() { id_=DerivedID;}
};

What would be better choice in general and what is pro/cons of them?

VladimirS
  • 600
  • 6
  • 14

3 Answers3

1

If you choose the second option with an ID in every derived class, you'll have an extra member in every object you create. If you use the first option of a virtual function, you'll have an extra pointer in the vtable which only exists once per class, not per object. If the class will have more than one virtual function, the first option is clearly better. Even if not, I think it more closely corresponds to what people will expect.

Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
  • Would you take the runtime overhead of calling a `virtual` function into consideration? Or is that too trivial in real life applications? The choice reads to me like _extra runtime storage vs. extra runtime processing_... – Drew Dormann Jul 03 '14 at 20:19
  • @DrewDormann the overhead of a virtual function is both tiny and common, so I've learned to stop worrying about it most of the time. But you're right. – Mark Ransom Jul 03 '14 at 20:27
  • 'an extra pointer in the vtable which only exists once per class, not per object' sounds plain wrong –  Jul 04 '14 at 18:32
  • @DieterLücking that's how vtables work. One per class shared among all objects of that class by a single pointer per object. – Mark Ransom Jul 04 '14 at 20:13
  • 1
    @Mark Ransom, I understand the difference between those 2 implementations, it is exactly why I have asked. I still see both implementations equally appearing so I am trying to figure out the best approach. I found single "weird" case when 2d is preferable, when you examine core it simplifies your life. – VladimirS Jul 07 '14 at 15:23
0

You don't need virtual inheritance from the immediate base class, or any base class member variables to support this feature at all

struct Interface {
    virtual void foo() = 0;
    virtual int bar() = 0;
};

template<class T>
class Base : public Interface {
public:
    int getType() const { return getTypeId(); }
    static int getTypeId() { return T::ClassId; }
};

class Derived : public Base<Derived> {
public:
   static const int ClassId = DerivedID;

   virtual void foo() { }
   virtual int bar() { return 1; }

};

Usage is like

Derived der;
Interface* iface = &der;
iface->foo();
iface->bar();

See a working sample here.

NOTE:
All of the types you use with a current configuration of such system need to be well known at compile time. This doesn't support a Plugin pattern based mechanism.

Community
  • 1
  • 1
πάντα ῥεῖ
  • 1
  • 13
  • 116
  • 190
  • 1
    I think that breaks the idea of `getType()` existing in a common base class, doesn't it? – Drew Dormann Jul 03 '14 at 20:04
  • @DrewDormann you can still introduce a vtable if necessary. But as the OP states without RTTI it may become useless anyway. I don't believe that `dynamic_cast<>()` based designs really lead to anything good. – πάντα ῥεῖ Jul 03 '14 at 20:08
  • If you have a pointer or reference to the base class, how are you going to return the proper derived ID? CRTP is for compile-time polymorphism, not run-time. – Mark Ransom Jul 03 '14 at 20:08
  • @MarkRansom Good point, but again the OP mentioned _without RTTI_ for which I have a reflex now that says `"Use static polymorphism"`. – πάντα ῥεῖ Jul 03 '14 at 20:10
  • OP isn't speaking at all, but **I think** they want a solution where `x->getType()` may be called for any `Base *x`. Run time, but not specifically [RTTI](http://en.wikipedia.org/wiki/Run-time_type_information) – Drew Dormann Jul 03 '14 at 20:28
  • @DrewDormann _'... `x->getType()` ...'_ I've fixed my sample a bit, that this requirement can be fulfilled :). It's more ore less introducing your own RTTI mechanism, doing so. I have the pattern in production, but where it's poor in, is the specification of the `DerivedID` et al. We have a solution now that the common stuff includes a project specific header, that contain some CPP macro calls. – πάντα ῥεῖ Jul 03 '14 at 20:40
  • @πάνταῥεῖ I can't remember the specifics, but I can remember working on a problem very similar to this. Every version of your solution seems great for _compile-time_, but this can't evaluate at runtime. This doesn't allow, for example, iterating through a container of these objects and inspecting each type. You can't have a container of `Base*`, and `Interface*` doesn't expose the type – Drew Dormann Jul 03 '14 at 20:41
0

Okay, I'll take a stab...

"What are the pros/cons of them?"

Your first example virtual int getType() const =0; incurs extra processing overhead. A virtual function call can be more difficult for a CPU to preempt. This issue may be trivial.

Your second example inline int getType() const { return id_; } incurs extra runtime storage. And extra int is stored for every instance of every Base. This may also be trivial.

"What would be better choice in general?"

In general, avoid premature optimization. Choose the pure virtual function in the base class, because its correctness is enforced by the compiler and runtime.

Community
  • 1
  • 1
Drew Dormann
  • 59,987
  • 13
  • 123
  • 180