dynamic_cast is ugly!

A

Andy Champ

One for DanielT, following on from the rest of the discussion.

Would you like to comment on the way C# has gone, with its

"foreach ... in ..." syntax and "as" keywords? I'm guessing that
there's something similar in Java...

Andy
 
D

Daniel T.

Since in a proper OO design, Shapes shouldn't know anything about its
specializations, any methods referring to specializations such as
rectangles are by definition very poor OO design.

I agree completely.
If you call cluttering the base interface with a plethora of highly
specialization-biased methods a ``correct way'' to deal with this problem
then any further discussion is pointless.

I am not advocating such cluttering.
I think few around here would argue that one should minimize the use
of [goto] and in particular not ``design'' anything that requires
it even for its most basic functions. But completely dismissing it
like you did and the ``correct'' workarounds you suggested are mere
fundamentalism as far as I'm concerned.

I did a word replace on your text above, can you see it? I completely
agree with the above, whether it is about dynamic_cast, or goto. The
above is exactly what I've been saying.
 
D

Daniel T.

With CORBA, ordering objects around is the norm.  A distributed
object model blindly translates the voodoo from one context to
another.  I think this is a weakness of CORBA that often works it's
way into the designs of those using CORBA.  On the other
hand, I have no idea what it means to "let objects do for
themselves," where the objects in question are inanimate.

Objects are not inanimate, data buckets are inanimate. But that's OK,
the world needs data buckets too. :)

When you have a data bucket, you tell it what state you want it to be
in. When you have an object, you tell it what state *you* are in, it
decides its own state.

To kind of paraphrase HS Lahman (I hope you don't mind :) Problem
space entities tend to be objects, solution space entities tend to be
data buckets (the C++ std library is a great example of a bunch of
useful data buckets.)
 
D

Daniel T.

Ian Collins said:
What this thread shows is that unlike some other languages, there is no
"correct way" to handle this problem in C++. There are those who
passionately believe in one way and those who equally passionately
believe in another.

So we end up arguing round and round in circles.

Walk away and rejoice in the flexibility of the language!

You have a good point there. In comp.lang.c++ the goto debate rears its
head now and then, and it seems to generate a lot of heat. For
comp.object, down-casting is very much the same.
 
D

Daniel T.

Andy Champ said:
One for DanielT, following on from the rest of the discussion.

Would you like to comment on the way C# has gone, with its "foreach
... in ..." syntax and "as" keywords?

I can't comment directly because of unfamiliarity with the language. Is
it anything like python's "for ... in" syntax?
I'm guessing that there's something similar in Java...

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.
 
D

Daniel T.

Matthias Buelow said:
Why is it harder to figure out?

I'm referring to the fact that in a dynamic typed language, I can't
easily know all the classes that can be used in a particular context. In
a static typed language, only classes derived from X can be used in a
context that uses X. This can simplify things greatly IMHO.
 
D

Daniel T.

Juha Nieminen said:
It's very easy to say that a design is flawed. It's much harder to
actually suggest a better design (which is not orders of magnitude more
awkward and difficult to use than just using dynamic_cast).

The same thing can be said about goto... If your only justification for
using dynamic_cast is that it's too hard to make a good design... Well,
there you go...
 
J

Juha Nieminen

Daniel said:
The same thing can be said about goto...

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.
If your only justification for
using dynamic_cast is that it's too hard to make a good design... Well,
there you go...

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

Juha Nieminen

Daniel said:
There are any number of ways to do it, Dave Mikes mentioned one,

He didn't mention any concrete, implementable design, just some
abstract theoretical stuff, with not even a single line of code.
another would be to have a virtual function in Shape that lets Shapes
know that a "change rectangles to yellow" request has been made.

That's exactly what I asked how to do, and asked if it should be done
like at the beginning of the quote above, which I shall repeat:

virtual void performAction(const std::string& action,
const std::string& parameters);

The answer to this was "no, it shouldn't be done like that", but the
actual way of doing it was not given.

Please show me some concrete C++ code, not just some abstract
theoretical concepts.
Again, there are so many correct ways to do it

From which you have presented none. Not in actual C++ code anyways.
Note that [the coder might be dealing with a poorly designed interface...]

If that's the case, then dynamic_cast may be the only solution.

You keep insisting it's a "poorly designed interface" but refuse to
give a better solution.
 
J

Juha Nieminen

Daniel said:
You have a good point there. In comp.lang.c++ the goto debate rears its
head now and then, and it seems to generate a lot of heat. For
comp.object, down-casting is very much the same.

You are completely missing the point. I am not defending dynamic
casting. I am *not* saying that dynamic casting is good or the correct
way to do this. I'm asking for a better alternative, in C++. I'm getting
none.

The closest thing I have got so far is a suggestion which sounds like
implementing the delegation paradigm (which some other OO languages
support). How to implement this without dynamic cast has not been
specified in any way.
 
J

Juha Nieminen

Daniel said:
I did a word replace on your text above, can you see it? I completely
agree with the above, whether it is about dynamic_cast, or goto. The
above is exactly what I've been saying.

You are completely missing the point. You are talking as if people
were saying "dynamic cast is good, and the correct tool to use". No,
that's not what at least I am saying. I am asking for better
alternatives, and got none.
 
J

Juha Nieminen

Dmitry said:
Dmitry said:
On Wed, 12 Mar 2008 13:34:36 +0200, Juha Nieminen wrote:

Dmitry A. Kazakov wrote:
A third alternative would be to convert std::vector<Shape*> to
std::vector<Square*>. What is the least evil depends on each concrete
situation.
But then you lose the possibility of having more than one shape type
in that container. How is that a viable solution?
Basically std::vector<Square*> is a view on an instance of
std::vector<Shape*>. The view is implemented using by-value semantics, so a
physical conversion is necessary [*]. When you know the constraint you
convert to that view (container of Squares) and pass it to a method which
statically knows that the container has only Squares. If you wanted it in
the in-out mode you could do it with copy-in / copy-out.
Sorry, I didn't understand that at all. You'll have to explain in
simpler and more concrete terms.

Pass std::vector<Shape*> where std::vector<Square*> expected.

test.cc:18: error: invalid initialization of reference of type 'const
std::vector<Square*, std::allocator<Square*> >&' from expression of type
'std::vector<Shape*, std::allocator<Shape*> >'
test.cc:11: error: in passing argument 1 of 'void foo(const
std::vector<Square*, std::allocator<Square*> >&)'

