dynamic_cast is ugly!

N

Nick Keighley

ok. sorry to be banging on about this.

Lets use my (slightly) more real world example. A UML editor
where there is a requirement to apply operations only to
certain elements.

// almost C++
    Ui::hiliteAllInterfaces()
    {
         apply (colour_item_yellow, all_interfaces_collection);
    }

So I was wondering where all_interfaces_collection came from.
It could be generated when needed from a collection of all
drawable objects. But that involves a dynamic cast.

So it could be generated as a side effect of updating
all_drawable_collection(). Code to add a new element
looks like this

// the users adds a drawable object to the current picture
Ui::add_object()
{
    Drawable* d = drawable_factory.create(curr_object_type);
    picture->add_object(drawable_factory.create(curr_object_type)));

}

Drawable* DrawableFactory::create (DrawableType t)
{
    switch (t)
    {
    case CLASS_TYPE:
        return new DrawableClass();
    case INTERFACE_TYPE:
        return new DrawableInterface();
    }

}

Now how to update the subclass collections. These are shown as deltas
from above

//// option A
//// add to each collection
Ui::add_object()
{
    Drawable* d = drawable_factory.create(curr_object_type))
    picture->add_object(d);

    switch (curr_object_type)
    {
    case CLASS_TYPE:
        all_classes.add_object(d)
    case INTERFACE_TYPE:
        all_interfaces.add_object(d)
    }

}

// BLETCH. two switch statements.
// brittle hard to modify code

//// option B
//// move more into factory

Ui::add_object()
{
    drawable_factory.create(curr_object_type)

}

Drawable* DrawableFactory::create (DrawableType t)
{
    Drawable* d;

    switch (t)
    {
    case CLASS_TYPE:
        d = new DrawableClass();
        all_classes.add_object(d)
    case INTERFACE_TYPE:
        d = new DrawableInterface();
        all_interfaces.add_object(d)
    }

    return d;

}

// the factory seems a bit "busy"

// option C
// move more into subclass

DrawableClass::created()
{
    all_classes.add_object(this)

}

Ui::add_object()
{
    Drawable* d = drawable_factory.create(curr_object_type))
    picture->add_object(d);
    d->created();

}

at least this follows the OCP! The drawable objects now know
they are held in collections. Is this bad? Could the collection
management be moved somewhere else?

back to visitor pattern! Which seems to be nearly as "bad"
as a dynamic cast.

// option D
// Visitor

DrawableClass::accept(Visitor* v)
{ v->visit(this) }

class AddCollectionVisitor: public Visitor
{...}

AddCollectionVisitor::visit(DrawableClass* d)
{ all_classes.add_object(d) }

Ui::add_object()
{
Drawable* d = drawable_factory.create(curr_object_type))
picture->add_object(d);
AddCollectionVisitor add_collection;
d->accept(&add_collection);
}

I don't like these collection classes hanging
around waiting for something to do. Wouldn't
it be better to generate them only when required?
Which seesm to require visitor or dynamic cast...
 
J

James Kanze

Well, reliable programs aren't written by the compiler;
programs become more reliable if the programmers working on
them are able to understand them and adapt them to changing
requirements without having to introduce gruesome ad-hoc hacks
or having to redesign the entire thing (which usually is
prohibitively expensive, and therefore not done.)

My experience is that flexibility which isn't designed in
doesn't work anyway. You can't derive from a class if it counts
on all of its functions working in a specific way. You need
some sort of a contract. Static typing is just a part of the
contract which is enforced by the compiler. It's certainly not
sufficient, and doesn't eliminate the need for readable
documentation, good code review and extensive testing, but it
does help reduce costs when slip-ups are caught immediately by
the compiler, rather than later in the development cycle.
From my experience, programs written in a "dynamically" typed
language are simply more malleable and also more concise and
hence easier to understand.

My experience is that regardless of the language, some things
end up poorly documented, and some things slip through code
review. The only real difference I see in well written code is
that static type checking forces you to make the changes that
you should make anyway, where as with dynamic type checking,
they're only comments, and tend to be forgotten.
Add to that interactive development (like in Lisp, or other
languages that provide an interactive toplevel), and the whole
development process shows a much faster turnaround because one
doesn't have to wait for the compiler and a new test run all
the time. At least that's my experience. Faster turnaround
means more testing of new ideas, and more testing of code and
debugging; in the end resulting in better code.

