Greetings, all.
I've often found it difficult to avoid dynamic casts in my designs, and I was wondering if we could brainstorm what strategies one could use to minimize their use. I would find that tremendously productive.
One instance that comes up a lot is with the Abstract Factory pattern and its variants. An often-used illustration of this pattern is found on Wikipedia:
http://en.wikipedia....act_factory.svg
Now, I would make this a little more complex by saying that the GUIFactory interface would also have methods for creating other controls, like createComboBox and createList. It may also have something like createWindow. And the Window interface might have an addControl method that can be used to add buttons/comboboxes/lists to the window.
So, the trickery comes when you're writing your concrete implementations. Let's imagine an imaginary GUI framework called Foo32 that you want to target. So you write FooFactory and concrete product classes like FooWindow, FooButton, FooComboBox, FooList, etc.
So, the class hierarchy would look something like this:
[attachment=11211:foo.gif]
And the factory would look something like this:
[source lang="cpp"]class FooFactory : public GUIFactory
{
public:
FooWindow* createWindow();
FooButton* createButton();
FooList* createList();
FooComboBox* createComboBox();
};[/source]
And here are some possible class definitions for FooButton and FooWindow:
[source lang="cpp"]
class FooButton : public Button
{
Foo::Button *m_button; // The actual button object.
public:
FooButton();
// Button interface
void paint();
};
class FooWindow: public Window
{
Foo::Window m_window; // The actual button object.
public:
FooButton();
// Window interface
void paint();
void addControl(Control *ctrl)
{
m_window.addControl(ctrl); // Error! Control is not a Foo::Control
}
};
[/source]
So the question is, how to solve that error. The naive approach would be to just test and see what concrete class we're dealing with.
[source lang="cpp"]
void addControl(Control *ctrl)
{
FooButton *fooButton = dynamic_cast<FooButton*>(ctrl);
if(fooButton != NULL)
{
m_window.addControl(fooButton->getFooButton()); // getFooButton returns the Foo::Button*
}
FooComboBox *fooCB = dynamic_cast<FooComboBox*>(ctrl);
if(fooCB != NULL)
{
m_window.addControl(fooCB->getFooComboBox()); // getFooButton returns the Foo::ComboBox*
}
/* and so on... */
}
[/source]
Ouch, though. I suppose I could use multiple inheritance, i.e. FooButton would inherit from both Button and Foo::Button. Since Foo::Button is a Foo::Control, then the method implementation is simplified:
[source lang="cpp"]
void addControl(Control *ctrl)
{
Foo::Control *fooCtrl = dynamic_cast<Foo::Control*>(ctrl);
if(fooCtrl!= NULL)
{
m_window.addControl(fooCtrl);
}
}
[/source]
Problem is, this uses multiple inheritance and we still have a dynamic_cast. I've spent enough time debugging dynamic_cast to know that it is not a quick or trivial thing (with MSVC, it does multiple strcmps). Perhaps that's not a problem for setting up a GUI (most of this is done during program initialization and GUI's typically spend most of their time waiting for user input anyway). But what if the above pattern is needed for something other than a GUI -- something for a real-time video game that requires dozens or hundreds of Control-like objects to be added and removed from a Window-like object each frame?
I am currently wrestling with a situation just like that -- a have a "View" (analogous to Window) that needs to have "Scene Objects" (analogous to Control) added to it. Examples of Scene Object types are Model, Particle System, Hud Element, etc. I feel that it is necessary to use something like an Abstract Factory because of a high possibility that I may want to change what graphics framework I'm using someday (I'm currently using OpenSceneGraph).
It's often said that, if you are using dynamic_cast a lot, you're probably doing something wrong. This makes me feel uneasy about my design.
It's also often said that your classes should be open for extension and closed for modification. I find it really difficult to close classes like GUIFactory, because one is always thinking of new types of controls that need to be created. Maybe a createTextBox method needs to be added at some point -- is it really desirable to subclass GUIFactory just because you want to call it 'closed'?
I'm getting better and better at OO design, but I'm still fledgling in a lot of ways, and as you can see, I could use some advice. From where I sit, there are two basic possibilities:
1) Don't try to abstract the graphics framework in something like a game, where performance is critical.
2) Minimize the amount of time that the code sequence crosses your abstraction layer, i.e...
[source lang="cpp"]
// This code can't know we're using Foo32. It just knows about the abstract interfaces.
// The typical way to do it...
Window w = guiFactory->createWindow(); // returns a FooWindow, but this code doesn't know or care
Button b = guiFactory->createButton(); // returns a FooButton, but this code doesn't know or care
w->addControl(b); // this will do a dynamic_cast (slow)
// Here is an alternate possibility and gets around the above issues...
int windowId = guiFactory->createWindow(); // returns the id of the created window, but the factory keeps the window for itself
int buttonId = guiFactory->createButton(); // as above, only the id of the button is returned
guiFactory->addControlToWindow(windowId, buttonId);
[/source]
Since FooFactory is owning the objects it creates in that second solution, it has concrete references to them, and thus no casting is needed. This has a very messy, non-OOP feel to it, though. Almost as if I'm working with OpenGL.
OO Design and avoiding dynamic casts
Just one more thought, real quick...
Another place this dynamic_cast issue comes up in a lot is with publish/subscribe messaging architectures. Often, you have an abstract interface called Message, and then any time you want to send data from one part of your program to another (where nothing knows at compile time who is communicating with whom), then you just subclass Message (e.g., FooMessage, BarMessage). However, the message handler method on the receiving end will get a Message& and it needs to down-cast that to the right message type before it can deal with it.
Another place this dynamic_cast issue comes up in a lot is with publish/subscribe messaging architectures. Often, you have an abstract interface called Message, and then any time you want to send data from one part of your program to another (where nothing knows at compile time who is communicating with whom), then you just subclass Message (e.g., FooMessage, BarMessage). However, the message handler method on the receiving end will get a Message& and it needs to down-cast that to the right message type before it can deal with it.
If your main issue is that Foo::Window takes Control rather than Foo::Control, why not have it take one? You're not really going to let people pass in arbitrary control implementations are you?
[edit: that's not so easy looking at it again; What I've done in the past is to separate how something is drawn from the user-controllable aspects. The 'how to draw' is either owned by the control or hidden within the factory instance.]
[edit: that's not so easy looking at it again; What I've done in the past is to separate how something is drawn from the user-controllable aspects. The 'how to draw' is either owned by the control or hidden within the factory instance.]
Yeah, I might be asking too broad a question here.
In this case, Foo32 is intended to be a third party GUI framework like Qt that I didn't build and don't have any control over. So, naturally a Foo::Window can only accept Foo::Controls. The reason for the abstraction is that I may want to switch GUI frameworks someday -- say, move over to Qt -- and to do so, I would hopefully only have to subclass a few things (Window, Button, etc.) and I'll be all set.
Problem is, in order to actually attach a button to a Foo::Window, then I am going to need to make sure that the concrete Button is a Foo::Button, or at least contains a Foo::Button that it can give to me. So, I need to down-cast.
Alternately, if I am writing a Qt implementation, I might have a QtWindow class that has (or is) a QDialog. When someone calls QtWindow::addControl and passes in a Button object, I will need to down-cast it to a QWidget before adding it to the QDialog internally.
I have never used Qt, so that example might seem off, but I hope you get the idea.
In this case, Foo32 is intended to be a third party GUI framework like Qt that I didn't build and don't have any control over. So, naturally a Foo::Window can only accept Foo::Controls. The reason for the abstraction is that I may want to switch GUI frameworks someday -- say, move over to Qt -- and to do so, I would hopefully only have to subclass a few things (Window, Button, etc.) and I'll be all set.
Problem is, in order to actually attach a button to a Foo::Window, then I am going to need to make sure that the concrete Button is a Foo::Button, or at least contains a Foo::Button that it can give to me. So, I need to down-cast.
Alternately, if I am writing a Qt implementation, I might have a QtWindow class that has (or is) a QDialog. When someone calls QtWindow::addControl and passes in a Button object, I will need to down-cast it to a QWidget before adding it to the QDialog internally.
I have never used Qt, so that example might seem off, but I hope you get the idea.
What's the relation between Foo::ComboBox and FooComboBox (or between Foo::Button and FooButton)? I think your example is very confusing (perhaps because of your nomenclature), and so it's not clear what you're asking.
Why not just have all controls implement an IControl interface? Done.
Why not just have all controls implement an IControl interface? Done.
Yeah, my apologies for the confusion.
Foo:ComboBox is a class that, in my example, is part of a fictional GUI framework called Foo32, that I didn't write and therefore have no control over. FooComboBox is a wrapper that implements ComboBox, which is a Control (analogous to the IControl interface that you suggested).
After looking at this, I think that the Foo::ClassName stuff is unnecessary to my question, and needlessly complicates things. I think I will spend some time re-formulating this question, and then maybe ask it again in a few days.
The main point is, I may have more than one concrete implementation of the abstract factory/product set. So, let's say I have:
[font=courier new,courier,monospace]IGUIFactory[/font]
[font=courier new,courier,monospace]+ createWindow() : Window[/font]
[font=courier new,courier,monospace]+ createButton() : Button[/font]
[font=courier new,courier,monospace]+ createComboBox() : ComboBox[/font]
[font=courier new,courier,monospace]IWindow[/font]
[font=courier new,courier,monospace]+ addChildControl(IControl) : void[/font]
[font=courier new,courier,monospace]IButton : public IControl[/font]
[font=courier new,courier,monospace]+ doButtonyThings() : void[/font]
[font=courier new,courier,monospace]IComboBox : public IControl[/font]
[font=courier new,courier,monospace]+ doComboBoxyThings() : void[/font]
And then lets say I create two complete sets of concrete implementations. One is for a keyboard and mouse interface, and another for a touch interface.
[font=courier new,courier,monospace]KBMFactory[/font]
[font=courier new,courier,monospace]+ createWindow() : KBMWindow[/font]
[font=courier new,courier,monospace]+ createButton() : KBMButton[/font]
[font=courier new,courier,monospace]+ createComboBox() : KBMComboBox[/font]
[font=courier new,courier,monospace]TouchFactory[/font]
[font=courier new,courier,monospace]+ createWindow() : TouchWindow
+ createButton() : TouchButton
+ createComboBox() : TouchComboBox[/font]
Ok, so this allows me to quickly switch which interface I'm using, just by changing whether I'm using a [font=courier new,courier,monospace]KBMFactory [/font]or a [font=courier new,courier,monospace]TouchFactory[/font]. It could even be decided at runtime.
But let's say that mixing KMB and Touch is a no-no. So, I cannot add a [font=courier new,courier,monospace]KBMButton [/font]to a [font=courier new,courier,monospace]TouchWindow[/font], or vice-versa. Obviously, I could of course call [font=courier new,courier,monospace]myKBMWindow->addChildControl(myTouchButton) [/font]without any compile issues. However, perhaps these two types are incompatible, so in KBMWindow::addChildControl, I would do a dynamic cast:
[source lang="cpp"]
void KBMWindow::addChildControl(IControl ctrl)
{
KBMButton *button = dynamic_cast<KBMButton*>(ctrl);
if(button != NULL)
{
m_buttons.add(button);
return;
}
KBMComboBox *cb = dynamic_cast<KBMComboBox*>(ctrl);
if(cb!= NULL)
{
m_comboBoxes.add(cb);
return;
}
}
[/source]
But ew, ew, ew. So, at this point it's obvious that I need some sort of base class that is common to all KBM controls, but where in the hierarchy do I stick it? Seems impossible to do without some sort of multiple (possibly diamond) inheritance.
[I'm using C++, by the way, if that last bit about multiple inheritance is confusing. No huge problem, though, because Java/C# interfaces can be sufficiently faked with a combination of virtual inheritance and pure virtual methods. The only sad thing is that dynamic_casts for those sorts of class hierarchies are not very fast. The type checking involves string compares, and beyond that, it's more involved than just doing a type check -- the pointer actually has to be offset by the correct amount when doing multiple inheritance.]
Foo:ComboBox is a class that, in my example, is part of a fictional GUI framework called Foo32, that I didn't write and therefore have no control over. FooComboBox is a wrapper that implements ComboBox, which is a Control (analogous to the IControl interface that you suggested).
After looking at this, I think that the Foo::ClassName stuff is unnecessary to my question, and needlessly complicates things. I think I will spend some time re-formulating this question, and then maybe ask it again in a few days.
The main point is, I may have more than one concrete implementation of the abstract factory/product set. So, let's say I have:
[font=courier new,courier,monospace]IGUIFactory[/font]
[font=courier new,courier,monospace]+ createWindow() : Window[/font]
[font=courier new,courier,monospace]+ createButton() : Button[/font]
[font=courier new,courier,monospace]+ createComboBox() : ComboBox[/font]
[font=courier new,courier,monospace]IWindow[/font]
[font=courier new,courier,monospace]+ addChildControl(IControl) : void[/font]
[font=courier new,courier,monospace]IButton : public IControl[/font]
[font=courier new,courier,monospace]+ doButtonyThings() : void[/font]
[font=courier new,courier,monospace]IComboBox : public IControl[/font]
[font=courier new,courier,monospace]+ doComboBoxyThings() : void[/font]
And then lets say I create two complete sets of concrete implementations. One is for a keyboard and mouse interface, and another for a touch interface.
[font=courier new,courier,monospace]KBMFactory[/font]
[font=courier new,courier,monospace]+ createWindow() : KBMWindow[/font]
[font=courier new,courier,monospace]+ createButton() : KBMButton[/font]
[font=courier new,courier,monospace]+ createComboBox() : KBMComboBox[/font]
[font=courier new,courier,monospace]TouchFactory[/font]
[font=courier new,courier,monospace]+ createWindow() : TouchWindow
+ createButton() : TouchButton
+ createComboBox() : TouchComboBox[/font]
Ok, so this allows me to quickly switch which interface I'm using, just by changing whether I'm using a [font=courier new,courier,monospace]KBMFactory [/font]or a [font=courier new,courier,monospace]TouchFactory[/font]. It could even be decided at runtime.
But let's say that mixing KMB and Touch is a no-no. So, I cannot add a [font=courier new,courier,monospace]KBMButton [/font]to a [font=courier new,courier,monospace]TouchWindow[/font], or vice-versa. Obviously, I could of course call [font=courier new,courier,monospace]myKBMWindow->addChildControl(myTouchButton) [/font]without any compile issues. However, perhaps these two types are incompatible, so in KBMWindow::addChildControl, I would do a dynamic cast:
[source lang="cpp"]
void KBMWindow::addChildControl(IControl ctrl)
{
KBMButton *button = dynamic_cast<KBMButton*>(ctrl);
if(button != NULL)
{
m_buttons.add(button);
return;
}
KBMComboBox *cb = dynamic_cast<KBMComboBox*>(ctrl);
if(cb!= NULL)
{
m_comboBoxes.add(cb);
return;
}
}
[/source]
But ew, ew, ew. So, at this point it's obvious that I need some sort of base class that is common to all KBM controls, but where in the hierarchy do I stick it? Seems impossible to do without some sort of multiple (possibly diamond) inheritance.
[I'm using C++, by the way, if that last bit about multiple inheritance is confusing. No huge problem, though, because Java/C# interfaces can be sufficiently faked with a combination of virtual inheritance and pure virtual methods. The only sad thing is that dynamic_casts for those sorts of class hierarchies are not very fast. The type checking involves string compares, and beyond that, it's more involved than just doing a type check -- the pointer actually has to be offset by the correct amount when doing multiple inheritance.]
I may want to switch GUI frameworks someday
YAGNI
There is invariably enough subtle changes in behavior for this sort of thing that you're going to end up doing a lot of re-write/re-work anyways. Just use what works.
While I can't imagine needing this kind of functionality, you could use a checked cast: dynamic_cast during debug to make sure your really are dealing with an object of the type you assume it to be, and static_cast in release.
Will static_cast offset the pointer in cases of multiple inheritance?
I don't actually wish to use multiple inheritance in the traditional sense, but I would indeed like to emulate the model found in other languages, where you can inherit from one base class, but implement several interfaces. Of course, C++ doesn't have interfaces per se, but they can be faked using ABC's with pure virtual methods only. Then, any diamond inheritance issues can be worked-around using virtual inheritance. Seems to work well, but it does mean that dynamic_cast has to offset the pointer as you cast back and forth between different types in the hierarchy.
What do you guys think about the other situation I mentioned where dynamic casts are often used, i.e. with publish/subscribe messaging systems? Should such systems be avoided in high-performance code where it needs to be done dozen or hundreds of times per frame? Or is it generally okay these days?
I don't actually wish to use multiple inheritance in the traditional sense, but I would indeed like to emulate the model found in other languages, where you can inherit from one base class, but implement several interfaces. Of course, C++ doesn't have interfaces per se, but they can be faked using ABC's with pure virtual methods only. Then, any diamond inheritance issues can be worked-around using virtual inheritance. Seems to work well, but it does mean that dynamic_cast has to offset the pointer as you cast back and forth between different types in the hierarchy.
What do you guys think about the other situation I mentioned where dynamic casts are often used, i.e. with publish/subscribe messaging systems? Should such systems be avoided in high-performance code where it needs to be done dozen or hundreds of times per frame? Or is it generally okay these days?
static_cast should give you the right pointer when it does the cast. Usually for messages, the C technique of using a message ID member variable and casting based on the ID works well enough. Again, you can use a checked cast if you're feeling paranoid.
This topic is closed to new replies.
Advertisement
Popular Topics
Advertisement