Re: Casting - bad design?
Quote:
Originally Posted by laasunde
The problem is that these module types have different features and require different information to be stored.
Well, this is the telling statement. Since there's little relationship between the module types, you're not going to get any useful common abstraction.
What you're looking for, then, is a design that is able to use dynamic information to select the correct operations for the actual type of an object.
As a first pass, have a look at the Visitor pattern.
This may not be the solution you want (it is quite hard to maintain), but it's a starting point from which you can examine other design patterns that may be more suitable.
Re: Casting - bad design?
Thanks for replying to the post, your input is much appreciated.
The modules in questions have a few properties in common; Address, Name, ModuleType and Software version. Then each module has atleast one seperate property.
So I suppose in some situations, the modules would require the same actions (adding a node to list-view, or tree-view etc would properly only require the name) and in other situation they would require different actions depending on their type (performing a module specific operation).
Thought I'd add this info.
Re: Casting - bad design?
The casting operators are there to be used, to be sure, but not without good cause.
Obviously that begs the question, "What is a good reason to use casting?"
I'm not sure I have a great answer for that, but for your design issue, I have some thoughts.
First, it seems that while there's very little in common between the derived classes, perhaps a minor commonality can be invoked.
The commonality may be nothing more than a virtual function "void DoYourStuff()".
Instead of assuming the GUI must select an item from a container and cast it in order to make specific calls, you can call a virtual "Do" function, which in turn calls an overload in the GUI - which effectively selects WHAT can be done based on type. No casting required - you're relying on the fact that at the point of the virtual function, it 'knows' what it is, and informs the GUI of that through the overloaded callback.
In case I'm not clear, GUI runs through a container, calling the "Do" function knowing only the base class type (and possibly providing a reference to the device context, the GUI object, etc). The "Do" in turn calls back to GUI, with 'this' as a parameter, which identifies the type - from there you have specific 'knowledge' within the GUI object of the type, and can make very specific calls to that object.
This does bring to mind the question as to which object does what....
You may have the viewpoint that GUI code does the drawing, and therefore the code that displays information, or provides GUI interfacing, is in the GUI objects. You could also have the view that these objects should do that themselves, using GUI objects as tool to do the work.
I have a situation where I have a collection of objects with few common elements, and lots of uniqueness in the derivatives. I have a list of them, the user may select one in a list, and the content of the dialog will change (sometimes drastically) to reflect the options of that item.
This is a little like a tabbed dialog, but without tabs on display. Due to the nature of our framework, these dialogs aren't taken from a dialog resource - long story on that - the bottom line is that we select an object based on the type the user selected in the list, and as I described above, the base has just a "virtual void select()" function which supplies that type in the descendant code - selecting an object which represents the appropriate dialog. That object, in turn, maps the unique elements to UI controls - etc, etc....
I have other situations where something similar happens, but some of the objects should not or can't be derived from a common base. These objects are so dissimilar that it defies the notion of a container capable of referencing them in a common base. Depending on the containment issues, I choose to create a 'mapping' object. The container has nothing to do with the target objects - it's just a virtual base for the 'virtual do' function(s). Descendants of the base mapper are created for each special case - I call 'do' on the base, the descendant calls whatever is appropriate for each special case on these otherwise unrelated objects.
Now I'm rambling.....
Re: Casting - bad design?
Thanks for the feedback JVene.
Must admit I'm curious as to why using casts is a sign of a poor design and in which situation, as you say, is a good reason to use a cast.
Can the suggestion below be linked to any known pattern ? Will try to see if your design would fit in with my requirements.
Re: Casting - bad design?
I think there's some gray area on what is a reasonable situation for a cast.
A very common use of casting I think most would agree is 'reasonable' is when a string object is used to provide a const TCHAR * to a 'printf' like function, even though there may be better alternatives (and I think the debate can meander). Some string classes insist on calling the c_str() (or similar name) to return a const TCHAR * of the string's contents, while others provide automatic conversion through a conversion operator, which is problematic in many cases for variable length functions like printf.
So while a cast is commonly found, there are good reasons to say the class should not provide the automatic conversion, especially since it means an ambiguous type might be supplied to printf.
One situation where dynamic casting might even be required is on the rarely used MI. One can hope that virtual functions suffice, but if you must contain objects using a secondary base pointer, dynamic cast is required and isn't bad, it's just unusual. Here again, though, a number of developers believe MI is a bad thing itself, and so the subject turns gray again.
Stroustrup suggested, I think it was in 'The Design and Evolution of C++', that one of the uses of dynamic cast is to test if a function call would be valid. He indicated that a dynamic cast would determine if an object was of a particular derived type, indicate a function unique to that derivative would be valid and possible, using the cast pointer if it returned non-null. Somehow I found that odd, but it does work that way.
I think generally if you can resolve a problem with a virtual function instead of a cast, you should use the virtual function if there's no other overriding reason against it. I found one design I had trouble arguing against where a large volume of objects was contained in a tree, and these could be one of 7 or 8 derived classes, all managed by pointers to a base object. The container was a template, and the only way a 'contained' object could have entered the container was if it were one and only one of the derived targets. Obviously the container could be any one of the 7 or 8 derivatives, the the contained objects were all of 1 type, that for which the container was created.
To save a little speed penalty and space, the write decided to avoid any virtual functions on the contained objects. Of those functions that had to perform acts requiring knowledge of the derived members, a cast was used to cast the contained objects downward. When they were deleted, they were deleted from the derived pointer, never the base pointer. The container enforced this, and the classes were designed to keep it so.
The casting seemed appropriate to me, given that a premium had been given to the minor speed improvement and space saving given the volume of objects in the container.
...also, in a verbose compiler, casting on native types to explicate you 'approve' of a conversion, like unsigned to signed, etc...can clean up an otherwise cluttered compile log.
I think it safe to say that if a cast can produce in an invalid, it's suspect. Other means should be considered. If one finds themselves always casting for a particular context of usage, even if the cast isn't likely to fail, one should consider the relationship between the objects/functions at the point where the cast is made, to see if a better relationship could be established which avoids the need for a cast, because that defined relationship is likely clearer as to what is really happening. The cast, in such situations, may represent a poorly defined relationship which is being locally overridden or locally specified by the cast.
Re: Casting - bad design?
Do apologise for not responding to this thread in a while.
Been reading a bit about the visitor pattern as suggested by Graham.
Just wondering if my understanding of the concept is correct - could the below code (using casts) be substituted
with the second block of code? (using uncompiled code, hope you understand the gist of it). Would this be a proper way of using the visitor pattern?
Code:
public class CGui
{
void foo(const CVehicle * vehcile)
{
Car * car = dynamic_cast<Car*>(vehcile);
Bike * bike = dynamic_cast<Bike*>(vehcile);
if (car)
{
// display cars' properties
}
else if (bike)
{
// display bikes' properties
}
}
};
Code:
class IVisitable
{
void Accept(IVisitor&) = 0;
};
class IVisitor
{
void visit(Car& car) = 0;
void visit(Bike& bike) = 0;
};
class Car : CVehicle, IVisitable
{
public void Accept(IVisitor visitor)
{
visitor.visit(this);
}
};
class Bike : CVehicle, IVisitable
{
public void Accept(IVisitor visitor)
{
visitor.visit(this);
}
};
public class CGui : IVisitor
{
void foo(IVisitable& obj)
{
obj.Accept(this);
}
void visit(Car& car) { // display cars' properties }
void visit(Bike& bike) { // display bikes' properties }
};
What are the great benefit of using the second approach beside avoid dynamic casts? Adding a derived class 'truck' would require both code sets to be updated.
Another thing I'm curious about, in Scott Meyers item 27, using cast in c++ was very different from using the in c, c# and java. One difference that was mentioned is th at c++ can have more than one address to a single object (one to base object and one to derived object). Is there any other reason why casting is considered so different in c++? Does that also mean that Casting usually is considered as bad software design does not apply for java, c# and c?
Re: Casting - bad design?
Casting is indicative of a bad (sub-optimal) design. It is by no means an absolute. At my company, we require documentation about why casts were chosen as a design and why other approachs were not feasible [fortunately we have a spec book, so the documentation is typically a 1 liner referencing a specific section of our design guidline book :D
I stongly prefer the Visitor pattern. In the pure form it is highly expandable. Right now your "devices" my only be visited by the GUI, but in the future there may be other items that need to visit every item in the list (perhaps a "remote monitor"). All of the infrastructure is already there, and none of your "devices" will need any modification. [Since you have read Scott Meyer's, this is "Programming in the future tense"]
One can not know what the future of a piece of code is. Extreme Programming says to simply ignore it now and deal with it later. The approach above says, let me implment something now that is not overly complicated, yet can be adapted to meet the future.
Also when dealing with object hierarchies, this pattern becomes even more powerful. Assume the following class hierarchy
Code:
root (abstract)
---b1 (abstract?)
------b1d1
------b1d2
---b2 (abstract?)
------b2d1
------b2d2
All classes in a given "b?" tree share SOME common behavior when visited, but have specific details depending on which "d?" they are....
In your visitor, make public methods for "b1" and "b2", implement non-public (or differently named) methods for each "d?".
Now the dispatcher will call into the appropriate location for the "general" logging, which in turn can delegate the details to the specific proper routine based on type. All without a single cast!
The code would quickly become very "messy" with casts....
Hope this is helpful..
ps: NEVER use "C-Style" casts [typename in parens], always use the proper C++ cast. Two reasons. First, the c++ styles indicate better what you are doing. Second, they are much easier to search for if you think you have a cast related error. It is virtually impossible to search for "C" style casts. Imagine you know that somewhere there is an error in 1 million lines of code casting a "foo" to a "bar". How would you locate:
Code:
bar badBoy = ( /*lets convert to*/bar/*from current type*/)myfoo;
Re: Casting - bad design?
Quote:
Originally Posted by laasunde
What are the great benefit of using the second approach beside avoid dynamic casts? Adding a derived class 'truck' would require both code sets to be updated.
Take your example of adding a derived class 'truck'. In the casting example, it would compile and run without any changes, perhaps leading to undesired behavior.
In the Visitor example, you will get a compile error if everything is not hooked up.
This may not be so crucial for your contrived example, but it becomes absolutely essential for real world maintainable code.
Jeff
Re: Casting - bad design?
TheCPUWizard: Thanks for your reply. Totally agree regarding c-style cast.
jfaust: Good point, did not think of that.
Re: Casting - bad design?
Regarding C# and Java - their type hierarchy is bound to need casting. Everything being derived from a common base "Object" is something they quote "Evil" since the introduction of generics. I was recently going through a C# Generics 2.0 book (.Net 2.0 Generic Wrox Publications) and just in the first few chapters the author must have repeated "type safety" about a hundred times! The problems were because the non-Generic containers were based on Object references (just like in pre-template days C++ would have had used void*?). That causes a direct problem of needing casting as soon as you expect to retrieve object out of the container.
For value types, an additional problem arises that of boxing and unboxing of those value types (for ease consider fundamental types - int, char, double etc) into Object while insert and retrieval respectively.
Other than that - if you have a custom made type heirarchy (except for when the most base for your own use is some abstract base or an interface ignoring Object class) - still it is better to maintain a good type safe and intelligent stack of virtual functions (non-final functions). Any saving of casting offers type safe code which of course is a good thing irrespective of those languages you mentioned (that promise type safe code). In C, casting becomes more needful since lack of generic types support. Example - see qsort.
Casts only look necessary in cases when your new code has to deal with the problematic existing design that you cannot do again from scratch. There may be more useful cases - but I can't think of one right now. The idea should be to not use casting anywhere in your code - when you will "need" to use it - you will know! And when you know you need to use it - ask questions about it being justified. :)
Re: Casting - bad design?
Quote:
The problems [in .Net 1.x] were because the non-Generic containers were based on Object references
While casts were definately required with 1.x containers it was always recommended to create a type safe wrapper class so that all of the casts were localized to that class (try saying that three times fast!) and that casts were not required outside the typesafe container.
Re: Casting - bad design?
Quote:
Originally Posted by TheCPUWizard
While casts were definately required with 1.x containers it was always recommended to create a type safe wrapper class so that all of the casts were localized to that class (try saying that three times fast!) and that casts were not required outside the typesafe container.
Writing a wrapper doesn't sort out the basic underlying issue of the type system. Isn't it? Rather than patch-work that needed a more basic fix in the framework. Limiting the scope of the problem doesn't mean that the problem doesn't exist. As for lassunde's question, he/she can use casting as much as wanted and localize that code to his own API layer. Is that justified? Moreover, there are efficiency issues related to that.
Also, when I said non-generic containers - that implied those off the pre-2.0 releases that included generics (1.x or whatever). Isn't it?
Re: Casting - bad design?
Even generics (available as of 2.0) actually use casting!, just not at a layer which is visible/controllable by the application programmer.
Also the "everything derives from Object" is bad, is misleading. In C++ you can say that every pointer"derives from" void * :eek: At least managed code ALWAYS knows the real type of the object!
And at the machine level, every pointer is jjust a raw address.
It all depends on how "exposed" the casting is, because this directly relates to the potential for error [in general] and the difficulty in making changes.
If casting is used, I would definately prefer that all casts between type X and Y are limited to a single file.
Finally (for this post at least ;) ), I have NEVER seen real performance issues [neglecting embedded extreme realtime systems, like missle guidance] where the performance diference between the various methodologies makes any "real" difference.
Re: Casting - bad design?
Quote:
Originally Posted by laasunde
What are the great benefit of using the second approach beside avoid dynamic casts? Adding a derived class 'truck' would require both code sets to be updated.
You've hit on the one real drawback of the Visitor pattern: it requires a stable hierarchy - extensions are not easily accommodated, since they require extensive updating to existing code, so it's best reserved for the situation where your hierarchy is well-defined at design time, with no need for adding new derived classes.