That's an excellent choice for prototypes, or applications where
you need ultra-rapid turn around regardless of reliability.
It's not a good choice when the application has to work.
The code also won't work for any kind of logics errors, which
no compiler could guard against.

Certainly. As I said above, you still need extensive code
review. But since programmers, including the code reviewers,
the authors of the test suite, and everyone else is human, it's
nice to know that at least a minimum can be guaranteed.
There's simply no substitute for testing.

I'd say the good code review is even more important. Some
things simply cannot be tested, period.

The situation is simple:

-- If the compiler indicates an error, you know right away
where it is, and can correct it immediately.

-- Ditto with code review, but you do have to wait until the
code review takes place; you can't do it on your own.

-- If there's an error in the tests, you know that something's
wrong, but you still have to figure out where. Well
designed tests can make this easier, but still not to the
degree of having a concrete indication as to the exact line.

If we could design a language in which the compiler would catch
all of the errors, that would be ideal. That's not possible, of
course, but the more it catches, the less development costs.
After testing, runtime type errors seem to be rather rare and
unproblematic compared to more serious program flaws which are
sometimes hard to detect, and even harder if the program logic
is further obfuscated by having to accomodate a complicated
type hierarchy.

Used correctly, the type hierarchy makes the program logic
considerably clearer. In dynamically typed languages, you have
to explicitly document all of what the type system manages
anyway.
Not every application can be properly specified before
programming starts, in some cases, what the application
actually ought to do in detail might be unclear in the
beginning and only become manifest during an exploratory
development process, and in addition, many applications change
quite a lot over their lifetime.

Sure. But that doesn't mean that you can write working code
without some sort of a design. Random changes don't work.
A complete up-front design is often not possible, or even
desirable. I think a lot more software suffers from a too
rigid specification and implementation process than from a too
loose one.

I don't know. You can create a lot of useless rules, but on the
whole, the companies I've seen which have produced the most
reliable software, and been the most flexible with regards to
modifications in it, have also been the ones with a fairly
strict development process, which required programmers to
actually do some design before writing code, and have that
design reviewed, to ensure that their changes didn't break
something elsewhere.
Unfortunately, exploratory development is a recipe for
disaster with inflexible languages like C++ or Java because
you'll end up with a horrible type mess.

I've never encountered this. In the end, objects do have a
type, regardless. And if you ignore the type, you get into
trouble very quickly.

Of course, I don't do much exploratory development---the
programs I write generally have to work, and have to be
developed within a fixed budget. I'm not a research scientist,
I'm an engineer.
IMHO, using a dynamically typed language can make a difference
here. The idea is not to restrict the programmer at all in
what he/she can do, while giving him/her as much expressive
power as possible. C++ is quite powerful (albeit in a
sometimes very awkward and verbose way) but also restrictive,
while Java only restricts and gives very little in the way of
expressive strength. Languages like ML have a more powerful
type system that is somewhat more expressive than the
primitive ones of C++ and Java but the main problem remains:
Once the program stands, no matter how beautifully designed it
is, it is hard to make substantial changes to it because of
the rigidity of the type structure.

Well, I more or less agree with your characterizations of C++
and Java. But I would content that the type structure is there,
regardless. The difference is that C++ and Java make you be
explicit about it, and declare it up front; languages like
Smalltalk leave it up to the documentation and programmer
discipline. In either case, poor design with regards to type
will cause problems in the long run, and violating the type
system will not work.
 
D

Daniel T.

Juha Nieminen said:
Actually that's not true. It's usually quite easy to suggest a better
concrete alternative for goto in almost any example. That's because
avoiding goto in a clean way is usually quite easy.

Without first seeing code that uses goto and knowing what it is
supposed to do, you can't provide *any* alternative, clean or
otherwise.
I have asked for a better design like ten times and got nothing. What
can I deduce from that?

You keep asking for a better design, better than what?
 
K

Kai-Uwe Bux

Juha said:
Actually that's not true. It's usually quite easy to suggest a better
concrete alternative for goto in almost any example. That's because
avoiding goto in a clean way is usually quite easy.

I have yet to see a concrete alternative to the dynamic casting
problem presented in this thread.

