dynamic_cast is ugly!

A

Andy Champ

Daniel said:
Unless the part of the program that is creating the objects is *also*
out of your control, and it is dumping the static information about the
object's type, you are not required to use dynamic_cast. Such a design
is, IMHO sub-standard.

I create the objects, and use them. However, their intermediate storage
is not under my full control.
Have you ever noticed all the arrows in UML diagrams? Arrows leading
from users to that which is used? 'dynamic_cast' goes against those
arrows.

I don't see that.
And I'd be interested to know how few projects you worked on before you
started breaking ideal design principles willy nilly. (I'm *kidding*! :)

I'm not. The answer is quite simple - no projects at all. It was in my
first job that it was pointed out to me that while the design we were
going to use was not ideal, and was going to store up trouble in the
long term, the correct design would take so much longer to implement
that if we tried it there would *be* no long term. This is the
principle that XP zealots call "YAGNI".
Seriously though, I've been programming professionally in C++ since 1998
and have credit in the release of over a dozen titles. For many of them
I was involved in the design of the framework that other team members
relied on. I am currently the most senior developer at my job, and so
often find myself as the chief code reviewer and example setter. I.E., I
can't afford to break the rules of good design without loosing
credibility. :)

You've allayed my suspicion :) You are neither a student nor an
academic, but do inhabit the real world! I must admit that a dozen
projects in 10 years indicates that they are somewhat smaller than the
ones I am involved with. For example I have been at my current employer
for 7 years, and have worked on 4 projects. One of these has been
cancelled, because the market went away. The other three are still
going. BTW the current one has over 100 classes, even though we've
compromised the design and folded some similar ones together.

Andy
 
A

Andy Champ

James said:
No. Goto never has its place.
Yes it does. I can think of several places where it's really valuable.

Assembler is impossible without it.

However, I've never written one in C or any of its derived languages,
and I've only seen them in Pascal used as the equivalent to "throw
FatalException".

Andy
 
D

Daniel T.

Responding to Nieminen...


I think what Daniel T. is saying is that from a design perspective you
have two choices:

(1) Use separate homogeneous collections rather than heterogeneous
collections. Then the client who needs specific types of objects can
navigate the appropriate relationship to the right collection.

(2) Provide the type information to the collection and provide an
interface to the collection that clients can access by providing a
desired type. Then the collection manages the {type, object} tuples in
its implementation.

Actually, I'm advocating choice 3:
Have the producer provide the type information directly to the
consumer, this doesn't necessarally have to be through the collection.
If the collection's job is to keep track of ordering information, then
that should be its only job. The consumer shouldn't be expected to use
that collection to *also* get all objects of a particular type.

I don't think it is necessarally a good idea to put type information
in the collection, but it is a good idea to only navigate
relationships along the trajectory they were designed to be navigated
along (i.e., follow the UML arrows, don't try to backtrack against
them.) And, I think it is important to follow the "tell, don't ask"
principle. (http://pragmaticprogrammer.com/articles/tell-dont-ask)
Asking the object its type and then telling the object to do something
based on the answer goes against this principle.
 
D

Daniel T.

Daniel T. wrote:

I'm afraid this is just too vague to be of any help to me...

Of course it's vague. Without a spicific problem statement, a spicific
answer simply doesn't exist.

Or to put it another way, there are hundreds of ways to get the type
information from the producer to the consumer. Pretend that
dynamic_cast doesn't exist and see what you can come up with in your
particular problem space.
 
D

Daniel T.

Daniel T. wrote:

You've allayed my suspicion :) You are neither a student nor an
academic, but do inhabit the real world!

Actually, I've never been a "student" as in I didn't go to college to
learn programming or C++ and I don't have any sort of CS or IT degree.
Obviously, I am a student in the sense that I have worked hard to
learn my craft.
I must admit that a dozen projects in 10 years indicates that they
are somewhat smaller than the ones I am involved with. For example
I have been at my current employer for 7 years, and have worked on 4
projects. One of these has been cancelled, because the market went
away. The other three are still going. BTW the current one has over
100 classes, even though we've compromised the design and folded
some similar ones together.

I'm not sure what to say to the above... My last completed project had
about 550 classes in it, just for the application code itself, not
including the framework which is another 350 classes (not all of which
is used in every project of course.) We usually finish a project
(i.e., from the day we get the first draft of the design doc to the
day we deliver the "gold master") in about 6-9 months. Needless to
say, the design doc is in a state of extreme flux while we are working
on the code, so it is very important that the code itself is very
flexible as well.

I have also worked on several "failed" projects. Strictly speaking,
only two actually failed, but there were others that were prototypes
which never managed to see the light of day.

So, when you say your projects must be bigger than mine because you
only complete 3 in 7 years and they only contain "over 100 classes",
I'm not sure what that means.
 
J

James Kanze

Yes it does. I can think of several places where it's really
valuable.
Assembler is impossible without it.

And Fortran IV:). (My assemblers didn't have goto. We used
something called B or JMP.)
However, I've never written one in C or any of its derived
languages, and I've only seen them in Pascal used as the
equivalent to "throw FatalException".

