• Announcements

    • khawk

      Download the Game Design and Indie Game Marketing Freebook   07/19/17

      GameDev.net and CRC Press have teamed up to bring a free ebook of content curated from top titles published by CRC Press. The freebook, Practices of Game Design & Indie Game Marketing, includes chapters from The Art of Game Design: A Book of Lenses, A Practical Guide to Indie Game Marketing, and An Architectural Approach to Level Design. The GameDev.net FreeBook is relevant to game designers, developers, and those interested in learning more about the challenges in game development. We know game development can be a tough discipline and business, so we picked several chapters from CRC Press titles that we thought would be of interest to you, the GameDev.net audience, in your journey to design, develop, and market your next game. The free ebook is available through CRC Press by clicking here. The Curated Books The Art of Game Design: A Book of Lenses, Second Edition, by Jesse Schell Presents 100+ sets of questions, or different lenses, for viewing a game’s design, encompassing diverse fields such as psychology, architecture, music, film, software engineering, theme park design, mathematics, anthropology, and more. Written by one of the world's top game designers, this book describes the deepest and most fundamental principles of game design, demonstrating how tactics used in board, card, and athletic games also work in video games. It provides practical instruction on creating world-class games that will be played again and again. View it here. A Practical Guide to Indie Game Marketing, by Joel Dreskin Marketing is an essential but too frequently overlooked or minimized component of the release plan for indie games. A Practical Guide to Indie Game Marketing provides you with the tools needed to build visibility and sell your indie games. With special focus on those developers with small budgets and limited staff and resources, this book is packed with tangible recommendations and techniques that you can put to use immediately. As a seasoned professional of the indie game arena, author Joel Dreskin gives you insight into practical, real-world experiences of marketing numerous successful games and also provides stories of the failures. View it here. An Architectural Approach to Level Design This is one of the first books to integrate architectural and spatial design theory with the field of level design. The book presents architectural techniques and theories for level designers to use in their own work. It connects architecture and level design in different ways that address the practical elements of how designers construct space and the experiential elements of how and why humans interact with this space. Throughout the text, readers learn skills for spatial layout, evoking emotion through gamespaces, and creating better levels through architectural theory. View it here. Learn more and download the ebook by clicking here. Did you know? GameDev.net and CRC Press also recently teamed up to bring GDNet+ Members up to a 20% discount on all CRC Press books. Learn more about this and other benefits here.
Sign in to follow this  
Followers 0
CDProp

OO Design and avoiding dynamic casts

23 posts in this topic

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:

[url="http://en.wikipedia.org/wiki/File:Abstract_factory.svg"]http://en.wikipedia....act_factory.svg[/url]

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 [i]and [/i]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.
1

Share this post


Link to post
Share on other sites
Just one more thought, real quick...

Another place this dynamic_cast issue comes up in a [i]lot[/i] 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.
0

Share this post


Link to post
Share on other sites
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 [i]how [/i]something is drawn from the user-controllable aspects. The 'how to draw' is either owned by the control or hidden within the factory instance.] Edited by Telastyn
2

Share this post


Link to post
Share on other sites
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.
0

Share this post


Link to post
Share on other sites
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.
0

Share this post


Link to post
Share on other sites
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, [i]ew[/i]. 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.]
0

Share this post


Link to post
Share on other sites
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.
2

Share this post


Link to post
Share on other sites
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?
0

Share this post


Link to post
Share on other sites
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.
0

Share this post


Link to post
Share on other sites
I'm pretty new to this stuff too but here are a few ideas:

Referring to your second example:

1. You don't need to know what type of class the IControl object is, just what family of classes it belongs to - KBM or Touch. Therefore you could:
* add two more interfaces called IKBMControl and ITouchControl, both of which extend from IControl, you only need to do a single cast (IControl -> IKBMControl / IControl -> ITouchControl) then.
* you could add an additional method to IControl called getFamily() which will return if it's a KBM or a Touch class.