I think you are comparing problems of completely different category.


I have asked for a better design like ten times and got nothing. What
can I deduce from that?

Somehow, I feel that this discussion is suffering from the lack of a
concrete non-trivial case. We would need to have some more or less
real-life OO component (say a library) before us which either (a) uses
dynamic_cast in its implementation or (b) might require client code to use
dynamic_cast. Then, the challenge would be to present a functionally
equivalent (i.e., at least equally powerful) replacement that doesn't do
so.

Since I do not do OO, I cannot really help in finding such a non-trivial
case-study, but maybe someone else has an idea.


Best

Kai-Uwe Bux
 
J

Juha Nieminen

Daniel said:
You keep asking for a better design, better than what?

Are you kidding? Maybe you should re-read this thread again?

I have explained my situation several times. I don't feel like
repeating it again.
 
J

Juha Nieminen

Michael said:
Given that, a dynamic_cast<> is the only elegant solution I can think of.

From all this discussion I tend to think likewise.

I can honestly say that I'm *not* arguing here. I'm honestly looking
for a better solution because I am developing this kind of system. I am
always looking for ways to make it cleaner, simpler and easier to use.
This dynamic casting problem, while not catastrophical, is slightly
annoying, and it would be nicer if there would be an easier and better
way. However, I just can't think of any alternative (at least not any
alternative which would actually be easier and cleaner instead of a lot
more complicated and hard to understand).

In my case a lot of the operations can be done without dynamic
casting, using virtual functions. There are some operations, however,
which just can't. Or at least I can't think of any better alternative.
(It's precisely the problem of needing to perform operations on the
objects in a certain order, and these operations cannot be performed by
the objects themselves but have to be performed by the container, and
the type of operation to be done depends on the object type.)
 
J

Juha Nieminen

std::vector<Drawable *> all_drawable_objects; // for the render loop
std::vector<Collidable *> all_collidable_objects; // for collision
detection

I only care about Collidables during collision detection. No need to
iterate over all objects and interrogate the type of each.

In my case this solution doesn't work for two reasons:

1) The order of the objects may change in the main container (and this
ordering is very relevant). I would have to maintain the same order in
the type-specific containers as well, which in some cases can become
exceedingly difficult. (Certainly more difficult than having to use
dynamic cast in a few places, so trying to do it becomes
counter-productive.)

2) In some cases I have to traverse *all* the objects in the order in
which they are in the main container and perform *type-specific*
operations to them (iow. operations which cannot be specified as virtual
functions in the base class). It would not be enough to traverse the
type-specific containers one after another (because it would mean that
the objects are traversed out-of-order with respect to the main container).

