February 14, 2012

bkamps bkamps
Ant Farmer
115 posts

Getting rid of dynamic_cast

 

I have a BaseClass and DerivedClass, both have a QVector of IKey *. IKey has derived: BaseKey and SpecialKey. In BaseClass the BaseKey is used and it has the same interface as IKey.

Now in DerivedClass i’m using SpecialKey which has some more methods than BaseKey. Now everytime in DerivedClass I need to dynamic_cast the IKey* to SpecialKey* to use the SpecialKey. Is there another way?

  1. class IKey{
  2.   virtual void PressKey() = 0;
  3. }

  1. class BaseKey : public IKey {
  2.   virtual void PressKey() { printf(" Base Pressed" }
  3. }

  1. class SpecialKey : public IKey {
  2.    virtual void PressKey() { printf(" Special Pressed" }
  3.    void DoSomethingSpecial { printf("special" }
  4. }

  1. class BaseClass{
  2. public:
  3.   BaseClass::BaseClass(      mKeys.push_back(new BaseKey()); ){}
  4.  
  5.   void BaseClass::UseKeys()
  6.   {
  7.       IKey * lKey;
  8.       foreach(lKey, mKeys)
  9.       {
  10.          lKey->PressKey();
  11.       }
  12.   }
  13.  
  14.   QVector<IKey*> mKeys;
  15. }

And I have a derived class:

  1. class DerivedClass : public BaseClass{
  2.  DerivedClass ::DerivedClass (      mKeys.push_back(new SpecialKey()); ){}
  3.  
  4.   void BaseClass::UseKeys()
  5.   {
  6.       IKey * lKey;
  7.       foreach(lKey, mKeys)
  8.       {
  9.          lKey->PressKey();
  10.          dynamic_cast<SpecialKey*>(lKey)->DoSomethingSpecial(); // <<<
  11.       }
  12.   }
  13. }

5 replies

February 14, 2012

Lukas Geyer Lukas Geyer
Gene Splicer
2074 posts

Well, your code already relies on the fact that in DerivedClass the vector mKeys contains only SpecialKey pointers, as dynamic_cast will return 0 if the cast isn’t typesafe and your code segfaults on line #10. Assuming that, just replace dynamic_cast with static_cast, which can be used to downcast as well.

If your vector contains both, BaseKey and SpecialKey pointers (which your example does, as BaseClass::BaseClass(), which you forgot to call by the way, pushes a BaseKey) you will have to do some kind of runtime type checking. You either use RTTI / dynamic_cast or QVariant.

If you want no cast at all you will have to make sure that every pointer in the vector shares the same interface, which means moving SpecialKey::DoSomeThingSpecial() to BaseKey or having DerivedClass an additional vector which contains the SpecialKey pointers and BaseClass::mKeys contains only BaseKey pointers (and having an additional loop), with the downside of keys beeing pressed in a different order than they were pushed.

February 14, 2012

Mikael Lindgren Mikael Lindgren
Lab Rat
1 posts

Please look at the vistor pattern
http://en.wikipedia.org/wiki/Visitor_pattern

February 15, 2012

bkamps bkamps
Ant Farmer
115 posts

Lukas: I just posted some psuedo code, this is no production code :). I want to remove the casts as it doesn’t feel good… And to use the same interface feels also strange because why should the BaseKey have a DoSomethingSpecial method that doesn’t do anything..

I have also seen other ‘solutions’ that override the mKeys members in the derived: QVector<SpecialKey*> mKeys…. But again this doesn’t feel like right.

Mikael, I will have a look at the visitor pattern. I was looking for a pattern that would solve my problem but I cannot really figure out which one to use… To make things more complicated I also have the derived class use some methods of the baseclass.

February 15, 2012

KA51O KA51O
Robot Herder
380 posts

Maybe the factory method pattern [en.wikipedia.org] is what your looking for.

February 15, 2012

Lukas Geyer Lukas Geyer
Gene Splicer
2074 posts

Having a single collection containing elements with different interfaces always results in some kind of differentiation at runtime, be it dynamic_cast, QMetaType::type() or a vtable look-up as in the visitor pattern. If you don’t want the differentiation at runtime you either have to

  • have all the elements in the collection sharing the same interface or
  • have a separate collection for each interface.

There is no way around that.

Even when using the visitor pattern your elements have to share the same interface (although a smaller one). I don’t know how diverse your BaseKey dervived classes are, but the visitor pattern could at least prevent your base class from beeing the least common multiple of all your derived classes – at the cost of another layer of complexity.

What is your concern? Performance? Maintainability?

I possibly would use a combination if I had to eliminate the dependency of a runtime type information system and the complexity of the visitor pattern:

  1. class IKey
  2. {
  3. public:
  4.     enum Action
  5.     {
  6.         BaseAction,
  7.         SpecialAction,
  8.         ...
  9.     };
  10.  
  11.     virtual void pressKey() = 0;
  12.     virtual void action(IKey::Action action) = 0;
  13. };
  14.  
  15. class BaseKey : public IKey
  16. {
  17.     ...
  18.     void action(IKey::Action action)
  19.     {
  20.         switch(action)
  21.         {
  22.             case IKey::BaseAction:
  23.                 ...
  24.                 break;
  25.             default:
  26.                 break;
  27.         }
  28.     }
  29. };
  30.  
  31. class SpecialKey : public BaseKey
  32. {
  33.     ...
  34.     void action(IKey::Action action)
  35.     {
  36.         switch(action)
  37.         {
  38.             case IKey::BaseAction:
  39.                 BaseKey::action(IKey::BaseAction);
  40.                 break;
  41.             case IKey::SpecialAction:
  42.                 ...
  43.                 break;
  44.             default:
  45.                 break;
  46.         }
  47.     }
  48. };
  49.  
  50. class BaseClass
  51. {
  52.     ...
  53. public:
  54.     void useKeys()
  55.     {
  56.         _useKeys(IKey::BaseAction);
  57.     }
  58.  
  59. protected:
  60.     void _useKeys(IKey::Action action)
  61.     {
  62.         IKey* key;
  63.         foreach(key, mKeys)
  64.         {
  65.             key->pressKey();
  66.             key->action(action);
  67.         }
  68.     }
  69. };
  70.  
  71. class DerivedClass : public BaseClass
  72. {
  73. public:
  74.     void useKeys()
  75.     {
  76.         _useKeys(IKey::SpecialAction);
  77.     }
  78. };

Brain to terminal. Not tested. Exemplary.

The switch can be replaced with a condition if every key needs to implement just one action. Won’t win a design price, but I haven’t seen a compromise yet that does.

bkamps wrote:
I have also seen other ‘solutions’ that override the mKeys members in the derived: QVector<SpecialKey*> mKeys.

You cannot overload or override member variables in dervied classes, you just hide them – and you end up with two seperate member variables.

 
  ‹‹ Destructor array of object      Wireless Communication ››

You must log in to post a reply. Not a member yet? Register here!