2. The Abstract Factory pattern will only work if IControl completely captures the dependency between IControl and its clients KBMWindow and TouchWindow. However as KBMWindow and TouchWindow need to know the type behind the IControl interface, it would seem the interface is not sufficient.

If KBMWindow can only take KBM controls then its interface should probably reflect that, and the same for TouchWindow. This would mean that they wouldn't be able to extend from Window though. I.e.

KBMIWindow
+ addChildControl(IKBMControl) : void

TouchIWindow
+ addChildControl(ITouchControl) : void

IKBMControl and ITouchControl could extend from the common interface IControl if there are other clients which require funcitonality which can be used without knowing if they are KBMControl or TouchControl objects.

3. Another thought would be that you seem to have two axis of change here,
1. Control type, i.e. Button, Combo Box, Window
2. GUI framework

So in some situations the Bridge pattern may be useful. It would depend on how easy it is to abstract the GUI framework.

Just a few thoughts. It's an interesting question and it'll be good to see how other people would approach your problem.

Matt
0

Share this post


Link to post
Share on other sites
Given your current constraints, what I would do is make each control responsible for adding itself.

i.e.:

[CODE]
class Control
{
public:
virtual void addToWindow( KBMWindow& window )
{
throw std::logic_error("This control type not implemented for KBM.");
}
virtual void addToWindow( TouchWindow& window )
{
throw std::logic_error("This control type not implemented for Touch.");
}
};

class Button: public Control
{
public:
virtual void addToWindow( KBMWindow& window )
{
window.uiWindow().addControl( getKBMButton() );
}
virtual void addToWindow( TouchWindow& window )
{
window.uiWindow().addControl( getTouchButton() );
}
};

class KBMWindow: public Window
{
KBM::Window m_window;
public:
void addControl(Control *ctrl)
{
ctrl->addToWindow(m_window);
}

KBM::Window& uiWindow(){ return m_window; }
};

class TouchWindow: public Window
{
Touch::Window m_window;
public:
void addControl(Control *ctrl)
{
ctrl->addToWindow(m_window);
}

Touch::Window& uiWindow(){ return m_window; }
};
[/CODE] Edited by krippy2k8
0

Share this post


Link to post
Share on other sites
[quote name='de_mattT' timestamp='1347404718' post='4979102']
1. You don't need to know what type of class the IControl object is, just what family of classes it belongs to - KBM or Touch. Therefore you could:
* add two more interfaces called IKBMControl and ITouchControl, both of which extend from IControl, you only need to do a single cast (IControl -> IKBMControl / IControl -> ITouchControl) then.
* you could add an additional method to IControl called getFamily() which will return if it's a KBM or a Touch class.
[/quote]
* You're still left with a cast.
* Yes, creating your own RTTI system with ids

[quote name='dmatter' timestamp='1347408627' post='4979116']
The issue is that you are violating the Liskov Substitution Principle (LSP).
So your interface has set up this contract stating that IWindow::addControl takes an IControl. In reality this is a lie because concrete subclasses such as FooWindow in fact require a FooControl, not an IControl.

The LSP only permits contravariance of function parameter types in subclasses (which means making the type more general), whereas what you're achieving through the use of casting is covariance (making the type more specific). Failure to abide by the LSP will mean that (without casting and therefore also violating the Open-Closed Principle) you cannot safely use the objects polymorphically.
[/quote]
This is the correct answer.

[quote name='krippy2k8' timestamp='1347424131' post='4979169']
Given your current constraints, what I would do is make each control responsible for adding itself.
[/quote]
No. You would be adding a dependency from every concrete control to every concrete window. Also these "not implemented" exceptions are awful in public interface.
0

Share this post


Link to post
Share on other sites
[quote name='CDProp' timestamp='1347388042' post='4978988']
Just one more thought, real quick...

Another place this dynamic_cast issue comes up in a [i]lot[/i] 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.
[/quote]