I actually am using your solution where I can (if for nothing else,
because it's more efficient, as less objects need to be traversed).
However, I can't do it in all cases.
 
J

Juha Nieminen

Dmitry said:
In order to do this, you have to convert std::vector<Shape*> to
std::vector<Square*> first. Explicitly or implicitly.

How do you suggest I do that? Not all objects in the first vector may
be of type Square. That conversion cannot be done in any other way than
using reinterpret_cast, which is a thousand times worse than any
dynamic_cast.

And what would be the point anyways? If I did that, the function
expecting a vector of Square pointers would believe that every object is
a Square even though that may not be the case. The program would crash.
 
R

Robert Martin

You've got it backwards. Exposing such details to the transport
layer would be very poor design, and a maintenance nightmare.

Gush, gush, gush!

Another common example of this kind of transport coupling is checked
exceptions. If I through an exception from A through B to C, and if
that exception is checked, then B knows about the exception because it
must appear in the 'throws' clause. If it's an unchecked exception
then B is ignorant of it, and is not coupled to it.

Interestingly enough, catch clauses are a kind of dynamic_cast.
 
I

Ian Collins

Robert said:
Gush, gush, gush!

Another common example of this kind of transport coupling is checked
exceptions. If I through an exception from A through B to C, and if
that exception is checked, then B knows about the exception because it
must appear in the 'throws' clause. If it's an unchecked exception then
B is ignorant of it, and is not coupled to it.

Interestingly enough, catch clauses are a kind of dynamic_cast.
An exceptional kind?
 
R

Robert Martin

I think what Daniel is getting at is that you shouldn't have to know whether
your request is only relevant to Squares.

Sometimes you *know* that the object you are holding is a square. And
yet the static type of that object is Shape*.

Shape* s = someplace.getShape();

You haven't lost the type information because you *know* that s points
to s square. You know it because the runtime logic is set up to make
it impossible for any other outcome. You may have lost the static
type, but you have held in in the dynamic state.

So using a dynamic cast is simply an assertion of what you know to
already be true. You are converting a fact held by dynamic state back
into a static type.

Square* sq = dynamic_cast<Square*>(s);

To suggest that this is bad design is to suggest that it is bad design
to move any static knowledge into dynamic state. If you take that
point of view you can rapidly determine that any dynamic transmforation
(e.g. i++) is bad design.

The problem with dynamic_cast is not that it can transform dynamic
knowledge back into static knowledge. Rather it is that it can be
abused as an element of a typecase.

if (dyami...
else if (dynami...
else if (dynami...
....
 
P

Phlip

Interestingly enough, catch clauses are a kind of dynamic_cast.
An exceptional kind?

And note the "only" place where dynamically typed languages bind calls directly
to input types is their 'catch' or 'rescue' clauses!
 
A

Andy Champ

Daniel said:
I can't comment directly because of unfamiliarity with the language. Is
it anything like python's "for ... in" syntax?


and I haven't touched Java in years.

I like SmallTalk's "no exposed iterators" approach. Such an approach
requires strong support for lambda functions though which is something
C++ is sorely lacking in... even with boost::lambda.

Ah well it was an idea. It appears our experience is in different
domains. I've never used Python or Smalltalk, and hardly touched Java!

Andy
 
M

Michael DOUBEZ

Juha Nieminen a écrit :
From all this discussion I tend to think likewise.

I can honestly say that I'm *not* arguing here. I'm honestly looking
for a better solution because I am developing this kind of system. I am
always looking for ways to make it cleaner, simpler and easier to use.
This dynamic casting problem, while not catastrophical, is slightly
annoying, and it would be nicer if there would be an easier and better
way. However, I just can't think of any alternative (at least not any
alternative which would actually be easier and cleaner instead of a lot
more complicated and hard to understand).

In my case a lot of the operations can be done without dynamic
casting, using virtual functions. There are some operations, however,
which just can't. Or at least I can't think of any better alternative.
(It's precisely the problem of needing to perform operations on the
objects in a certain order, and these operations cannot be performed by
the objects themselves but have to be performed by the container, and
the type of operation to be done depends on the object type.)

IMHO, the true problem in this situation is that even if you knew the
underlying type of the object, you cannot know if this object inherits
from the type you are looking for at runtime (unless type_info is
modified). Only dynamic_cast<> allows that.

Michael
 
D

Dmitry A. Kazakov

How do you suggest I do that? Not all objects in the first vector may
be of type Square.

So what? In that case your program is just incorrect, no matter *what* you
do. Either it deals non-squares (by ignoring them for example), or else it
is simply wrong.
That conversion cannot be done in any other way than
using reinterpret_cast, which is a thousand times worse than any
dynamic_cast.

Of course it can be. If non-squares are to be ignored, you create a *new*
container with only squares in it. If they manifest an exceptional (yet
correct!) situation, you raise an exception out of conversion, etc.

[ It is a part of OO design to describe the behavior of the container type.
That is the problem. You first chose a solution for a problem which was not
even specified, and then started to wonder what to do with the mess. This
not just a bad design, it is actually no design. ]
 
J

James Kanze

On 2008-03-12 05:38:19 -0500, James Kanze <[email protected]> said:
Gush, gush, gush!
Another common example of this kind of transport coupling is checked
exceptions. If I through an exception from A through B to C, and if
that exception is checked, then B knows about the exception because it
must appear in the 'throws' clause. If it's an unchecked exception
then B is ignorant of it, and is not coupled to it.
Interestingly enough, catch clauses are a kind of dynamic_cast.

Now that's a very interesting observation. It hadn't occurred
to me, but the behind the scenes propagation of an exception is
exactly the sort of "middleware" I was thinking of. Which
exposes itself explicitly when you arrange to propagate the
exception accross a join. Similarly, anytime you are
communicating between processes or machines: Corba certainly
needs the equivalent of a dynamic_cast internally.

Even within a single thread, you may end up passing objects
through lower level layers. Which really have no business
knowing the details of the contract between the higher levels.
 
J

James Kanze

Actually that's not true. It's usually quite easy to suggest a
better concrete alternative for goto in almost any example.
That's because avoiding goto in a clean way is usually quite
easy.

That's not strictly true. Goto is more a language issue than
anything else. The necessary flow control structures for clean
programming are known (and have been for a long time), and the
advantages of using them have been proven. Most modern
languages provide all of the necessary structures (and more), so
there is no reason to use goto.

The important point is that the "goto" be used correctly, and
that modern languages provide structured alternatives which use
it correctly without exposing it. (You can't write a loop
without a goto at some level.) This is not yet the case with
regards to dynamic_cast (although Robert Martin has pointed out
one case---exceptions---where the language does move it behind
the scenes, and Corba would be another). In a way, perhaps,
dynamic_cast is like goto in Fortran IV. You need it, because
the language doesn't provide all of the other structures which
hide its correct use.
I have yet to see a concrete alternative to the dynamic
casting problem presented in this thread.

The sum total of the argument seems to reduce to "it's bad
design", without any explinations as to why, or what
alternatives would be better.
I think you are comparing problems of completely different
category.

If you limit your thinking to C++ (or modern languages in
general). If you compare dynamic_cast in C++ with goto in
Fortran IV, I don't think there's any problem.
I have asked for a better design like ten times and got
nothing. What can I deduce from that?

That it's easier to talk than to do? (Or that we ain't got
religion.)
 
J

James Kanze

Somehow, I feel that this discussion is suffering from the
lack of a concrete non-trivial case. We would need to have
some more or less real-life OO component (say a library)
before us which either (a) uses dynamic_cast in its
implementation or (b) might require client code to use
dynamic_cast. Then, the challenge would be to present a
functionally equivalent (i.e., at least equally powerful)
replacement that doesn't do so.

The problem is that dynamic_cast is really only justified in
fairly large applications, where layering becomes an important
issue. As long as you can manage the entire application as a
single layer, it is possible to avoid "loosing" type (or at
least, I've not seen an exception). It's when you start having
to define separate layers, frameworks and such, that it becomes
important.

And Robert Martin has suggested one very good example:
exceptions. In this case, the "dynamic_cast" takes place behind
the scenes, but that really doesn't change the issue. You have
a lower level layer (in this case, the compiler and its run-time
library) transporting data in a generic fashion, and then
reestablishing the original type at another site.
 
J

James Kanze

On 2008-03-12 06:19:13 -0500, Kai-Uwe Bux <[email protected]> said:
Sometimes you *know* that the object you are holding is a
square. And yet the static type of that object is Shape*.
Shape* s = someplace.getShape();
You haven't lost the type information because you *know* that
s points to s square. You know it because the runtime logic
is set up to make it impossible for any other outcome. You
may have lost the static type, but you have held in in the
dynamic state.
So using a dynamic cast is simply an assertion of what you
know to already be true. You are converting a fact held by
dynamic state back into a static type.

That's more or less my point when I talk about middleware. I
think that Daniel's contention is that it's bad design for
someplace to store Shape*, when all you put in it is Square.
Which is another way of saying that generic programming is bad
design, and that we shouldn't use generic middleware.

Templates, of course, eliminate the need of some dynamic
typechecking, by moving the resolution of the genericity up to
compile time. But practically speaking, in larger projects,
there are lower level components which are really to large to be
done using templates, or and that are used by several different
subsystems, each subsystem with its own types, and for reasons
of encapulation, you don't want to move the information about
the high level types down to the lower level components. In
such cases, dynamic_cast can be seen as the equivalent of Herb
Sutter's compilation firewall idiom---a means of reducing
coupling between different parts of the application.
 
J

Juha Nieminen

Dmitry said:
If non-squares are to be ignored, you create a *new*
container with only squares in it.

And you lose the ordering relation the squares had with respect to the
other objects.

Sometimes type-specific operations need to be done in the exact order
in which the objects appear in the container.

Of course if I can do something to the square only, I do that already.
That's not the problem here.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,997
Messages
2,570,241
Members
46,831
Latest member
RusselWill

Latest Threads

Top