I still don't get it, sorry.
 
N

Nick Keighley

You can solve that by putting homogeneous elements in their own
container for just such operations  If you want to globally change the
line style of connectors, operate on the Connector collection, don't
rifle through the generic one interrogating each object

apply (set_to_yellow_func, all_squares_collection);

Is what I was thinking about this. But then I thought
how do I generate ASC without a dynamic cast.

forall drob in all_drawable_collection
Square* square = dynamic_cast<Square*>drob;
if (square != 0)
all_squares_collection.add(square);

I suppose the answer is to generate it in parallel
with ADC. If I add (or remove) a square to ADC then
I add (or remove) it to ASC.

Though Juha Nieminen may have problems as that
application seems to need ordering as well.

Perhaps JN could give a more concrete example?
 
N

Nick Keighley

No, that is not how to do it. Quit telling objects what to do, try to
decentralize your thinking and let objects do for themselves.

To borrowNick Keighley'sexample (which Dave Mikes answered
admirably,) don't parse through a container of Shapes, looking for all
the squares and telling them to turn yellow (even if that is only
something a square can do.) Let all the shapes know that a "turn
yellow" request has been made for squares. Some non-squares may be
interested (maybe lines connected to squares want to be in a
contrasting color,) maybe some squares don't want to change color
(they have been asked specifically to stay blue.)

this seems to be specifically waht Juha was objecting to!!
The Shape class ends up with a very application specific

turn_square_yellow()

method. I can understand his horror! Even if we try a more
real world UML editor.

hilight_interface()

that still clutters the base class with UI issues. And sub-class
specific calls. I'm a beginner I looking for design advice!
 
D

Dmitry A. Kazakov

Dmitry said:
Dmitry A. Kazakov wrote:
On Wed, 12 Mar 2008 13:34:36 +0200, Juha Nieminen wrote:

Dmitry A. Kazakov wrote:
A third alternative would be to convert std::vector<Shape*> to
std::vector<Square*>. What is the least evil depends on each concrete
situation.
But then you lose the possibility of having more than one shape type
in that container. How is that a viable solution?
Basically std::vector<Square*> is a view on an instance of
std::vector<Shape*>. The view is implemented using by-value semantics, so a
physical conversion is necessary [*]. When you know the constraint you
convert to that view (container of Squares) and pass it to a method which
statically knows that the container has only Squares. If you wanted it in
the in-out mode you could do it with copy-in / copy-out.
Sorry, I didn't understand that at all. You'll have to explain in
simpler and more concrete terms.

Pass std::vector<Shape*> where std::vector<Square*> expected.

test.cc:18: error: invalid initialization of reference of type 'const
std::vector<Square*, std::allocator<Square*> >&' from expression of type
'std::vector<Shape*, std::allocator<Shape*> >'
test.cc:11: error: in passing argument 1 of 'void foo(const
std::vector<Square*, std::allocator<Square*> >&)'