Yes. The problem isn't the goto itself; the problem is the
program flow structure. One of the cleanest programs I ever saw
was written in Fortran IV, and of course, it used GOTO. But
only to emulate the structures that were missing in the
language.

In C++, of course, we have all of the structures we need (and
even some we don't), so there's really no place for goto.
 
J

James Kanze

How do you know that the provider and the consumer are in
agreement? Or to put it another way, the fact that
dynamic_cast is in the code shows that the provider may very
well give the consumer something it can't consume (i.e., they
aren't in agreement.)
No, I don't think the transporting code should necessarily
know what it is transporting, but just like in the real world,
consumers shouldn't have to open the box after receiving the
item to see if it is really what they asked for either.

Agreed, but you can't really have it both ways. At least
dynamic_cast is like finding a detailed inventory at the top of
the box when you open it (or arguably, in an envelop pasted to
the outside of the box). I rather prefer it to the Smalltalk
solution where you have to ask about each method.

And you still haven't responded to Juta's question about what
you do when the transporter is managing several different
producer/consumer relationships, using different types, and the
order is significant. Keeping the actual objects in separate
containers, one for each type, means managing the order
externally. That's a lot of extra program logic just to avoid
one dynamic_cast at the consumer side.

In the end, of course, it's a trade off. Loosing the type, and
thus needing the dynamic_cast, means that what should have been
a compile time error becomes a runtime error. I've done enough
Java in the past (before Java had templates) to know what that
can cost. But in certain limited cases, it's manageable, and
increasing the code size of the transport mechanism by a factor
of 10, at the same time moving all of its code into templates
(and thus into the header files), isn't free either.

And of course, there are other cases where you might want it as
well: I've used in for version management in the past, and I'm
not alone. You might want to look at java.awt.LayoutManager2,
for example, and how it is used in some of the client classes.
(The name speaks for itself.) Obviously, support for existing
client code---which probably already derived from
java.awt.LayoutManager, and didn't provide the new functions,
but you don't want to limit yourself forever to the interface
that came up in your first design. In my case, I've often used
it in distributed systems---you can't expect 60,000 client
machines to all be updated at the same instant, so your code has
to handle both the older version, and the new, extended version.
(Obviously, the old clients won't ask for features that are only
present in the new version.)

And so on. The fact remains that I've found cases where
dynamic_cast was justified, in the sense that it was more cost
efficient than the alternatives. (With cost being measured in
program readability and maintainability, which basically end up
the equivalent of monitary cost in the end.)
 
J

James Kanze

Actually, I'm advocating choice 3:
Have the producer provide the type information directly to the
consumer, this doesn't necessarally have to be through the
collection.

Isn't this exactlly what dynamic_cast does? What's the
difference between a member function "supportsInterfaceX()" and
dynamic_cast< X* >. Except that providing the member function
means that the base interface must know about the extended
interface.
If the collection's job is to keep track of ordering information, then
that should be its only job. The consumer shouldn't be expected to use
that collection to *also* get all objects of a particular type.
I don't think it is necessarally a good idea to put type information
in the collection, but it is a good idea to only navigate
relationships along the trajectory they were designed to be navigated
along (i.e., follow the UML arrows, don't try to backtrack against
them.) And, I think it is important to follow the "tell, don't ask"
principle. (http://pragmaticprogrammer.com/articles/tell-dont-ask)
Asking the object its type and then telling the object to do something
based on the answer goes against this principle.

The problem with this is that it means that everything any
derived class supports must be known and declared in the base
class. And how do you handle the different levels of
functionality? What happens if you tell an object to do
something it's not capable of? (Note that the article you site
is mainly concerned with not exposing mutable state, in order to
reduce coupling. There's nothing "mutable" about the results of
dynamic_cast. In fact, used correctly, it reduce coupling, by
keeping not only the state, but the extended interface, out of
sight except to those who need to know.)
 
M

Matthias Buelow

Jeff said:
"modularity" makes the list of
things covered in typical undergraduate courses, but "static type
safety" does not. What really irritates me is that kids coming out of
school seem to have even less appreciation for static type safety than
the old-timers, so there's not a whole lot of hope in sight.

That's probably because static typing is not a desirable feature in
programming in the first place. It is added complexity, which is
(arguably) sometimes necessary, more necessary with some languages than
others. The languages I know that put emphasis on static type
correctness (for example, ML and Haskell) are extremely unwieldy to
program in. No fun at all. Nobody I think, not even here, would say,
"oh, I miss static typing. It was so neat." It's a burden on the
programmer best avoided if one can. That's why schools and universities
probably don't put much importance on it in programming courses (at
least not the good ones). It's just ballast, nothing more. Sometimes you
need the ballast, often you don't.
The Python
community in particular seems to believe static typing is about as
obsolete as magnetic core memory. It's depressing.