You can automate this with templates (not tested, but the concept works):
[CODE]
class message_dispatcher
{
private:
struct abstract_listener
{
void* listener;
void (*handle)( void* listener , const void* message );
};
template< class MessageType , class ReceivingType >
static void handle_func( void* listener , const void* message )
{
ReceivingType& typed_listener = *static_cast< ReceivingType* >( listener );
const MessageType& typed_message = *static_cast< const MessageType* >( message );
typed_listener.handle( typed_message );
}

std::multimap< type_info , abstract_listener > listeners;

public:
template< class MessageType , class ReceivingType >
void subscribe( ReceivingType& r )
{
abstract_listener al{ &r , &handle_func< MessageType , ReceivingType > };
type_info info = typeid( MessageType );

listeners.insert( { inf , al } );
}
template< class MessageType >
void dispatch( const MessageType& msg )
{
type_info info = typeid( MessageType );
auto range = listeners.equal_range( info );

while( range.first != range.second )
{
( *(range.first->handle_func) )( range.first->listener , &msg );
++range.first;
}
}
};
struct message_a
{};
class specific_listener
{
public:
void handle( message_a& m )
{

}
};

specific_listener listener;
message_dispatcher dispatcher;
dispatcher.subscribe< message_a >( listener );
dispatcher.dispatch( message_a() );
[/CODE]

Notice, however, that you can't use a message hierarchy. If you dispatch a B : A, a handler that has subscribed for A won't get that message.
If you need the performance, you can change std::multimap to something like a hash map and replace type_info with your own ids, e.g.
[CODE]
template< class T >
void* t_id()
{
static char c;
return c;
}
typedef void* msg_id;
msg_id id = t_id< message_a >();

//or if you need to communicate between different dlls

struct message_a
{
static const unsigned int id = //some unique value
};
[/CODE]
0

Share this post


Link to post
Share on other sites
[quote name='dmatter' timestamp='1347408627' post='4979116']
The issue is that you are violating the Liskov Substitution Principle (LSP).
[/quote]
This is the most correct answer (I did not quote more because it is not necessary).

I have never used a dynamic cast in my life, over probably 100 projects, big and small.

If you are ever using dynamic_cast, you are doing it wrong, either in principle or in implementation. In this case it is on principle.
There is [url="http://en.wikibooks.org/wiki/Optimizing_C%2B%2B/Code_optimization/Run-time_support#The_dynamic_cast_operator"]always[/url] a better solution to dynamic_cast.


L. Spiro
2

Share this post


Link to post
Share on other sites
[quote name='Telastyn' timestamp='1347399780' post='4979072']
[url="http://en.wikipedia.org/wiki/You_ain"]YAGNI[/url]

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.
[/quote]

This is the pragmatically correct answer to the OP. The only way you could possibly get this kind generic-ness into a meta-GUI framework and have it actually work for a variety of concrete frameworks is by knowing the thousands of details required to be taken care of for the set of GUI frameworks that you want to wrap. You would need to know ahead of time that you want to wrap Qt, wxWidgets, Foo32, ... e.g. oh, Foo32 doesn't let buttons contain child objects but all the other ones do and I expect button labels to be normal label objects that have buttons as parents, etc. etc. etc. etc. times a million.
1

Share this post


Link to post
Share on other sites
Thanks, guys. I'm not ignoring your responses, I am just processing them. I'll have some more questions/thoughts later.
0

Share this post


Link to post
Share on other sites
[quote name='CDProp' timestamp='1347399456' post='4979066']
The main point is, I may have more than one concrete implementation of the abstract factory/product set. So, let's say I have:
[/quote]