I still don't get it, sorry.

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

(It is just like when you do fabs(-1), -1 int is converted to double.
Whatever operations fabs applies in its body to its argument, they all are
double.)
 
M

Michael DOUBEZ

Juha Nieminen a écrit :
Daniel said:
There are any number of ways to do it, Dave Mikes mentioned one,

He didn't mention any concrete, implementable design, just some
abstract theoretical stuff, with not even a single line of code.
another would be to have a virtual function in Shape that lets Shapes
know that a "change rectangles to yellow" request has been made.

That's exactly what I asked how to do, and asked if it should be done
like at the beginning of the quote above, which I shall repeat:

virtual void performAction(const std::string& action,
const std::string& parameters);

The answer to this was "no, it shouldn't be done like that", but the
actual way of doing it was not given.

Please show me some concrete C++ code, not just some abstract
theoretical concepts.
Again, there are so many correct ways to do it

From which you have presented none. Not in actual C++ code anyways.
Note that [the coder might be dealing with a poorly designed interface...]
If that's the case, then dynamic_cast may be the only solution.

You keep insisting it's a "poorly designed interface" but refuse to
give a better solution.

I don't know how he could do it. You already have some design decisions
which apply forces on your design:
- obviously, you cannot compress your code or you could use a
visitor pattern
- your ordered container seems to be your persistence root and you
cannot unify the interfaces.
- the message passing solution (smalltalk like) ? It is not
natively possible in C++ but using a unsafe solution would be possible (
a virtual int action(int id, void* param) member in the common
interface). It doesn't seem to fit your design (it is true that is is
ugly).

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

Michael
 
R

Richard Herring

H. S. Lahman said:
Responding to Herring...


It is a problem space property that is important to the requirements
and software solution. It is abstracted as an intrinsic knowledge
attribute of an object. That is basic problem OOA space abstraction.

Yes, but _which_ object? According to you (and I agree) the ordering
should be the responsibility of the collection.
I also don't follow how it was previously encapsulated in the
collection; the type, which includes the size property, is fully
exposed in the dynamic_cast solution.

No. The _ordering relation_ is not exposed by the dynamic_cast. The
client has no reason to know whether it's "less" or "greater", or
something more complicated calculated by an oracle from multiple
properties of the object, or even some abstraction not present _in_ the
object at all, such as its position in an external database. All the
client needs to know is that the objects will be presented in the
correct order.

The "size" property was _your_ invention for the purpose of argument.
And even if the ordering is in fact as simple as comparing a simple
property, the property might be part of a different interface that the
dynamic_cast doesn't expose.
I disagree that it doesn't scale well because the number of machine
instructions executed is going to be essentially the same in either
case,

But their location is not. It's is now distributed over more lines of
code, with more maintenance overhead. In your solution the class which
should be responsible for the ordering (the collection) has passed some
of the buck back to the client.
but let's not go there.
The relevant point to the thread is that the client checks the size
attribute rather than the object type via dynamic_cast.

My point is that the implementation detail of the ordering should be
encapsulated within the collection, not exposed to the client.
Making flow of control decisions based on problem space properties will
always be more robust during maintenance than making such decisions on
3GL implementation properties. The goal is to make the application more
maintainable and avoid foot-shooting; not elegance, reduced keystrokes,
minimizing static structure, or even being convenient for the developer.
Are you really suggesting that there's no correlation between "elegance,
[...] convenient for the developer" and "more maintainable"?
 
N

Nick Keighley

apply (set_to_yellow_func, all_squares_collection);

Is what I was thinking about this. But then I thought
how do I generate ASC without a dynamic cast.

   forall drob in all_drawable_collection
      Square* square = dynamic_cast<Square*>drob;
      if (square != 0)
           all_squares_collection.add(square);

I suppose the answer is to generate it in parallel
with ADC. If I add (or remove) a square to ADC then
I add (or remove) it to ASC.

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?

<snip>
 
D

dave_mikesell

He didn't mention any concrete, implementable design, just some
abstract theoretical stuff, with not even a single line of code.

This is what I was suggesting -- keeping references of some objects in
multiple containers depending on use.

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.
 
J

James Kanze

You can solve that by putting homogeneous elements in their
own container for just such operations If you want to
globally change the line style of connectors, operate on the
Connector collection, don't rifle through the generic one
interrogating each object.

Which means that the lower level managing the collections has to
know about the possible higher level types. Sounds like a
serious breach of encapsulation to me.
 

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,780
Messages
2,569,611
Members
45,276
Latest member
Sawatmakal

Latest Threads

Top