While I'm not a Python fan, they're correct here (imho).
 
H

H. S. Lahman

Responding to Daniel T....
I don't think it is necessarally a good idea to put type information
in the collection, but it is a good idea to only navigate
relationships along the trajectory they were designed to be navigated
along (i.e., follow the UML arrows, don't try to backtrack against
them.)

That was pretty much the point I was trying to make with the caveat.
Including object type information explicitly in the collection is only
slightly better than navigating generalizations with dynamic_cast.

There has to be some way to describe the problem context that determines
which particular object type is relevant to the client at the time of
the collaboration. The design problem is to abstract that problem
context into a solution element -- like a context variable or dedicated
relationship. Then the client and collection use the problem context
during all collaborations. One can then isolate and encapsulate the
mapping of problem context to OOPL implementation types around
instantiation rather than collaboration.


--
There is nothing wrong with me that could
not be cured by a capful of Drano.

H. S. Lahman
(e-mail address removed)
Pathfinder Solutions
http://www.pathfindermda.com
blog: http://pathfinderpeople.blogs.com/hslahman
"Model-Based Translation: The Next Step in Agile Development". Email
(e-mail address removed) for your copy.
Pathfinder is hiring:
http://www.pathfindermda.com/about_us/careers_pos3.php.
(888)OOA-PATH
 
H

H. S. Lahman

Responding to Kanze...
Isn't this exactlly what dynamic_cast does? What's the
difference between a member function "supportsInterfaceX()" and
dynamic_cast< X* >. Except that providing the member function
means that the base interface must know about the extended
interface.

The difference is that in a 3GL type system the interface defines what
the object is while dynamic_cast is just a check to determine what the
object is.

dynamic_cast is bad because relationship navigation should always
guarantee that the right objects (types) are there for a collaboration.
In a well-formed OO application one should never have to check whether
the object on the other end of the relationship path is the right object
for the collaboration. IOW, it is the job of relationship instantiation
to ensure that the right object is *always* on the other end of the
relationship path when it is time to collaborate.

dynamic_cast exists in C++ because there is no other facility for
dispatching from an external stream of objects of different types in a
subsystem interface. But that is the only place that it should be used.

It is especially bad to use it to navigate generalizations to determine
the subtype. OO generalization relations are not navigable because a
single object instantiates the entire limb of the generalization, so
there is nothing to navigate; the object in hand is what it is. So it is
either the right object to collaborate with or not. If not, then there
is a problem with the way one got to the object in hand.

If one accesses the tree through a supertype, then it is an abuse of
polymorphic dispatch to use dynamic_cast to qualify that dispatch by
accessing properties that are not common to all descending types.
Polymorphic dispatch *depends* on all descendants being substitutable.
If one needs specific properties one needs to access the generalization
at the level where those properties are common to all descendants. IOW,
one needs to implement and instantiate relationships to the proper level
of property definition in the generalization for the collaborations.



--
There is nothing wrong with me that could
not be cured by a capful of Drano.

H. S. Lahman
(e-mail address removed)
Pathfinder Solutions
http://www.pathfindermda.com
blog: http://pathfinderpeople.blogs.com/hslahman
"Model-Based Translation: The Next Step in Agile Development". Email
(e-mail address removed) for your copy.
Pathfinder is hiring:
http://www.pathfindermda.com/about_us/careers_pos3.php.
(888)OOA-PATH
 
R

Richard Herring

In message
And Fortran IV:).

Not quite. Don't forget Fortran 4's main control structure was the
"arithmetic if", which could jump backwards ;-/

If-then-else didn't appear until Fortran 77.
 
M

Micah Cowan

James Kanze said:
And Fortran IV:). (My assemblers didn't have goto. We used
something called B or JMP.)

There are several cases where goto is quite useful in C; particularly
for "exception handling" within a function, where using goto or
similar unstructured mechanisms to jump to a single point of
cleanup-and-return, are often more readable than the alternatives.

Some of the spots where it is useful in C are still useful in C++,
such as jumping to an outer loop; of course, this is avoidable
with the use of special "condition" variables; but IMO such variables
are more annoying to read than the jump they were meant to avoid.

OTOH, I've run into such a situation _maybe_ once, myself, so
perhaps it doesn't tend to crop up too often.

For all that people complain of goto leading to spaghetti code, my
experience is that spaghetti code is just as easy/common _without_
leaving the confines of "structured programming", and is most often
due to poor encapsulation.