This post you made (first half) nails the whole idea between abstract factory correctly. Abstract factory is specifically for this exact special case, where there are multiple sets of classes that work with each other, and only each other, but other families of classes that have the same pattern. Such as if you had the touch / keyboard interface you describe, or my standard answer, the OpenGL and DirectX widget implementations. Obviously for these types of situations the OUTSIDE user wants to use JUST the interfaces (in other words the GAME doesn't care about OpenGL vs DirectX, so it uses the IControl, and IWindows interfaces EXCLUSIVELY). However of course, as you are aware the classes themselves, internally, DO care. An OpenGL control only consumes OpenGL textures and draws in OpenGL windows. So the key here is that the INTERNAL logic is more complex than the EXTERNAL logic.

Understand that Abstract Factory isn't telling you about how to make the OpenGL family talk to each other, its telling you how to make it so the outside world instantiates a factory once (and only once) and uses that ONE factory to create all of its widgets, so that it can be completely ignorant of what type of widget they are. Kinda like saying, as long as you are in a Verizon store, any phones you buy will work on the Verizon network. So once you decided which store to walk into, you can remain ignorant of the compatibility issue.

So, will you need down-casts for this case ... probably (although there are some techniques and languages that do not require it). However realize that your example of such a need is very flawed.

An OpenGL window should not be running around casting OpenGL controls to a long list of all possible children types, that is just madness. That is an unmanageable, unextendable non-oo design. However each OpenGL control can and likely will cast the IWindow to an IOpenGL window (fully expecting it to be one, since the whole assumption is the creation of everything from exactly 1 factory instance, so that a mismatch would be impossible. Also, an OpenGL window would keep a list of IOpenGL controls, and cast each control on add from IControl to IOpenGL control ... because the open gl family is going to be implemented internally to receive, associate with and use only other open gl family controls. The base interfaces are just used as the lesser API for externally facing interfaces.

So seeing the logical equivalent of:

List<IGLControl*> controls;
controls.add(dynamic_cast<IGLControl*>(control);

is totally normal for this type pattern.

But seeing

dynamic_cast<IGLComboBox*>(control);

would NOT be normal. The difference? In the first case you are downcasting from some interface, to its FAMILY SPECIFIC SPECIALIZATION (of which there can only be 1), which has the exact same PURPOSE within the family as the base interface, but just adds necessary family specific details to allow proper operation. In the second case you are trying to downcast from some general interface to another interface, which has a more specialized purpose - of what use is a window having a list of control, if the window has to know what type they are to use them ... this is the opposite of polymorphic

Another example - if would be like - instead of letting a class (Teacher) walk through a list of children (Students) asking them to perform the same operation (AreYouReadyForTheFeildTrip?) - which each student determine using different methods. Your flawed example would be like the teacher deciding what to ask the student based on first identifying who they are (Tommy or Jane?) and then if Tommy, doing the Tommy specific code, and if Jane doing the Jane specific code. When the whole point of polymorphic is to let Tommy, Jane and Ivan's creator (programmer / parents) make each one unique as they should be - but their Teacher no longer has to understand these differences to interact with them. Edited by Xai
0

Share this post


Link to post
Share on other sites
[quote name='Xai' timestamp='1347516239' post='4979596']
Another example - if would be like - instead of letting a class (Teacher) walk through a list of children (Students) asking them to perform the same operation (AreYouReadyForTheFeildTrip?) - which each student determine using different methods. Your flawed example would be like the teacher deciding what to ask the student based on first identifying who they are (Tommy or Jane?) and then if Tommy, doing the Tommy specific code, and if Jane doing the Jane specific code. When the whole point of polymorphic is to let Tommy, Jane and Ivan's creator (programmer / parents) make each one unique as they should be - but their Teacher no longer has to understand these differences to interact with them.
[/quote]

I like your example, it makes a lot of sense. I have run into a similar problem in another context. Yet, I am still not sure what the best solution might be. The difficulties arose when I was thinking about how to design a shader system that would allow a lot of reuse and flexibility. Let us consider the following simplified case with the following elements/classes:

Effect - The compiled and linked shader program. It expects a certain set of inputs that have to be bound in order to execute properly. This is kind of important: The requirements in terms of inputs are dictated by the effect.
Material - Contains a set of uniforms that define the appearance of an object. In order to render an object, the material is bound to the effect.
Geometry - Contains attribute buffers that describe the geometry of the object. In order to render an object, the geometry is bound to the effect.
Object - Contains a Material and a Geometry (possibly also an Effect, depends on whether we want to be able to specify different effects for different objects). Can be rendered in some way (Renderer::render(Object object) or something like that).

So far I believe this is fairly standard. Now, in OOP an obvious approach would be to define generic base classes and subclass them. But now, I have exactly the two options that were described, but I am not happy with either one. Let me explain (and I will just focus on the material, because the geometry is more or less analogous):

[b]First Option:[/b]
The base classes define an interface like this:

[source]Effect
+ void Load(string file) // load / compile / link
+ void Bind(Material material)

Material
* Empty *[/source]

And subclasses might look like this:

[source]ColorEffect
- int AmbientLocation
- int DiffuseLocation
+ void Load(string file) // base.load and ensure the program takes the required inputs, get AmbientLocation, DiffuseLocation
+ void Bind(Material material) // bind the uniforms to the appropriate locations

ColorMaterial
+ vec3 Ambient
+ vec3 Diffuse[/source]

Now, this design that would be considered wrong, I suppose. It requires exactly the kind of dynamic_cast that has been criticized. The Effect::Bind(Material material) method would contain something like this (Pseudocode):

[source]ColorMaterial colorMaterial = dynamic_cast<ColorMaterial>(material);

glUniform(AmbientLocation, colorMaterial.AmbientColor);
glUniform(DiffuseLocation, colorMaterial.DiffuseColor);[/source]

What is kind of neat about this is that the Effect knows exactly what it needs and takes it from the material. You could pass a more derived material and it would still work. Even less derived materials could work, however they would need special handling and the effect would provide default values for the missing uniforms.

[b]Second Option:[/b]
Base classes look like this:

[source]Effect
+ void Load(string file) // load / compile / link

Material
+ void BindTo(Effect effect)[/source]

And subclasses look like this:

[source]ColorEffect
+ int AmbientLocation
+ int DiffuseLocation
+ void Load(string file) // load / compile / link

ColorMaterial
+ vec3 Ambient
+ vec3 Diffuse
+ void BindTo(Effect effect)[/source]

At first glance, this may look better. However, it still requires the dynamic_cast to get the concrete ColorEffect in the BindTo method. One could argue that it is an improvement to do the binding code in the material, because the effect no longer has to differentiate between different materials. But you lose some of the good properties of the first variant. In order to make more derived materials bind to less derived effects, you have to introduce special cases in the BindTo method.

So, yeah, this is probably a flawed design, but I find it difficult to come up with a better solution without sacrificing flexibility. Maybe my mistake is that I am trying to be too generic (base classes are empty/almost empty). Essentially, it would be nice to have a system where you can change the effect without changing the material, and everything still works as long as the material provides the necessary inputs to the effect.

I think this post is getting pretty long, I'm curious to hear your thoughts. Edited by kloffy
0

Share this post


Link to post
Share on other sites
[quote name='kloffy' timestamp='1347536016' post='4979671']
Essentially, it would be nice to have a system where you can change the effect without changing the material, and everything still works as long as the material provides the necessary inputs to the effect.[/quote]
As you demonstrated in your first example a material has no behaviour and in particular no polymorphic behaviour, this is why you gave it an empty interface. If something has no behaviour then it's just data. The problem then becomes how to model that data so as to meet your goals.

If we recognise that there are only a few different kinds of thing that any effect could possibly ever want from a material then all we have to do is design a Material class that is able to provide any and all of those different things to an effect:

[code]
class Material
{
public:
const vector3 & getVector(const std::string & propertyName) const;
const matrix44 & getMatrices(const std::string & propertyName) const;
float getScalars(const std::string & propertyName) const;
// etc
};
[/code]

Now you're in a position where you model different types of material in a data-driven fashion; a Material is a "ColorMaterial" not because you subclassed it but because you populated it with ambient and diffuse properties.

Edit: Annoying that the Code tags don't format indent properly, I always have to fight with it to get things aligned. Edited by dmatter
1

Share this post


Link to post
Share on other sites
Yes, I have considered this approach as well (but I didn't want to make my post any longer). It certainly has some good arguments going for it (and the empty material base class felt wrong from the beginning). However, the nice thing about using inheritance is that I can use a cast to make sure a certain set of properties will always be present. Once the dynamic cast succeeds, I can access them very efficiently, whereas - if I understand correctly - in your data driven approach, I would have look them up in a map. But I agree, this is kind of an "abuse" of inheritance without any real polymorphic behaviour and it brings some weirdness with it.
0

Share this post


Link to post
Share on other sites
[quote name='Xai' timestamp='1347516239' post='4979596']
An OpenGL window should not be running around casting OpenGL controls to a long list of all possible children types, that is just madness. That is an unmanageable, unextendable non-oo design. However each OpenGL control can and likely will cast the IWindow to an IOpenGL window (fully expecting it to be one, since the whole assumption is the creation of everything from exactly 1 factory instance, so that a mismatch would be impossible. Also, an OpenGL window would keep a list of IOpenGL controls, and cast each control on add from IControl to IOpenGL control ... because the open gl family is going to be implemented internally to receive, associate with and use only other open gl family controls. The base interfaces are just used as the lesser API for externally facing interfaces.
[/quote]

Keep in mind that my casts "to a list of all possible children types" was an illustration of what [i]not [/i]to do. Then, I offered another solution that is very similar to what you proposed:

[quote name='CDProp' timestamp='1347387544' post='4978985']
[source lang="cpp"]void addControl(Control *ctrl)
{
Foo::Control *fooCtrl = dynamic_cast(ctrl);
if(fooCtrl!= NULL)
{
m_window.addControl(fooCtrl);
}
}[/source]
[/quote]

There is a problem with this, though. It works, but there is a bit of ugliness which I can't seem to get around, which is that it almost inevitably requires multiple inheritance. Here is one possible hierarchy (this time using OpenGL as an example, as you did).

[img]http://i.imgur.com/rE4wX.gif[/img]

OpenGLControl could also derive from IControl, but then you'd have diamond inheritance. You could use virtual inheritance to solve that; in fact, it's probably a good idea to do that anyway, because if your hierarchy gets any deeper (think ISwitchButton and IRadioButton), you're going to end up with diamond inheritance anyway. Once you have diamond inheritance, virtual inheritance becomes and issue, and once you have virtual base classes, you can no longer downcast them using static_cast, which means that SiCrane's checked cast solution is out of the question.

So, let's suppose that you keep your hierarchy clean enough that there are no diamonds, and thus virtual inheritance is unnecessary. Well, you still have multiple inheritance, which lends overhead to dynamic casting (and static casting too, if I understand it correctly) because now you have multiple vtables in your object, and a cast involves offsetting the pointer.

These are the sorts of ugly sins that I've been dealing with with this design.

I am warming up to Telastyn YAGNI remark. As jwezorek said, it may be impractical to think that I can wrap everything that I need to, even future frameworks that I haven't thought of yet.

dmatter, thank you very much for your reply. It seems to be clearest way to put into words what I'm doing wrong. The duck typing idea sounds intriguing, but I think my project is a bit too complicated for that. Your idea for having the window produce controls is also intriguing. It seems like it would be difficult to ever close the Window class to modification, though, because every time you come up with a new control type, you have to add a new method. Still, that may be a lesser sin than violating LSP.
0

Share this post


Link to post
Share on other sites
You missed exactly 1 thing in my example CDProp. The OpenGL Window does NOT have a list of IControls, it has a list of IOpenGLControls. So your original example (with multiple inheritance and/or dynamic_casting still needed is OK, even for a 500 object inner loop of a game engine, if and only if the dynamic_casts can be amortized by being NOT in the read / render, but instead in the add ... this is just like having indexes on a db that hurt insert / update but speed up search. But if every frame you are rebuilding or completely reorganizing your objects - then it would not perform well. But the goal of this design is that the casts are AT ASSOCIATION TIME, not AT USAGE TIME.

This design is simply does of course require the key interfaced based programming techniques. So as you mention the diamond problem, multiple inheritance, virtual inheritance, etc. all come along for the ride - stealing a little cost here and there, but hopefully applied judiciously they benefit will outweigh the cost.
0

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now
Sign in to follow this  
Followers 0