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?
@
class IKey{
virtual void PressKey() = 0;
}
@@
class BaseKey : public IKey {
virtual void PressKey() { printf(" Base Pressed" }
}
@@
class SpecialKey : public IKey {
virtual void PressKey() { printf(" Special Pressed" }
void DoSomethingSpecial { printf("special" }
}
@@
class BaseClass{
public:
BaseClass::BaseClass( mKeys.push_back(new BaseKey()); ){}void BaseClass::UseKeys()
{
IKey * lKey;
foreach(lKey, mKeys)
{
lKey->PressKey();
}
}QVector<IKey*> mKeys;
}
@And I have a derived class:
@
class DerivedClass : public BaseClass{
DerivedClass ::DerivedClass ( mKeys.push_back(new SpecialKey()); ){}void BaseClass::UseKeys()
{
IKey * lKey;
foreach(lKey, mKeys)
{
lKey->PressKey();
dynamic_cast<SpecialKey*>(lKey)->DoSomethingSpecial(); // <<<
}
}
}
@ -
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.
-
Please look at the vistor pattern
http://en.wikipedia.org/wiki/Visitor_pattern -
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.
-
Maybe the "factory method pattern":http://en.wikipedia.org/wiki/Factory_method_pattern is what your looking for.
-
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:
@
class IKey
{
public:
enum Action
{
BaseAction,
SpecialAction,
...
};virtual void pressKey() = 0; virtual void action(IKey::Action action) = 0;
};
class BaseKey : public IKey
{
...
void action(IKey::Action action)
{
switch(action)
{
case IKey::BaseAction:
...
break;
default:
break;
}
}
};class SpecialKey : public BaseKey
{
...
void action(IKey::Action action)
{
switch(action)
{
case IKey::BaseAction:
BaseKey::action(IKey::BaseAction);
break;
case IKey::SpecialAction:
...
break;
default:
break;
}
}
};class BaseClass
{
...
public:
void useKeys()
{
_useKeys(IKey::BaseAction);
}protected:
void _useKeys(IKey::Action action)
{
IKey* key;
foreach(key, mKeys)
{
key->pressKey();
key->action(action);
}
}
};class DerivedClass : public BaseClass
{
public:
void useKeys()
{
_useKeys(IKey::SpecialAction);
}
};
@
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.
[quote author="bkamps" date="1329295734"]I have also seen other 'solutions' that override the mKeys members in the derived: QVector<SpecialKey*> mKeys.[/quote]
You cannot overload or override member variables in dervied classes, you just hide them - and you end up with two seperate member variables.