I've read C programs containing goto statements where I would not have
used them, that nonetheless remained quite readable, while I've
encountered several programs where goto is not employed at all, which
remain very cumbersome to read because every snippet of code gets its
hands dirty with the implementation details of several distinct
facilities. This leads me to believe that the taboo against goto
(while of course well-justified) has been grossly overemphasized,
while proper encapsulation, despite the fairly widespread recognition
of its benefits, remains one of the most neglected skills among
lesser-skilled (but professionally employed) programmers.
 
A

andrew queisser

H. S. Lahman said:
dynamic_cast exists in C++ because there is no other facility for
dispatching from an external stream of objects of different types in a
subsystem interface. But that is the only place that it should be used.
Redefine "external" and "subsystem interface" just a little bit and
everyone's happy. I think traversing a collection and only acting on certain
types is preferable to having the same object in two collections, just to
avoid dynamic_cast or, even worse, forcing functionality with do-nothing
defaults into the superclass (although I didn't hear anyone advocating
that.)

I think most people will strive to avoid type-testing elements in
heterogenous collections but when the time comes where you need it most
design-arounds end up being dynamic_cast in disguise. Why not just embrace
it - you know you want to.

Andrew
 
P

Phlip

andrew said:
Redefine "external" and "subsystem interface" just a little bit and
everyone's happy. I think traversing a collection and only acting on certain
types is preferable to having the same object in two collections, just to
avoid dynamic_cast or, even worse, forcing functionality with do-nothing
defaults into the superclass (although I didn't hear anyone advocating
that.)

Add no-op defaults to the parent class!
 
I

Ian Collins

Phlip said:
Add no-op defaults to the parent class!
This requires you know what extensions derived classes will add and what
types they will use, or have access to the base to add the no-ops.
 
A

andrew queisser

Ian Collins said:
This requires you know what extensions derived classes will add and what
types they will use, or have access to the base to add the no-ops.
I'll hazard a guess: Phlip was just trying to throw me a bone by asking for
something everybody is already objecting to.
 
A

Andy Champ

Matthias said:
While I'm not a Python fan, they're correct here (imho).

IMHO, you're completely wrong :p

The last experience I had of picking up an untyped language was some C#
written in .Net 1 - without ...err... is it Generics in .Net?

The code was very efficient, but not very readable, and this was not in
the least help by the major data structure being a collection of
objects. Which were, upon examination, each made of objects. Which,
upon further examination, turned out to be strings.

A strongly typed language stops you doing anything so stupid. And I
note that at release 2.0, M$ fixed this particular problem.

But this is like which is better - Car or motorbike? Opinions are bound
to differ.

Andy
 
A

Andy Champ

Daniel said:
I'm not sure what to say to the above... My last completed project had
about 550 classes in it, just for the application code itself, not
including the framework which is another 350 classes (not all of which
is used in every project of course.) We usually finish a project
(i.e., from the day we get the first draft of the design doc to the
day we deliver the "gold master") in about 6-9 months. Needless to
say, the design doc is in a state of extreme flux while we are working
on the code, so it is very important that the code itself is very
flexible as well.

I have also worked on several "failed" projects. Strictly speaking,
only two actually failed, but there were others that were prototypes
which never managed to see the light of day.

So, when you say your projects must be bigger than mine because you
only complete 3 in 7 years and they only contain "over 100 classes",
I'm not sure what that means.

Now you've got me curious. What do you print your UML diagrams on?
I've spent over a year trying to get a large-format printer. It's well
over 100 classes BTW, I lost count at 250 today and don't have a tool
that'll do it manually - but less than your 550.

But if our projects take us a couple of years, and yours 6-9 months,
something doesn't add up.

And I can't give too many details of what we're up to, commercial
confidentiality and all that.

Andy
 
K

Kai-Uwe Bux

Andy said:
IMHO, you're completely wrong :p

The last experience I had of picking up an untyped language was some C#
written in .Net 1 - without ...err... is it Generics in .Net?

The code was very efficient, but not very readable, and this was not in
the least help by the major data structure being a collection of
objects. Which were, upon examination, each made of objects. Which,
upon further examination, turned out to be strings.

A strongly typed language stops you doing anything so stupid. And I
note that at release 2.0, M$ fixed this particular problem.

From your description, I have a hard time seeing any problem at all. So
there was a collection of collections of collections of strings? What is
stupid about that?

Besides, I can have a set< set< set< string > > > anytime I want in the
strongly typed language C++. Actually, in my programs a data-structure
depth of four or more is not at all rare. Of course, this is usually hidden
by typedefs; but if I see the template instantiation error messages running
down the terminal, I can tell that I am going pretty deep.

But this is like which is better - Car or motorbike? Opinions are bound
to differ.

Well, sure. But that does not mean that all opinions are equally well
supported.


Best

Kai-Uwe Bux
 

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,240
Members
46,830
Latest member
HeleneMull

Latest Threads

Top