How many times have you written
someType* var = dynamic_cast<someType*>(someOtherPtr);
and wondered WTH you have to quote the type twice?
So I built this:
template <class T> class AutoCastProxy
{
public:
AutoCastProxy(T object): m_Object(object) {};
template <class U> operator U()
{
return dynamic_cast<U>(m_Object);
};
private:
T m_Object;
};
template <class T> AutoCastProxy<T> AutoCast(T object)
{
return AutoCastProxy<T>(object);
};
.... which lets you type
someType* var = AutoCast(someOtherPtr);
Surely it's been done before? If not:
I hereby place this code in the public domain. I'm not even asking for GPL.
Andy
|
|
0
|
|
|
|
Reply
|
no.way7055 (86)
|
3/5/2008 11:05:25 PM |
|
Andy Champ <no.way@nospam.com> wrote:
> How many times have you written
>
> someType* var = dynamic_cast<someType*>(someOtherPtr);
In professional code? Never.
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/6/2008 12:49:24 AM
|
|
Daniel T. wrote:
> Andy Champ <no.way@nospam.com> wrote:
>
>> How many times have you written
>>
>> someType* var = dynamic_cast<someType*>(someOtherPtr);
>
> In professional code? Never.
Why not?
Andy
|
|
0
|
|
|
|
Reply
|
no.way7055 (86)
|
3/6/2008 8:15:38 PM
|
|
Andy Champ <no.way@nospam.com> writes:
> Daniel T. wrote:
>> Andy Champ <no.way@nospam.com> wrote:
>>
>>> How many times have you written
>>>
>>> someType* var = dynamic_cast<someType*>(someOtherPtr);
>>
>> In professional code? Never.
>
> Why not?
Because it pretty much always indicates a design flaw. Polymorphism is
the preferred idiom.
Of course, sometimes design flaws are unavoidable, particularly when
not all the code involved is under your control.
--
Micah J. Cowan
Programmer, musician, typesetting enthusiast, gamer...
http://micah.cowan.name/
|
|
0
|
|
|
|
Reply
|
micah6888 (27)
|
3/6/2008 9:22:34 PM
|
|
On Mar 5, 3:05=A0pm, Andy Champ <no....@nospam.com> wrote:
> How many times have you written
>
> someType* var =3D dynamic_cast<someType*>(someOtherPtr);
>
> and wondered WTH you have to quote the type twice?
The C++ cast operators, including dynamic_cast<>, were deliberately
designed to have an unwieldy syntax precisely to discourage C++
programmers from using them. Why, after all, does a program need to
perform a downcast to someType? Either "SomeOtherPtr" should have been
a pointer to a someType object all along - or class-specific behavior
that the client needs should have been modeled inside the class
implementation itself.
Greg
|
|
0
|
|
|
|
Reply
|
greghe3 (115)
|
3/6/2008 11:04:24 PM
|
|
Micah Cowan wrote:
> Andy Champ <no.way@nospam.com> writes:
>
>> Daniel T. wrote:
>>> Andy Champ <no.way@nospam.com> wrote:
>>>
>>>> How many times have you written
>>>>
>>>> someType* var = dynamic_cast<someType*>(someOtherPtr);
>>> In professional code? Never.
>> Why not?
>
> Because it pretty much always indicates a design flaw. Polymorphism is
> the preferred idiom.
>
> Of course, sometimes design flaws are unavoidable, particularly when
> not all the code involved is under your control.
>
In my case, it isn't the code, it's the data structures that the
objects/classes represent that triggered off me doing this so much.
However, in the general case where you have classes that exhibit very
similar behaviour you are going to want to treat them the same most of
the time, calling common interfaces. Just occasionally you want to know
which of the specialised classes you have, in order to invoke the
specialised behaviour. That's when dynamic_cast comes in.
Greg Herlihy wrote:
> The C++ cast operators, including dynamic_cast<>, were deliberately
> designed to have an unwieldy syntax precisely to discourage C++
> programmers from using them. Why, after all, does a program need to
> perform a downcast to someType? Either "SomeOtherPtr" should have been
> a pointer to a someType object all along - or class-specific behavior
> that the client needs should have been modeled inside the class
> implementation itself.
>
> Greg
I'd love to see a reference on that. I'm guessing it was just easier
for the compiler designers not to have to affect the right side with the
object on the left side of the operator.
With a raw pointer, this works fine. If the object on the LHS has
multiple constructors I can imagine the compiler getting a bit confused
- which is another reason for keeping the traditional, clumsy, syntax at
least some of the time, as it *forces* the type of conversion.
Without templates, you can't do my trick, and templates are a late
addition to the language.
Andy
|
|
0
|
|
|
|
Reply
|
no.way7055 (86)
|
3/6/2008 11:23:50 PM
|
|
Andy Champ <no.way@nospam.com> wrote:
> Daniel T. wrote:
> > Andy Champ <no.way@nospam.com> wrote:
> >
> > > How many times have you written
> > >
> > > someType* var = dynamic_cast<someType*>(someOtherPtr);
> >
> > In professional code? Never.
>
> Why not?
Because I program using OO idioms, not representational ones. Or to put
it another way, I specifically design my programs such that I don't need
to use dynamic_cast... even occasionally.
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/7/2008 1:56:44 AM
|
|
Micah Cowan wrote:
> Andy Champ <no.way@nospam.com> writes:
>
>> Daniel T. wrote:
>>> Andy Champ <no.way@nospam.com> wrote:
>>>
>>>> How many times have you written
>>>>
>>>> someType* var = dynamic_cast<someType*>(someOtherPtr);
>>> In professional code? Never.
>> Why not?
>
> Because it pretty much always indicates a design flaw. Polymorphism is
> the preferred idiom.
I think there are a few cases where trying to avoid dynamic_cast at
all costs becomes counter-productive, and the code would only get uglier.
For example, let's assume you have some kind of primitive manager
which can contain all types of primitives (ie. it contains pointers to
objects derived from some 'Primitive' base class).
For the user to be able to perform some operations to those primitives
when the manager so requests, there are basically two options: The
manager either calls a virtual function defined in the 'Primitive' base
class (which the derived classes can implement), or the manager calls a
callback function given to it by the user, giving this callback function
a reference/pointer to the object (this reference/pointer has to be of
type 'Primitive', of course, because the actual object can be of any type).
The first option would be the nicest, but it becomes awkward in some
cases. For example, if the operation to be performed requires something
the object itself cannot do. This would mean that the object would have
to have a pointer to the actual module which can do that operation, and
it has to call a function through that pointer, and it has to have
permission to do so (the function must be public, or the object must be
a friend of the module).
This also means you can't have pre-made primitives (eg. provided by
the same library which provides the primitive manager) which can simply
be instantiated and given to the manager. At the very least you will
have to create your own class derived from this pre-made primitive, and
in it you will have to implement the store-pointer-call-function
functionality. This may quickly become laborious if you want to use lots
of different pre-made primitives.
The other alternative is to use the callback mechanism and
dynamic_cast, for example like this:
void MyClass::doSomethingToPrimitive(Primitive* p)
{
Circle* c = dynamic_cast<Circle*>(p);
if(c)
{
// Do something to the circle
}
}
Ugly? Maybe. But IMO less ugly, and especially less laborious than the
first option.
The usual reason to avoid dynamic_cast is that it can fail: This
happens if the object behind the pointer is not of the type being casted
to. However, in this case this is not a problem at all, and in fact part
of the very functionality of the system: The dynamic_cast is actually a
"check" to see if the given object was of a certain type (and if it was,
then some operation is done to it).
Sure, with many objects you end up with a large amount of dynamic
casts and if-blocks, but the alternative is not any less laborious. One
could even argue that the alternative is less readable because the
actual functionality is dispersed and not concisely located at one
place. It all depends on the actual situation, I suppose.
|
|
0
|
|
|
|
Reply
|
nospam270 (2853)
|
3/7/2008 9:04:47 AM
|
|
Greg Herlihy wrote:
> On Mar 5, 3:05 pm, Andy Champ <no....@nospam.com> wrote:
>> How many times have you written
>>
>> someType* var = dynamic_cast<someType*>(someOtherPtr);
>>
>> and wondered WTH you have to quote the type twice?
>
> The C++ cast operators, including dynamic_cast<>, were deliberately
> designed to have an unwieldy syntax precisely to discourage C++
> programmers from using them.
Do you have any reference to corroborate this?
One could argue that the different casts were made keywords to help
making the code more readable (and make it easier to, for example, find
the places where a certain type of cast is being done).
|
|
0
|
|
|
|
Reply
|
nospam270 (2853)
|
3/7/2008 9:07:35 AM
|
|
Juha Nieminen a �crit :
> Greg Herlihy wrote:
>> On Mar 5, 3:05 pm, Andy Champ <no....@nospam.com> wrote:
>>> How many times have you written
>>>
>>> someType* var = dynamic_cast<someType*>(someOtherPtr);
>>>
>>> and wondered WTH you have to quote the type twice?
>> The C++ cast operators, including dynamic_cast<>, were deliberately
>> designed to have an unwieldy syntax precisely to discourage C++
>> programmers from using them.
>
> Do you have any reference to corroborate this?
I guess it is one of those urban legend.
> One could argue that the different casts were made keywords to help
> making the code more readable (and make it easier to, for example, find
> the places where a certain type of cast is being done).
IMO you are right.
The cast the most likely to be used is the static_cast<> and it is not
used that much: mainly when the implicit cast is dangerous or ambiguous.
The const_cast<> and reinterpret_cast<> can yield UB unless used with
care and dynamic_cast<> is so seldom used that it is surprising to see it.
Michael
|
|
0
|
|
|
|
Reply
|
michael.doubez (922)
|
3/7/2008 11:39:24 AM
|
|
Juha Nieminen <nospam@thanks.invalid> wrote:
> Micah Cowan wrote:
> > Andy Champ <no.way@nospam.com> writes:
> > > Daniel T. wrote:
> > > > Andy Champ <no.way@nospam.com> wrote:
> > > >
> > > > > How many times have you written
> > > > >
> > > > > someType* var = dynamic_cast<someType*>(someOtherPtr);
> > > >
> > > > In professional code? Never.
> > >
> > > Why not?
> >
> > Because it pretty much always indicates a design flaw.
> > Polymorphism is the preferred idiom.
>
> I think there are a few cases where trying to avoid dynamic_cast
> at all costs becomes counter-productive, and the code would only
> get uglier.
>
> For example, let's assume you have [snipped example of poor
> design containing a "manager".]
>
> The other alternative is to use [snipped example of the Acyclic
> Visitor pattern (http://www.objectmentor.com/resources/articles/acv.pdf)
> to get around the poor design.]
>
> Ugly? Maybe. But IMO less ugly, and especially less laborious
> than the first option.
Agreed. If those were our only two options, then you are 100% correct.
Fortunately, there are other options. This is exactly why OO books warn
against "manager" objects.
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/7/2008 11:50:57 AM
|
|
Daniel T. wrote:
> Agreed. If those were our only two options, then you are 100% correct.
> Fortunately, there are other options.
Thanks for not giving even a hint of these other options and were to
find more info about them.
|
|
0
|
|
|
|
Reply
|
nospam270 (2853)
|
3/7/2008 3:37:10 PM
|
|
On Mar 7, 10:37=A0am, Juha Nieminen <nos...@thanks.invalid> wrote:
> Daniel T. wrote:
> > Agreed. If those were our only two options, then you are 100% correct.
> > Fortunately, there are other options.
>
> Thanks for not giving even a hint of these other options and were to
> find more info about them.
You didn't provide a requirements document so I can't give any hint of
what the hundereds of other design possibilities might be.
Instead you presented one spicific design that had a spicific problem,
and showed how dynamic_cast can fix the problem. I'm simply saying
that if you don't design your code like that in the first place, you
won't have the problem presented, and therefore won't need
dynamic_cast to fix it.
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/7/2008 4:48:52 PM
|
|
Michael DOUBEZ wrote:
> Juha Nieminen a �crit :
>> Greg Herlihy wrote:
>>> On Mar 5, 3:05 pm, Andy Champ <no....@nospam.com> wrote:
>>>> How many times have you written
>>>>
>>>> someType* var = dynamic_cast<someType*>(someOtherPtr);
>>>>
>>>> and wondered WTH you have to quote the type twice?
>>> The C++ cast operators, including dynamic_cast<>, were deliberately
>>> designed to have an unwieldy syntax precisely to discourage C++
>>> programmers from using them.
>>
>> Do you have any reference to corroborate this?
>
> I guess it is one of those urban legend.
See Design & Evolution, or Stroustrup's FAQ.
http://www.research.att.com/~bs/bs_faq2.html#static-cast
"An ugly operation should have an ugly syntactic form."
>> One could argue that the different casts were made keywords to help
>> making the code more readable (and make it easier to, for example, find
>> the places where a certain type of cast is being done).
>
> IMO you are right.
>
> The cast the most likely to be used is the static_cast<> and it is not
> used that much: mainly when the implicit cast is dangerous or ambiguous.
>
> The const_cast<> and reinterpret_cast<> can yield UB unless used with
> care and dynamic_cast<> is so seldom used that it is surprising to see it.
I see dynamic_cast all the time. The ensuing conversation between me
and the original coder usually is something like this:
<conversation>
Coder: "I'm dynamically allocating a sequence of objects, each of which
may be of any of ten different types D0-D9, all implementing a common
run-time interface B (base class with virtual methods). I allocate the
objects with operator new, and store pointers to them in a monolothic
collection of pointers-to-B. If I later need all the objects of type
D1, I walk the list and dynamic cast every single element to find out
whether it's of type D1."
Me: "Why not store ten different collections for D0-D9? They would be
statically type-safe."
Coder: "Usually, I have to work with all the objects, and I don't want
to have to traverse ten different collections."
Me (ashamed to be encouraging premature optimization): "Dynamic cast is
slow."
Coder: "Ten lists have more overhead than one list. It's a trade-off."
Me: "You're not letting the compiler help you."
Coder: "I don't need the compiler's help here. The run-time environment
does what I need. Why should I go out of my way to get the compiler's
approval? I've been doing this for ## years. I know what I'm doing."
Me: (Thinks about explaining the manifold potential safety and
performance blessings that come from moving computation to compile-time,
and danger of violating an excellent rule of thumb for no good reason.
Decides it's not worth further irritating Coder.) OK, thanks very much
for going over that with me. I appreciate your time.
</conversation>
In the end, I just request that there be documented APIs between
different parts of the code, so that I will have a decent shot at
figuring out who's code is causing the bugs I know we'll be seeing soon.
Even that is an up-hill battle, but modularity is lower-hanging fruit
than compile-time safety. I think programmers just continue to believe
whatever they were told by their Computer Science professors, without
ever really thinking for themselves; "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. The Python
community in particular seems to believe static typing is about as
obsolete as magnetic core memory. It's depressing.
|
|
0
|
|
|
|
Reply
|
jeff34 (1594)
|
3/7/2008 5:39:58 PM
|
|
On Mar 6, 3:23=A0pm, Andy Champ <no....@nospam.com> wrote:
> Greg Herlihy wrote:
>
> =A0> The C++ cast operators, including dynamic_cast<>, were deliberately
> =A0> designed to have an unwieldy syntax precisely to discourage C++
> =A0> programmers from using them. Why, after all, does a program need to
> =A0> perform a downcast to someType? Either "SomeOtherPtr" should have bee=
n
> =A0> a pointer to a someType object all along - or class-specific behavior=
> =A0> that the client needs should have been modeled inside the class
> =A0> implementation itself.
> =A0>
> =A0> Greg
>
> I'd love to see a reference on that. =A0I'm guessing it was just easier
> for the compiler designers not to have to affect the right side with the
> object on the left side of the operator.
For a reference, please see this Bjarne Stroustrup post to comp.std.c+
+ (and which also contains a link to his C++ faq):
http://groups.google.com/group/comp.std.c++/msg/b8974d7d5f71ca5a
Greg
|
|
0
|
|
|
|
Reply
|
greghe3 (115)
|
3/7/2008 7:02:21 PM
|
|
Daniel T. wrote:
> Andy Champ <no.way@nospam.com> wrote:
>> Daniel T. wrote:
>>> Andy Champ <no.way@nospam.com> wrote:
>>>
>>>> How many times have you written
>>>>
>>>> someType* var = dynamic_cast<someType*>(someOtherPtr);
>>> In professional code? Never.
>> Why not?
>
> Because I program using OO idioms, not representational ones. Or to put
> it another way, I specifically design my programs such that I don't need
> to use dynamic_cast... even occasionally.
There are occasions when the interface forces your hand. For example
when implementing the W3C Document Object Model, where all of the
container types are collections of the the base object (Node). Node is
seldom used, most containers end up storing derived objects (Elements or
Attributes) that extend the functionality of Node.
--
Ian Collins.
|
|
0
|
|
|
|
Reply
|
ian-news (9881)
|
3/7/2008 7:58:33 PM
|
|
On Mar 7, 2:58=A0pm, Ian Collins <ian-n...@hotmail.com> wrote:
> Daniel T. wrote:
>
> > Because I program using OO idioms, not representational ones. Or to put
> > it another way, I specifically design my programs such that I don't need=
> > to use dynamic_cast... even occasionally.
>
> There are occasions when the interface forces your hand. =A0For example
> when implementing the W3C Document Object Model, where all of the
> container types are collections of the the base object (Node). =A0Node is
> seldom used, most containers end up storing derived objects (Elements or
> Attributes) that extend the functionality of Node.
All I can say to that is that I have never had my hand forced. :-) The
question with the above is, do you know statically (i.e., at compile
time) the types of the objects? Even with generic containers like you
mention, if you only put in objects of type X, then you know that all
objects in the container are of type X.
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/7/2008 8:24:14 PM
|
|
Daniel T. wrote:
> On Mar 7, 2:58 pm, Ian Collins <ian-n...@hotmail.com> wrote:
>> Daniel T. wrote:
>>
>>> Because I program using OO idioms, not representational ones. Or to put
>>> it another way, I specifically design my programs such that I don't need
>>> to use dynamic_cast... even occasionally.
>> There are occasions when the interface forces your hand. For example
>> when implementing the W3C Document Object Model, where all of the
>> container types are collections of the the base object (Node). Node is
>> seldom used, most containers end up storing derived objects (Elements or
>> Attributes) that extend the functionality of Node.
>
> All I can say to that is that I have never had my hand forced. :-) The
> question with the above is, do you know statically (i.e., at compile
> time) the types of the objects? Even with generic containers like you
> mention, if you only put in objects of type X, then you know that all
> objects in the container are of type X.
No, that's the nasty bit.
Say you have an element type with an attribute you want to use (the link
in an XHTML anchor element for instance) and you wish to process all of
these elements in a document. The DOM interface provides a means of
extracting a list of them by name, but that list is a list of Nodes and
Node doesn't even have attributes!
I prefer to be able to write something like
dom::NodeList anchors(document.getElementsByTagName("A"));
html::Anchor a(anchors[n]);
and let the library do the conversion from Node to Anchor under the
hood. One benefit of using dynamic_cast is the conversion will fail if
the Node isn't the expected type.
--
Ian Collins.
|
|
0
|
|
|
|
Reply
|
ian-news (9881)
|
3/7/2008 8:40:55 PM
|
|
On Mar 7, 3:40=A0pm, Ian Collins <ian-n...@hotmail.com> wrote:
> Daniel T. wrote:
> > On Mar 7, 2:58 pm, Ian Collins <ian-n...@hotmail.com> wrote:
> >> Daniel T. wrote:
>
> >>> Because I program using OO idioms, not representational ones. Or to pu=
t
> >>> it another way, I specifically design my programs such that I don't ne=
ed
> >>> to use dynamic_cast... even occasionally.
> >> There are occasions when the interface forces your hand. =A0For example=
> >> when implementing the W3C Document Object Model, where all of the
> >> container types are collections of the the base object (Node). =A0Node =
is
> >> seldom used, most containers end up storing derived objects (Elements o=
r
> >> Attributes) that extend the functionality of Node.
>
> > All I can say to that is that I have never had my hand forced. :-) The
> > question with the above is, do you know statically (i.e., at compile
> > time) the types of the objects? Even with generic containers like you
> > mention, if you only put in objects of type X, then you know that all
> > objects in the container are of type X.
>
> No, that's the nasty bit.
>
> Say you have an element type with an attribute you want to use (the link
> in an XHTML anchor element for instance) and you wish to process all of
> these elements in a document. =A0The DOM interface provides a means of
> extracting a list of them by name, but that list is a list of Nodes and
> Node doesn't even have attributes!
>
> I prefer to be able to write something like
>
> dom::NodeList anchors(document.getElementsByTagName("A"));
>
> html::Anchor a(anchors[n]);
>
> and let the library do the conversion from Node to Anchor under the
> hood. =A0One benefit of using dynamic_cast is the conversion will fail if
> the Node isn't the expected type.
I will be happy to grant that if you are coding in a representational
style instead of Object Oriented, you may very well have to use
dynamic_cast. I don't code that way, nor do any of the libraries I
use.
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/7/2008 9:50:08 PM
|
|
Jeff Schwab wrote:
> [...]
> <conversation>
> Coder: "I'm dynamically allocating a sequence of objects, each of which
> may be of any of ten different types D0-D9, all implementing a common
> run-time interface B (base class with virtual methods). I allocate the
> objects with operator new, and store pointers to them in a monolothic
> collection of pointers-to-B. If I later need all the objects of type
> D1, I walk the list and dynamic cast every single element to find out
> whether it's of type D1."
:-o
That being said, I wonder if there is a technical reason why const static
virtual member variables are not allowed. For example, this does not
compile:
class base {
public:
int i;
virtual static const enum types {
type_base = 0, type_1, type_2, type_3, type_n
} id = type_base;
};
class sub1 : public base {
static const types type = type_1;
};
But why, I ask you, should the vtable only contain method pointers? The one
problem I see in syntactical: Is the following a pure virtual member or a
member initialised to 0:
class c { virtual static const int i = 0; };
Only half serious,
Paul
|
|
0
|
|
|
|
Reply
|
paul.brettschneider (105)
|
3/7/2008 9:52:09 PM
|
|
Daniel T. wrote:
> On Mar 7, 3:40 pm, Ian Collins <ian-n...@hotmail.com> wrote:
>>> On Mar 7, 2:58 pm, Ian Collins <ian-n...@hotmail.com> wrote:
>>>> There are occasions when the interface forces your hand. For example
>>>> when implementing the W3C Document Object Model, where all of the
>>>> container types are collections of the the base object (Node). Node is
>>>> seldom used, most containers end up storing derived objects (Elements or
>>>> Attributes) that extend the functionality of Node.
>>
>> Say you have an element type with an attribute you want to use (the link
>> in an XHTML anchor element for instance) and you wish to process all of
>> these elements in a document. The DOM interface provides a means of
>> extracting a list of them by name, but that list is a list of Nodes and
>> Node doesn't even have attributes!
>>
>> I prefer to be able to write something like
>>
>> dom::NodeList anchors(document.getElementsByTagName("A"));
>>
>> html::Anchor a(anchors[n]);
>>
>> and let the library do the conversion from Node to Anchor under the
>> hood. One benefit of using dynamic_cast is the conversion will fail if
>> the Node isn't the expected type.
>
> I will be happy to grant that if you are coding in a representational
> style instead of Object Oriented, you may very well have to use
> dynamic_cast. I don't code that way, nor do any of the libraries I
> use.
OK, given an interface with the restrictions I mentioned above, how
would you code it in an "OO" style?
--
Ian Collins.
|
|
0
|
|
|
|
Reply
|
ian-news (9881)
|
3/7/2008 9:57:34 PM
|
|
Daniel T. wrote:
> On Mar 7, 10:37 am, Juha Nieminen <nos...@thanks.invalid> wrote:
>> Daniel T. wrote:
>
>>> Agreed. If those were our only two options, then you are 100% correct.
>>> Fortunately, there are other options.
>> Thanks for not giving even a hint of these other options and were to
>> find more info about them.
>
> You didn't provide a requirements document so I can't give any hint of
> what the hundereds of other design possibilities might be.
>
> Instead you presented one spicific design that had a spicific problem,
> and showed how dynamic_cast can fix the problem. I'm simply saying
> that if you don't design your code like that in the first place, you
> won't have the problem presented, and therefore won't need
> dynamic_cast to fix it.
Daniel,
if the base class does not have an interface that exhibits the enhanced
behaviour, and if the base class is not under your control (hence cannot
be enhanced to add the new interface) you have no choice but to use
dynamic_cast to determine whether your object exhibits the behaviour you
require.
I'd be interested to know how many projects you have worked on that
you've never had to break the ideal design of the language, just to get
something done.
Andy
|
|
0
|
|
|
|
Reply
|
no.way7055 (86)
|
3/7/2008 11:11:10 PM
|
|
Ian Collins <ian-news@hotmail.com> wrote:
> Daniel T. wrote:
> > On Mar 7, 3:40 pm, Ian Collins <ian-n...@hotmail.com> wrote:
> >>> On Mar 7, 2:58 pm, Ian Collins <ian-n...@hotmail.com> wrote:
>
> >>>> There are occasions when the interface forces your hand. For example
> >>>> when implementing the W3C Document Object Model, where all of the
> >>>> container types are collections of the the base object (Node). Node is
> >>>> seldom used, most containers end up storing derived objects (Elements or
> >>>> Attributes) that extend the functionality of Node.
>
> >>
> >> Say you have an element type with an attribute you want to use (the link
> >> in an XHTML anchor element for instance) and you wish to process all of
> >> these elements in a document. The DOM interface provides a means of
> >> extracting a list of them by name, but that list is a list of Nodes and
> >> Node doesn't even have attributes!
> >>
> >> I prefer to be able to write something like
> >>
> >> dom::NodeList anchors(document.getElementsByTagName("A"));
> >>
> >> html::Anchor a(anchors[n]);
> >>
> >> and let the library do the conversion from Node to Anchor under the
> >> hood. One benefit of using dynamic_cast is the conversion will fail if
> >> the Node isn't the expected type.
> >
> > I will be happy to grant that if you are coding in a representational
> > style instead of Object Oriented, you may very well have to use
> > dynamic_cast. I don't code that way, nor do any of the libraries I
> > use.
>
> OK, given an interface with the restrictions I mentioned above, how
> would you code it in an "OO" style?
If I was forced to use an interface with the restrictions you mention,
then I couldn't code in an OO style. So I'm not sure how to answer your
question.
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/7/2008 11:39:18 PM
|
|
Daniel T. wrote:
> Ian Collins <ian-news@hotmail.com> wrote:
>> OK, given an interface with the restrictions I mentioned above, how
>> would you code it in an "OO" style?
>
> If I was forced to use an interface with the restrictions you mention,
> then I couldn't code in an OO style. So I'm not sure how to answer your
> question.
So your original contention
"Because I program using OO idioms, not representational ones. Or to put
it another way, I specifically design my programs such that I don't need
to use dynamic_cast... even occasionally."
Doesn't hold water?
--
Ian Collins.
|
|
0
|
|
|
|
Reply
|
ian-news (9881)
|
3/7/2008 11:43:26 PM
|
|
Andy Champ <no.way@nospam.com> wrote:
> Daniel T. wrote:
> > Juha Nieminen <nos...@thanks.invalid> wrote:
> > > Daniel T. wrote:
> >
> > > > Agreed. If those were our only two options, then you are 100%
> > > > correct. Fortunately, there are other options.
> > >
> > > Thanks for not giving even a hint of these other options and
> > > were to find more info about them.
> >
> > You didn't provide a requirements document so I can't give any
> > hint of what the hundereds of other design possibilities might
> > be.
> >
> > Instead you presented one spicific design that had a spicific
> > problem, and showed how dynamic_cast can fix the problem. I'm
> > simply saying that if you don't design your code like that in the
> > first place, you won't have the problem presented, and therefore
> > won't need dynamic_cast to fix it.
>
> if the base class does not have an interface that exhibits the
> enhanced behaviour, and if the base class is not under your control
> (hence cannot be enhanced to add the new interface) you have no
> choice but to use dynamic_cast to determine whether your object
> exhibits the behaviour you require.
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.
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'd be interested to know how many projects you have worked on that
> you've never had to break the ideal design of the language, just to
> get something done.
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*! :-)
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. :-)
If you feel you absolutely must use dynamic_cast in a particular
situation, by all means do so. The OP asked how many times have I used
it, and the answer is never in professional code. I, of course, have
used it when helping others learn its mechanics, toy programs and
tutorials...
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/8/2008 12:19:54 AM
|
|
Good discussion. I have a perhaps naive question directed to any and
all in this thread. If you have to work with a hierarchy of classes
that extend the base with their own methods, would it be reasonable to
create a parallel adapter hierarchy with no-op methods on classes that
have no "real" implementation of the individual leaf methods? Or does
this just mask the original design problem with a fat interface?
|
|
0
|
|
|
|
Reply
|
dave_mikesell (189)
|
3/8/2008 12:28:25 AM
|
|
Ian Collins <ian-news@hotmail.com> wrote:
> Daniel T. wrote:
> > Ian Collins <ian-news@hotmail.com> wrote:
>
> > > OK, given an interface with the restrictions I mentioned above,
> > > how would you code it in an "OO" style?
> >
> > If I was forced to use an interface with the restrictions you
> > mention, then I couldn't code in an OO style. So I'm not sure how
> > to answer your question.
>
> So your original contention
>
> "Because I program using OO idioms, not representational ones. Or
> to put it another way, I specifically design my programs such that
> I don't need to use dynamic_cast... even occasionally."
>
> Doesn't hold water?
Of course it does, it's completely true. I don't design interfaces with
the restrictions you mentioned, and I don't use interfaces with such
restrictions. I program using OO idioms, not representational ones. I
design my programs such that I don't need to use dynamic_cast.
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/8/2008 12:31:44 AM
|
|
Jeff Schwab wrote:
> Me: "Why not store ten different collections for D0-D9? They would be
> statically type-safe."
What if the class which handles the collection is in a library which
you cannot modify, or which you can but doing so is very laborious (more
laborious than simply using dynamic_cast)?
What if the different objects need to be in a certain order inside the
collection? (For example, assume that you need to perform operations
like "from all the objects in the collection which fulfill certain
conditions, give me the one which is closest to the end of the
collection.") How would you manage this if you had 10 collections
instead of one?
|
|
0
|
|
|
|
Reply
|
nospam270 (2853)
|
3/8/2008 12:37:06 AM
|
|
dave_mikesell@fastmail.fm wrote:
> Good discussion. I have a perhaps naive question directed to any and
> all in this thread. If you have to work with a hierarchy of classes
> that extend the base with their own methods, would it be reasonable to
> create a parallel adapter hierarchy with no-op methods on classes that
> have no "real" implementation of the individual leaf methods? Or does
> this just mask the original design problem with a fat interface?
If I understand you right, you are describing the visitor pattern. I
don't use it either.
Virtual no-op member-functions, if they are not used by the class they
are defined in, are also IMHO indicative of poor design. If they *are*
used by the class they are defined in (i.e., the template method
pattern,) then they are useful and generally a good idea.
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/8/2008 12:54:32 AM
|
|
Juha Nieminen <nospam@thanks.invalid> wrote:
> Jeff Schwab wrote:
> > Me: "Why not store ten different collections for D0-D9? They
> > would be statically type-safe."
>
> What if [you are forced to work with classes that are designed
> poorly?]
That's what dynamic_cast is there for.
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/8/2008 1:00:32 AM
|
|
Daniel T. wrote:
> Ian Collins <ian-news@hotmail.com> wrote:
>> Daniel T. wrote:
>>> Ian Collins <ian-news@hotmail.com> wrote:
>>>> OK, given an interface with the restrictions I mentioned above,
>>>> how would you code it in an "OO" style?
>>> If I was forced to use an interface with the restrictions you
>>> mention, then I couldn't code in an OO style. So I'm not sure how
>>> to answer your question.
>> So your original contention
>>
>> "Because I program using OO idioms, not representational ones. Or
>> to put it another way, I specifically design my programs such that
>> I don't need to use dynamic_cast... even occasionally."
>>
>> Doesn't hold water?
>
> Of course it does, it's completely true. I don't design interfaces with
> the restrictions you mentioned, and I don't use interfaces with such
> restrictions. I program using OO idioms, not representational ones. I
> design my programs such that I don't need to use dynamic_cast.
Luck boy. Unfortunately I have clients who sometimes ask me to
implement an interface that does not fit well with OO idioms.
One of these days I might have your luxury of picking and choosing
projects based on idioms....
--
Ian Collins.
|
|
0
|
|
|
|
Reply
|
ian-news (9881)
|
3/8/2008 1:22:51 AM
|
|
Ian Collins <ian-news@hotmail.com> wrote:
> Luck boy. Unfortunately I have clients who sometimes ask me to
> implement an interface that does not fit well with OO idioms.
>
> One of these days I might have your luxury of picking and choosing
> projects based on idioms....
I don't pick the projects, I pick the design. Do you write a lot of
applications that use databases? I have heard that dynamic_cast is much
more prevalent in those types of programs.
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/8/2008 1:52:57 AM
|
|
Daniel T. wrote:
> Ian Collins <ian-news@hotmail.com> wrote:
>
>> Luck boy. Unfortunately I have clients who sometimes ask me to
>> implement an interface that does not fit well with OO idioms.
>>
>> One of these days I might have your luxury of picking and choosing
>> projects based on idioms....
>
> I don't pick the projects, I pick the design. Do you write a lot of
> applications that use databases? I have heard that dynamic_cast is much
> more prevalent in those types of programs.
Yes, frequently. I haven't had to resort to dynamic_cast in that domain.
A quick search of my active code base reveals two instances of
dynamic_cast, const and non-const conversion operations in my DOM code!
--
Ian Collins.
|
|
0
|
|
|
|
Reply
|
ian-news (9881)
|
3/8/2008 2:08:37 AM
|
|
On Mar 7, 1:04=A0am, Juha Nieminen <nos...@thanks.invalid> wrote:
> =A0 The other alternative is to use the callback mechanism and
> dynamic_cast, for example like this:
>
> void MyClass::doSomethingToPrimitive(Primitive* p)
> {
> =A0 =A0 Circle* c =3D dynamic_cast<Circle*>(p);
> =A0 =A0 if(c)
> =A0 =A0 {
> =A0 =A0 =A0 =A0 // Do something to the circle
> =A0 =A0 }
>
> }
>
> =A0 Ugly? Maybe. But IMO less ugly, and especially less laborious than the=
first option.
Does the callback really care whether the class of the object (or one
of its base classes) is named "Circle" - or is the callback code more
interested in whether the shape represented by the object - is a
circle (or at least some shape with "circle-like" qualities)?
Almost certainly, the name of the object's class type is not the real
test being applied in the above example. It is easy to image that a
French C++ programmer might decide to name the very same type
differently - "Cercle" perhaps. So in reality, the name of the class
is acting as a kind of proxy for some other aspect or combination of
aspects embodied by the type.
So one of the drawbacks with using dynamic_cast<> and the class name
as a proxy for the properties that actually matter to the call back
routine, is that the dynammic_cast leaves us with little idea what it
is about a circle exactly - that sets it apart from other primitive
types as far as this callback routine is concerned. Whereas, if the
dynamic cast<> were replaced by any of the following member function
calls:
if (p->getNumberOfSides() =3D=3D 1)
..
if (p->isRound())
or even:
if (p->getShapeType() =3D=3D kCircleShapeType)
we would have a much better idea what the callback routine is actually
doing with these primitive types. And knowing what a routine is trying
to do goes a long way toward making that routine easy-to-maintain.
Now, calling getShapeType() in the last example might appear to be the
"moral equivalent" to a dynamic_cast<Circle*>. Certainly, the test
being performed is inelegant. Nonetheless, calling a member function
to test for circle types is nonetheless a marked improvement over
calling dynamic_cast<> to do the same.
For one, calling getShapeType() is likely to be much more efficient
than calling dynamic_cast<> (in fact getShapeType() does not
necessarily even have to be a virtual method). Moreover, by using the
class's own interface to distinguish between different types of
objects, we now have a much better understanding of the criteria being
applied. We can conclude from the test for circle type object that the
client requires that the shape not merely be round or one-sided - but
that the object's class type must represent a perfect circle.
Greg
|
|
0
|
|
|
|
Reply
|
greghe3 (115)
|
3/8/2008 5:43:45 AM
|
|
Andy Champ wrote:
> Daniel T. wrote:
>> On Mar 7, 10:37 am, Juha Nieminen <nos...@thanks.invalid> wrote:
>>> Daniel T. wrote:
>>
>>>> Agreed. If those were our only two options, then you are 100%
>>>> correct. Fortunately, there are other options.
>>> Thanks for not giving even a hint of these other options and were
>>> to find more info about them.
>>
>> You didn't provide a requirements document so I can't give any
>> hint of what the hundereds of other design possibilities might be.
>>
>> Instead you presented one spicific design that had a spicific
>> problem, and showed how dynamic_cast can fix the problem. I'm
>> simply saying that if you don't design your code like that in the
>> first place, you won't have the problem presented, and therefore
>> won't need dynamic_cast to fix it.
>
> Daniel,
>
> if the base class does not have an interface that exhibits the
> enhanced behaviour, and if the base class is not under your control
> (hence cannot be enhanced to add the new interface) you have no
> choice but to use dynamic_cast to determine whether your object
> exhibits the behaviour you require.
>
So you mean that if dynamic_cast was really, really hard to use, you
would just have had go back and demand a proper redesign of the base
class?
Then making dynamic_cast easier to use is perhaps not the best
solution. :-)
Bo Persson
|
|
0
|
|
|
|
Reply
|
bop (1069)
|
3/8/2008 9:32:18 AM
|
|
On 8 mar, 00:11, Andy Champ <no....@nospam.com> wrote:
> Daniel T. wrote:
> > On Mar 7, 10:37 am, Juha Nieminen <nos...@thanks.invalid> wrote:
> >> Daniel T. wrote:
> >>> Agreed. If those were our only two options, then you are 100% correct.=
> >>> Fortunately, there are other options.
> >> Thanks for not giving even a hint of these other options
> >> and were to find more info about them.
> > You didn't provide a requirements document so I can't give
> > any hint of what the hundereds of other design possibilities
> > might be.
> > Instead you presented one spicific design that had a
> > spicific problem, and showed how dynamic_cast can fix the
> > problem. I'm simply saying that if you don't design your
> > code like that in the first place, you won't have the
> > problem presented, and therefore won't need dynamic_cast to
> > fix it.
> if the base class does not have an interface that exhibits the
> enhanced behaviour, and if the base class is not under your
> control (hence cannot be enhanced to add the new interface)
> you have no choice but to use dynamic_cast to determine
> whether your object exhibits the behaviour you require.
If if the base class is under your control, the extended
interface may not logically belong there. Dynamic_cast has its
place in good OO design; it just shouldn't be abused.
--
James Kanze (GABI Software) email:james.kanze@gmail.com
Conseils en informatique orient=E9e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S=E9mard, 78210 St.-Cyr-l'=C9cole, France, +33 (0)1 30 23 00 34
|
|
0
|
|
|
|
Reply
|
james.kanze (9594)
|
3/8/2008 10:08:22 AM
|
|
On 8 mar, 00:39, "Daniel T." <danie...@earthlink.net> wrote:
> Ian Collins <ian-n...@hotmail.com> wrote:
> > Daniel T. wrote:
> > > On Mar 7, 3:40 pm, Ian Collins <ian-n...@hotmail.com> wrote:
> > >>> On Mar 7, 2:58 pm, Ian Collins <ian-n...@hotmail.com> wrote:
> > >>>> There are occasions when the interface forces your
> > >>>> hand. For example when implementing the W3C Document
> > >>>> Object Model, where all of the container types are
> > >>>> collections of the the base object (Node). Node is
> > >>>> seldom used, most containers end up storing derived
> > >>>> objects (Elements or Attributes) that extend the
> > >>>> functionality of Node.
> > >> Say you have an element type with an attribute you want
> > >> to use (the link in an XHTML anchor element for instance)
> > >> and you wish to process all of these elements in a
> > >> document. The DOM interface provides a means of
> > >> extracting a list of them by name, but that list is a
> > >> list of Nodes and Node doesn't even have attributes!
> > >> I prefer to be able to write something like
> > >> dom::NodeList anchors(document.getElementsByTagName("A"));
> > >> html::Anchor a(anchors[n]);
> > >> and let the library do the conversion from Node to Anchor
> > >> under the hood. One benefit of using dynamic_cast is the
> > >> conversion will fail if the Node isn't the expected type.
> > > I will be happy to grant that if you are coding in a
> > > representational style instead of Object Oriented, you may
> > > very well have to use dynamic_cast. I don't code that way,
> > > nor do any of the libraries I use.
> > OK, given an interface with the restrictions I mentioned
> > above, how would you code it in an "OO" style?
> If I was forced to use an interface with the restrictions you
> mention, then I couldn't code in an OO style. So I'm not sure
> how to answer your question.
So how would you design a DOM interface in your definition of OO
style?
The basic (abstract) problem that Ian has raised is that of a
heterogeneous collection of objects. It's a frequent problem,
and it's a typical example of a case where dynamic_cast is a
good solution. You basically have the choice between moving all
of the possible derived class interfaces down to the base class,
or using dynamic_cast to get at them. I find the latter
preferable (and more OO) than the former.
Note that in general, what I would avoid is dynamic_cast to a
concrete type (although there too, there might be exceptions,
e.g. when implementing a surrogate for multiple dispatch).
Generally, if I need an extended interface, I define an extended
interface (using virtual inheritance from the base interface)
with the added functionality. Concrete classes then derive from
the extended interface (or from several extended interfaces, if
they offer several extensions). Users interogate whether an
instance supports the extension by using dynamic_cast to the
extended interface.
--
James Kanze (GABI Software) email:james.kanze@gmail.com
Conseils en informatique orient=E9e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S=E9mard, 78210 St.-Cyr-l'=C9cole, France, +33 (0)1 30 23 00 34
|
|
0
|
|
|
|
Reply
|
james.kanze (9594)
|
3/8/2008 10:21:09 AM
|
|
Daniel T. wrote:
> Juha Nieminen <nospam@thanks.invalid> wrote:
>> Jeff Schwab wrote:
>
>>> Me: "Why not store ten different collections for D0-D9? They
>>> would be statically type-safe."
>> What if [you are forced to work with classes that are designed
>> poorly?]
>
> That's what dynamic_cast is there for.
How is it a poor design that a collection has object in a certain
order (in a context where this order is relevant)?
One good example of a collection where order is relevant is a
collection of screen elements: The order of these screen elements in the
collection is the order in which they are drawn on screen, which
determines which elements are drawn on top of the others.
I can't even begin to imagine how you could easily determine the
drawing order of screen primitives if each primitive was in a
type-specific container. You would need an additional container which
contains the pointers in the order in which the primitives should be
drawn, duplicating the amount of data stored and making the whole system
quite a lot more complicated to manage (for example swapping the drawing
order of two given primitives becomes needlessly complicated).
|
|
0
|
|
|
|
Reply
|
nospam270 (2853)
|
3/8/2008 10:40:20 AM
|
|
Juha Nieminen <nospam@thanks.invalid> wrote:
> Daniel T. wrote:
> > Juha Nieminen <nospam@thanks.invalid> wrote:
> > > Jeff Schwab wrote:
> >
> > > What if [you are forced to work with classes that are designed
> > > poorly?]
> >
> > That's what dynamic_cast is there for.
>
> How is it a poor design that a collection has object in a certain
> order (in a context where this order is relevant)?
Why would you even ask the question? Nobody has claimed that the above
was poor design.
> One good example of a collection where order is relevant is a
> collection of screen elements: The order of these screen elements
> in the collection is the order in which they are drawn on screen,
> which determines which elements are drawn on top of the others.
>
> I can't even begin to imagine how you could easily determine the
> drawing order of screen primitives if each primitive was in a
> type-specific container. You would need an additional container
> which contains the pointers in the order in which the primitives
> should be drawn, duplicating the amount of data stored and making
> the whole system quite a lot more complicated to manage (for
> example swapping the drawing order of two given primitives becomes
> needlessly complicated).
Since I work with such issues on a regular basis, and I never use
dynamic_cast, let me assure you that you can keep track of drawing order
without throwing away type information. How you go about doing it
depends on the design goals.
Trying to go backwards along an inheritance arrow is as poor design as
trying to go backwards along an association arrow. The only difference
is that with the former, the language provides a primitive to allow it,
while with the latter, it doesn't. Just because the primitive is
provided, doesn't mean it should be used willy nilly.
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/8/2008 2:18:02 PM
|
|
Ian Collins <ian-news@hotmail.com> wrote:
> Daniel T. wrote:
>
> > Do you write a lot of applications that use databases? I have
> > heard that dynamic_cast is much more prevalent in those types of
> > programs.
>
> Yes, frequently. I haven't had to resort to dynamic_cast in that
> domain.
>
> A quick search of my active code base reveals two instances of
> dynamic_cast, const and non-const conversion operations in my DOM
> code!
It would be fun to sit down with you and see if we can't connect the
code that creates the objects to the code that uses the objects, then
remove those two dynamic_casts.
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/8/2008 2:34:56 PM
|
|
James Kanze <james.kanze@gmail.com> wrote:
>
> The basic (abstract) problem that Ian has raised is that of a
> heterogeneous collection of objects. It's a frequent problem,
> and it's a typical example of a case where dynamic_cast is a
> good solution.
The basic problem that he raised is that the type information is being
thrown away when it is still needed.
Now, maybe there really are situations where you have no choice and must
throw away type information. All I can say is that I've never had to
face such a situation.
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/8/2008 2:52:21 PM
|
|
On Mar 8, 4:32=A0am, "Bo Persson" <b...@gmb.dk> wrote:
> Andy Champ wrote:
> > if the base class does not have an interface that exhibits the
> > enhanced behaviour, and if the base class is not under your control
> > (hence cannot be enhanced to add the new interface) you have no
> > choice but to use dynamic_cast to determine whether your object
> > exhibits the behaviour you require.
>
> So you mean that if dynamic_cast was really, really hard to use, you
> would just have had go back and demand a proper redesign of the base
> class?
Good point. Also, what if dynamic_cast didn't exist? Note that we have
no language primitive way of knowing who the caller of a function is
(i.e., we can't backtrack along an association arrow,) what if we
couldn't backtrack along an inheritance arrow either? How would that
base class, and all the code that uses it, be designed then?
I doubt that Andy would claim that those programs that use
dynamic_cast simply could not be written without it. After all, the
lack of dynamic_cast would not destroy the turing completeness of the
language.
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/8/2008 5:47:38 PM
|
|
On Mar 8, 5:08=A0am, James Kanze <james.ka...@gmail.com> wrote:
> Dynamic_cast has its place in good OO design; it just shouldn't be
> abused.
Just like goto? :-)
I don't agree that dynamic_cast has its place in *good* OO design, but
it does have its place.
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/8/2008 5:51:21 PM
|
|
On 8 mar, 18:51, "Daniel T." <danie...@earthlink.net> wrote:
> On Mar 8, 5:08 am, James Kanze <james.ka...@gmail.com> wrote:
> > Dynamic_cast has its place in good OO design; it just
> > shouldn't be abused.
> Just like goto? :-)
No. Goto never has its place.
> I don't agree that dynamic_cast has its place in *good* OO
> design, but it does have its place.
You're obviously using a different definition of OO than I am,
then. Generally speaking, IMHO you should access the object
through defined interfaces; whether they are in the concrete
class, or in an abstract base class, isn't that important in the
absolute, but there will definitely be cases where a concrete
class will want to implement an extended interface. In an ideal
world, in such cases, you wouldn't loose the type, and you'd
always have access to the extended interface. Practically
speaking, however, there are cases where the object will be
passed through generic layers which do loose the type. Being
able to do so is a fundamental part of OO.
Think for a moment about Smalltalk---the granddaddy of OO
languages. Where everything was an Object, and containers only
contained Object. Of course, you didn't need dynamic_cast,
because there was no static typing to begin with. Now, it may
not be OO---in fact, according to most OO proponents, it goes
against OO---but I sort of like static type checking. As long
as I have the flexibility to work around it in cases where the
design forces a loss of the original type.
--
James Kanze (GABI Software) email:james.kanze@gmail.com
Conseils en informatique orient=E9e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S=E9mard, 78210 St.-Cyr-l'=C9cole, France, +33 (0)1 30 23 00 34
|
|
0
|
|
|
|
Reply
|
james.kanze (9594)
|
3/8/2008 8:49:21 PM
|
|
Daniel T. wrote:
> Since I work with such issues on a regular basis, and I never use
> dynamic_cast, let me assure you that you can keep track of drawing order
> without throwing away type information. How you go about doing it
> depends on the design goals.
I'm interested in hearing alternative techniques because I have to do
this as well (as my payjob), and I'm always looking to improve my
library designs.
|
|
0
|
|
|
|
Reply
|
nospam270 (2853)
|
3/8/2008 8:52:06 PM
|
|
On 8 mar, 15:34, "Daniel T." <danie...@earthlink.net> wrote:
> Ian Collins <ian-n...@hotmail.com> wrote:
> > Daniel T. wrote:
> > > Do you write a lot of applications that use databases? I
> > > have heard that dynamic_cast is much more prevalent in
> > > those types of programs.
> > Yes, frequently. I haven't had to resort to dynamic_cast in
> > that domain.
> > A quick search of my active code base reveals two instances
> > of dynamic_cast, const and non-const conversion operations
> > in my DOM code!
> It would be fun to sit down with you and see if we can't
> connect the code that creates the objects to the code that
> uses the objects, then remove those two dynamic_casts.
The question is more: do you want to. Should the code between
the provider and the consumer need to know about all of the
possible interfaces, as long as the provider and the consumer
are in agreement?
And in the end, isn't this the age old argument between static
type checking (a la C++) and dynamic type checking (a la
Smalltalk)? I think most of us here agree that static type
checking provides an additional, very useful measure of
security (and it's worth pointing out that all of the modern OO
languages use static type checking, even though Smalltalk
didn't). On the other hand, that security comes at a loss of
flexibility (true OO). IMHO, dynamic_cast is an almost perfect
way of recovering enough of that flexibility when it's needed,
without any more loss of security than is necessary to achieve
that flexibility---I can't just call any function on an object,
hoping that the object implements it: I must specifically ask
for a different, statically declared interface, in order to
access the additional functions. In other words, in OO in its
"truest" (or at least its original) form, you don't have
dynamic_cast, because the flexibility is built into the
language, because of the absense of static type checking. In
C++ without dynamic_cast, you loose a lot of that flexibility.
In many cases, it doesn't matter, of course, and it's often
preferable to design your code so that it won't. But many cases
isn't all, and there are cases where you need that extra measure
of flexibility.
--
James Kanze (GABI Software) email:james.kanze@gmail.com
Conseils en informatique orient=E9e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S=E9mard, 78210 St.-Cyr-l'=C9cole, France, +33 (0)1 30 23 00 34
|
|
0
|
|
|
|
Reply
|
james.kanze (9594)
|
3/8/2008 8:57:40 PM
|
|
On 8 mar, 15:52, "Daniel T." <danie...@earthlink.net> wrote:
> James Kanze <james.ka...@gmail.com> wrote:
> > The basic (abstract) problem that Ian has raised is that of a
> > heterogeneous collection of objects. It's a frequent problem,
> > and it's a typical example of a case where dynamic_cast is a
> > good solution.
> The basic problem that he raised is that the type information
> is being thrown away when it is still needed.
> Now, maybe there really are situations where you have no
> choice and must throw away type information. All I can say is
> that I've never had to face such a situation.
So you've never used a generic framework?
--
James Kanze (GABI Software) email:james.kanze@gmail.com
Conseils en informatique orient=E9e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S=E9mard, 78210 St.-Cyr-l'=C9cole, France, +33 (0)1 30 23 00 34
|
|
0
|
|
|
|
Reply
|
james.kanze (9594)
|
3/8/2008 8:58:27 PM
|
|
"James Kanze" wrote in message
On 8 mar, 18:51, "Daniel T." wrote:
> On Mar 8, 5:08 am, James Kanze wrote:
> > > Dynamic_cast has its place in good OO design; it just
> > > shouldn't be abused.
> > Just like goto? :-)
> No. Goto never has its place.
Oh?
How, then...? :
(Console app.)
for(;;) {
//no exit condition is programmable.
//live, *vital*, user interaction via keyboard.
//check for a keypress (ch).
switch(ch) {
//case
//case
case: 'q' : goto end;
//case: }
end:
return 0;
}
--
Peace
JB
jb@tetrahedraverse.com
Web: http://tetrahedraverse.com
|
|
0
|
|
|
|
Reply
|
jgbrawley (78)
|
3/8/2008 9:55:54 PM
|
|
James Kanze <james.ka...@gmail.com> wrote:
> "Daniel T." <danie...@earthlink.net> wrote:
>
> > I don't agree that dynamic_cast has its place in *good* OO
> > design, but it does have its place.
>
> You're obviously using a different definition of OO than I am,
> then. Generally speaking, IMHO you should access the object
> through defined interfaces; whether they are in the concrete
> class, or in an abstract base class, isn't that important in the
> absolute, but there will definitely be cases where a concrete
> class will want to implement an extended interface. In an ideal
> world, in such cases, you wouldn't loose the type, and you'd
> always have access to the extended interface.
It sounds like we aren't that far apart after all. Sometimes
dynamic_cast is necessary, but designing your framework so that one
must use dynamic_cast to use it properly... It doesn't sound like you
would approve of such a design.
> Think for a moment about Smalltalk---the granddaddy of OO
> languages.
Even in Smalltalk, sending a message to an object without knowing if
the object will understand the message is frowned upon.
I'm not going to pretend that the matter is settled. There is a
regular debate in comp.object over whether designs that require down-
casting are appropriate, but I think it is fair to say that it isn't
*necessary*, and I happen to believe that it *isn't* appropriate.
Which is why, when the Andy asked how often I use it, I said "never,"
and when he asked why not, I said that I don't use it because I design
my code such that its use isn't necessary.
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/8/2008 10:32:13 PM
|
|
On Mar 8, 3:58=A0pm, James Kanze <james.ka...@gmail.com> wrote:
> On 8 mar, 15:52, "Daniel T." <danie...@earthlink.net> wrote:
> > James Kanze <james.ka...@gmail.com> wrote:
> >
> > > The basic (abstract) problem that Ian has raised is that of a
> > > heterogeneous collection of objects. =A0It's a frequent problem,
> > > and it's a typical example of a case where dynamic_cast is a
> > > good solution.
> >
> > The basic problem that he raised is that the type information
> > is being thrown away when it is still needed.
> > Now, maybe there really are situations where you have no
> > choice and must throw away type information. All I can say is
> > that I've never had to face such a situation.
>
> So you've never used a generic framework?
Of course I have. My company developed a generic framework 10 years
ago (I helped in designing it) which we still use to this day. It's a
little long in the tooth in some parts, but nobody ever has to
dynamic_cast anything.
Without the use of templates, I can think of situations where
dynamic_cast is necessary, but I would still not design a program such
that I didn't know *at compile time* that the cast would succeed.
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/8/2008 10:38:33 PM
|
|
On Mar 8, 3:52 pm, Juha Nieminen <nos...@thanks.invalid> wrote:
> Daniel T. wrote:
>
> > Since I work with [heterogeneous containers] on a regular
> > basis, and I never use dynamic_cast, let me assure you that you
> > can keep track of drawing order without throwing away type
> > information. How you go about doing it depends on the design
> > goals.
>
> I'm interested in hearing alternative techniques because I have to
> do this as well (as my payjob), and I'm always looking to improve my
> library designs.
This conversation should probably migrate over to comp.object at this
point.
Take the example that Ian brought up. You have a heterogeneous
container that is maintaining object order, but you occasionally seem
to need to pull out objects and query their type... Before you put the
object in the container, you knew its type, then you threw it away
even though you needed it in some latter portion of the program. The
key is to not throw that information away. How you pass that
information from the creator to the user depends on the situation.
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/8/2008 10:56:33 PM
|
|
* John Brawley:
>
> How, then...? :
> (Console app.)
>
> for(;;) {
> //no exit condition is programmable.
> //live, *vital*, user interaction via keyboard.
> //check for a keypress (ch).
> switch(ch) {
> //case
> //case
> case: 'q' : goto end;
> //case: }
> end:
> return 0;
> }
for( bool finished = false; !finished; )
{
char const ch = keypress();
switch( ch )
{
case 'q':
{
finished = true;
}
}
}
Another possibility is to use "return" from a function.
Cheers, & hth.
- Alf
--
A: Because it messes up the order in which people normally read text.
Q: Why is it such a bad thing?
A: Top-posting.
Q: What is the most annoying thing on usenet and in e-mail?
|
|
0
|
|
|
|
Reply
|
alfps (7389)
|
3/8/2008 11:26:46 PM
|
|
On Mar 6, 7:05=A0am, Andy Champ <no....@nospam.com> wrote:
> How many times have you written
>
> someType* var =3D dynamic_cast<someType*>(someOtherPtr);
>
> and wondered WTH you have to quote the type twice?
>
> So I built this:
>
> template <class T> class AutoCastProxy
> {
> public:
> =A0 AutoCastProxy(T object): m_Object(object) {};
> =A0 template <class U> operator U()
> =A0 {
> =A0 =A0return dynamic_cast<U>(m_Object);
> =A0 };
> private:
> =A0 T m_Object;
>
> };
>
> template <class T> AutoCastProxy<T> AutoCast(T object)
> {
> =A0 =A0 =A0 =A0 return AutoCastProxy<T>(object);
>
> };
>
> ... which lets you type
>
> someType* var =3D AutoCast(someOtherPtr);
>
> Surely it's been done before? =A0If not:
>
> I hereby place this code in the public domain. =A0I'm not even asking for =
GPL.
>
> Andy
Anyway, I don't think dynamic_cast ugly, on the contrary, It very
useful when to use in polymiorphism. By dynamic_cast we can realize
RTTI.
|
|
0
|
|
|
|
Reply
|
jinggcc (8)
|
3/9/2008 5:54:01 AM
|
|
On 8 mar, 23:32, "Daniel T." <danie...@earthlink.net> wrote:
> James Kanze <james.ka...@gmail.com> wrote:
> > "Daniel T." <danie...@earthlink.net> wrote:
> > > I don't agree that dynamic_cast has its place in *good* OO
> > > design, but it does have its place.
> > You're obviously using a different definition of OO than I am,
> > then. Generally speaking, IMHO you should access the object
> > through defined interfaces; whether they are in the concrete
> > class, or in an abstract base class, isn't that important in the
> > absolute, but there will definitely be cases where a concrete
> > class will want to implement an extended interface. In an ideal
> > world, in such cases, you wouldn't loose the type, and you'd
> > always have access to the extended interface.
> It sounds like we aren't that far apart after all. Sometimes
> dynamic_cast is necessary, but designing your framework so
> that one must use dynamic_cast to use it properly... It
> doesn't sound like you would approve of such a design.
It depends on the context. Making some intermediary aware of
all of the details of what it transporting isn't something I'd
approve of either. (The context was Java Swing, and not C++,
but I've used very generic code for linking up application
specific actions and the buttons in toolbars, etc.)
What is bad, of course, is a long list of:
if ( dynamic_cast< Concrete1* >( p ) ) {
// ...
} else if ( dynamic_cast< Concrete2* >( p ) {
// ...
} else if ( dynamic_cast< Concrete3* >( p ) {
}
I don't find anything particularly bad, however, in the idea
that some concrete types support an extended interface, and that
the ultimate user might do something like:
Extended* px =3D dynamic_cast< Extended* >( p ) ;
if ( px !=3D NULL ) {
// use the extended interface, e.g. to get more
// information...
} else {
// carry out some default action...
}
> > Think for a moment about Smalltalk---the granddaddy of OO
> > languages.
> Even in Smalltalk, sending a message to an object without
> knowing if the object will understand the message is frowned
> upon.
Certainly. But in Smalltalk, you could also ask the object if
it understood the method, and do something by default if it
didn't. (But I prefer the C++ solution, where you have to ask
the object if it supports a specific extended interface, and not
just whether it happens to have a method with the targetted
name.)
> I'm not going to pretend that the matter is settled. There is
> a regular debate in comp.object over whether designs that
> require down-casting are appropriate, but I think it is fair
> to say that it isn't *necessary*, and I happen to believe that
> it *isn't* appropriate.
I would say rather that it is associated with a certain "cost"
(in terms of readability and maintainability), and shouldn't be
used unless the alteratives have a greater cost.
> Which is why, when the Andy asked how often I use it, I said
> "never," and when he asked why not, I said that I don't use it
> because I design my code such that its use isn't necessary.
Well, I don't use it very often, but there are times when the
alteratives involve even greater cost.
--
James Kanze (GABI Software) email:james.kanze@gmail.com
Conseils en informatique orient=E9e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S=E9mard, 78210 St.-Cyr-l'=C9cole, France, +33 (0)1 30 23 00 34
|
|
0
|
|
|
|
Reply
|
james.kanze (9594)
|
3/9/2008 12:34:13 PM
|
|
On 8 mar, 23:38, "Daniel T." <danie...@earthlink.net> wrote:
> On Mar 8, 3:58 pm, James Kanze <james.ka...@gmail.com> wrote:
>
> > On 8 mar, 15:52, "Daniel T." <danie...@earthlink.net> wrote:
> > > James Kanze <james.ka...@gmail.com> wrote:
>
> > > > The basic (abstract) problem that Ian has raised is that of a
> > > > heterogeneous collection of objects. It's a frequent problem,
> > > > and it's a typical example of a case where dynamic_cast is a
> > > > good solution.
>
> > > The basic problem that he raised is that the type information
> > > is being thrown away when it is still needed.
> > > Now, maybe there really are situations where you have no
> > > choice and must throw away type information. All I can say is
> > > that I've never had to face such a situation.
>
> > So you've never used a generic framework?
>
> Of course I have. My company developed a generic framework 10 years
> ago (I helped in designing it) which we still use to this day. It's a
> little long in the tooth in some parts, but nobody ever has to
> dynamic_cast anything.
>
> Without the use of templates, I can think of situations where
> dynamic_cast is necessary, but I would still not design a program such
> that I didn't know *at compile time* that the cast would succeed.
|
|
0
|
|
|
|
Reply
|
james.kanze (9594)
|
3/9/2008 12:34:58 PM
|
|
On 8 mar, 23:38, "Daniel T." <danie...@earthlink.net> wrote:
> On Mar 8, 3:58 pm, James Kanze <james.ka...@gmail.com> wrote:
> > On 8 mar, 15:52, "Daniel T." <danie...@earthlink.net> wrote:
> > > James Kanze <james.ka...@gmail.com> wrote:
> > > > The basic (abstract) problem that Ian has raised is that of a
> > > > heterogeneous collection of objects. It's a frequent problem,
> > > > and it's a typical example of a case where dynamic_cast is a
> > > > good solution.
> > > The basic problem that he raised is that the type information
> > > is being thrown away when it is still needed.
> > > Now, maybe there really are situations where you have no
> > > choice and must throw away type information. All I can say is
> > > that I've never had to face such a situation.
> > So you've never used a generic framework?
> Of course I have. My company developed a generic framework 10 years
> ago (I helped in designing it) which we still use to this day. It's a
> little long in the tooth in some parts, but nobody ever has to
> dynamic_cast anything.
> Without the use of templates, I can think of situations where
> dynamic_cast is necessary, but I would still not design a
> program such that I didn't know *at compile time* that the
> cast would succeed.
Except that templates also have their cost. I'd rather see a
dynamic cast that see a 100,000 LOC framework end up as
templates, all in the header files. And as Juha has pointed
out, using separate queues or lists for each time also looses
order---again, you can organize things to avoid this loss (or to
recover the information later), but is it worth it? Or wouldn't
the extra code be worse than just using dynamic_cast?
--
James Kanze (GABI Software) email:james.kanze@gmail.com
Conseils en informatique orient=E9e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S=E9mard, 78210 St.-Cyr-l'=C9cole, France, +33 (0)1 30 23 00 34
|
|
0
|
|
|
|
Reply
|
james.kanze (9594)
|
3/9/2008 12:38:41 PM
|
|
James Kanze <james.kanze@gmail.com> wrote:
> "Daniel T." <danie...@earthlink.net> wrote:
> > Ian Collins <ian-n...@hotmail.com> wrote:
> >
> > > A quick search of my active code base reveals two instances
> > > of dynamic_cast, const and non-const conversion operations
> > > in my DOM code!
> >
> > It would be fun to sit down with you and see if we can't
> > connect the code that creates the objects to the code that
> > uses the objects, then remove those two dynamic_casts.
>
> The question is more: do you want to. Should the code between
> the provider and the consumer need to know about all of the
> possible interfaces, as long as the provider and the consumer
> are in agreement?
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.
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/9/2008 12:54:21 PM
|
|
Daniel T. wrote:
> The
> key is to not throw that information away. How you pass that
> information from the creator to the user depends on the situation.
I'm afraid this is just too vague to be of any help to me...
|
|
0
|
|
|
|
Reply
|
nospam270 (2853)
|
3/9/2008 2:33:19 PM
|
|
On Mar 8, 2:55=A0pm, "John Brawley" <jgbraw...@charter.net> wrote:
> > On Mar 8, 5:08 am, James Kanze wrote:
> > No. =A0Goto never has its place.
>
> Oh?
> How, then...? :
> (Console app.)
>
> for(;;) {
> //no exit condition is programmable.
> //live, *vital*, user interaction via keyboard.
> //check for a keypress (ch).
> switch(ch) {
> //case
> //case
> case: 'q' : goto end;
> //case: }
> end:
> return 0;
>
> }
How about:
int main()
{
for(;;)
{
// no exit condition is programmable.
// live, *vital*, user interaction via keyboard.
// check for a keypress (ch).
char ch =3D keypress();
switch (ch)
{
case 'a':
// do something...
continue;
break;
case 'q':
break;
default:
continue;
break;
}
break;
}
return 0;
}
Greg
|
|
0
|
|
|
|
Reply
|
greghe3 (115)
|
3/9/2008 2:48:25 PM
|
|
Responding to Nieminen...
>> The
>> key is to not throw that information away. How you pass that
>> information from the creator to the user depends on the situation.
>
> I'm afraid this is just too vague to be of any help to me...
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.
Caveat. In both cases the client should understand some problem space
context that happens to map into a type of the collected objects rather
than the object type itself. Then the relationships in (1) can be
instantiated and navigated based on that context while the 'type' in (2)
can be a separate context variable. That allows the client to be
properly decoupled from the object type since the access is defined in
terms of the problem space context rather than OOPL implementation
types. IOW, the mapping of the object type to the problem context that
requires access to a specific type is isolated and encapsulated in
whoever defines the collection content; everyone else deals with the
problem space context.
--
There is nothing wrong with me that could
not be cured by a capful of Drano.
H. S. Lahman
hsl@pathfindermda.com
Pathfinder Solutions
http://www.pathfindermda.com
blog: http://pathfinderpeople.blogs.com/hslahman
"Model-Based Translation: The Next Step in Agile Development". Email
info@pathfindermda.com for your copy.
Pathfinder is hiring:
http://www.pathfindermda.com/about_us/careers_pos3.php.
(888)OOA-PATH
|
|
0
|
|
|
|
Reply
|
hsl (217)
|
3/9/2008 3:38:01 PM
|
|
Daniel T. wrote:
> 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.
>> I'd be interested to know how many projects you have worked on that
>> you've never had to break the ideal design of the language, just to
>> get something done.
>
> 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
|
|
0
|
|
|
|
Reply
|
no.way7055 (86)
|
3/9/2008 3:42:10 PM
|
|
James Kanze wrote:
> On 8 mar, 18:51, "Daniel T." <danie...@earthlink.net> wrote:
>> On Mar 8, 5:08 am, James Kanze <james.ka...@gmail.com> wrote:
>
>>> Dynamic_cast has its place in good OO design; it just
>>> shouldn't be abused.
>
>> Just like goto? :-)
>
> 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
|
|
0
|
|
|
|
Reply
|
no.way7055 (86)
|
3/9/2008 3:49:32 PM
|
|
On Mar 9, 11:38=A0am, "H. S. Lahman" <h...@pathfindermda.com> wrote:
> Responding to Nieminen...
>
> > > The key is to not throw that information away. How you pass that
> > > information from the creator to the user depends on the situation.
> >
> > I'm afraid this is just too vague to be of any help to me...
>
> 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.
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/9/2008 4:09:31 PM
|
|
On Mar 9, 10:33=A0am, Juha Nieminen <nos...@thanks.invalid> wrote:
> Daniel T. wrote:
> > The key is to not throw that information away. How you pass that
> > information from the creator to the user depends on the situation.
>
> 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.
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/9/2008 4:13:24 PM
|
|
On Mar 9, 11:42 am, Andy Champ <no....@nospam.com> wrote:
> Daniel T. wrote:
> > 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!
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.
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/9/2008 4:42:04 PM
|
|
On Mar 9, 4:49 pm, Andy Champ <no....@nospam.com> wrote:
> James Kanze wrote:
> > On 8 mar, 18:51, "Daniel T." <danie...@earthlink.net> wrote:
> >> On Mar 8, 5:08 am, James Kanze <james.ka...@gmail.com> wrote:
> >>> Dynamic_cast has its place in good OO design; it just
> >>> shouldn't be abused.
> >> Just like goto? :-)
> > 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.
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.
--
James Kanze (GABI Software) email:james.kanze@gmail.com
Conseils en informatique orient=E9e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S=E9mard, 78210 St.-Cyr-l'=C9cole, France, +33 (0)1 30 23 00 34
|
|
0
|
|
|
|
Reply
|
james.kanze (9594)
|
3/10/2008 10:23:50 AM
|
|
On Mar 9, 1:54 pm, "Daniel T." <danie...@earthlink.net> wrote:
> James Kanze <james.ka...@gmail.com> wrote:
> > "Daniel T." <danie...@earthlink.net> wrote:
> > > Ian Collins <ian-n...@hotmail.com> wrote:
> > > > A quick search of my active code base reveals two instances
> > > > of dynamic_cast, const and non-const conversion operations
> > > > in my DOM code!
> > > It would be fun to sit down with you and see if we can't
> > > connect the code that creates the objects to the code that
> > > uses the objects, then remove those two dynamic_casts.
> > The question is more: do you want to. Should the code between
> > the provider and the consumer need to know about all of the
> > possible interfaces, as long as the provider and the consumer
> > are in agreement?
> 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.)
--
James Kanze (GABI Software) email:james.kanze@gmail.com
Conseils en informatique orient=E9e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S=E9mard, 78210 St.-Cyr-l'=C9cole, France, +33 (0)1 30 23 00 34
|
|
0
|
|
|
|
Reply
|
james.kanze (9594)
|
3/10/2008 10:39:50 AM
|
|
On Mar 9, 5:09 pm, "Daniel T." <danie...@earthlink.net> wrote:
> On Mar 9, 11:38 am, "H. S. Lahman" <h...@pathfindermda.com> wrote:
> > Responding to Nieminen...
> > > > The key is to not throw that information away. How you
> > > > pass that information from the creator to the user
> > > > depends on the situation.
> > > I'm afraid this is just too vague to be of any help to me...
> > 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.
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.)
--
James Kanze (GABI Software) email:james.kanze@gmail.com
Conseils en informatique orient=E9e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S=E9mard, 78210 St.-Cyr-l'=C9cole, France, +33 (0)1 30 23 00 34
|
|
0
|
|
|
|
Reply
|
james.kanze (9594)
|
3/10/2008 10:51:12 AM
|
|
Jeff Schwab wrote:
> "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).
|
|
0
|
|
|
|
Reply
|
mkb (996)
|
3/10/2008 2:13:44 PM
|
|
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
hsl@pathfindermda.com
Pathfinder Solutions
http://www.pathfindermda.com
blog: http://pathfinderpeople.blogs.com/hslahman
"Model-Based Translation: The Next Step in Agile Development". Email
info@pathfindermda.com for your copy.
Pathfinder is hiring:
http://www.pathfindermda.com/about_us/careers_pos3.php.
(888)OOA-PATH
|
|
0
|
|
|
|
Reply
|
hsl (217)
|
3/10/2008 3:52:31 PM
|
|
Responding to 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.
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
hsl@pathfindermda.com
Pathfinder Solutions
http://www.pathfindermda.com
blog: http://pathfinderpeople.blogs.com/hslahman
"Model-Based Translation: The Next Step in Agile Development". Email
info@pathfindermda.com for your copy.
Pathfinder is hiring:
http://www.pathfindermda.com/about_us/careers_pos3.php.
(888)OOA-PATH
|
|
0
|
|
|
|
Reply
|
hsl (217)
|
3/10/2008 4:30:03 PM
|
|
In message
<ac9e1f74-851b-40f6-bcd9-8b7ae6fad865@60g2000hsy.googlegroups.com>,
James Kanze <james.kanze@gmail.com> writes
>On Mar 9, 4:49 pm, Andy Champ <no....@nospam.com> wrote:
>> James Kanze wrote:
>> > On 8 mar, 18:51, "Daniel T." <danie...@earthlink.net> wrote:
>> >> On Mar 8, 5:08 am, James Kanze <james.ka...@gmail.com> wrote:
>
>> >>> Dynamic_cast has its place in good OO design; it just
>> >>> shouldn't be abused.
>
>> >> Just like goto? :-)
>
>> > 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.
>
>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.
--
Richard Herring
|
|
0
|
|
|
|
Reply
|
Richard
|
3/10/2008 4:47:25 PM
|
|
James Kanze <james.kanze@gmail.com> writes:
> On Mar 9, 4:49 pm, Andy Champ <no....@nospam.com> wrote:
>> James Kanze wrote:
>> > On 8 mar, 18:51, "Daniel T." <danie...@earthlink.net> wrote:
>> >> On Mar 8, 5:08 am, James Kanze <james.ka...@gmail.com> wrote:
>
>> >>> Dynamic_cast has its place in good OO design; it just
>> >>> shouldn't be abused.
>
>> >> Just like goto? :-)
>
>> > 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.
>
> 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".
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.
--
(My 2¢, of course)
Micah J. Cowan
Programmer, musician, typesetting enthusiast, gamer...
http://micah.cowan.name/
|
|
0
|
|
|
|
Reply
|
micah6888 (27)
|
3/10/2008 5:33:29 PM
|
|
"H. S. Lahman" <hsl@pathfindermda.com> wrote in message
news:fmdBj.7096$hr3.3033@trnddc04...
>
> 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
|
|
0
|
|
|
|
Reply
|
andrew.queisser (48)
|
3/10/2008 6:29:45 PM
|
|
andrew queisser wrote:
> 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!
--
Phlip
http://assert2.rubyforge.org/
|
|
0
|
|
|
|
Reply
|
phlip2005 (2147)
|
3/10/2008 6:36:56 PM
|
|
Phlip wrote:
> andrew queisser wrote:
>
>> 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!
>
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.
--
Ian Collins.
|
|
0
|
|
|
|
Reply
|
ian-news (9881)
|
3/10/2008 8:24:26 PM
|
|
"Ian Collins" <ian-news@hotmail.com> wrote in message
news:63ljnsF28giq2U3@mid.individual.net...
> Phlip wrote:
>> andrew queisser wrote:
>>
>>> 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!
>>
> 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.
|
|
0
|
|
|
|
Reply
|
andrew.queisser (48)
|
3/10/2008 8:40:48 PM
|
|
Matthias Buelow wrote:
>
> 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
|
|
0
|
|
|
|
Reply
|
no.way7055 (86)
|
3/10/2008 9:19:06 PM
|
|
Daniel T. wrote:
>
> 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
|
|
0
|
|
|
|
Reply
|
no.way7055 (86)
|
3/10/2008 9:21:14 PM
|
|
Andy Champ wrote:
> Matthias Buelow wrote:
>>
>> 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.
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
|
|
0
|
|
|
|
Reply
|
jkherciueh (3186)
|
3/10/2008 10:18:55 PM
|
|
H. S. Lahman wrote:
> (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.
In my particular case I am (usually) keeping objects of a certain type
in a container of that type, and I access this container directly when
it's possible to do so (eg. to perform an action on some or all the
objects of that type, as long as this action is independent of any other
objects in the system).
The problem is that not all actions I need to perform can be applied
to objects independently of other objects. In particular, sometimes some
actions need to be performed in a certain order. The two problems are
that the objects in the object-specific container might not be in this
order already, and secondly, even if they were, some actions have to be
performed to all existing objects (regardless of their type) in a strict
order, and cannot be performed on a per-type basis.
Currently I'm maintaining this order by putting base-class-type
pointers in a common container. The order of the pointers in this
container defines the order of all the objects. (Note that there are
also other reasons for doing this. Ordering is just one of them.)
One possibility would be to add a virtual function in the common base
class to perform this specific action. This way all the object types
could implement this action by implementing this virtual function, and
there would be no need for dynamic_cast.
The problem with this is that this base class and some of the objects
derived from it (which are often used here and there in the program) are
in a library. It would mean that I would have to add these new (often
program-specific) features to the library.
This would not only be cumbersome, but I also feel it goes against
proper OO design: You don't go and add application-specific features to
an abstract base class or existing objects in a library. If I did this
for every feature of every program I develop, it would make the library
and the base class less abstract and more bloated.
If dynamic_cast has to be avoided at all costs, some other solution is
needed. Maybe a complete re-design of the library. But even so, I really
haven't come up with a better solution. That's why I'm interested in
hearing new ideas.
> (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.
I'm not exactly sure what this means, and how it is radically
different from using dynamic_cast. (It sounds to me like dynamic_cast
would simply have been replaced with some kind of checking of the 'type'
inside those tuples. If this is so, then it would simply make the whole
implementation more complicated with no real benefit.)
|
|
0
|
|
|
|
Reply
|
nospam270 (2853)
|
3/10/2008 11:24:30 PM
|
|
On Mar 7, 4:50 am, "Daniel T." <danie...@earthlink.net> wrote:
> Agreed. If those were our only two options, then you are 100% correct.
> Fortunately, there are other options. This is exactly why OO books warn
> against "manager" objects.
And in real life you fix the code you're given and
don't tell your "manager" that you're first going
to spend a few months redesigning the legacy code
to remove the need for ugly workarounds.
Of course, if you're designing new code then the
idea of having to create a template to make dynamic
casting easier should be a warning sign that the
design needs improvment. In older legacy code that
you can't redesign you may as well leave in the ugly
syntax.
--
Darin Johnson
|
|
0
|
|
|
|
Reply
|
darin (103)
|
3/10/2008 11:50:58 PM
|
|
James Kanze <james.kanze@gmail.com> wrote:
> "Daniel T." <danie...@earthlink.net> wrote:
>
> > 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?
No, dynamic_cast punts. It's a query on object state.
> 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.
Nothing.
> > 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.
No, it doesn't.
> 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?
What happens is, you have a design error.
> (Note that the article you site is mainly concerned with not
> exposing mutable state, in order to reduce coupling.
The type of the object is part of the objects state. The word "mutable"
is nowhere to be found in the article. "as the caller, you should not be
making decisions based on the state of the called object that result in
you then changing the state of the object."
The idea is that you shouldn't be telling the object to do X, you
shouldn't be telling it to change its state to X, you should be telling
it about changes in its environment.
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/11/2008 1:06:25 AM
|
|
James Kanze <james.kanze@gmail.com> wrote:
> "Daniel T." <danie...@earthlink.net> wrote:
> > James Kanze <james.ka...@gmail.com> wrote:
>
> > > The question is more: do you want to. Should the code between
> > > the provider and the consumer need to know about all of the
> > > possible interfaces, as long as the provider and the consumer
> > > are in agreement?
>
> > 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 can see it now. I order some RAM for my computer, and the producer
ships me 30 boxes for which I have to check each invoice to see if it is
what I *really* want, shipping the others back to the producer... How
about this instead. I ask for type X and I get type X?
> 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.
I also haven't answered what a designer should do if all his classes
derive from Object and all his pointers are Object*, forcing him to
dynamic_cast every time he wants to call a member-function. There are
uncountable numbers of designs that would require dynamic_cast. If the
design requires you to use dynamic_cast, then (as I've said before) use
it. But IMHO, the design could have been better.
> 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.)
This is simply a difference of experience I guess. I'm certainly not
going to second guess your sense that dyanic_cast is justified, all I
can say is that I have never found a case in OO code where it is.
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/11/2008 1:41:25 AM
|
|
Andy Champ <no.way@nospam.com> wrote:
> Daniel T. wrote:
>
> > 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.
Print UML? You got to be kidding. :-) The tool I used to count classes
is called "find 'class'". :-)
> But if our projects take us a couple of years, and yours 6-9 months,
> something doesn't add up.
Maybe we have much smaller classes?
> And I can't give too many details of what we're up to, commercial
> confidentiality and all that.
The project with 550 classes (BTW that doesn't include structs :-) was
this one: (http://www.gorilla.com/#hannah)
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/11/2008 1:45:47 AM
|
|
On 9 Mar, 14:33, Juha Nieminen <nos...@thanks.invalid> wrote:
> Daniel T. wrote:
> > The
> > key is to not throw that information away. How you pass that
> > information from the creator to the user depends on the situation.
>
> =A0 I'm afraid this is just too vague to be of any help to me...
I'm finding this entire thread like that!
Could someone start using a concrete example please!
Why do the objects need to be ordered? (some sort of
ordering was the nearest to an example I've seen).
So will a graphical editor do?
class Shape
{
};
class Square: public Shape
{
};
class Circle: public Shape
{
};
So a heterogeneous collection might be
std::vector<Shape*> picture;
and you'd use a dynamic cast if you needed to know if a shape
was a Square (though why you'd need to know that...).
Is the sugestion that picture holds type info?
--
Nick Keighley
|
|
0
|
|
|
|
Reply
|
nick_keighley_nospam (4574)
|
3/11/2008 11:35:01 AM
|
|
On 9 Mar, 15:38, "H. S. Lahman" <h...@pathfindermda.com> wrote:
> Responding to Nieminen...
> >> The
> >> key is to not throw that information away. How you pass that
> >> information from the creator to the user depends on the situation.
>
> > =A0 I'm afraid this is just too vague to be of any help to me...
>
> 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.
with my Shape example (there are some heavy trimmers on this group...)
use seperate Circle and Square collections rather than a heterogeneous
Shape 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.
the Shape collection also holds a field with type info (yuck!)
> Caveat. In both cases the client should understand some problem space
> context that happens to map into a type of the collected objects rather
> than the object type itself.
for instance? Could you give a simple concrete example?
> Then the relationships in (1) can be
> instantiated and navigated based on that context while the 'type' in (2)
> can be a separate context variable. That allows the client to be
> properly decoupled from the object type since the access is defined in
> terms of the problem space context rather than OOPL implementation
> types. IOW, the mapping of the object type to the problem context that
> requires access to a specific type is isolated and encapsulated in
> whoever defines the collection content; everyone else deals with the
> problem space context.
<snip>
--
Nick Keighley
|
|
0
|
|
|
|
Reply
|
nick_keighley_nospam (4574)
|
3/11/2008 11:38:59 AM
|
|
On 9 Mar, 16:09, "Daniel T." <danie...@earthlink.net> wrote:
> On Mar 9, 11:38=A0am, "H. S. Lahman" <h...@pathfindermda.com> wrote:
> > Responding to Nieminen...
> > > > The key is to not throw that information away. How you pass that
> > > > information from the creator to the user depends on the situation.
>
> > > I'm afraid this is just too vague to be of any help to me...
>
> > 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.
so if the consumer wants Squares give him Squares and only Squares.
If he wants all the Shapes in a picture, in order, then give him that.
Is "order" in the nearly-example an ordering (a <=3D b <=3D c ...) and
not a purchase ("I'd like to order 3000 left handed blivets")
> 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.
so if you had two "collections" or streams of objects or whatever
then you'd have two different producers. One "ordered" and one "all
objects"?
> 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
|
|
0
|
|
|
|
Reply
|
nick_keighley_nospam (4574)
|
3/11/2008 11:44:11 AM
|
|
On 10 Mar, 10:51, James Kanze <james.ka...@gmail.com> wrote:
> On Mar 9, 5:09 pm, "Daniel T." <danie...@earthlink.net> wrote:
> > On Mar 9, 11:38 am, "H. S. Lahman" <h...@pathfindermda.com> wrote:
> > > Responding to Nieminen...
> > > > > The key is to not throw that information away. How you
> > > > > pass that information from the creator to the user
> > > > > depends on the situation.
> > > > I'm afraid this is just too vague to be of any help to me...
> > > 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.
>
> Isn't this exactlly what dynamic_cast does?
could *you* give a simple concrete example. Why don't you know the
type?
>=A0What's the
> difference between a member function "supportsInterfaceX()" and
> dynamic_cast< X* >. =A0Except that providing the member function
> means that the base interface must know about the extended
> interface.
yes
> > If the collection's job is to keep track of ordering information,
what is "ordering information"? Z order? creation order?
> > 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.
but why would you want all the objects in certain order
unless you were going to apply a common operation to them?
If they don't all support the common operation why do you want them?
Would the Visitor pattern be any use?
>=A0And how do you handle the different levels of
> functionality? =A0
for example?
> What happens if you tell an object to do
> something it's not capable of?
why would you do that?
>=A0(Note that the article you site
> is mainly concerned with not exposing mutable state, in order to
> reduce coupling. =A0There's nothing "mutable" about the results of
> dynamic_cast. =A0In 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.)
<snip>
--
Nick Keighley
|
|
0
|
|
|
|
Reply
|
nick_keighley_nospam (4574)
|
3/11/2008 11:49:11 AM
|
|
On Mar 10, 4:40 pm, "andrew queisser" <andrew.queis...@hp.com> wrote:
> "Ian Collins" <ian-n...@hotmail.com> wrote in message
> > Phlip wrote:
> >
> > > 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.
>
> I'll hazard a guess: Phlip was just trying to throw me a bone by asking for
> something everybody is already objecting to.
I'm not so sure I would categorically object to no-op defaults. They
are a valid design choice in some situations IMO. Valid in more
situations than down casting for sure.
To Ian's comment, adding no-op defaults requires only that the base
class interface be designed for the contexts it is used in. Which of
course, has to be done in any case.
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/11/2008 12:10:28 PM
|
|
Nick Keighley wrote:
> std::vector<Shape*> picture;
>
> and you'd use a dynamic cast if you needed to know if a shape
> was a Square (though why you'd need to know that...).
It's not really that you need dynamic cast to *know* if the shape is a
Square. In this case you need it if you want to *do* something to the
object if it's a Square, and this operation is not supported by Shape,
only by Square.
You could have two vectors:
std::vector<Square*> squares;
std::vector<Circle*> circles;
But then you lose their relative ordering. If you need to perform some
operations to all the shapes in order, you can't do it with only that
information.
You could have both:
std::vector<Shape*> picture;
std::vector<Square*> squares;
std::vector<Circle*> circles;
The pointers in 'picture' point to the same objects and the pointers
in the two other vectors. Now you can both directly access the Squares
and Circles, and you can perform something to all objects in order.
However, that's not good either. Now many operations become
exceedingly complicated. For example, removing the 5th shape in 'picture'.
Another problem is that if you need to perform an operation to all
Squares, but in the order in which they are in 'picture', it also
becomes complicated: Either you need to use dynamic_cast, or you need to
keep 'squares' in the same order as the pointers are in 'picture'. The
latter can be very complicated if the relative order of the shapes is
changed.
|
|
0
|
|
|
|
Reply
|
nospam270 (2853)
|
3/11/2008 2:12:30 PM
|
|
On Mar 10, 5:47 pm, Richard Herring <junk@[127.0.0.1]> wrote:
> In message
> <ac9e1f74-851b-40f6-bcd9-8b7ae6fad...@60g2000hsy.googlegroups.com>,
> James Kanze <james.ka...@gmail.com> writes
> >On Mar 9, 4:49 pm, Andy Champ <no....@nospam.com> wrote:
> >> James Kanze wrote:
> >> > On 8 mar, 18:51, "Daniel T." <danie...@earthlink.net> wrote:
> >> >> On Mar 8, 5:08 am, James Kanze <james.ka...@gmail.com> wrote:
> >> >>> Dynamic_cast has its place in good OO design; it just
> >> >>> shouldn't be abused.
> >> >> Just like goto? :-)
> >> > 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.
> >And Fortran IV:-).
>
> Not quite. Don't forget Fortran 4's main control structure was
> the "arithmetic if", which could jump backwards ;-/
So you could replace all of the goto's with arithmetic if's in
which all three targets were the same:-).
The classical implementation of a while loop in Fortran-IV did
use a goto, however:
10 IF (condition) 20,30,20
20 C While loop code here...
GOTO 10
30 CONTINUE
--
James Kanze (GABI Software) email:james.kanze@gmail.com
Conseils en informatique orient=E9e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S=E9mard, 78210 St.-Cyr-l'=C9cole, France, +33 (0)1 30 23 00 34
|
|
0
|
|
|
|
Reply
|
james.kanze (9594)
|
3/11/2008 2:15:09 PM
|
|
andrew queisser wrote:
>>> 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.
>>
> I'll hazard a guess: Phlip was just trying to throw me a bone by asking for
> something everybody is already objecting to.
Sorry - I didn't read the thread.
Going forward, if you can't change that source, you have an "outer problem". Not
an OO problem. Dependency management does not mean "never change this source".
That's a good goal; it just makes the times when you _do_ change the base class
more safe and meaningful.
--
Phlip
|
|
0
|
|
|
|
Reply
|
phlip2005 (2147)
|
3/11/2008 2:36:01 PM
|
|
On Mar 11, 1:10 pm, "Daniel T." <danie...@earthlink.net> wrote:
> On Mar 10, 4:40 pm, "andrew queisser" <andrew.queis...@hp.com> wrote:
> > "Ian Collins" <ian-n...@hotmail.com> wrote in message
> > > Phlip wrote:
> > > > 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.
> > I'll hazard a guess: Phlip was just trying to throw me a
> > bone by asking for something everybody is already objecting
> > to.
> I'm not so sure I would categorically object to no-op
> defaults.
I don't think anyone would, although it always depends.
> They are a valid design choice in some situations IMO. Valid
> in more situations than down casting for sure.
The problem is encapsulation. The base class shouldn't
necessarily know about what additional interfaces the derived
class may decide to add. (Sometimes, it's appropriate that it
know it, and other times, it isn't.)
> To Ian's comment, adding no-op defaults requires only that the
> base class interface be designed for the contexts it is used
> in. Which of course, has to be done in any case.
I think you're missing the point. The base class interface is
only designed for a specific use. Some derived classes may want
to define additional functionality. The reason the client code
uses a dynamic_cast is precisely because the base class wasn't
designed to be used in the given context.
Consider a concrete example. Objects are identified by DN's.
You certainly don't want all of the functionality of every
possible object declared down in the basic object type
manipulated by the directory services. All you see there are
the basic functionalities: create, destroy, get, set and action.
Suppose that certain "attributes" are used to set up
relationships; when you receive a set request, the attribute
value is a DN. But of course, in your actual object, you don't
support arbitrary relationships; the relationship depends on the
type of the target object. So in the set request, you ask the
directory service for the object (a pointer to the object, in
fact), and dynamic_cast it to the type you support. If the
dynamc_cast fails, you reject the request with an error; if it
succeeds, you establish the relationship (whatever that means in
the context of the object). Thus, for example, a connection
point or a termination point has an implementedBy relationship
(which in this case will normally be specified as an argument to
the constructor, rather than using a set method, later); the
only way to communicate it is via a DN, so you have to
dynamc_cast, and reject the argument if it isn't the right type
(e.g. if someone requests the creation of a termination point
implemented by an event forwarding discriminator).
As soon as you're dealing with any sort of middleware (even
middleware in the same process), you'll probably need
dynamc_cast at the receiving end---the middleware should not
know the details of what it is connecting. As soon as you have
to deal with multiple versions in the clients or the servers,
you're likely to need dynamic_cast as well---you can avoid it,
but avoiding it more or less comes down to reimplementing what
it does yourself. I suspect that there are other cases as well,
but I've had to deal with those two often enough in practice to
be aware of them. (When we implemented the system I described
above, dynamic_cast didn't exist yet in the language. So we
invented our own version of it. With a lot more work than would
have been necessary with direct support in the language.)
--
James Kanze (GABI Software) email:james.kanze@gmail.com
Conseils en informatique orient=E9e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S=E9mard, 78210 St.-Cyr-l'=C9cole, France, +33 (0)1 30 23 00 34
|
|
0
|
|
|
|
Reply
|
james.kanze (9594)
|
3/11/2008 2:41:18 PM
|
|
On Mar 11, 2:06 am, "Daniel T." <danie...@earthlink.net> wrote:
> James Kanze <james.ka...@gmail.com> wrote:
> > "Daniel T." <danie...@earthlink.net> wrote:
> > > 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?
> No, dynamic_cast punts. It's a query on object state.
Sorry, but I fail to see any difference between having the
producer provide type information, and having the compiler do it
for him. In the end, it boils down to the same thing, except
that 1) having the producer do it explicitly is more work for
the programmer, and 2) the producer can lie.
> > 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.
> Nothing.
> > > 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.
> No, it doesn't.
> > 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?
> What happens is, you have a design error.
> > (Note that the article you site is mainly concerned with not
> > exposing mutable state, in order to reduce coupling.
> The type of the object is part of the objects state. The word
> "mutable" is nowhere to be found in the article. "as the
> caller, you should not be making decisions based on the state
> of the called object that result in you then changing the
> state of the object."
The article didn't mention "mutable", but all of the examples
and the reasoning it applied only applied to mutable.
> The idea is that you shouldn't be telling the object to do X,
> you shouldn't be telling it to change its state to X, you
> should be telling it about changes in its environment.
Agreed. You tell it to do something. In practice, however,
objects may pass through many layers of intermediate software,
which knows nothing about (or should know nothing about) what
services the provider actually offers, and what services the
consumer needs. In practice, however, real software often has
to deal with different versions: you must query whether your
partner supports some new functionality, and be prepared to use
an alternate strategy if it doesn't. Those are, IMHO, two cases
where dynamic_cast is called for---in the first case, it
actually helps design (by encapsulating the functionality at the
relevant level).
--
James Kanze (GABI Software) email:james.kanze@gmail.com
Conseils en informatique orient=E9e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S=E9mard, 78210 St.-Cyr-l'=C9cole, France, +33 (0)1 30 23 00 34
|
|
0
|
|
|
|
Reply
|
james.kanze (9594)
|
3/11/2008 2:49:16 PM
|
|
On Mar 11, 12:49 pm, Nick Keighley <nick_keighley_nos...@hotmail.com>
wrote:
> On 10 Mar, 10:51, James Kanze <james.ka...@gmail.com> wrote:
> > On Mar 9, 5:09 pm, "Daniel T." <danie...@earthlink.net> wrote:
> > > On Mar 9, 11:38 am, "H. S. Lahman" <h...@pathfindermda.com> wrote:
> > > > Responding to Nieminen...
> > > > > > The key is to not throw that information away. How you
> > > > > > pass that information from the creator to the user
> > > > > > depends on the situation.
> > > > > I'm afraid this is just too vague to be of any help to me...
> > > > I think what Daniel T. is saying is that from a design perspective y=
ou
> > > > 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.
> > Isn't this exactlly what dynamic_cast does?
> could *you* give a simple concrete example. Why don't you know
> the type?
Because the middleware didn't provide it. Because the
middleware doesn't care about the type---it's just there to
ensure communication.
Because I have no control over what version of the software the
client is running. So I have to be prepared to handle two
different versions.
> > 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.
> yes
> > > If the collection's job is to keep track of ordering
> > > information,
> what is "ordering information"? Z order? creation order?
The order of the requests?
> > > 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.
> but why would you want all the objects in certain order
> unless you were going to apply a common operation to them?
Who knows? But it doesn't seem so unreasonable to me that
different operations may still require ordering between them.
> If they don't all support the common operation why do you want
> them?
> Would the Visitor pattern be any use?
Maybe.
My point is just that there are a number of different tools
available, and no one tool is always the right one.
> > And how do you handle the different levels of
> > functionality?
> for example?
The fact that the middleware doesn't know (and isn't supposed to
know) the services provided by the higher levels.
In large projects, the software tends to be structured very much
like network protocols. The IP layer doesn't know beans about
NNTP, and shouldn't. Similarly, the various layers that connect
the different components of a large application shouldn't know
beans about the services provided by those components.
--
James Kanze (GABI Software) email:james.kanze@gmail.com
Conseils en informatique orient=E9e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S=E9mard, 78210 St.-Cyr-l'=C9cole, France, +33 (0)1 30 23 00 34
|
|
0
|
|
|
|
Reply
|
james.kanze (9594)
|
3/11/2008 2:57:09 PM
|
|
On Mar 10, 3:13 pm, Matthias Buelow <m...@incubus.de> wrote:
> Jeff Schwab wrote:
> > "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.
That depends on whether you want your program to work reliably
or not. Violate the type system (static or dynamic), and the
code doesn't work. If the language you're using uses static
type checking, you get a compile time error. If it uses dynamic
type checking, you get long debugging sessions at the customer
site.
> 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.
I know a couple of Smalltalk experts who migrated to Java, and
had nothing but good things to say about the static type
checking provided by that language. I tend to work on more or
less critical applications, and find that even Java's static
typing is too weak for my tastes. Apparently, I'm not alone,
since since I've used it, the Java authors have felt it
necessary to add templates (improved static type checking).
> 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.
It's a burden on the programmer, yes. It means that he actually
has to design what he writes, and document it up front, in a
standard way that even a compiler can understand.
--
James Kanze (GABI Software) email:james.kanze@gmail.com
Conseils en informatique orient=E9e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S=E9mard, 78210 St.-Cyr-l'=C9cole, France, +33 (0)1 30 23 00 34
|
|
0
|
|
|
|
Reply
|
james.kanze (9594)
|
3/11/2008 3:17:44 PM
|
|
Responding to queisser...
>> 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.)
The OO paradigm manages access through relationship instantiation.
That's why relationships are instantiated at the object level rather
than at the class level. It is also why one is encouraged to have
multiple relationships between the same classes to support different
collaborations (e.g., different relationship roles in UML).
One reason for doing that is to separate the concerns of *who*
participates in collaboration from the concerns of *when* to
collaborate. That's because the business rules and policies of who
collaborates are usually quite different than those that govern when to
collaborate, so they need to be isolated and encapsulated in their own
methods that are logically "owned" by the right object abstraction.
Thus the client in an OO collaboration should normally only need to know
when to collaborate, not who to collaborate with. The client has faith
that whoever is on the other end of the relationship path is *always*
the right object. Even in the rare situations where the client object
also has the responsibility to determine who to collaborate with in the
solution context, that responsibility would be encapsulated in a
different method.
The name of the game in OO development is robustness in the face of
volatile requirements. The application will *always* be more robust if
one separates the concerns of who vs. when during collaboration, whether
that is convenient or not. If one limits dynamic_cast to subsystem
interface dispatch, then the subsystem implementation will be more
robust because whatever one did to avoid it will be tacitly separating
those concerns.
[FWIW, I agree with the no-op position. If the behavior is a no-op for
all members of a descendant leaf subclass in all solution contexts, then
the behavior is defined at the wrong level in the generalization.
Objects have responsibilities to know things and to do things. They do
not have responsibilities to not do things. By that logic every object
would have a no-op method for every behavior in every other object
because it is not supposed to do anything for those other object
responsibilities. IOW, if the object is not supposed to do something,
then it doesn't have a responsibility to do it. But let's not go there.]
> 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.
Because if one gets to the point where one thinks one needs it, it is a
sure sign that one has somehow screwed up the problem space abstraction
in OOA/D. B-)
<aside>
Alas, this sort of argument is not persuasive to me in general. That's
because I grew up before Djikstra when people thought GoTos were a boon
and FORTRAN's assigned GoTo and COBOL's ALTER statements were even
better. I was there when the view shifted to "sometimes you need it" and
that seemed persuasive. But then I programmed in a language, BLISS, that
didn't even have a GoTo construct and I found I never missed it. Nor
have I needed it since using languages that did have the construct. And
about the same time programming managers were making using assigned GoTo
or ALTER grounds for summary dismissal, and nobody missed them either.
No offense intended but... IME, when developers agree that a practice is
something to be avoided but that there are exceptions, those exceptions
turn out to mean they just haven't looked hard enough for an
alternative. It is usually a very safe bet that OOA/D methodology
designers have considered the problem and have provided the means to
deal with it. That might means might not be compact and might require
some extra keystrokes, but compactness and keystrokes aren't an issue
for OO development; otherwise we would all be doing functional programming.
</aside>
--
There is nothing wrong with me that could
not be cured by a capful of Drano.
H. S. Lahman
hsl@pathfindermda.com
Pathfinder Solutions
http://www.pathfindermda.com
blog: http://pathfinderpeople.blogs.com/hslahman
"Model-Based Translation: The Next Step in Agile Development". Email
info@pathfindermda.com for your copy.
Pathfinder is hiring:
http://www.pathfindermda.com/about_us/careers_pos3.php.
(888)OOA-PATH
|
|
0
|
|
|
|
Reply
|
hsl (217)
|
3/11/2008 3:20:09 PM
|
|
On 11 Mar, 14:57, James Kanze <james.ka...@gmail.com> wrote:
> On Mar 11, 12:49 pm, Nick Keighley <nick_keighley_nos...@hotmail.com>
> wrote:
> > On 10 Mar, 10:51, James Kanze <james.ka...@gmail.com> wrote:
> > > On Mar 9, 5:09 pm, "Daniel T." <danie...@earthlink.net> wrote:
> > > > On Mar 9, 11:38 am, "H. S. Lahman" <h...@pathfindermda.com> wrote:
> > > > > Responding to Nieminen...
<snip>
> > > > > 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 c=
an
> > > > > 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} tuple=
s 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.
>
> > > Isn't this exactlly what dynamic_cast does?
>
> > could *you* give a simple concrete example. Why don't you know
> > the type?
>
> Because the middleware didn't provide it. =A0Because the
> middleware doesn't care about the type---it's just there to
> ensure communication.
>
> Because I have no control over what version of the software the
> client is running. =A0So I have to be prepared to handle two
> different versions.
ah, this sounds like Lahman's Exception:
"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."
Which is where I usually allow it.
<snip>
> > > > If the collection's job is to keep track of ordering
> > > > information,
>
> > what is "ordering information"? Z order? creation order?
>
> The order of the requests?
>
> > > > then
> > > > that should be its only job. The consumer shouldn't be expected to u=
se
> > > > 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 navigate=
d
> > > > 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 somethi=
ng
> > > > 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.
>
> > but why would you want all the objects in certain order
> > unless you were going to apply a common operation to them?
>
> Who knows? =A0But it doesn't seem so unreasonable to me that
> different operations may still require ordering between them.
>
> > If they don't all support the common operation why do you want
> > them?
> > Would the Visitor pattern be any use?
>
> Maybe.
>
> My point is just that there are a number of different tools
> available, and no one tool is always the right one.
>
> > > And how do you handle the different levels of
> > > functionality?
>
> > for example?
>
> The fact that the middleware doesn't know (and isn't supposed to
> know) the services provided by the higher levels.
>
> In large projects, the software tends to be structured very much
> like network protocols. =A0The IP layer doesn't know beans about
> NNTP, and shouldn't. =A0Similarly, the various layers that connect
> the different components of a large application shouldn't know
> beans about the services provided by those components.
that sounds a very resonable place for a dynamic cast.
--
Nick Keighley
|
|
0
|
|
|
|
Reply
|
nick_keighley_nospam (4574)
|
3/11/2008 3:43:44 PM
|
|
James Kanze wrote:
> That depends on whether you want your program to work reliably
> or not.
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.) From my
experience, programs written in a "dynamically" typed language are
simply more malleable and also more concise and hence easier to
understand. 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.
> Violate the type system (static or dynamic), and the
> code doesn't work.
The code also won't work for any kind of logics errors, which no
compiler could guard against. There's simply no substitute for testing.
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.
> It's a burden on the programmer, yes. It means that he actually
> has to design what he writes, and document it up front, in a
> standard way that even a compiler can understand.
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. 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. 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. 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.
|
|
0
|
|
|
|
Reply
|
mkb (996)
|
3/11/2008 4:03:25 PM
|
|
James Kanze <james.ka...@gmail.com> wrote:
> "Daniel T." <danie...@earthlink.net> wrote:
> > To Ian's comment, adding no-op defaults requires only that the
> > base class interface be designed for the contexts it is used
> > in. Which of course, has to be done in any case.
>
> I think you're missing the point. The base class interface is
> only designed for a specific use.
I think *you* are missing the point. The base class interface is
designed for a specific use, and we are talking about code that *uses*
the base class interface. A container that contains objects of the
base class, and requesters that request objects of the base class.
The fact that once you receive the base class object you are querying
it for what *other* functionality the object may have is the problem.
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/11/2008 4:21:08 PM
|
|
Responding to Nieminen...
>> (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.
>
> In my particular case I am (usually) keeping objects of a certain type
> in a container of that type, and I access this container directly when
> it's possible to do so (eg. to perform an action on some or all the
> objects of that type, as long as this action is independent of any other
> objects in the system).
>
> The problem is that not all actions I need to perform can be applied
> to objects independently of other objects. In particular, sometimes some
> actions need to be performed in a certain order. The two problems are
> that the objects in the object-specific container might not be in this
> order already, and secondly, even if they were, some actions have to be
> performed to all existing objects (regardless of their type) in a strict
> order, and cannot be performed on a per-type basis.
This is actually an easy problem to solve with multiple homogeneous
collections without any dynamic_cast.
Ordering is a natural responsibility of collections and the natural
approach is for a getNext collection interface to provide elements in
the correct order from its implementation. So the ordering is not
directly a concern of clients processing the collection elements.
Consequently the ordering is only related to the element type through
problem space serendipity. Hold that thought for a moment...
If I understand the problem correctly you have:
[ClassA]
|*
| accesses
|
| R1 <<ordered>>
|
| 1
[Client]
+ actionForA()
+ actionForB()
+ actionForAll()
| 1
|
| R2 <<ordered>>
|
| accesses
| *
[ClassB]
where the relevant collections implement the R1 and R2 associations. The
ordering of the R1 collection is suitable for actionForA() that needs to
process members of [ClassA]. Similarly, the ordering of the R1
collection is suitable for actionForB() that needs to process members of
[ClassB].
So the tricky part is dealing with the ordering for actionForAll(). The
simplest case is that ordering of R1 and R2 is the same and that
actionForAll() just needs to interleave the results of getNext from the
two collections. I'll deal with such interleaving in the discussion of
(2) below.
A trickier problem is that the R1 and R2 ordering are not the same or
that actionForAll() requires some completely different ordering. There
is no restriction on the R1 and R2 collections that they must provide
only one ordering. In practice it is usually more efficient for a single
collection class to managing multiple orderings of the same set.
So all we need is to provide an additional interface to the R1 and R2
collections to present elements in a different order (e.g.,
getNextOrder1() and getNextOrder2()), where getNextOrder1() provides the
ordering for access by actionForA() or actionForB() while
getNextOrder2() provides the ordering for access by actionForAll(). Now
each actionFor... method employs the correct interface fro dealing with
each collection. In the case of actionForAll(), we are back to
interleaving again, as above.
Note that it is quite fair for the client to utilize a particular
interface for ordering because ordering is a *collection* responsibility
and a characteristic of the relationship that has nothing directly to do
with what classes the collection objects belong to.
>> (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.
>
> I'm not exactly sure what this means, and how it is radically
> different from using dynamic_cast. (It sounds to me like dynamic_cast
> would simply have been replaced with some kind of checking of the 'type'
> inside those tuples. If this is so, then it would simply make the whole
> implementation more complicated with no real benefit.)
This becomes relevant for the interleaving. There has to be some
attribute that is a context variable for the ordering or else the
collection sets could not be ordered in the first place. For the sake of
example, let's assume all the objects have a 'size' attribute and
actionForA() and actionForB() require ordering in ascending order of
size, while actionForAll() requires descending order of size.
It is quite fair for actionForAll() to obtain the next element from each
collection and compare sizes to determine which one of the two to
process next. actionForAll() then does getNext() on the appropriate
collection to replace the element just processed, and continues the
interleaving in this manner.
This processing does not directly depend on the object types. It just
depends on the 'size' attribute and the fact that the R1 and R2
collections provide the correct order for individual elements when taken
one-at-a-time. So 'size' is a context variable that is synchronized with
the R1 and R2 collection ordering to make interleaving simple for
actionForAll().
The only way the object type comes into the picture is when
actionForAll() actually processes one of the objects returned from
getNext. But that object is *always* the right type for the association
that was navigated. That is, when actionForAll() navigates R1, it always
gets back a ClassA object. So the type safety issues are explicitly
associated with the relationship rather than type checking of random
objects.
[Of course somebody has to ensure that only [ClassA] objects are put
into the R1 collection. But that is a different trade union from the
responsibilities of [Client] that drive the collaboration. So we
separate the concerns and absolve [Client] from any dependence on those
sorts of type issues. So [Client] can do its thing with whoever is there
and navigating separate relationships ensures type safety.]
In this case the problem context has been abstracted in two quite
different ways that play together. One way is the context variable,
size, that is used to compare individual elements and order the R1 and
R2 collections. The other way is through abstracting separate
relationship collections that are ordered in a particular fashion to
suite the needs of the collaboration between [Client] and both [ClassA]
and [ClassB].
The value of those separate collections is that they unambiguously tie
type safety to static structure. The cost of doing that is really not
very much, other than the static declaration boilerplate. The members of
[ClassA] and [ClassB] are already peer objects of [Client]. In OOA/D we
would naturally have separate relationships between them, as my Class
Model fragment above showed.
That is, in OOA/D relationships are always between individual classes.
So using a heterogeneous collection class is actually a rather unnatural
way to solve this problem from an OOA/D perspective. In UML the closest
one comes is an n-ary relationship where the implementation collection
class *might* be heterogeous.
[And n-ary associations are not necessary to OOA/D. In fact, in the MDA
profile I use for OOA/D the UML subset does not include n-ary
associations, so there is no direct way to even represent such a
collection. Yet, as a translationist, my UML models are unambiguous
specifications for a full code generator that can target C++, C, Java,
or even MS Word documentation.]
--
There is nothing wrong with me that could
not be cured by a capful of Drano.
H. S. Lahman
hsl@pathfindermda.com
Pathfinder Solutions
http://www.pathfindermda.com
blog: http://pathfinderpeople.blogs.com/hslahman
"Model-Based Translation: The Next Step in Agile Development". Email
info@pathfindermda.com for your copy.
Pathfinder is hiring:
http://www.pathfindermda.com/about_us/careers_pos3.php.
(888)OOA-PATH
|
|
0
|
|
|
|
Reply
|
hsl (217)
|
3/11/2008 4:56:51 PM
|
|
Responding to Keighley...
>> (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.
>
> the Shape collection also holds a field with type info (yuck!)
>
>
>> Caveat. In both cases the client should understand some problem space
>> context that happens to map into a type of the collected objects rather
>> than the object type itself.
>
> for instance? Could you give a simple concrete example?
See the example at the end of my most recent response (today) to the OP.
As I indicated to Daniel T., my main point with the caveat here is that
including specific type information with the collection is only
marginally better than the dynamic_cast kludge. One really shouldn't do
either one.
--
There is nothing wrong with me that could
not be cured by a capful of Drano.
H. S. Lahman
hsl@pathfindermda.com
Pathfinder Solutions
http://www.pathfindermda.com
blog: http://pathfinderpeople.blogs.com/hslahman
"Model-Based Translation: The Next Step in Agile Development". Email
info@pathfindermda.com for your copy.
Pathfinder is hiring:
http://www.pathfindermda.com/about_us/careers_pos3.php.
(888)OOA-PATH
|
|
0
|
|
|
|
Reply
|
hsl (217)
|
3/11/2008 5:03:37 PM
|
|
On Mar 11, 10:12=A0am, Juha Nieminen <nos...@thanks.invalid> wrote:
>
> It's not really that you need dynamic cast to *know* if the shape is a
> Square. In this case you need it if you want to *do* something to the
> object if it's a Square, and this operation is not supported by Shape,
> only by Square.
And therein lies the problem. "you want to *do* something to the
object if it's a Square". If you find yourself in that situation, the
design has already slid downhill. IMHO.
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/11/2008 6:12:03 PM
|
|
In message <nRyBj.5561$Y33.3445@trndny07>, H. S. Lahman
<hsl@pathfindermda.com> writes
>Responding to Nieminen...
>
>>> (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.
>> In my particular case I am (usually) keeping objects of a certain
>>type
>> in a container of that type, and I access this container directly when
>> it's possible to do so (eg. to perform an action on some or all the
>> objects of that type, as long as this action is independent of any other
>> objects in the system).
>> The problem is that not all actions I need to perform can be
>>applied
>> to objects independently of other objects. In particular, sometimes some
>> actions need to be performed in a certain order. The two problems are
>> that the objects in the object-specific container might not be in this
>> order already, and secondly, even if they were, some actions have to be
>> performed to all existing objects (regardless of their type) in a strict
>> order, and cannot be performed on a per-type basis.
>
>This is actually an easy problem to solve with multiple homogeneous
>collections without any dynamic_cast.
>
>Ordering is a natural responsibility of collections and the natural
>approach is for a getNext collection interface to provide elements in
>the correct order from its implementation. So the ordering is not
======================
>directly a concern of clients processing the collection elements.
================================================================
> Consequently the ordering is only related to the element type through
>problem space serendipity. Hold that thought for a moment...
>
>If I understand the problem correctly you have:
>
>[ClassA]
> |*
> | accesses
> |
> | R1 <<ordered>>
> |
> | 1
>[Client]
>+ actionForA()
>+ actionForB()
>+ actionForAll()
> | 1
> |
> | R2 <<ordered>>
> |
> | accesses
> | *
>[ClassB]
>
>where the relevant collections implement the R1 and R2 associations.
>The ordering of the R1 collection is suitable for actionForA() that
>needs to process members of [ClassA]. Similarly, the ordering of the R1
>collection is suitable for actionForB() that needs to process members
>of [ClassB].
>
>So the tricky part is dealing with the ordering for actionForAll(). The
>simplest case is that ordering of R1 and R2 is the same and that
>actionForAll() just needs to interleave the results of getNext from the
>two collections. I'll deal with such interleaving in the discussion of
>(2) below.
>
[snip trickier problem, not relevant to my point]
>
>Note that it is quite fair for the client to utilize a particular
>interface for ordering because ordering is a *collection*
==========================
>responsibility and a characteristic of the relationship that has
==============
>nothing directly to do with what classes the collection objects belong
>to.
>
[...]
>
>This becomes relevant for the interleaving. There has to be some
>attribute that is a context variable for the ordering or else the
>collection sets could not be ordered in the first place. For the sake
>of example, let's assume all the objects have a 'size' attribute and
>actionForA() and actionForB() require ordering in ascending order of
>size, while actionForAll() requires descending order of size.
>
>It is quite fair
!
>for actionForAll() to obtain the next element from each collection and
>compare sizes to determine which one of the two to process next.
>actionForAll() then does getNext() on the appropriate collection to
>replace the element just processed, and continues the interleaving in
>this manner.
>
>This processing does not directly depend on the object types. It just
>depends on the 'size' attribute and the fact that the R1 and R2
>collections provide the correct order for individual elements when
>taken one-at-a-time. So 'size' is a context variable that is
>synchronized with the R1 and R2 collection ordering to make
>interleaving simple for actionForAll().
So now you have to expose that attribute, and the client has to have
access to the ordering relation which was previously encapsulated in the
collection.
And if the number of collections increases beyond two, that "simple
interleaving" doesn't scale well, as it starts to look more and more
like a complete (partial) sort, so really the client, not the
collection, is now responsible for the ordering.
An interesting tradeoff.
--
Richard Herring
|
|
0
|
|
|
|
Reply
|
Richard
|
3/11/2008 6:20:50 PM
|
|
On 11 mar, 17:21, "Daniel T." <danie...@earthlink.net> wrote:
> James Kanze <james.ka...@gmail.com> wrote:
> > "Daniel T." <danie...@earthlink.net> wrote:
> > > To Ian's comment, adding no-op defaults requires only that the
> > > base class interface be designed for the contexts it is used
> > > in. Which of course, has to be done in any case.
> > I think you're missing the point. The base class interface is
> > only designed for a specific use.
> I think *you* are missing the point. The base class interface
> is designed for a specific use, and we are talking about code
> that *uses* the base class interface. A container that
> contains objects of the base class, and requesters that
> request objects of the base class.
And that's the point you seem to be missing. We're talking
about code which uses a specific interface, and other code which
produces objects which have that specific interface.
And in between them, there is a transport layer which doesn't
want to know about the details of that user interface, since
it's transporting between a lot of different suppliers and
consumers.
> The fact that once you receive the base class object you are
> querying it for what *other* functionality the object may have
> is the problem.
The fact is that once you receive an object through the
transport layer, you have to query, since things do go wrong,
and you can end up with an object intended for someone else.
--
James Kanze (GABI Software) email:james.kanze@gmail.com
Conseils en informatique orient=E9e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S=E9mard, 78210 St.-Cyr-l'=C9cole, France, +33 (0)1 30 23 00 34
|
|
0
|
|
|
|
Reply
|
james.kanze (9594)
|
3/11/2008 9:41:17 PM
|
|
dave_mikesell@fastmail.fm wrote:
> Good discussion. I have a perhaps naive question directed to any and
> all in this thread. If you have to work with a hierarchy of classes
> that extend the base with their own methods, would it be reasonable to
> create a parallel adapter hierarchy with no-op methods on classes that
> have no "real" implementation of the individual leaf methods? Or does
> this just mask the original design problem with a fat interface?
It depends, but usually the latter IMO. I know that some of the
experienced regulars like the technique, but I personally consider it an
anti-pattern in most cases. The one sort-of exception I would make is
for a policy-based design in which "do-nothing" policies are acceptable.
|
|
0
|
|
|
|
Reply
|
jeff34 (1594)
|
3/12/2008 2:55:36 AM
|
|
James Kanze <james.kanze@gmail.com> wrote:
> "Daniel T." <danie...@earthlink.net> wrote:
> > James Kanze <james.ka...@gmail.com> wrote:
> > > "Daniel T." <danie...@earthlink.net> wrote:
> > >
> > > > To Ian's comment, adding no-op defaults requires only that the
> > > > base class interface be designed for the contexts it is used
> > > > in. Which of course, has to be done in any case.
> > >
> > > I think you're missing the point. The base class interface is
> > > only designed for a specific use.
> >
> > I think *you* are missing the point. The base class interface
> > is designed for a specific use, and we are talking about code
> > that *uses* the base class interface. A container that
> > contains objects of the base class, and requesters that
> > request objects of the base class.
>
> And that's the point you seem to be missing. We're talking
> about code which uses a specific interface, and other code which
> produces objects which have that specific interface.
>
> And in between them, there is a transport layer which doesn't
> want to know about the details of that user interface, since
> it's transporting between a lot of different suppliers and
> consumers.
Right, I understand that. You are talking about a poor design. As I have
said many times, if you have a poor design, whether its as you describe
above or whether it is because all objects are derived from Object and
held in Object*s, you must use dynamic_cast.
> > The fact that once you receive the base class object you are
> > querying it for what *other* functionality the object may have
> > is the problem.
>
> The fact is that once you receive an object through the
> transport layer, you have to query, since things do go wrong,
> and you can end up with an object intended for someone else.
Right, a poor design.
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/12/2008 9:53:20 AM
|
|
James Kanze <james.kanze@gmail.com> wrote:
> "Daniel T." <danie...@earthlink.net> wrote:
>
> > The idea is that you shouldn't be telling the object to do X,
> > you shouldn't be telling it to change its state to X, you
> > should be telling it about changes in its environment.
>
> Agreed. You tell it to do something. In practice, however,
> objects may pass through many layers of intermediate software,
> which knows nothing about (or should know nothing about) what
> services the provider actually offers, and what services the
> consumer needs. In practice, however, real software often has
> to deal with different versions: you must query whether your
> partner supports some new functionality, and be prepared to use
> an alternate strategy if it doesn't. Those are, IMHO, two cases
> where dynamic_cast is called for---in the first case, it
> actually helps design (by encapsulating the functionality at the
> relevant level).
That may be the practice in poor designs, but it is not the general case.
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/12/2008 9:55:54 AM
|
|
On 8 Mar, 21:55, "John Brawley" <jgbraw...@charter.net> wrote:
> "James Kanze" wrote in message
>
> On 8 mar, 18:51, "Daniel T." =A0wrote:
>
> > On Mar 8, 5:08 am, James Kanze wrote:
> > > > Dynamic_cast has its place in good OO design; it just
> > > > shouldn't be abused.
> > > Just like goto? :-)
> > No. =A0Goto never has its place.
>
> Oh?
> How, then...? :
> (Console app.)
>
> for(;;) {
> //no exit condition is programmable.
> //live, *vital*, user interaction via keyboard.
> //check for a keypress (ch).
> switch(ch) {
> //case
> //case
> case: 'q' : goto end;
> //case: }
> end:
> return 0;
>
> }
your layout is shit
do
{
check_for_keypress (&ch);
switch(ch)
{
case 'a':
do_A()
break;
case 'b':
do_B()
break;
}
} while (ch !=3D 'q');
--
Nick Keighley
|
|
0
|
|
|
|
Reply
|
nick_keighley_nospam (4574)
|
3/12/2008 9:59:19 AM
|
|
Nick Keighley <nick_keighley_nospam@hotmail.com> wrote:
> > 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.
>
> so if you had two "collections" or streams of objects or whatever
> then you'd have two different producers. One "ordered" and one "all
> objects"?
That is one of many ways to handle it, but the fundamental question is
still there; what is it you are wanting to notify all the Squares of
that you want to keep it secret from the rest of the Shapes?
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/12/2008 10:00:32 AM
|
|
Daniel T. wrote:
> On Mar 11, 10:12 am, Juha Nieminen <nos...@thanks.invalid> wrote:
>> It's not really that you need dynamic cast to *know* if the shape is a
>> Square. In this case you need it if you want to *do* something to the
>> object if it's a Square, and this operation is not supported by Shape,
>> only by Square.
>
> And therein lies the problem. "you want to *do* something to the
> object if it's a Square". If you find yourself in that situation, the
> design has already slid downhill. IMHO.
And what is the alternative you propose?
I can only think of one possibility: Clutter the 'Shape' base class
with Square-specific virtual functions. This defies all good OO design.
(Note that the circles and squares is just an *example*. There are
similar situations where it indeed makes a lot of sense, and moreover
it's mandatory, to be able to perform some type-specific operations on
objects of that type, and for example the order (or other such
attribute) in which these operations are performed is significant.)
|
|
0
|
|
|
|
Reply
|
nospam270 (2853)
|
3/12/2008 10:13:32 AM
|
|
Matthias Buelow <mkb@incubus.de> wrote:
> James Kanze wrote:
>
> > That depends on whether you want your program to work reliably
> > or not.
>
> 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.)
I'll throw in my 2p. just because. :-)
The good news, and bad news, with a dynamic-typed language, is that less
of the design is actually written out in code. Because of this, the code
can be written faster, and the the code is harder to figure out after it
is written.
In other words, I think you are both right. :-)
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/12/2008 10:36:20 AM
|
|
On Mar 12, 10:53 am, "Daniel T." <danie...@earthlink.net> wrote:
> James Kanze <james.ka...@gmail.com> wrote:
> > "Daniel T." <danie...@earthlink.net> wrote:
> > > James Kanze <james.ka...@gmail.com> wrote:
> > > > "Daniel T." <danie...@earthlink.net> wrote:
> > > > > To Ian's comment, adding no-op defaults requires only that the
> > > > > base class interface be designed for the contexts it is used
> > > > > in. Which of course, has to be done in any case.
> > > > I think you're missing the point. The base class interface is
> > > > only designed for a specific use.
> > > I think *you* are missing the point. The base class interface
> > > is designed for a specific use, and we are talking about code
> > > that *uses* the base class interface. A container that
> > > contains objects of the base class, and requesters that
> > > request objects of the base class.
> > And that's the point you seem to be missing. We're talking
> > about code which uses a specific interface, and other code which
> > produces objects which have that specific interface.
> > And in between them, there is a transport layer which doesn't
> > want to know about the details of that user interface, since
> > it's transporting between a lot of different suppliers and
> > consumers.
> Right, I understand that. You are talking about a poor design.
You've got it backwards. Exposing such details to the transport
layer would be very poor design, and a maintenance nightmare.
> As I have said many times, if you have a poor design, whether
> its as you describe above or whether it is because all objects
> are derived from Object and held in Object*s, you must use
> dynamic_cast.
> > > The fact that once you receive the base class object you
> > > are querying it for what *other* functionality the object
> > > may have is the problem.
> > The fact is that once you receive an object through the
> > transport layer, you have to query, since things do go
> > wrong, and you can end up with an object intended for
> > someone else.
> Right, a poor design.
Reality is a poor design?
I'd say that robustness is an important property of a good
design. Being able to continue functionning when some network
component fails is an essential requirement of almost all of the
software I work on.
--
James Kanze (GABI Software) email:james.kanze@gmail.com
Conseils en informatique orient=E9e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S=E9mard, 78210 St.-Cyr-l'=C9cole, France, +33 (0)1 30 23 00 34
|
|
0
|
|
|
|
Reply
|
james.kanze (9594)
|
3/12/2008 10:38:19 AM
|
|
Juha Nieminen <nospam@thanks.invalid> wrote:
> Daniel T. wrote:
> > Juha Nieminen <nos...@thanks.invalid> wrote:
> > > It's not really that you need dynamic cast to *know* if the
> > > shape is a Square. In this case you need it if you want to *do*
> > > something to the object if it's a Square, and this operation is
> > > not supported by Shape, only by Square.
> >
> > And therein lies the problem. "you want to *do* something to the
> > object if it's a Square". If you find yourself in that situation,
> > the design has already slid downhill. IMHO.
>
> And what is the alternative you propose?
Don't try to *do* things to objects. Objects are supposed to do for
themselves, clients *notify* the objects of things, they don't order
them around.
> I can only think of one possibility: Clutter the 'Shape' base class
> with Square-specific virtual functions. This defies all good OO
> design.
What is it you are trying to tell squares that you don't want any other
shapes to know about? Why the big secret?
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/12/2008 10:45:20 AM
|
|
On Mar 12, 11:00 am, "Daniel T." <danie...@earthlink.net> wrote:
> Nick Keighley <nick_keighley_nos...@hotmail.com> wrote:
> > > 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.
> > so if you had two "collections" or streams of objects or
> > whatever then you'd have two different producers. One
> > "ordered" and one "all objects"?
> That is one of many ways to handle it, but the fundamental
> question is still there; what is it you are wanting to notify
> all the Squares of that you want to keep it secret from the
> rest of the Shapes?
You've got it backwards. Why do you want to encumber all the
rest of the Shapes with something that is only relevant to
Squares?
--
James Kanze (GABI Software) email:james.kanze@gmail.com
Conseils en informatique orient=E9e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S=E9mard, 78210 St.-Cyr-l'=C9cole, France, +33 (0)1 30 23 00 34
|
|
0
|
|
|
|
Reply
|
james.kanze (9594)
|
3/12/2008 10:45:51 AM
|
|
On Wed, 12 Mar 2008 12:13:32 +0200, Juha Nieminen wrote:
> Daniel T. wrote:
>> On Mar 11, 10:12 am, Juha Nieminen <nos...@thanks.invalid> wrote:
>>> It's not really that you need dynamic cast to *know* if the shape is a
>>> Square. In this case you need it if you want to *do* something to the
>>> object if it's a Square, and this operation is not supported by Shape,
>>> only by Square.
>>
>> And therein lies the problem. "you want to *do* something to the
>> object if it's a Square". If you find yourself in that situation, the
>> design has already slid downhill. IMHO.
>
> And what is the alternative you propose?
In order to solve this, the container shall be of a dynamically constrained
type, which templates cannot deliver.
> I can only think of one possibility: Clutter the 'Shape' base class
> with Square-specific virtual functions. This defies all good OO design.
The design should consider how the container of shapes gets constrained.
The problem is that a choice of templates as in your example limits
constraints to be static. So a container of Squares is either of another
type or else of exactly same type as the container of Shapes.
This design is bad and there is nothing to do about it, rather than to
patch it by 1) making 'Shape' fat or by 2) applying dynamic casts.
A third alternative would be to convert std::vector<Shape*> to
std::vector<Square*>. What is the least evil depends on each concrete
situation.
(BTW, using pointers is also bad design. The language should allow
construction of containers of polymorphic elements.)
> (Note that the circles and squares is just an *example*. There are
> similar situations where it indeed makes a lot of sense, and moreover
> it's mandatory, to be able to perform some type-specific operations on
> objects of that type, and for example the order (or other such
> attribute) in which these operations are performed is significant.)
Certainly there are lots of situations where templates (static
polymorphism, macros, call it as you like) come short.
--
Regards,
Dmitry A. Kazakov
http://www.dmitry-kazakov.de
|
|
0
|
|
|
|
Reply
|
mailbox2 (6354)
|
3/12/2008 10:55:32 AM
|
|
James Kanze wrote:
> On Mar 12, 11:00 am, "Daniel T." <danie...@earthlink.net> wrote:
>> Nick Keighley <nick_keighley_nos...@hotmail.com> wrote:
>> > > 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.
>
>> > so if you had two "collections" or streams of objects or
>> > whatever then you'd have two different producers. One
>> > "ordered" and one "all objects"?
>
>> That is one of many ways to handle it, but the fundamental
>> question is still there; what is it you are wanting to notify
>> all the Squares of that you want to keep it secret from the
>> rest of the Shapes?
>
> You've got it backwards. Why do you want to encumber all the
> rest of the Shapes with something that is only relevant to
> Squares?
I think what Daniel is getting at is that you shouldn't have to know whether
your request is only relevant to Squares. You should just notify all
objects in your collection that a certain condition obtains and those
objects will decide autonomously whether they have to respond. It might
happen (somewhat by accident) that only Squares feel the need to react, but
that is nothing that client code should need to care about. In other words,
if you find yourself dealing with a collection of objects and you want to
send notifications that cannot be _understood_ by all objects in the
collection, then there is something fishy. However, not all objects that
understand the message need to react in the same way (or even react at
all).
Best
Kai-Uwe Bux
|
|
0
|
|
|
|
Reply
|
jkherciueh (3186)
|
3/12/2008 11:19:13 AM
|
|
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?
> (BTW, using pointers is also bad design. The language should allow
> construction of containers of polymorphic elements.)
Well, we have to live with what C++ has to offer. I don't think that's
the main issue here.
|
|
0
|
|
|
|
Reply
|
nospam270 (2853)
|
3/12/2008 11:34:36 AM
|
|
On 12 Mar, 10:45, "Daniel T." <danie...@earthlink.net> wrote:
> Juha Nieminen <nos...@thanks.invalid> wrote:
> > Daniel T. wrote:
> > > Juha Nieminen <nos...@thanks.invalid> wrote:
> > > > It's not really that you need dynamic cast to *know* if the
> > > > shape is a Square. In this case you need it if you want to *do*
> > > > something to the object if it's a Square, and this operation is
> > > > not supported by Shape, only by Square.
>
> > > And therein lies the problem. "you want to *do* something to the
> > > object if it's a Square". If you find yourself in that situation,
> > > the design has already slid downhill. IMHO.
>
> > And what is the alternative you propose?
>
> Don't try to *do* things to objects. Objects are supposed to do for
> themselves, clients *notify* the objects of things, they don't order
> them around.
>
> > I can only think of one possibility: Clutter the 'Shape' base class
> > with Square-specific virtual functions. This defies all good OO
> > design.
>
> What is it you are trying to tell squares that you don't want any other
> shapes to know about? Why the big secret?
"colour all the squares yellow"
If the shapes example is a bit fanciful make it a UML editor.
I want all the interfaces to stand out by changeing their
colour and my model is huge.
"colour all the interfaces yellow"
--
Nick Keighley
|
|
0
|
|
|
|
Reply
|
nick_keighley_nospam (4574)
|
3/12/2008 11:38:53 AM
|
|
Daniel T. wrote:
> Don't try to *do* things to objects. Objects are supposed to do for
> themselves, clients *notify* the objects of things, they don't order
> them around.
How exactly do you propose to do that? Perhaps have a virtual function
in the base class like:
virtual void performAction(const std::string& action,
const std::string& parameters);
and then have the derived classes parse those parameters?
I think I prefer dynamic_cast.
>> I can only think of one possibility: Clutter the 'Shape' base class
>> with Square-specific virtual functions. This defies all good OO
>> design.
>
> What is it you are trying to tell squares that you don't want any other
> shapes to know about? Why the big secret?
It's not like they must not know about it. It's just that it can be
something which only Square can rationally implement. The others have no
rational use for that functionality, and it would only be useless
clutter for their public interface to have such a function (which does
nothing).
As for an abstract "perform this action if you support it" function
like above... No thanks? For one, it's quite inefficient.
|
|
0
|
|
|
|
Reply
|
nospam270 (2853)
|
3/12/2008 11:39:18 AM
|
|
Daniel T. wrote:
> The good news, and bad news, with a dynamic-typed language, is that less
> of the design is actually written out in code. Because of this, the code
> can be written faster, and the the code is harder to figure out after it
> is written.
Why is it harder to figure out? If you mean instead, harder to
understand what is actually going on "under the hood", then yes. This
isn't obvious unless you know how the compiler/interpreter works.
However, the idea is, that these are irrelevant details that can be
ignored. Actually, it's the same with languages like C++ -- do you
really want to have to care about how the compiler implements virtual
functions, how certain templates expand, etc.? The problem is, sometimes
you need to know, if only for performance reasons, to avoid copying etc.
So it isn't really that much different for a "statically" typed language
like C++.
|
|
0
|
|
|
|
Reply
|
mkb (996)
|
3/12/2008 11:55:27 AM
|
|
On Mar 12, 5:45 am, James Kanze <james.ka...@gmail.com> wrote:
> On Mar 12, 11:00 am, "Daniel T." <danie...@earthlink.net> wrote:
>
> > Nick Keighley <nick_keighley_nos...@hotmail.com> wrote:
> > > > 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.
> > > so if you had two "collections" or streams of objects or
> > > whatever then you'd have two different producers. One
> > > "ordered" and one "all objects"?
> > That is one of many ways to handle it, but the fundamental
> > question is still there; what is it you are wanting to notify
> > all the Squares of that you want to keep it secret from the
> > rest of the Shapes?
>
> You've got it backwards. Why do you want to encumber all the
> rest of the Shapes with something that is only relevant to
> Squares?
You don't. Whatever functionality that we're talking about here
belongs either in Square or some other interface (not Shape) that it
implements. The problem here is that Shape, in this example, is too
generic to be very useful.
This is starting to go around in Circles.
|
|
0
|
|
|
|
Reply
|
dave_mikesell (189)
|
3/12/2008 12:05:14 PM
|
|
On Mar 12, 6:34 am, Juha Nieminen <nos...@thanks.invalid> 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?
If you want to maintain substitutability in your class hierarchy
through the abstract base, you wouldn't create a heterogeneous
container. Depends on your design goals I guess.
|
|
0
|
|
|
|
Reply
|
dave_mikesell (189)
|
3/12/2008 12:14:45 PM
|
|
On Mar 12, 6:38 am, Nick Keighley <nick_keighley_nos...@hotmail.com>
wrote:
> On 12 Mar, 10:45, "Daniel T." <danie...@earthlink.net> wrote:
>
>
>
> > Juha Nieminen <nos...@thanks.invalid> wrote:
> > > Daniel T. wrote:
> > > > Juha Nieminen <nos...@thanks.invalid> wrote:
> > > > > It's not really that you need dynamic cast to *know* if the
> > > > > shape is a Square. In this case you need it if you want to *do*
> > > > > something to the object if it's a Square, and this operation is
> > > > > not supported by Shape, only by Square.
>
> > > > And therein lies the problem. "you want to *do* something to the
> > > > object if it's a Square". If you find yourself in that situation,
> > > > the design has already slid downhill. IMHO.
>
> > > And what is the alternative you propose?
>
> > Don't try to *do* things to objects. Objects are supposed to do for
> > themselves, clients *notify* the objects of things, they don't order
> > them around.
>
> > > I can only think of one possibility: Clutter the 'Shape' base class
> > > with Square-specific virtual functions. This defies all good OO
> > > design.
>
> > What is it you are trying to tell squares that you don't want any other
> > shapes to know about? Why the big secret?
>
> "colour all the squares yellow"
>
> If the shapes example is a bit fanciful make it a UML editor.
> I want all the interfaces to stand out by changeing their
> colour and my model is huge.
>
> "colour all the interfaces yellow"
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.
|
|
0
|
|
|
|
Reply
|
dave_mikesell (189)
|
3/12/2008 12:23:15 PM
|
|
On Mar 12, 7:39 am, Juha Nieminen <nos...@thanks.invalid> wrote:
> Daniel T. wrote:
> > Don't try to *do* things to objects. Objects are supposed to do
> > for themselves, clients *notify* the objects of things, they don't
> > order them around.
>
> How exactly do you propose to do that? Perhaps have a virtual
> function in the base class like:
>
> virtual void performAction(const std::string& action,
> const std::string& parameters);
>
> and then have the derived classes parse those parameters?
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 borrow Nick Keighley's example (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.)
Also, refer to Kai-Uwe Bux's post from this morning... Very well put!
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/12/2008 1:05:37 PM
|
|
On Mar 12, 7:19 am, Kai-Uwe Bux <jkherci...@gmx.net> wrote:
> I think what Daniel is getting at is that you shouldn't have to know whether
> your request is only relevant to Squares. You should just notify all
> objects in your collection that a certain condition obtains and those
> objects will decide autonomously whether they have to respond. It might
> happen (somewhat by accident) that only Squares feel the need to react, but
> that is nothing that client code should need to care about. In other words,
> if you find yourself dealing with a collection of objects and you want to
> send notifications that cannot be _understood_ by all objects in the
> collection, then there is something fishy. However, not all objects that
> understand the message need to react in the same way (or even react at
> all).
Exactly! Well put. The moment you find you *need* to use dynamic_cast
in order for the program to work correctly, is the moment you learn
that the program's design is flawed. Mmaybe you don't have a choice,
maybe it isn't even your design. If that is so, then use dynamic_cast
and be done with it.
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/12/2008 1:12:15 PM
|
|
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.
---------------------------------------
* If C++ supported constrained types it would be all the compiler's job,
which could avoid physical copying when the constraint is statically known
and the object representation is not changed by the conversion.
--
Regards,
Dmitry A. Kazakov
http://www.dmitry-kazakov.de
|
|
0
|
|
|
|
Reply
|
mailbox2 (6354)
|
3/12/2008 2:19:24 PM
|
|
Daniel T. wrote:
> On Mar 12, 7:19 am, Kai-Uwe Bux <jkherci...@gmx.net> wrote:
>> I think what Daniel is getting at is that you shouldn't have to know whether
>> your request is only relevant to Squares. You should just notify all
>> objects in your collection that a certain condition obtains and those
>> objects will decide autonomously whether they have to respond.
It almost sounds like you are talking about a delegation paradigm.
Note that we are talking about C++ here, not objective-C. C++ does not
support delegation natively.
> Exactly! Well put. The moment you find you *need* to use dynamic_cast
> in order for the program to work correctly, is the moment you learn
> that the program's design is flawed.
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).
|
|
0
|
|
|
|
Reply
|
nospam270 (2853)
|
3/12/2008 3:16:55 PM
|
|
Daniel T. wrote:
>> virtual void performAction(const std::string& action,
>> const std::string& parameters);
>
> To borrow Nick Keighley's example (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.
You have still not answered my question: How do you propose I do that?
It almost sounds like you are proposing a delegation paradigm, something
which C++ doesn't support directly.
Note that some operations may be required to be performed in a precise
order. It may not be enough to simple tell objects of certain type to do
something: It may be necessary for them to do that in a given order (eg.
the order in which they are in the container which has all the shapes).
Also, in some cases the operation which has to be performed *with* the
objects might not be doable *by* the objects. The operation to be
performed with the objects might read some values from the objects or
whatever, but whatever it's doing might not be doable by the objects
themselves (eg. because the objects themselves have no access to
something required to perform the operation).
|
|
0
|
|
|
|
Reply
|
nospam270 (2853)
|
3/12/2008 3:26:14 PM
|
|
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.
|
|
0
|
|
|
|
Reply
|
nospam270 (2853)
|
3/12/2008 3:28:12 PM
|
|
Responding to Herring...
> So now you have to expose that attribute, and the client has to have
> access to the ordering relation which was previously encapsulated in the
> collection.
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.
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.
> And if the number of collections increases beyond two, that "simple
> interleaving" doesn't scale well, as it starts to look more and more
> like a complete (partial) sort, so really the client, not the
> collection, is now responsible for the ordering.
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 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. 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.
--
There is nothing wrong with me that could
not be cured by a capful of Drano.
H. S. Lahman
hsl@pathfindermda.com
Pathfinder Solutions
http://www.pathfindermda.com
blog: http://pathfinderpeople.blogs.com/hslahman
"Model-Based Translation: The Next Step in Agile Development". Email
info@pathfindermda.com for your copy.
Pathfinder is hiring:
http://www.pathfindermda.com/about_us/careers_pos3.php.
(888)OOA-PATH
|
|
0
|
|
|
|
Reply
|
hsl (217)
|
3/12/2008 3:29:00 PM
|
|
On Mar 12, 11:26=A0am, Juha Nieminen <nos...@thanks.invalid> wrote:
> Daniel T. wrote:
> >> virtual void performAction(const std::string& action,
> >> =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0const std::strin=
g& parameters);
>
> > To borrow Nick Keighley's example (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.
>
> You have still not answered my question: How do you propose I do that?
> It almost sounds like you are proposing a delegation paradigm, something
> which C++ doesn't support directly.
There are any number of ways to do it, Dave Mikes mentioned one,
another would be to have a virtual function in Shape that lets Shapes
know that a "change rectangles to yellow" request has been made.
Again, there are so many correct ways to do it, that without a
spicific problem domain, I can't just spout out some answer that is
guaranteed to be correct for all domains.
> 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.
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/12/2008 3:34:28 PM
|
|
On Wed, 12 Mar 2008 17:28:12 +0200, Juha Nieminen wrote:
> 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.
--
Regards,
Dmitry A. Kazakov
http://www.dmitry-kazakov.de
|
|
0
|
|
|
|
Reply
|
mailbox2 (6354)
|
3/12/2008 6:13:19 PM
|
|
Daniel T. wrote:
> On Mar 12, 11:26 am, Juha Nieminen <nos...@thanks.invalid> wrote:
>> Daniel T. wrote:
>>>> virtual void performAction(const std::string& action,
>>>> const std::string& parameters);
>>> To borrow Nick Keighley's example (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.
>> You have still not answered my question: How do you propose I do that?
>> It almost sounds like you are proposing a delegation paradigm, something
>> which C++ doesn't support directly.
>
> There are any number of ways to do it, Dave Mikes mentioned one,
> another would be to have a virtual function in Shape that lets Shapes
> know that a "change rectangles to yellow" request has been made.
>
Which, if you excuse the pun, completes the circle on this thread.
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!
--
Ian Collins.
|
|
0
|
|
|
|
Reply
|
ian-news (9881)
|
3/12/2008 6:57:34 PM
|
|
On Mar 12, 7:05=A0am, "Daniel T." <danie...@earthlink.net> wrote:
> On Mar 12, 7:39 am, Juha Nieminen <nos...@thanks.invalid> wrote:
>
> > Daniel T. wrote:
> > > Don't try to *do* things to objects. Objects are supposed to do
> > > for themselves, clients *notify* the objects of things, they don't
> > > order them around.
>
> > How exactly do you propose to do that? Perhaps have a virtual
> > function in the base class like:
>
> > virtual void performAction(const std::string& action,
> > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0const std::string=
& parameters);
>
> > and then have the derived classes parse those parameters?
>
> 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 borrow Nick Keighley's example (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.)
Hmm.
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.
>
> Also, refer to Kai-Uwe Bux's post from this morning... Very well put!
I found his post helpful as well. One small thing about that post: he
used the word 'obtains' where it seemed like the word he wanted was
exists or pertains.
Brian Wood
Ebenezer Enterprises
www.webebenezer.net
|
|
0
|
|
|
|
Reply
|
coal (257)
|
3/12/2008 7:48:16 PM
|
|
On Wed, 12 Mar 2008 08:34:28 -0700 (PDT), Daniel T. wrote:
> On Mar 12, 11:26�am, Juha Nieminen <nos...@thanks.invalid> wrote:
> > Daniel T. wrote:
> > >> virtual void performAction(const std::string& action,
> > >> � � � � � � � � � � � � � �const std::string& parameters);
> >
> > > To borrow Nick Keighley's example (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.
> >
> > You have still not answered my question: How do you propose I do that?
> > It almost sounds like you are proposing a delegation paradigm, something
> > which C++ doesn't support directly.
>
> There are any number of ways to do it, Dave Mikes mentioned one,
> another would be to have a virtual function in Shape that lets Shapes
> know that a "change rectangles to yellow" request has been made.
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.
> Again, there are so many correct ways to do it, that without a
> spicific problem domain, I can't just spout out some answer that is
> guaranteed to be correct for all domains.
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. In my book that's by several
orders of magnitude worse than the occasional dynamic_cast.
I think few around here would argue that one should minimize the use
of dynamic_cast 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.
Andreas
--
Dr. Andreas Dehmel Ceterum censeo
FLIPME(ed.enilno-t@nouqraz) Microsoft esse delendam
http://www.zarquon.homepage.t-online.de (Cato the Much Younger)
|
|
0
|
|
|
|
Reply
|
blackhole.8.zarquon42 (44)
|
3/12/2008 8:26:14 PM
|
|
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
|
|
0
|
|
|
|
Reply
|
no.way7055 (86)
|
3/12/2008 9:53:22 PM
|
|
On Mar 12, 4:26 pm, Andreas Dehmel <blackhole.
8.zarquo...@spamgourmet.com> wrote:
> On Wed, 12 Mar 2008 08:34:28 -0700 (PDT), Daniel T. wrote:
> 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.
> > Again, there are so many correct ways to do it, that without a
> > specific problem domain, I can't just spout out some answer that is
> > guaranteed to be correct for all domains.
>
> 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.
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/12/2008 9:54:07 PM
|
|
On Mar 12, 3:48=A0pm, c...@mailvault.com wrote:
> With CORBA, ordering objects around is the norm. =A0A distributed
> object model blindly translates the voodoo from one context to
> another. =A0I think this is a weakness of CORBA that often works it's
> way into the designs of those using CORBA. =A0On 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.)
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/12/2008 9:59:38 PM
|
|
Ian Collins <ian-news@hotmail.com> wrote:
> 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.
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/12/2008 11:56:38 PM
|
|
Andy Champ <no.way@nospam.com> wrote:
> 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.
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/13/2008 12:44:01 AM
|
|
Matthias Buelow <mkb@incubus.de> wrote:
> Daniel T. wrote:
>
> > The good news, and bad news, with a dynamic-typed language, is that less
> > of the design is actually written out in code. Because of this, the code
> > can be written faster, and the the code is harder to figure out after it
> > is written.
>
> 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.
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/13/2008 1:01:41 AM
|
|
Juha Nieminen <nospam@thanks.invalid> wrote:
> 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...
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/13/2008 1:03:10 AM
|
|
Daniel T. wrote:
> Juha Nieminen <nospam@thanks.invalid> wrote:
>
>> 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...
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?
|
|
0
|
|
|
|
Reply
|
nospam270 (2853)
|
3/13/2008 8:14:54 AM
|
|
Daniel T. wrote:
> On Mar 12, 11:26 am, Juha Nieminen <nos...@thanks.invalid> wrote:
>> Daniel T. wrote:
>>>> virtual void performAction(const std::string& action,
>>>> const std::string& parameters);
>>> To borrow Nick Keighley's example (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.
>> You have still not answered my question: How do you propose I do that?
>> It almost sounds like you are proposing a delegation paradigm, something
>> which C++ doesn't support directly.
>
> 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.
|
|
0
|
|
|
|
Reply
|
nospam270 (2853)
|
3/13/2008 8:20:57 AM
|
|
Daniel T. wrote:
> 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.
|
|
0
|
|
|
|
Reply
|
nospam270 (2853)
|
3/13/2008 8:23:44 AM
|
|
Daniel T. wrote:
> 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.
|
|
0
|
|
|
|
Reply
|
nospam270 (2853)
|
3/13/2008 8:26:09 AM
|
|
Dmitry A. Kazakov wrote:
> On Wed, 12 Mar 2008 17:28:12 +0200, Juha Nieminen wrote:
>
>> 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.
|
|
0
|
|
|
|
Reply
|
nospam270 (2853)
|
3/13/2008 8:32:18 AM
|
|
On 12 Mar, 12:23, dave_mikes...@fastmail.fm wrote:
> On Mar 12, 6:38 am,Nick Keighley<nick_keighley_nos...@hotmail.com>
> wrote:
>
>
>
>
>
> > On 12 Mar, 10:45, "Daniel T." <danie...@earthlink.net> wrote:
>
> > > Juha Nieminen <nos...@thanks.invalid> wrote:
> > > > Daniel T. wrote:
> > > > > Juha Nieminen <nos...@thanks.invalid> wrote:
> > > > > > It's not really that you need dynamic cast to *know* if the
> > > > > > shape is a Square. In this case you need it if you want to *do*
> > > > > > something to the object if it's a Square, and this operation is
> > > > > > not supported by Shape, only by Square.
>
> > > > > And therein lies the problem. "you want to *do* something to the
> > > > > object if it's a Square". If you find yourself in that situation,
> > > > > the design has already slid downhill. IMHO.
>
> > > > And what is the alternative you propose?
>
> > > Don't try to *do* things to objects. Objects are supposed to do for
> > > themselves, clients *notify* the objects of things, they don't order
> > > them around.
>
> > > > I can only think of one possibility: Clutter the 'Shape' base class
> > > > with Square-specific virtual functions. This defies all good OO
> > > > design.
>
> > > What is it you are trying to tell squares that you don't want any othe=
r
> > > shapes to know about? Why the big secret?
>
> > "colour all the squares yellow"
>
> > If the shapes example is a bit fanciful make it a UML editor.
> > I want all the interfaces to stand out by changeing their
> > colour and my model is huge.
>
> > "colour all the interfaces yellow"
>
> You can solve that by putting homogeneous elements in their own
> container for just such operations =A0If 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 =3D dynamic_cast<Square*>drob;
if (square !=3D 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?
--
Nick Keighley
|
|
0
|
|
|
|
Reply
|
nick_keighley_nospam (4574)
|
3/13/2008 9:14:50 AM
|
|
On 12 Mar, 13:05, "Daniel T." <danie...@earthlink.net> wrote:
> On Mar 12, 7:39 am, Juha Nieminen <nos...@thanks.invalid> wrote:
>
> > Daniel T. wrote:
> > > Don't try to *do* things to objects. Objects are supposed to do
> > > for themselves, clients *notify* the objects of things, they don't
> > > order them around.
>
> > How exactly do you propose to do that? Perhaps have a virtual
> > function in the base class like:
>
> > virtual void performAction(const std::string& action,
> > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0const std::string=
& parameters);
>
> > and then have the derived classes parse those parameters?
>
> 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!
> Also, refer to Kai-Uwe Bux's post from this morning... Very well put!
--
Nick Keighley
|
|
0
|
|
|
|
Reply
|
nick_keighley_nospam (4574)
|
3/13/2008 9:21:04 AM
|
|
On Thu, 13 Mar 2008 10:32:18 +0200, Juha Nieminen wrote:
> Dmitry A. Kazakov wrote:
>> On Wed, 12 Mar 2008 17:28:12 +0200, Juha Nieminen wrote:
>>
>>> 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.)
--
Regards,
Dmitry A. Kazakov
http://www.dmitry-kazakov.de
|
|
0
|
|
|
|
Reply
|
mailbox2 (6354)
|
3/13/2008 9:23:48 AM
|
|
Juha Nieminen a �crit :
> Daniel T. wrote:
>> On Mar 12, 11:26 am, Juha Nieminen <nos...@thanks.invalid> wrote:
>>> Daniel T. wrote:
>>>>> virtual void performAction(const std::string& action,
>>>>> const std::string& parameters);
>>>> To borrow Nick Keighley's example (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.
>>> You have still not answered my question: How do you propose I do that?
>>> It almost sounds like you are proposing a delegation paradigm, something
>>> which C++ doesn't support directly.
>> 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
|
|
0
|
|
|
|
Reply
|
michael.doubez (922)
|
3/13/2008 9:38:49 AM
|
|
In message <0FSBj.7629$y83.2338@trndny06>, H. S. Lahman
<hsl@pathfindermda.com> writes
>Responding to Herring...
>
>> So now you have to expose that attribute, and the client has to have
>> access to the ordering relation which was previously encapsulated in the
>> collection.
>
>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.
>
>> And if the number of collections increases beyond two, that "simple
>> interleaving" doesn't scale well, as it starts to look more and more
>> like a complete (partial) sort, so really the client, not the
>> collection, is now responsible for the ordering.
>
>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"?
--
Richard Herring
|
|
0
|
|
|
|
Reply
|
Richard
|
3/13/2008 10:12:22 AM
|
|
On 13 Mar, 09:14, Nick Keighley <nick_keighley_nos...@hotmail.com>
wrote:
> On 12 Mar, 12:23, dave_mikes...@fastmail.fm wrote:
> > On Mar 12, 6:38 am,Nick Keighley<nick_keighley_nos...@hotmail.com>
> > wrote:
> > > On 12 Mar, 10:45, "Daniel T." <danie...@earthlink.net> wrote
> > > > Juha Nieminen <nos...@thanks.invalid> wrote:
> > > > > Daniel T. wrote:
> > > > > > Juha Nieminen <nos...@thanks.invalid> wrote:
> > > > > > > It's not really that you need dynamic cast to *know* if the
> > > > > > > shape is a Square. In this case you need it if you want to *do=
*
> > > > > > > something to the object if it's a Square, and this operation i=
s
> > > > > > > not supported by Shape, only by Square.
>
> > > > > > And therein lies the problem. "you want to *do* something to the=
> > > > > > object if it's a Square". If you find yourself in that situation=
,
> > > > > > the design has already slid downhill. IMHO.
>
> > > > > And what is the alternative you propose?
>
> > > > Don't try to *do* things to objects. Objects are supposed to do for
> > > > themselves, clients *notify* the objects of things, they don't order=
> > > > them around.
>
> > > > > I can only think of one possibility: Clutter the 'Shape' base clas=
s
> > > > > with Square-specific virtual functions. This defies all good OO
> > > > > design.
>
> > > > What is it you are trying to tell squares that you don't want any ot=
her
> > > > shapes to know about? Why the big secret?
>
> > > "colour all the squares yellow"
>
> > > If the shapes example is a bit fanciful make it a UML editor.
> > > I want all the interfaces to stand out by changeing their
> > > colour and my model is huge.
>
> > > "colour all the interfaces yellow"
>
> > You can solve that by putting homogeneous elements in their own
> > container for just such operations =A0If 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.
>
> =A0 =A0forall drob in all_drawable_collection
> =A0 =A0 =A0 Square* square =3D dynamic_cast<Square*>drob;
> =A0 =A0 =A0 if (square !=3D 0)
> =A0 =A0 =A0 =A0 =A0 =A0all_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 =3D 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 =3D 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 =3D new DrawableClass();
all_classes.add_object(d)
case INTERFACE_TYPE:
d =3D 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 =3D 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>
--
Nick Keighley
|
|
0
|
|
|
|
Reply
|
nick_keighley_nospam (4574)
|
3/13/2008 10:29:56 AM
|
|
On Mar 13, 3:20 am, Juha Nieminen <nos...@thanks.invalid> wrote:
> Daniel T. wrote:
> > On Mar 12, 11:26 am, Juha Nieminen <nos...@thanks.invalid> wrote:
> >> Daniel T. wrote:
> >>>> virtual void performAction(const std::string& action,
> >>>> const std::string& parameters);
> >>> To borrow Nick Keighley's example (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.
> >> You have still not answered my question: How do you propose I do that?
> >> It almost sounds like you are proposing a delegation paradigm, something
> >> which C++ doesn't support directly.
>
> > 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.
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.
|
|
0
|
|
|
|
Reply
|
dave_mikesell (189)
|
3/13/2008 11:55:02 AM
|
|
On Mar 12, 1:23 pm, dave_mikes...@fastmail.fm wrote:
> On Mar 12, 6:38 am, Nick Keighley <nick_keighley_nos...@hotmail.com>
> wrote:
> > On 12 Mar, 10:45, "Daniel T." <danie...@earthlink.net> wrote:
> > > Juha Nieminen <nos...@thanks.invalid> wrote:
> > > > Daniel T. wrote:
> > > > > Juha Nieminen <nos...@thanks.invalid> wrote:
> > > > > > It's not really that you need dynamic cast to *know* if the
> > > > > > shape is a Square. In this case you need it if you want to *do*
> > > > > > something to the object if it's a Square, and this operation is
> > > > > > not supported by Shape, only by Square.
> > > > > And therein lies the problem. "you want to *do*
> > > > > something to the object if it's a Square". If you find
> > > > > yourself in that situation, the design has already
> > > > > slid downhill. IMHO.
> > > > And what is the alternative you propose?
> > > Don't try to *do* things to objects. Objects are supposed
> > > to do for themselves, clients *notify* the objects of
> > > things, they don't order them around.
> > > > I can only think of one possibility: Clutter the 'Shape'
> > > > base class with Square-specific virtual functions. This
> > > > defies all good OO design.
> > > What is it you are trying to tell squares that you don't
> > > want any other shapes to know about? Why the big secret?
> > "colour all the squares yellow"
> > If the shapes example is a bit fanciful make it a UML editor.
> > I want all the interfaces to stand out by changeing their
> > colour and my model is huge.
> > "colour all the interfaces yellow"
> 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.
--
James Kanze (GABI Software) email:james.kanze@gmail.com
Conseils en informatique orient=E9e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S=E9mard, 78210 St.-Cyr-l'=C9cole, France, +33 (0)1 30 23 00 34
|
|
0
|
|
|
|
Reply
|
james.kanze (9594)
|
3/13/2008 12:37:39 PM
|
|
On 13 Mar, 10:29, Nick Keighley <nick_keighley_nos...@hotmail.com>
wrote:
> On 13 Mar, 09:14, Nick Keighley <nick_keighley_nos...@hotmail.com>
> wrote:
> > On 12 Mar, 12:23, dave_mikes...@fastmail.fm wrote:
> > > On Mar 12, 6:38 am,Nick Keighley<nick_keighley_nos...@hotmail.com>
> > > wrote:
> > > > On 12 Mar, 10:45, "Daniel T." <danie...@earthlink.net> wrote
> > > > > Juha Nieminen <nos...@thanks.invalid> wrote:
> > > > > > Daniel T. wrote:
> > > > > > > Juha Nieminen <nos...@thanks.invalid> wrote:
> > > > > > > > It's not really that you need dynamic cast to *know* if the
> > > > > > > > shape is a Square. In this case you need it if you want to *=
do*
> > > > > > > > something to the object if it's a Square, and this operation=
is
> > > > > > > > not supported by Shape, only by Square.
>
> > > > > > > And therein lies the problem. "you want to *do* something to t=
he
> > > > > > > object if it's a Square". If you find yourself in that situati=
on,
> > > > > > > the design has already slid downhill. IMHO.
>
> > > > > > And what is the alternative you propose?
>
> > > > > Don't try to *do* things to objects. Objects are supposed to do fo=
r
> > > > > themselves, clients *notify* the objects of things, they don't ord=
er
> > > > > them around.
>
> > > > > > I can only think of one possibility: Clutter the 'Shape' base cl=
ass
> > > > > > with Square-specific virtual functions. This defies all good OO
> > > > > > design.
>
> > > > > What is it you are trying to tell squares that you don't want any =
other
> > > > > shapes to know about? Why the big secret?
>
> > > > "colour all the squares yellow"
>
> > > > If the shapes example is a bit fanciful make it a UML editor.
> > > > I want all the interfaces to stand out by changeing their
> > > > colour and my model is huge.
>
> > > > "colour all the interfaces yellow"
>
> > > You can solve that by putting homogeneous elements in their own
> > > container for just such operations =A0If you want to globally change t=
he
> > > 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.
>
> > =A0 =A0forall drob in all_drawable_collection
> > =A0 =A0 =A0 Square* square =3D dynamic_cast<Square*>drob;
> > =A0 =A0 =A0 if (square !=3D 0)
> > =A0 =A0 =A0 =A0 =A0 =A0all_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++
> =A0 =A0 Ui::hiliteAllInterfaces()
> =A0 =A0 {
> =A0 =A0 =A0 =A0 =A0apply (colour_item_yellow, all_interfaces_collection);
> =A0 =A0 }
>
> 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()
> {
> =A0 =A0 Drawable* d =3D drawable_factory.create(curr_object_type);
> =A0 =A0 picture->add_object(drawable_factory.create(curr_object_type)));
>
> }
>
> Drawable* DrawableFactory::create (DrawableType t)
> {
> =A0 =A0 switch (t)
> =A0 =A0 {
> =A0 =A0 case CLASS_TYPE:
> =A0 =A0 =A0 =A0 return new DrawableClass();
> =A0 =A0 case INTERFACE_TYPE:
> =A0 =A0 =A0 =A0 return new DrawableInterface();
> =A0 =A0 }
>
> }
>
> Now how to update the subclass collections. These are shown as deltas
> from above
>
> //// option A
> //// add to each collection
> Ui::add_object()
> {
> =A0 =A0 Drawable* d =3D drawable_factory.create(curr_object_type))
> =A0 =A0 picture->add_object(d);
>
> =A0 =A0 switch (curr_object_type)
> =A0 =A0 {
> =A0 =A0 case CLASS_TYPE:
> =A0 =A0 =A0 =A0 all_classes.add_object(d)
> =A0 =A0 case INTERFACE_TYPE:
> =A0 =A0 =A0 =A0 all_interfaces.add_object(d)
> =A0 =A0 }
>
> }
>
> // BLETCH. two switch statements.
> // brittle hard to modify code
>
> //// option B
> //// move more into factory
>
> Ui::add_object()
> {
> =A0 =A0 drawable_factory.create(curr_object_type)
>
> }
>
> Drawable* DrawableFactory::create (DrawableType t)
> {
> =A0 =A0 Drawable* d;
>
> =A0 =A0 switch (t)
> =A0 =A0 {
> =A0 =A0 case CLASS_TYPE:
> =A0 =A0 =A0 =A0 d =3D new DrawableClass();
> =A0 =A0 =A0 =A0 all_classes.add_object(d)
> =A0 =A0 case INTERFACE_TYPE:
> =A0 =A0 =A0 =A0 d =3D new DrawableInterface();
> =A0 =A0 =A0 =A0 all_interfaces.add_object(d)
> =A0 =A0 }
>
> =A0 =A0 return d;
>
> }
>
> // the factory seems a bit "busy"
>
> // option C
> // move more into subclass
>
> DrawableClass::created()
> {
> =A0 =A0 all_classes.add_object(this)
>
> }
>
> Ui::add_object()
> {
> =A0 =A0 Drawable* d =3D drawable_factory.create(curr_object_type))
> =A0 =A0 picture->add_object(d);
> =A0 =A0 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 =3D 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...
--
Nick Keighley
|
|
0
|
|
|
|
Reply
|
nick_keighley_nospam (4574)
|
3/13/2008 1:11:59 PM
|
|
On Mar 11, 5:03 pm, Matthias Buelow <m...@incubus.de> wrote:
> James Kanze wrote:
> > That depends on whether you want your program to work reliably
> > or not.
> 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.
> > Violate the type system (static or dynamic), and the
> > code doesn't 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.
> > It's a burden on the programmer, yes. It means that he
> > actually has to design what he writes, and document it up
> > front, in a standard way that even a compiler can
> > understand.
> 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.
--
James Kanze (GABI Software) email:james.kanze@gmail.com
Conseils en informatique orient=E9e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S=E9mard, 78210 St.-Cyr-l'=C9cole, France, +33 (0)1 30 23 00 34
|
|
0
|
|
|
|
Reply
|
james.kanze (9594)
|
3/13/2008 1:13:33 PM
|
|
Juha Nieminen <nos...@thanks.invalid> wrote:
> Daniel T. wrote:
> > Juha Nieminen <nos...@thanks.invalid> wrote:
>
> > > 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...
>
> 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.
> > 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?
You keep asking for a better design, better than what?
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/13/2008 5:41:23 PM
|
|
Juha Nieminen wrote:
> Daniel T. wrote:
>> Juha Nieminen <nospam@thanks.invalid> wrote:
>>
>>> 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...
>
> 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?
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
|
|
0
|
|
|
|
Reply
|
jkherciueh (3186)
|
3/13/2008 5:48:32 PM
|
|
Daniel T. wrote:
> 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.
|
|
0
|
|
|
|
Reply
|
nospam270 (2853)
|
3/13/2008 5:48:39 PM
|
|
Michael DOUBEZ wrote:
> 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.)
|
|
0
|
|
|
|
Reply
|
nospam270 (2853)
|
3/13/2008 5:58:55 PM
|
|
dave_mikesell@fastmail.fm wrote:
> 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.
|
|
0
|
|
|
|
Reply
|
nospam270 (2853)
|
3/13/2008 6:06:15 PM
|
|
Dmitry A. Kazakov wrote:
> 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.
|
|
0
|
|
|
|
Reply
|
nospam270 (2853)
|
3/13/2008 6:09:00 PM
|
|
Daniel T. wrote:
> Andy Champ <no.way@nospam.com> wrote:
>
>> 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.
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
|
|
0
|
|
|
|
Reply
|
no.way7055 (86)
|
3/13/2008 9:25:23 PM
|
|
Juha Nieminen a �crit :
> Michael DOUBEZ wrote:
>> 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.)
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
|
|
0
|
|
|
|
Reply
|
michael.doubez (922)
|
3/14/2008 8:40:49 AM
|
|
On Thu, 13 Mar 2008 20:09:00 +0200, Juha Nieminen wrote:
> Dmitry A. Kazakov wrote:
>> 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.
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. ]
--
Regards,
Dmitry A. Kazakov
http://www.dmitry-kazakov.de
|
|
0
|
|
|
|
Reply
|
mailbox2 (6354)
|
3/14/2008 9:58:54 AM
|
|
On Mar 13, 9:14 am, Juha Nieminen <nos...@thanks.invalid> wrote:
> Daniel T. wrote:
> > Juha Nieminen <nos...@thanks.invalid> wrote:
> >> 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...
> 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.
> > 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?
That it's easier to talk than to do? (Or that we ain't got
religion.)
--
James Kanze (GABI Software) email:james.kanze@gmail.com
Conseils en informatique orient=E9e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S=E9mard, 78210 St.-Cyr-l'=C9cole, France, +33 (0)1 30 23 00 34
|
|
0
|
|
|
|
Reply
|
james.kanze (9594)
|
3/14/2008 10:37:59 AM
|
|
On Mar 13, 6:48 pm, Kai-Uwe Bux <jkherci...@gmx.net> wrote:
> Juha Nieminen wrote:
> > Daniel T. wrote:
> >> Juha Nieminen <nos...@thanks.invalid> wrote:
> > 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.
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.
--
James Kanze (GABI Software) email:james.kanze@gmail.com
Conseils en informatique orient=E9e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S=E9mard, 78210 St.-Cyr-l'=C9cole, France, +33 (0)1 30 23 00 34
|
|
0
|
|
|
|
Reply
|
james.kanze (9594)
|
3/14/2008 10:45:36 AM
|
|
Dmitry A. Kazakov wrote:
> 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.
|
|
0
|
|
|
|
Reply
|
nospam270 (2853)
|
3/14/2008 1:20:06 PM
|
|
<dave_mikesell@fastmail.fm> wrote in message
news:107c12a2-81f4-43c6-9c4f-b5af0fc75046@p73g2000hsd.googlegroups.com...
> On Mar 12, 5:45 am, James Kanze <james.ka...@gmail.com> wrote:
>> On Mar 12, 11:00 am, "Daniel T." <danie...@earthlink.net> wrote:
>> > That is one of many ways to handle it, but the fundamental
>> > question is still there; what is it you are wanting to notify
>> > all the Squares of that you want to keep it secret from the
>> > rest of the Shapes?
>>
>> You've got it backwards. Why do you want to encumber all the
>> rest of the Shapes with something that is only relevant to
>> Squares?
>
> You don't. Whatever functionality that we're talking about here
> belongs either in Square or some other interface (not Shape) that it
> implements. The problem here is that Shape, in this example, is too
> generic to be very useful.
So let's suppose that Square for example also inherits from Cornered
(this isn't a great example, but without changing from Shape/Square to
something else, I'm stuck with it).
So you'd be happy to dynamic_cast from Shape * to Cornered *,
but not from Shape * to Square *?
Or in other words if you need to dynamic_cast, prefer a cross-cast
to another abstract base class to a downcast to a concrete derived class?
That makes reasonable sense to me. I think of the two dynamic_casts
I can recall using, one was this, and one was imposed by a form of
reality not unlike the ordering problem, though not actually equal to it.
|
|
0
|
|
|
|
Reply
|
chris.dearlove (38)
|
3/14/2008 1:52:30 PM
|
|
On Mar 14, 8:20=A0am, Juha Nieminen <nos...@thanks.invalid> wrote:
> Dmitry A. Kazakov wrote:
> > If non-squares are to be ignored, you create a *new*
> > container with only squares in it.
>
> =A0 And you lose the ordering relation the squares had with respect to the=
> other objects.
>
> =A0 Sometimes type-specific operations need to be done in the exact order
> in which the objects appear in the container.
Can you give an example or two of these type-specific operations?
|
|
0
|
|
|
|
Reply
|
dave_mikesell (189)
|
3/14/2008 2:18:38 PM
|
|
On Fri, 14 Mar 2008 15:20:06 +0200, Juha Nieminen wrote:
> Dmitry A. Kazakov wrote:
>> 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.
Who implements the relation the container or the elements?
> Sometimes type-specific operations need to be done in the exact order
> in which the objects appear in the container.
The operations are of squares, so the order (with is defined by an
operation) can be of squares only.
> Of course if I can do something to the square only, I do that already.
> That's not the problem here.
Yes, you have a design problem.
--
Regards,
Dmitry A. Kazakov
http://www.dmitry-kazakov.de
|
|
0
|
|
|
|
Reply
|
mailbox2 (6354)
|
3/14/2008 2:33:07 PM
|
|
On Mar 14, 8:52 am, "Christopher Dearlove"
<chris.dearl...@baesystems.com> wrote:
> <dave_mikes...@fastmail.fm> wrote in message
>
> news:107c12a2-81f4-43c6-9c4f-b5af0fc75046@p73g2000hsd.googlegroups.com...
>
> > On Mar 12, 5:45 am, James Kanze <james.ka...@gmail.com> wrote:
> >> On Mar 12, 11:00 am, "Daniel T." <danie...@earthlink.net> wrote:
> >> > That is one of many ways to handle it, but the fundamental
> >> > question is still there; what is it you are wanting to notify
> >> > all the Squares of that you want to keep it secret from the
> >> > rest of the Shapes?
>
> >> You've got it backwards. Why do you want to encumber all the
> >> rest of the Shapes with something that is only relevant to
> >> Squares?
>
> > You don't. Whatever functionality that we're talking about here
> > belongs either in Square or some other interface (not Shape) that it
> > implements. The problem here is that Shape, in this example, is too
> > generic to be very useful.
>
> So let's suppose that Square for example also inherits from Cornered
> (this isn't a great example, but without changing from Shape/Square to
> something else, I'm stuck with it).
>
> So you'd be happy to dynamic_cast from Shape * to Cornered *,
> but not from Shape * to Square *?
No, I would avoid heterogeneous containers in the first place. If my
container contains Shape *, I expect only to use Shape operations on
its elements.
|
|
0
|
|
|
|
Reply
|
dave_mikesell (189)
|
3/14/2008 4:06:41 PM
|
|
<dave_mikesell@fastmail.fm> wrote in message
news:bfa10f51-4374-443e-a573-d6e6462374d1@2g2000hsn.googlegroups.com...
> No, I would avoid heterogeneous containers in the first place. If my
> container contains Shape *, I expect only to use Shape operations on
> its elements.
There's a split here between the purists, who will do anything to avoid
this,
and the realists, who agree that you should rarely do it, but recognise that
occasionally it's necessary (meaning that the alternatives are worse).
Addressing my comments to the realists, do you think a dynamic cross-cast
to an alternative abstract base class is generally to be preferred to a
dynamic down-cast to a non-abstract derived class in such cases?
(I've deleted comp.object, as I expect the realists to be in comp.lang.c++.)
|
|
0
|
|
|
|
Reply
|
chris.dearlove (38)
|
3/14/2008 5:07:50 PM
|
|
James Kanze wrote:
> On Mar 13, 9:14 am, Juha Nieminen <nos...@thanks.invalid> wrote:
>> Daniel T. wrote:
>>> Juha Nieminen <nos...@thanks.invalid> wrote:
>
>>>> 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...
>
>> 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.
Although the vast majority of code requires no goto's, they are mostly
just out of fashion. I still see and use them frequently in embedded
code and device drivers.
Gotos are extremely useful for implementing finite state machines, since
there is generally no need to care how a particular state was reached,
and no need for a return address. You don't have any choice in a
pre-stack environment; you can't call a function until you have
initialized the memory controller, so you do a lot of (very orderly)
direct branching.
In device drivers, goto is used locally in (e.g.) almost all ioctl
handlers. There are typically two labels near the end of such a
function, corresponding to successful return and error-condition return.
You could, in principle, use try/catch instead, but the performance
would be prohibitively poor, the code would be less clear, and woe
betide you if ever someone forgot a catch block. (By contrast,
forgetting a goto's label produces a compile-time error.)
|
|
0
|
|
|
|
Reply
|
jeff34 (1594)
|
3/14/2008 5:12:56 PM
|
|
James Kanze wrote:
> On Mar 13, 6:48 pm, Kai-Uwe Bux <jkherci...@gmx.net> wrote:
>> Juha Nieminen wrote:
>>> Daniel T. wrote:
>>>> Juha Nieminen <nos...@thanks.invalid> wrote:
>
>>> 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.
>
> 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.
The suggestion that dynamic_case is necessary for passing
application-layer objects through lower level transport layers is only
marginally true. It is true that casts are used for this purpose, but
IME they are almost always of the reinterpret_cast variety. They have
to be: A general-purpose transport layer does not know about the higher
layer's object types. The transport mechanism is just sending bits over
a wire, or at most doing some kind of byte-order or line-ending
conversion. For dynamic_cast to be relevant, the transport mechanism
has to know about some base type used by the higher level. The only
case in which I could see this happening is if the transport layer
provides a base type with virtual methods, maybe "serializable_object"
or something, from which higher-layer objects are supposed to derive.
IMO, that kind of design is completely upside-down and unnecessarily
invasive.
|
|
0
|
|
|
|
Reply
|
jeff34 (1594)
|
3/14/2008 5:14:49 PM
|
|
>>> So now you have to expose that attribute, and the client has to have
>>> access to the ordering relation which was previously encapsulated in the
>>> collection.
>>
>> 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.
The problem is that the collection classes for the individual
associations can only capture <at most> part of the ordering constraint...
>> 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.
I see your concern, but I don't see it as a significant problem. (There
is also a way to address the concern directly that I'll get to later.)
The client has relationships with two different sorts of objects and
that, quite naturally, are abstracted as two separate binary
associations among peer classes in the OOA. That's because the real
collaboration is the processing that [Client] does with the [ClassA] or
[ClassB] object in hand. Each of those associations is necessarily
ordered in order to solve the problem in hand but that ordering only
applies to the simple binary association.
The fact that both sets of objects also need to be somehow ordered
*together* is a separate problem constraint than the ordering of the
individual collections. That joint constraint spans the associations so
it can (at most) only be partially implemented within the collections
(e.g., as an alternative ordering as suggested in my example). It is
that coordination _between collections_ that appears in the client
because the client logically owns any coordination among its
participation in its associations.
Now one can get around that by providing yet another class to act as a
collection of collections to encapsulate that synchronization:
[Client]
| 1
|
| R1 <<ordered>>
|
| accesses
| 1 1
[HCollection] ------------------------+
| 1 |
| |
| R2 <<ordered>> | R3 <<ordered>>
| |
| coordinates with | coordinates with
| * | *
[ClassA] [ClassB]
While we have conveniently encapsulated the overall ordering constraint,
there are several problems with this. We have introduced [HCollection]
as a peer object (i.e., at a higher level of abstraction than the R2 and
R3 collections) that has no counterpart in the customer domain; it is a
pure OOP implementation entity from the computing domain. So it has no
business being in the OOA solution where functional requirements are
resolved.
The second problem is that for the OP's situation we have obscured the
access to just [ClassA] or [ClassB] objects. That is especially
troublesome when the ordering for those situations is different that the
overall ordering. We can solve that by providing additional direct
associations between [Client] and [ClassA] and [ClassB], but that
complicates the design and introduces more collections to be managed at
the OOP level.
The third problem is that [Client] now receives a stream of both
[ClassA] and [ClassB] objects from [HCollection] but the client needs to
process objects from each class differently. The [Client] could do that
in a typesafe manner if it were navigating R2 and R3 directly, but it
has no way of knowing which type the next object from [HCollection] is.
Since C++ has no builtin facility for managing heterogeneous collections
in a typesafe manner, the convenient way to do this is via dynamic_cast.
But to do that [Client] must understand that [HCollection] manages the
[ClassA] and [ClassB] collections; it must know what types [HCollection]
manages just to properly use dynamic_cast. IOW, [Client] must know who
[HCollection] collaborates with (i.e., [Client] must understand the R2
and R3 associations) and that knowledge is hard-wired into [Client]'s
implementation.
The point is that as soon as one introduces [HCollection] as a peer
problem space entity, one potentially opens a can of worms that creates
implementation dependencies in [Client]'s implementation on software
structure that is removed from its immediate context (i.e., it depends
on [HCollection]'s associations rather than its own associations). Using
dynamic_cast just manifests that more fundamental OOA/D problem.
To put it a different way, the overall ordering constraint needs to be
implemented somewhere. It can't by fully implemented within the binary
associations to [ClassA] and [ClassB] that are defined in the problem
domain. IOW, one must encapsulate the coordination somewhere and the
problem space entity with both associations in common seems like a good
choice.
One could argue that the deficiency of C++ is the root problem. If one
had a language that has builtin, typesafe support of heterogeneous
collections (e.g., Ada), one can encapsulate the synchronization in a
collection. But lacking that, having the client coordinate its binary
associations explicitly is the better choice.
Having said all this, there is a way to encapsulate the overall ordering
in [HCollection] to satisfy your concerns and not use dynamic_cast. If
we had additional, conditional relationships:
0..1 current for R4 0..1
[Client] ----------------------------- [ClassA]
0..1 current for R5 0..1
[Client] ----------------------------- [ClassB]
to capture the notion of the current object for [Client] to process,
then only one relationship would be "live" (instantiated) at a time.
Then HCollection::getNext() doesn't return an object. Instead it removes
any existing instantiation of R3 and R4 and instantiates the appropriate
relationship based on comparing the sizes of the next object in each
collection. When that action returns the [Client] object then looks to
see which conditional association is instantiated.
This removes any responsibility from [Client] for knowing which object
to process is next. It also serializes the concerns of a heterogeneous
object stream into managing relationship instantiation for the "current"
object.
I might use this solution if I could identify an entity in the problem
space that naturally has the responsibility for managing disparate
entities. For example, the notion of a Queue Manager might be a relevant
concept for a domain expert for doing something like managing messages
in different formats from different sources in a FIFO manner. Then one
rationalizes the R2 and R3 collections as particular source queues and
one renames [HCollection] to be [QueueManager].
However, another good OOA/D practice is to try to minimize conditional
associations because they require additional executable code to manage
and they tend to be more fragile during maintenance. So for something as
simple as the OP's example, I would still probably let [Client] do the
interleaving.
<aside>
Note that this would be even better if one decided to break up [Client]
into different objects for the processing around [ClassA] and [ClassB]
objects. Continuing the example above, that might be the case if the
message processor is subclassed by format, which just happens to map
directly to message source.
Now [QueueManager] instantiates the association to the right subclass
client. This changes the flow of control design because now
[QueueManager] would be the one to trigger the processing by also
sending a message to announce a message was ready to the right format
processor that, in turn, would navigate the relationship. That is, when
it is time to process another message, a <OO> message is sent to
[QueueManager] who selects the right message from among the queues,
instantiates the relationship, and sends a <OO> message to the right
client to do its thing. That's actually a much more OO-like way to
connect the dots of flow of control than telling the client to do its
thing and having the client, in turn, tell [QueueManager] to get the
next widget.
</aside>
>> 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"?
Not quite. I am suggesting that violating good OOA/D practice to provide
elegance, reduced keystrokes, or developer convenience tends to reduce
long-term maintainability. IOW, when the choice is between misusing
dynamic_cast in order to have the convenience of a heterogeneous
collection vs. maintainability, maintainability wins.
--
There is nothing wrong with me that could
not be cured by a capful of Drano.
H. S. Lahman
hsl@pathfindermda.com
Pathfinder Solutions
http://www.pathfindermda.com
blog: http://pathfinderpeople.blogs.com/hslahman
"Model-Based Translation: The Next Step in Agile Development". Email
info@pathfindermda.com for your copy.
Pathfinder is hiring:
http://www.pathfindermda.com/about_us/careers_pos3.php.
(888)OOA-PATH
|
|
0
|
|
|
|
Reply
|
hsl (217)
|
3/14/2008 7:12:08 PM
|
|
Kai-Uwe Bux wrote:
>
> 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.
>
How about W3C DOM (http://www.w3.org/TR/DOM-Level-3-Core/core.html)
model I cited earlier?
The DOM provides a family of objects derived form a Node. The Node
objects contains two containers (both of Nodes), a NodeList of child
nodes and a NamedNodeMap of Attribute Nodes. When parsing or
manipulating a document most children are Element and Text objects and
the attribute nodes are Attr objects.
One could add the extension member functions of the known child objects
as virtual methods, but there are other classes of objects the derive
form DOM objects in other standards, XHTML for instance.
Like all things XML, DOM is designed to be extensible, so there is no
way of knowing up front which virtual methods would be required. In my
opinion giving the base class knowledge of its children through virtual
methods is poor design that breaks the fundamental principal of
extensibility.
--
Ian Collins.
|
|
0
|
|
|
|
Reply
|
ian-news (9881)
|
3/14/2008 7:18:51 PM
|
|
Jeff Schwab wrote:
<snip>
> You don't have any choice in a
> pre-stack environment; you can't call a function until you have
> initialized the memory controller, so you do a lot of (very orderly)
> direct branching.
>
Not completely true. You have a couple of registers - SP and BP on an
x86 machine spring to mind - which aren't a lot of use without memory.
Except that they can be a handy place to put a return address!
Been there, done that, got the EPROM.
Andy
|
|
0
|
|
|
|
Reply
|
no.way7055 (86)
|
3/14/2008 10:46:24 PM
|
|
Andy Champ wrote:
> Jeff Schwab wrote:
> <snip>
>> You don't have any choice in a pre-stack environment; you can't call a
>> function until you have initialized the memory controller, so you do a
>> lot of (very orderly) direct branching.
>>
>
> Not completely true.
What's not true?
> You have a couple of registers - SP and BP on an
> x86 machine spring to mind - which aren't a lot of use without memory.
>
> Except that they can be a handy place to put a return address!
Right, so you do indirect branching as well as direct. :)
> Been there, done that, got the EPROM.
How do you use store and use the address in the register? You can't use
call/return. You have to branch explicitly. You can indeed store
addresses in registers, and frequently do; you can even have one level
of "call depth" by using only only the lower halves of the
general-purpose registers for local data, left-shifting before a "call,"
and right-shifting after the "return." But none of this gets you around
the branch, or jump, or -- if you're writing C++ code -- the goto.
|
|
0
|
|
|
|
Reply
|
jeff34 (1594)
|
3/14/2008 11:42:38 PM
|
|
Juha Nieminen <nospam@thanks.invalid> wrote:
> Daniel T. wrote:
> > Juha Nieminen <nos...@thanks.invalid> wrote:
> > > Daniel T. wrote:
> > > > > virtual void performAction(const std::string& action,
> > > > > const std::string& parameters);
> > > >
> > > > To borrow Nick Keighley's example (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.
> > >
> > > You have still not answered my question: How do you propose I
> > > do that? It almost sounds like you are proposing a delegation
> > > paradigm, something which C++ doesn't support directly.
> >
> > 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.
This is unreal. Of course he didn't post code, the person presenting the
problem didn't post code either, but I don't hear you saying a word
about that.
> > 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...
You don't know how to make a virtual function in a base class?
> Please show me some concrete C++ code, not just some abstract
> theoretical concepts.
Please post a concrete problem.
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/15/2008 1:22:46 AM
|
|
Juha Nieminen <nospam@thanks.invalid> wrote:
>
> 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).
Post the problem statement that requires the above, post some code doing
it and maybe we can provide a better solution.
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/15/2008 1:25:30 AM
|
|
On Mar 14, 11:14=A0am, Jeff Schwab <j...@schwabcenter.com> wrote:
> James Kanze wrote:
>
> > The problem is that dynamic_cast is really only justified in
> > fairly large applications, where layering becomes an important
> > issue. =A0As 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). =A0It's when you start having
> > to define separate layers, frameworks and such, that it becomes
> > important.
>
> The suggestion that dynamic_case is necessary for passing
> application-layer objects through lower level transport layers is only
> marginally true. =A0It is true that casts are used for this purpose, but
> IME they are almost always of the reinterpret_cast variety. =A0They have
> to be: =A0A general-purpose transport layer does not know about the higher=
> layer's object types. =A0The transport mechanism is just sending bits over=
> a wire, or at most doing some kind of byte-order or line-ending
> conversion. =A0For dynamic_cast to be relevant, the transport mechanism
> has to know about some base type used by the higher level.
I think the compiler should be involved in generating the
code used by the transport layer.
http://preview.tinyurl.com/38femh
>=A0The only
> case in which I could see this happening is if the transport layer
> provides a base type with virtual methods, maybe "serializable_object"
> or something, from which higher-layer objects are supposed to derive.
> IMO, that kind of design is completely upside-down and unnecessarily
> invasive.- Hide quoted text -
>
Yes, that would be a mess.
Going back to what I outlined in that prior thread... I don't
use any dynamic_casts so far in terms of the code that is written
programmatically. If a vector<Shape*> is being transmitted, a
constant integer precedes the data for each object. The "compiler"
(my software isn't a compiler, but I think compilers should do what
my stuff does.) outputs a constant integer for each distinct type it
encounters. If the classes are:
Shape
|
Square
the constants output would be:
unsigned int const Shape_Num =3D 4201;
unsigned int const Square_Num =3D 4202;
And the Send functions would embed whichever one of those two values
is appropriate into the output stream. The receiving end
uses that value to interpret the subsequent data in the stream.
So far I don't find a need for dynamic_cast in this context. In
general I'm not completely opposed to dynamic_cast, but I like to
avoid it, and a search of what I'm working on turns up no uses of
it.
Brian Wood
Ebenezer Enterprises
www.webebenezer.net
|
|
0
|
|
|
|
Reply
|
coal (257)
|
3/15/2008 4:27:01 AM
|
|
On 14 mar, 18:14, Jeff Schwab <j...@schwabcenter.com> wrote:
> James Kanze wrote:
> > On Mar 13, 6:48 pm, Kai-Uwe Bux <jkherci...@gmx.net> wrote:
> >> Juha Nieminen wrote:
> >>> Daniel T. wrote:
> >>>> Juha Nieminen <nos...@thanks.invalid> wrote:
> >>> 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.
> > 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.
> The suggestion that dynamic_case is necessary for passing
> application-layer objects through lower level transport layers
> is only marginally true. It is true that casts are used for
> this purpose, but IME they are almost always of the
> reinterpret_cast variety.
That very much depends on the transport layer, I would think.
> They have to be: A general-purpose transport layer does not
> know about the higher layer's object types.
And a specialize transport layer only knows a little. It
doesn't take much specialization to ensure that all of the
objects transported have a common base (i.e.
TransportableObject?), and are polymorphic.
> The transport mechanism is just sending bits over a wire,
Maybe, maybe not. There's not necessarily a wire; it's sending
objects between to components. There are many different types
of transport layer. (And even if there is a wire, many
protocols will have all transportable objects derive from some
common base type, or a small set of common base types.)
> or at most doing some kind of byte-order or line-ending
> conversion.
I think you're confusing the transport layer of OSI network
protocols (TCP, etc.) with the transport layer of OO design.
> For dynamic_cast to be relevant, the transport mechanism has
> to know about some base type used by the higher level. The
> only case in which I could see this happening is if the
> transport layer provides a base type with virtual methods,
> maybe "serializable_object" or something, from which
> higher-layer objects are supposed to derive. IMO, that kind
> of design is completely upside-down and unnecessarily
> invasive.
Why? If you want an object to be transportable, you say so. It
seems to me to be part of the basic principles of static type
checking. You're transporting message objects between higher
level components which have connected to the transport layer.
You don't (and can't) transport just anything.
--
James Kanze (GABI Software) email:james.kanze@gmail.com
Conseils en informatique orient=E9e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S=E9mard, 78210 St.-Cyr-l'=C9cole, France, +33 (0)1 30 23 00 34
|
|
0
|
|
|
|
Reply
|
james.kanze (9594)
|
3/15/2008 9:54:26 AM
|
|
On 15 mar, 02:22, "Daniel T." <danie...@earthlink.net> wrote:
> Juha Nieminen <nos...@thanks.invalid> wrote:
> > > 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...
> You don't know how to make a virtual function in a base class?
In this context, not without breaking encapsulation.
--
James Kanze (GABI Software) email:james.kanze@gmail.com
Conseils en informatique orient=E9e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S=E9mard, 78210 St.-Cyr-l'=C9cole, France, +33 (0)1 30 23 00 34
|
|
0
|
|
|
|
Reply
|
james.kanze (9594)
|
3/15/2008 9:56:20 AM
|
|
James Kanze <james.kanze@gmail.com> wrote:
> On 15 mar, 02:22, "Daniel T." <danie...@earthlink.net> wrote:
> > Juha Nieminen <nos...@thanks.invalid> wrote:
> > > > 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...
> >
> > You don't know how to make a virtual function in a base class?
>
> In this context, not without breaking encapsulation.
Tell me, how does a virtual function that has no pre-condition and no
post-condition break encapsulation?
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/15/2008 12:50:48 PM
|
|
In article <ac86eb66-ae72-4d2c-adc7-38304d387cd6
@y77g2000hsy.googlegroups.com>, daniel_t@earthlink.net says...
[ ... ]
> There are any number of ways to do it, Dave Mikes mentioned one,
> another would be to have a virtual function in Shape that lets Shapes
> know that a "change rectangles to yellow" request has been made.
I would posit that (at least as stated) this would constitute quite a
poor design. Rather than "change rectangles to yellow", the request
should be dealt with much more abstractly -- something more like
"highlight interfaces", using a relatively abstract description of the
desired action instead of directly describing its physical
manifestation.
Although I can't say I've seen a use for this specifically in UML, let's
assume for the moment that it really was something you wanted. In that
case, I think stepping through the collection of all the objects and
setting the interfaces to highlighted is only a minor improvement over
stepping through them and setting rectangles to yellow.
Instead, if we want to be able to highlight all the interfaces (or
whatever) we'd share the "highlighted" vs. "normal" state (or perhaps
the current color) among all objects of that type:
struct UML_object {
int x_, y_;
virtual void draw(surface_t &) = 0;
};
class UML_interface : public UML_object {
static color_t color;
public:
static void highlight(bool state=true) {
static color_t colors[] = {
RGB(0,0,0),
RGB(255, 0,0)
};
color = colors[state];
// code to force re-draw goes here.
}
square(int x, int y) : x_(x), y_(y) {}
virtual void draw(surface_t &s) {
// draw "interface" on specified surface
}
};
class UML_class : public UML_object {
public:
UML_class(int x, int y) : x_(x), y_(y) {}
virtual void draw(surface_t &s) {
// draw "class" on specified surface
}
};
color_t UML_interface::color;
This way we don't need separate containers OR a dynamic_cast to
highlight all your UML_interface objects -- instead, you call:
UML_interface::highlight();
and they all highlight. To change them all back to normal, you call:
UML_interface::highlight(false);
IMO, if you want shared state, it's better to create real shared state
than to force all objects of that type to the same state, independently
of each other.
--
Later,
Jerry.
The universe is a figment of its own imagination.
|
|
0
|
|
|
|
Reply
|
jcoffin (2240)
|
3/15/2008 2:29:29 PM
|
|
Jeff Schwab wrote:
> Andy Champ wrote:
>> Jeff Schwab wrote:
>> <snip>
>>> You don't have any choice in a pre-stack environment; you can't call
>>> a function until you have initialized the memory controller, so you
>>> do a lot of (very orderly) direct branching.
>>>
>>
>> Not completely true.
>
> What's not true?
>
>> You have a couple of registers - SP and BP on an x86 machine spring to
>> mind - which aren't a lot of use without memory.
>>
>> Except that they can be a handy place to put a return address!
>
> Right, so you do indirect branching as well as direct. :)
>
>> Been there, done that, got the EPROM.
>
> How do you use store and use the address in the register? You can't use
> call/return. You have to branch explicitly. You can indeed store
> addresses in registers, and frequently do; you can even have one level
> of "call depth" by using only only the lower halves of the
> general-purpose registers for local data, left-shifting before a "call,"
> and right-shifting after the "return." But none of this gets you around
> the branch, or jump, or -- if you're writing C++ code -- the goto.
Well for one thing I was working in Assembler, not C++. No high level
language that I'm aware of will give you that much control.
The return?
mov ax,esp ; or bp
jmp [ax]
I think it goes. But my assembler books are at work, and I haven't
written much lately.
And no, you can't write assembler without some sort of goto, even when
you are using it to emulate a cleaner structure. OTOH, you don't have a
dynamic_cast either, which is where I started this thread!
Andy
|
|
0
|
|
|
|
Reply
|
no.way7055 (86)
|
3/15/2008 3:06:56 PM
|
|
On Mar 15, 3:54=A0am, James Kanze <james.ka...@gmail.com> wrote:
> On 14 mar, 18:14, Jeff Schwab <j...@schwabcenter.com> wrote:
>
> > The suggestion that dynamic_case is necessary for passing
> > application-layer objects through lower level transport layers
> > is only marginally true. =A0It is true that casts are used for
> > this purpose, but IME they are almost always of the
> > reinterpret_cast variety.
>
> That very much depends on the transport layer, I would think.
>
> > They have to be: =A0A general-purpose transport layer does not
> > know about the higher layer's object types.
>
> And a specialize transport layer only knows a little. =A0It
> doesn't take much specialization to ensure that all of the
> objects transported have a common base (i.e.
> TransportableObject?), and are polymorphic.
I don't think there is anything gained from doing it that way
and with virtual functions there is probably some perf. hit.
>
> > The transport mechanism is just sending bits over a wire,
>
> Maybe, maybe not. =A0There's not necessarily a wire; it's sending
> objects between to components. =A0There are many different types
> of transport layer. =A0(And even if there is a wire, many
> protocols will have all transportable objects derive from some
> common base type, or a small set of common base types.)
>
> > or at most doing some kind of byte-order or line-ending
> > conversion.
>
> I think you're confusing the transport layer of OSI network
> protocols (TCP, etc.) with the transport layer of OO design.
>
> > For dynamic_cast to be relevant, the transport mechanism has
> > to know about some base type used by the higher level. =A0The
> > only case in which I could see this happening is if the
> > transport layer provides a base type with virtual methods,
> > maybe "serializable_object" or something, from which
> > higher-layer objects are supposed to derive. =A0IMO, that kind
> > of design is completely upside-down and unnecessarily
> > invasive.
>
> Why? =A0If you want an object to be transportable, you say so. =A0It
> seems to me to be part of the basic principles of static type
> checking. =A0
The way I do it, you "say so" by adding Send/Receive function
prototypes to a class. The compiler will complain if it finds
implementations for those functions, but the class doesn't have
prototypes for them.
> You're transporting message objects between higher
> level components which have connected to the transport layer.
> You don't (and can't) transport just anything.
>
There has to be action from the programmer indicating what
types are going to be used in messages, but I don't think that
translates to inheritance being required. Using inheritance
like that seems just to get in the way and makes multiple
inheritance much more likely.
Brian Wood
Ebenezer Enterprises
www.webebenezer.net
|
|
0
|
|
|
|
Reply
|
coal (257)
|
3/15/2008 4:42:42 PM
|
|
James Kanze wrote:
> On 14 mar, 18:14, Jeff Schwab <j...@schwabcenter.com> wrote:
>> James Kanze wrote:
>>> On Mar 13, 6:48 pm, Kai-Uwe Bux <jkherci...@gmx.net> wrote:
>>>> Juha Nieminen wrote:
>>>>> Daniel T. wrote:
>>>>>> Juha Nieminen <nos...@thanks.invalid> wrote:
>
>>>>> 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.
>
>>> 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.
>
>> The suggestion that dynamic_case is necessary for passing
>> application-layer objects through lower level transport layers
>> is only marginally true. It is true that casts are used for
>> this purpose, but IME they are almost always of the
>> reinterpret_cast variety.
>
> That very much depends on the transport layer, I would think.
>
>> They have to be: A general-purpose transport layer does not
>> know about the higher layer's object types.
>
> And a specialize transport layer only knows a little. It
> doesn't take much specialization to ensure that all of the
> objects transported have a common base (i.e.
> TransportableObject?), and are polymorphic.
>
>> The transport mechanism is just sending bits over a wire,
>
> Maybe, maybe not. There's not necessarily a wire; it's sending
> objects between to components. There are many different types
> of transport layer. (And even if there is a wire, many
> protocols will have all transportable objects derive from some
> common base type, or a small set of common base types.)
>
>> or at most doing some kind of byte-order or line-ending
>> conversion.
>
> I think you're confusing the transport layer of OSI network
> protocols (TCP, etc.) with the transport layer of OO design.
I have been using the terms somewhat interchangeably: not specifically
meaning OSI layer 4, but "the next layer down" from whichever layer is
being discussed. This seemed to be enough for the current discussion,
but by all means let me know if there is a relevant difference that I'm
missing. I'm always (well, usually) happy to learn. :)
>> For dynamic_cast to be relevant, the transport mechanism has
>> to know about some base type used by the higher level. The
>> only case in which I could see this happening is if the
>> transport layer provides a base type with virtual methods,
>> maybe "serializable_object" or something, from which
>> higher-layer objects are supposed to derive. IMO, that kind
>> of design is completely upside-down and unnecessarily
>> invasive.
>
> Why? If you want an object to be transportable, you say so. It
> seems to me to be part of the basic principles of static type
> checking. You're transporting message objects between higher
> level components which have connected to the transport layer.
> You don't (and can't) transport just anything.
The "message" objects you're describing are just utilities. It is not,
IMO, reasonable for a C++ library to dictate what classes must be
derived from by a higher-layer client. The principle applies as
strongly to application-specific libraries as to general-purpose ones.
Once you do this, you are effectively implementing a framework rather
than a library. Especially if multiple such pseudo-libraries are to
co-exist in the same application, there is, I think, a real danger that
the client objects will be forced to implement an unwieldy number of
different, sometimes conflicting, interfaces.
A better solution is for "message" to be a concrete type provided by the
transport layer, such that each message is a configurable object tasked
with carrying an arbitrary bundle of data. It is none of the transport
layer's business what is or is not being sent between two higher-layer
objects. This is one of the few valid uses of void*.
There should also be a way for the message to include meta-data about
its payload, such that type information may be retrieved by the message
recipient. A dynamic_cast may be a convenient way of achieving some of
this functionality (if void* is replaced with e.g. transportable*), but
it is not a general solution; seeing dynamic_cast used to retrieve
run-time type information after transport would set off alarms in my
mind, and would seem a good reason to scrutinize both the transport and
application layers' design.
There is, of course, the Java-style design option to have run-time
tagging interfaces of the java.io.Serializable variety. I do not care
for such interfaces even in Java. I note that they are a frequent
source of confusion to new Java programmers, and I would not be happy to
see them rearing their wart-covered heads in C++.
|
|
0
|
|
|
|
Reply
|
jeff34 (1594)
|
3/15/2008 5:14:07 PM
|
|
Andy Champ wrote:
> Jeff Schwab wrote:
>> Andy Champ wrote:
>>> Jeff Schwab wrote:
>>> <snip>
>>>> You don't have any choice in a pre-stack environment; you can't call
>>>> a function until you have initialized the memory controller, so you
>>>> do a lot of (very orderly) direct branching.
>>>>
>>>
>>> Not completely true.
>>
>> What's not true?
>>
>>> You have a couple of registers - SP and BP on an x86 machine spring
>>> to mind - which aren't a lot of use without memory.
>>>
>>> Except that they can be a handy place to put a return address!
>>
>> Right, so you do indirect branching as well as direct. :)
>>
>>> Been there, done that, got the EPROM.
>>
>> How do you use store and use the address in the register? You can't
>> use call/return. You have to branch explicitly. You can indeed store
>> addresses in registers, and frequently do; you can even have one level
>> of "call depth" by using only only the lower halves of the
>> general-purpose registers for local data, left-shifting before a
>> "call," and right-shifting after the "return." But none of this gets
>> you around the branch, or jump, or -- if you're writing C++ code --
>> the goto.
>
> Well for one thing I was working in Assembler, not C++. No high level
> language that I'm aware of will give you that much control.
You can write (or use a library of) platform-specific macros and
functions. A couple of years ago, I used the ones that came with an
ancient copy of Borland 5 for PC-DOS on 32-bit x86. It's still useful,
but I would like to find a modernized version, since the C++ compiler is
pre-standard. (While we're on the topic, I'd also like a C++ compiler
that supported big real/unreal/32-bit real mode. 64-bit addressing in
real mode would also be nice, but I don't even know how to get that in
assembly.)
> The return?
>
> mov ax,esp ; or bp
> jmp [ax]
>
> I think it goes. But my assembler books are at work, and I haven't
> written much lately.
I'm with you on both counts! That looks correct-ish, though, for a near
jump.
> And no, you can't write assembler without some sort of goto, even when
> you are using it to emulate a cleaner structure.
You can once you get stack. Modern x86 chips have not only explicit
instructions for procedure call and return, but enter and exit
instruction that automatically prepare and clean up each stack frame.
> OTOH, you don't have a
> dynamic_cast either, which is where I started this thread!
True. :) You do have its moral equivalent, which is the comparison of
one or more function-table addresses.
|
|
0
|
|
|
|
Reply
|
jeff34 (1594)
|
3/15/2008 5:29:49 PM
|
|
In article <9ce9997f-1a62-4120-8eb4-e21b54a5a8e6
@e6g2000prf.googlegroups.com>, nick_keighley_nospam@hotmail.com says...
[ ... ]
> 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!
As I noted elsethread, I'd start from the fact that as we're postulating
the design, we're using a shared action among all objects of a given
type to simulate sharing state among all objects of a given type.
If we want objects to act like they're sharing state, I think we should
express that directly rather than simulating it by sharing an action
across those objects. My earlier post contained an implementation that
answered the question that was asked, but wasn't (IMO) particularly
extensible.
Realistically, we know that if you want to be able to highlight objects
of one type, you probably also want to be able to highlight objects of
other types as well -- in fact, you _probably_ want to be able to
highlight objects of _any_ type.
The approach I used elsethread _could_ do that, but would require code
duplicated across all the descendants of 'shape' to do so (to create and
manipulate a static variable in each). Realistically, we also know that
there's more to the UI of an object than just its current color. That
implies that there would be quite a lot of code duplicated across the
hierarchy, something we'd generally much rather avoid.
To accomplish this, we probably want to start by creating a UI class
that describes the current UI state of an object. We create an instance
of this UI class for each class of object in our hierarchy. When we
create each object in the hierarchy, we include a pointer (or reference)
to the appropriate UI state object that's shared among all objects of
that type. When we want to (for example) highlight objects of a
specified type, we do NOT manipulate the individual objects of that type
-- rather, we manipulate the UI state object shared by all objects of
that type:
class UI_state {
enum { NORMAL, HIGHLIGHTED } highlight_state;
color colors[2];
int x_, y_;
/* other UI state here */
public:
highlight() { highlight_state = HIGHLIGHTED; }
normal() { highlight_state = NORMAL; }
color current_color() const { return colors[highlight_state]; }
};
enum UML_objects {
UML_CLASS,
UML_INTERFACE,
UML_COMPONENT,
/* ... */
UML_OBJECT_LAST
};
std::vector<UI_state> UI_states(UML_object_last);
class UML_object {
UI_state &ui_;
public:
virtual void draw(surface_t &surface) const = 0;
}
class UML_class : public UML_object {
public:
UML_class(int x, int y)
: x_(x), y_(y), ui_(UI_states[UML_CLASS])
{}
virtual void draw(surface_t &surface) const {
// draw self onto surface using ui
}
};
class UML_interface : public UML_object {
public:
UML_interface(int x, int y)
: x_(x), y_(y), ui(UI_states[UML_INTERFACE])
{}
virtual void draw(surface_t &surface) const {
// draw self onto surface using ui
}
};
Now, to highlight all interfaces, we do NOT look through our collection
of objects looking for interface objects, and then manipulate the color
of each. Instead, 'UI_states[UML_INTERFACE].highlight()' does the whole
job at once. Highlighting objects of a different type obviously requires
nothing more than choosing the appropriate constant. In any case, we're
manipulating a single shared state instead of searching for a set of
objects with duplicated state.
I should add that I doubt this design would really apply to a UML
editor. I can't remember ever having wanted to highlight all objects of
a given type. Rather, highlighting would typically be based on a
relationship, such as "everything that inherits from X", or "everything
that implements Y", or "everything that depends on Z". As such, you're
probably going to determine the highlight state based on the
relationships defined in the model, not simply the type of an object.
As such, I think the design is probably for something nobody has ever
(and probably will ever) want, at least as presented. OTOH, I can
imagine situations where such a thing really would be useful, and for
such a situation, I think this is a much cleaner implementation than
anything using dynamic_cast.
IMO, this design is much more adaptable to a real-world scenario. If I
was designing a UML editor, I don't think I'd use a static class
hierarchy to represent the UML elements. Rather, I'd put almost
everything related to each UML element into initalization files of some
sort, and simply load those up during program startup. For example, an
implementation on Window would load a series of metafiles, and when it
needed to draw an object, it would use PlayMetafile or PlayEnhMetafile
to do the job.
Using this design, all the UML objects might easily be of precisely the
same type. Sharing the UI state would remain easy, because when we
create an object we know what kind of object we're creating, and we only
need to use a suitable value to relate that object to the appropriate UI
state. In this situation, dynamic_cast wouldn't work at all -- it would
do nothing to distinguish between one UML element and another, since
from the viewpoint of C++ they would all just be objects of a single
type.
--
Later,
Jerry.
The universe is a figment of its own imagination.
|
|
0
|
|
|
|
Reply
|
jcoffin (2240)
|
3/15/2008 5:51:57 PM
|
|
Andy Champ <no....@nospam.com> wrote:
> > 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. =A0It appears our experience is in different
> domains. =A0I've never used Python or Smalltalk, and hardly touched Java!
I think this is part of the disconnect in this thread, and others like
it, for me. My domain is not business/database apps like many of the
others in this discussion. I am a video game programmer, so most of my
work is in the simulation domain... often on systems that are much
like what embedded programmers have to deal with.
OO programming's roots are in simulation work so I sometimes think
that I'm more steeped in the paradigm than many others.
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/16/2008 1:12:15 AM
|
|
On Mar 15, 10:29=A0am, Jerry Coffin <jcof...@taeus.com> wrote:
> danie...@earthlink.net says...
>
> > There are any number of ways to do it, Dave Mikes mentioned one,
> > another would be to have a virtual function in Shape that lets Shapes
> > know that a "change rectangles to yellow" request has been made.
>
> I would posit that (at least as stated) this would constitute quite a
> poor design.
In many cases, yes it would. There are almost as many ways to design
the system as there are systems. :-)
> Instead, if we want to be able to highlight all the interfaces (or
> whatever) we'd share the "highlighted" vs. "normal" state (or perhaps
> the current color) among all objects of that type:
>
> struct UML_object {
> =A0 =A0 =A0 =A0 int x_, y_;
> =A0 =A0 =A0 =A0 virtual void draw(surface_t &) =3D 0;
>
> };
>
> class UML_interface : public UML_object {
> =A0 =A0 =A0 =A0 static color_t color;
> public:
> =A0 =A0 =A0 =A0 static void highlight(bool state=3Dtrue) {
> =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 static color_t colors[] =3D {
> =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 RGB(0,0,0),
> =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 RGB(255, 0,0)
> =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 };
>
> =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 color =3D colors[state];
> =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 // code to force re-draw goes here.
> =A0 =A0 =A0 =A0 }
>
> =A0 =A0 =A0 =A0 square(int x, int y) : =A0x_(x), y_(y) {}
>
> =A0 =A0 =A0 =A0 virtual void draw(surface_t &s) {
> =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 // draw "interface" on specified surface
> =A0 =A0 =A0 =A0 }
>
> };
>
> class UML_class : public UML_object {
> public:
> =A0 =A0 =A0 =A0 UML_class(int x, int y) : x_(x), y_(y) {}
>
> =A0 =A0 =A0 =A0 virtual void draw(surface_t &s) {
> =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 // draw "class" on specified surface
> =A0 =A0 =A0 =A0 }
>
> };
>
> color_t UML_interface::color;
>
> This way we don't need separate containers OR a dynamic_cast to
> highlight all your UML_interface objects -- instead, you call:
> UML_interface::highlight();
> and they all highlight. To change them all back to normal, you call:
> UML_interface::highlight(false);
>
> IMO, if you want shared state, it's better to create real shared state
> than to force all objects of that type to the same state, independently
> of each other.
Excellent post! Another way would be to have some sort of state object
that each of the elements literally share, kind of like a style sheet.
That way it would be easy to support exceptions to the rule, simply
give them a different/non-standard style sheet.
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/16/2008 1:26:52 AM
|
|
On Mar 13, 4:23=A0am, Juha Nieminen <nos...@thanks.invalid> wrote:
> Daniel T. wrote:
> > 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.
>
> =A0 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.
>
> =A0 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.
Jerry Coffin recently submitted two posts that provide concrete code
that may help you.
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/16/2008 1:33:21 AM
|
|
On 15 mar, 17:42, c...@mailvault.com wrote:
> On Mar 15, 3:54 am, James Kanze <james.ka...@gmail.com> wrote:
> > On 14 mar, 18:14, Jeff Schwab <j...@schwabcenter.com> wrote:
> > > The suggestion that dynamic_case is necessary for passing
> > > application-layer objects through lower level transport layers
> > > is only marginally true. It is true that casts are used for
> > > this purpose, but IME they are almost always of the
> > > reinterpret_cast variety.
> > That very much depends on the transport layer, I would think.
> > > They have to be: A general-purpose transport layer does not
> > > know about the higher layer's object types.
> > And a specialize transport layer only knows a little. It
> > doesn't take much specialization to ensure that all of the
> > objects transported have a common base (i.e.
> > TransportableObject?), and are polymorphic.
> I don't think there is anything gained from doing it that way
> and with virtual functions there is probably some perf. hit.
There's a definite advantage in that you state up front which
objects are transportable. In many cases, such objects may have
to fulfill additional requirements as well. Deriving from a
common base allows some additional compile time checking.
As for the performance hit, be serious. You're going through an
extra layer; that will cost something in terms of runtime. More
than any virtual function call, anyway.
> > > The transport mechanism is just sending bits over a wire,
> > Maybe, maybe not. There's not necessarily a wire; it's sending
> > objects between to components. There are many different types
> > of transport layer. (And even if there is a wire, many
> > protocols will have all transportable objects derive from some
> > common base type, or a small set of common base types.)
> > > or at most doing some kind of byte-order or line-ending
> > > conversion.
> > I think you're confusing the transport layer of OSI network
> > protocols (TCP, etc.) with the transport layer of OO design.
> > > For dynamic_cast to be relevant, the transport mechanism has
> > > to know about some base type used by the higher level. The
> > > only case in which I could see this happening is if the
> > > transport layer provides a base type with virtual methods,
> > > maybe "serializable_object" or something, from which
> > > higher-layer objects are supposed to derive. IMO, that kind
> > > of design is completely upside-down and unnecessarily
> > > invasive.
> > Why? If you want an object to be transportable, you say so. It
> > seems to me to be part of the basic principles of static type
> > checking.
> The way I do it, you "say so" by adding Send/Receive function
> prototypes to a class. The compiler will complain if it finds
> implementations for those functions, but the class doesn't have
> prototypes for them.
Which can work too, but is less sure than if the programmer
explicitly states that he implements the TransportableObject
contract. And at least in C++, doing this way means using
templates, with the resulting increase in coupling, code bloat
and explosion of compile times. (This would be partially
mitigated if your compiler supports export, but the code bloat
and some additional coupling is inevitable.)
> > You're transporting message objects between higher
> > level components which have connected to the transport layer.
> > You don't (and can't) transport just anything.
> There has to be action from the programmer indicating what
> types are going to be used in messages, but I don't think that
> translates to inheritance being required. Using inheritance
> like that seems just to get in the way and makes multiple
> inheritance much more likely.
Inhertance remains the simplest and most efficient means of
specifying conformance to a contract. It does get in the way of
the programmer accidentally creating a class which conforms to
the superficial aspects (presence of such and such a function),
but being unaware of the actual contract, and not implementing
the desired semantics, yes.
And of course, multiple inheritance is a fact of life as soon as
you have static type checking. Big deal.
--
James Kanze (GABI Software) email:james.kanze@gmail.com
Conseils en informatique orient=E9e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S=E9mard, 78210 St.-Cyr-l'=C9cole, France, +33 (0)1 30 23 00 34
|
|
0
|
|
|
|
Reply
|
james.kanze (9594)
|
3/16/2008 10:37:06 AM
|
|
On 15 mar, 18:14, Jeff Schwab <j...@schwabcenter.com> wrote:
> James Kanze wrote:
> >> They have to be: A general-purpose transport layer does not
> >> know about the higher layer's object types.
> > And a specialize transport layer only knows a little. It
> > doesn't take much specialization to ensure that all of the
> > objects transported have a common base (i.e.
> > TransportableObject?), and are polymorphic.
> >> The transport mechanism is just sending bits over a wire,
> > Maybe, maybe not. There's not necessarily a wire; it's sending
> > objects between to components. There are many different types
> > of transport layer. (And even if there is a wire, many
> > protocols will have all transportable objects derive from some
> > common base type, or a small set of common base types.)
> >> or at most doing some kind of byte-order or line-ending
> >> conversion.
> > I think you're confusing the transport layer of OSI network
> > protocols (TCP, etc.) with the transport layer of OO design.
> I have been using the terms somewhat interchangeably: not specifically
> meaning OSI layer 4, but "the next layer down" from whichever layer is
> being discussed.
OK. That's the sense I'm using as well. (I probably should
have chosen a different term, since transport layer does suggest
the OSI layer 4, and the lower layer isn't always involved with
"transport", in the strictest sense---just hooking components
together somehow.)
> This seemed to be enough for the current discussion, but by
> all means let me know if there is a relevant difference that
> I'm missing. I'm always (well, usually) happy to learn. :)
The OSI layer 4 is a special case of the general case.
> >> For dynamic_cast to be relevant, the transport mechanism has
> >> to know about some base type used by the higher level. The
> >> only case in which I could see this happening is if the
> >> transport layer provides a base type with virtual methods,
> >> maybe "serializable_object" or something, from which
> >> higher-layer objects are supposed to derive. IMO, that kind
> >> of design is completely upside-down and unnecessarily
> >> invasive.
> > Why? If you want an object to be transportable, you say so. It
> > seems to me to be part of the basic principles of static type
> > checking. You're transporting message objects between higher
> > level components which have connected to the transport layer.
> > You don't (and can't) transport just anything.
> The "message" objects you're describing are just utilities.
> It is not, IMO, reasonable for a C++ library to dictate what
> classes must be derived from by a higher-layer client.
It's not reasonable for it not to. A C++ library must specify
very clearly which classes are designed to be used as base
classes, and in what ways. By default, the assumption is (or
should be) that you cannot reasonable derive from a library
class. The exceptions should be clearly documented.
> The principle applies as strongly to application-specific
> libraries as to general-purpose ones. Once you do this, you
> are effectively implementing a framework rather than a
> library.
In an application, the lower levels are a framework. Or part of
one.
> Especially if multiple such pseudo-libraries are to co-exist
> in the same application, there is, I think, a real danger that
> the client objects will be forced to implement an unwieldy
> number of different, sometimes conflicting, interfaces.
If the interfaces conflict, then you do have a problem. I've
not found this to be a problem in practice, however.
> A better solution is for "message" to be a concrete type
> provided by the transport layer, such that each message is a
> configurable object tasked with carrying an arbitrary bundle
> of data. It is none of the transport layer's business what is
> or is not being sent between two higher-layer objects. This
> is one of the few valid uses of void*.
Which is, IMHO, even less type-safe than using a common base
class. And more or less requires the client code to implement
the equivalent of dynamic_cast itself: a lot of extra work, and
additional potential for errors.
> There should also be a way for the message to include meta-data about
> its payload, such that type information may be retrieved by the message
> recipient. A dynamic_cast may be a convenient way of achieving some of
> this functionality (if void* is replaced with e.g. transportable*), but
> it is not a general solution; seeing dynamic_cast used to retrieve
> run-time type information after transport would set off alarms in my
> mind, and would seem a good reason to scrutinize both the transport and
> application layers' design.
dynamic_cast is never a general solution. I don't think a
"general solution" exists for this problem. dynamic_cast is
part of an appropriate solution in some specific cases. In
others, of course, you'll use other tools.
> There is, of course, the Java-style design option to have run-time
> tagging interfaces of the java.io.Serializable variety.
Java's serialization is broken. I've never found a case where
it was an appropriate solution. Probably because it attempts to
do too much.
But I'm not sure it's relevant here. The "framework" or the
"transport layer" we're talking about doesn't necessarily
involve serialization.
--
James Kanze (GABI Software) email:james.kanze@gmail.com
Conseils en informatique orient=E9e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S=E9mard, 78210 St.-Cyr-l'=C9cole, France, +33 (0)1 30 23 00 34
|
|
0
|
|
|
|
Reply
|
james.kanze (9594)
|
3/16/2008 10:47:28 AM
|
|
On 15 mar, 02:25, "Daniel T." <danie...@earthlink.net> wrote:
> Post the problem statement that requires the above, post some
> code doing it and maybe we can provide a better solution.
Two concrete cases where using dynamic_cast seems to be
preferable to the alternatives:
-- A network element implementing connections, which is part of
a distributed management information base (MIB). A
connection is specified by means of an external create
request for a cross connection object (CC---in the
application domain, such abbreviations are widely used and
understood). A connection is established between to
termination points (TP), which must have been created
previously, and are specified by means of distinguished
names (DN). In the application framework, of course, you
have a data dictionary mapping DNs to managed objects (MO);
the constructor of CC (which derives from MO itself, of
course, as does TP and anything else which can be accesses
through the MIB) requests pointers to the corresponding TPs,
using the DNs: the data dictionary returns an MO*, which it
dynamically casts to a TP*---if the dynamic_cast fails, it
throws an exception, generating an error response to the
creation request.
In practice, the code would probably want to distinguish
between a lookup failure (the data dictionary returned a
null pointer) and a type failure (the dynamic_cast failed).
In addition, there are different types of TP's, the code
would go on to verify that the source and the sink were
compatible with one another. This probably shouldn't
involve dynamic_cast---the role of a TP is to be a source or
a sink, and the type and direction of the date stream is a
characteristic of all TPs, so having a virtual member
function which returns this information in the TP class
seems a more reasonable solution.
-- A GUI framework allows you to connect listeners to handle
specific events. In the case of some events, you want to do
something common to all of the buttons in a toolbar (maybe
disenable them, say until some exteral operation has
finished). So you get the toolbar, and iterate over its
components. You've constructed the toolbar, so you know
that all of its components are in fact MyButton (although of
course, for the generic toolbar code and the GUI framework
in general, they're just GUIComponent). In this case, you
use dynamic_cast to convert the GUIComponent* you get back
from the toolbar to a MyButton*, in order to call
MyButton::disable() on it.
In this case, you could argue that you should also derive
from GUIToolBar, and maintain a container of MyButton* in
parallel with it, in order not to loose the type when you
inserted the MyButton into the toolbar. This, of course,
avoids the dynamic_cast, But this means a lot of extra code,
and a significantly increased possibility of errors (the
container in the derived toolbar class getting out of sync
with the one in the base GUIToolBar class). As designs go,
it's not as good as the one using the dynamic_cast.
--
James Kanze (GABI Software) email:james.kanze@gmail.com
Conseils en informatique orient=E9e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S=E9mard, 78210 St.-Cyr-l'=C9cole, France, +33 (0)1 30 23 00 34
|
|
0
|
|
|
|
Reply
|
james.kanze (9594)
|
3/16/2008 11:10:09 AM
|
|
Jeff Schwab wrote:
> You can write (or use a library of) platform-specific macros and
> functions. A couple of years ago, I used the ones that came with an
> ancient copy of Borland 5 for PC-DOS on 32-bit x86. It's still useful,
> but I would like to find a modernized version, since the C++ compiler is
> pre-standard. (While we're on the topic, I'd also like a C++ compiler
> that supported big real/unreal/32-bit real mode. 64-bit addressing in
> real mode would also be nice, but I don't even know how to get that in
> assembly.)
>
>> The return?
>>
>> mov ax,esp ; or bp
>> jmp [ax]
>>
>> I think it goes. But my assembler books are at work, and I haven't
>> written much lately.
>
> I'm with you on both counts! That looks correct-ish, though, for a near
> jump.
>
>> And no, you can't write assembler without some sort of goto, even when
>> you are using it to emulate a cleaner structure.
>
> You can once you get stack. Modern x86 chips have not only explicit
> instructions for procedure call and return, but enter and exit
> instruction that automatically prepare and clean up each stack frame.
>
Well IIRC the order of operations was:
- walk ones and zeroes through all registers
- Test base 64Kb of RAM
If either failed, put out a message through the might-be-operating
serial port, then loop repeating the test so an oscilloscope can be used
for diagnosis
- Initialise stack into base 64Kb
then you can do more normal stuff,like local variable and calls.
You are of course running in 8086 emulation mode, this is straight after
power-on reset.
Andy
|
|
0
|
|
|
|
Reply
|
no.way7055 (86)
|
3/16/2008 12:04:34 PM
|
|
Daniel T. wrote:
>>> 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...
>
> You don't know how to make a virtual function in a base class?
You want me to add a virtual function named "changeSquareColor" to the
Shape base class if I want to be able to change the color of squares?
That breaks object-oriented design quite horribly. For one, it breaks
the "is-a" relationship. More precisely, it breaks the "behaves like"
property of an "is-a" relationship: If you specify that "Shape" supports
the functionality "changeSquareColor", you are effectively saying that
*all* classes derived from Shape support that functionality. However,
only one does, the others don't.
The base class would be cluttered with functions specific to some
derived classes. And besides, in some cases you *can't* modify the base
class (eg. because it's in a library).
|
|
0
|
|
|
|
Reply
|
nospam270 (2853)
|
3/16/2008 11:26:30 PM
|
|
Jerry Coffin wrote:
> struct UML_object {
> int x_, y_;
> virtual void draw(surface_t &) = 0;
> };
>
> class UML_interface : public UML_object {
> static color_t color;
> public:
> static void highlight(bool state=true) {
> static color_t colors[] = {
> RGB(0,0,0),
> RGB(255, 0,0)
> };
>
> color = colors[state];
> // code to force re-draw goes here.
> }
>
> square(int x, int y) : x_(x), y_(y) {}
>
> virtual void draw(surface_t &s) {
> // draw "interface" on specified surface
> }
> };
>
> class UML_class : public UML_object {
> public:
> UML_class(int x, int y) : x_(x), y_(y) {}
>
> virtual void draw(surface_t &s) {
> // draw "class" on specified surface
> }
> };
I don't really understand what you are doing there. It's not even
valid C++. (And public member variables?)
> This way we don't need separate containers OR a dynamic_cast to
> highlight all your UML_interface objects
Maybe one of us is missing the point here?
The problem is not how to implement a feature which is common to all
the objects. The problem is how to implement a feature which is not.
Of course if a feature is common to all the objects (such as your
highlighting above), you simply make that feature part of the base
class. The problem is that not all features are common to all objects
and cannot be logically placed in the base class.
|
|
0
|
|
|
|
Reply
|
nospam270 (2853)
|
3/16/2008 11:40:37 PM
|
|
Daniel T. wrote:
> Excellent post!
All I understood from his post was: If you have a feature which is
common to *all* the objects, put that feature in the base class.
Well, duh. Hello? That isn't the issue here at all. (*Of course* I do
that every time I can. You don't have to tell me that.)
The issue is that some derived classes might have features which do
not belong to the base class at all. (And, in some cases, couldn't even
be added there.)
|
|
0
|
|
|
|
Reply
|
nospam270 (2853)
|
3/16/2008 11:42:45 PM
|
|
Juha Nieminen wrote:
> All I understood from his post was: If you have a feature which is
> common to *all* the objects, put that feature in the base class.
>
> Well, duh. Hello? That isn't the issue here at all. (*Of course* I do
> that every time I can. You don't have to tell me that.)
Uh, actually you shouldn't do that. Most re-use should move out to
delegates, not up to base classes.
--
Phlip
http://assert2.rubyforge.org/
|
|
0
|
|
|
|
Reply
|
phlip2005 (2147)
|
3/17/2008 12:29:09 AM
|
|
On Mar 16, 11:42=A0pm, Juha Nieminen <nos...@thanks.invalid> wrote:
> Daniel T. wrote:
> > Excellent post!
>
> =A0 All I understood from his post was: If you have a feature which is
> common to *all* the objects, put that feature in the base class.
>
> =A0 Well, duh. Hello? That isn't the issue here at all. (*Of course* I do
> that every time I can. You don't have to tell me that.)
>
> =A0 The issue is that some derived classes might have features which do
> not belong to the base class at all. (And, in some cases, couldn't even
> be added there.)
FWIW Using dynamic_cast is fine in my book. It does affect
performance for complicated hierarchies, and if that is important I
would add a virtual getID() function in base class. Use the getID()
function to ascertain the class is a form of square or whatever,and
then use dynamic cast, if necessary. Often though dynamic_cast works
well enough without adding an ID.
Another approach is a union of pointers with an ID. This can reduce
the size of objects over trying to mung them into some common base
class when they are only loosely related, but need to be grouped
together in a container.
Fact is that how well objects are related is actually highly variable,
contrary to what OOP afficionados would have us belive. dynamic
polymorphism is a theory beloved of academics.
Being cynical OOP is a useful kludge to make different entities look
the same to satisfy type checking, generally when you have to put them
in a collection. It has a big effect because you have to use pointers
and mostly that implies allocating on the heap, and a big loss in
preformance over using value_types.
It works because its a simple "elegant" solution to a complicated
problem, but it shouldnt be seen as perfect. The types "ARE"
different, so outside the ideal world of academia and textbooks,
dynamic_cast is required to discriminate. If dynamic_cast was
unneccessary, it wouldnt exist.
regards
Andy Little
|
|
0
|
|
|
|
Reply
|
andy199 (808)
|
3/17/2008 2:04:51 AM
|
|
On Mar 16, 6:42 pm, Juha Nieminen <nos...@thanks.invalid> wrote:
> The issue is that some derived classes might have features which do
> not belong to the base class at all. (And, in some cases, couldn't even
> be added there.)
Which is why I wouldn't generally put those Derived in a collection of
Base *, but in a collection of Derived *.
|
|
0
|
|
|
|
Reply
|
lbonafide (83)
|
3/17/2008 2:15:05 AM
|
|
On Mar 17, 12:29=A0am, "Phlip" <phlip2...@gmail.com> wrote:
> Juha Nieminen wrote:
> > =A0All I understood from his post was: If you have a feature which is
> > common to *all* the objects, put that feature in the base class.
>
> > =A0Well, duh. Hello? That isn't the issue here at all. (*Of course* I do=
> > that every time I can. You don't have to tell me that.)
>
> Uh, actually you shouldn't do that. Most re-use should move out to
> delegates, not up to base classes.
Please Do enlighten us all with an example ...
regards
Andy Little
|
|
0
|
|
|
|
Reply
|
andy199 (808)
|
3/17/2008 2:36:40 AM
|
|
In article <47ddb07d$0$8168$4f793bc4@news.tdc.fi>, nospam@thanks.invalid
says...
> Jerry Coffin wrote:
> > struct UML_object {
> > int x_, y_;
> > virtual void draw(surface_t &) = 0;
> > };
> >
> > class UML_interface : public UML_object {
> > static color_t color;
> > public:
> > static void highlight(bool state=true) {
> > static color_t colors[] = {
> > RGB(0,0,0),
> > RGB(255, 0,0)
> > };
> >
> > color = colors[state];
> > // code to force re-draw goes here.
> > }
> >
> > square(int x, int y) : x_(x), y_(y) {}
> >
> > virtual void draw(surface_t &s) {
> > // draw "interface" on specified surface
> > }
> > };
> >
> > class UML_class : public UML_object {
> > public:
> > UML_class(int x, int y) : x_(x), y_(y) {}
> >
> > virtual void draw(surface_t &s) {
> > // draw "class" on specified surface
> > }
> > };
>
> I don't really understand what you are doing there. It's not even
> valid C++.
Sorry -- I tested the code with the classes named 'circle' and 'square',
and when I edited them for posting, I missed changing the 'square' ctor
to 'UML_interface'. My apologies for that.
Here's the code, edited in my normal code editor, with no further
editing after compiling, to ensure against my fat-fingering (which means
it stil includes working drawing code and such, though that's neither
portable nor really topical):
struct UML_object {
virtual void draw(CDC *) = 0;
// Note that this does NOT include highlight().
};
class UML_interface : public UML_object {
int x_, y_, cx_, cy_;
static DWORD color;
public:
// This implements highlight() ONLY for UML_interface
static void highlight(bool state=true) {
static DWORD colors[] = {
RGB(0,0,0),
RGB(255, 0,0)
};
color = colors[state];
}
UML_interface(int x, int y, int cx, int cy) :
x_(x), y_(y), cx_(cx), cy_(cy)
{}
virtual void draw(CDC *pDC) {
CPen pen;
pen.CreatePen(0, 0, color);
pDC->SelectObject(&pen);
pDC->Rectangle(x_, y_, x_+cx_, y_+cy_);
}
};
class UML_class : public UML_object {
int x_, y_, r_;
DWORD color;
public:
// UML_class doesn't include highlight() either.
//
UML_class(int x, int y, int r) : x_(x), y_(y), r_(r),
color(RGB(0,0,0))
{}
virtual void draw(CDC *pDC) {
CPen pen(0, 0, color);
pDC->SelectObject(pen);
pDC->Ellipse(x_-r_, y_-r_, x_+r_, y_+r_);
}
};
> (And public member variables?)
Public member variables have a lot worse reputation than they deserve --
though in many cases, they should be of types that ensure against
misuse. As you can see above, they were originally private, but this
increases the amount of code, without contributing anything relevant to
the question at hand.
> > This way we don't need separate containers OR a dynamic_cast to
> > highlight all your UML_interface objects
>
> Maybe one of us is missing the point here?
Yes, you are -- or at least you were. Knowing how Usenet (or the human
psyche) works, you probably figured out that highlight() was specific to
UML_interface about 10 seconds _after_ your post hit the server. Been
there, done that!
> The problem is not how to implement a feature which is common to all
> the objects. The problem is how to implement a feature which is not.
>
> Of course if a feature is common to all the objects (such as your
> highlighting above), you simply make that feature part of the base
> class. The problem is that not all features are common to all objects
> and cannot be logically placed in the base class.
What you've probably already realized is that highlight() really IS
specific to UML_interface, not common to all the objects.
Of course, you may instead have decided I'm such an idiot that you've
plonked me, so you won't see this either... :-)
--
Later,
Jerry.
The universe is a figment of its own imagination.
|
|
0
|
|
|
|
Reply
|
jcoffin (2240)
|
3/17/2008 2:49:13 AM
|
|
In article <47ddb0fe$0$8168$4f793bc4@news.tdc.fi>, nospam@thanks.invalid
says...
> Daniel T. wrote:
> > Excellent post!
>
> All I understood from his post was: If you have a feature which is
> common to *all* the objects, put that feature in the base class.
Perhaps I was unnecessarily kind in my previous post. Contrary to the
common situation, you _don't_ seem to have understood the post 10
seconds after you replied. Oh well, chances are pretty good that have by
now in any case...
> Well, duh. Hello? That isn't the issue here at all. (*Of course* I do
> that every time I can. You don't have to tell me that.)
>
> The issue is that some derived classes might have features which do
> not belong to the base class at all. (And, in some cases, couldn't even
> be added there.)
Which is, of course, exactly what I demonstrated how to do.
Now, don't get me wrong: I'm not claiming that this particular solution
is _always_ suitable. It just happens that this particular problem was
one where dynamic_cast was being used for little more than forcing a
class of objects to act as if they shared state, and really sharing
state was a much more straightforward solution.
--
Later,
Jerry.
The universe is a figment of its own imagination.
|
|
0
|
|
|
|
Reply
|
jcoffin (2240)
|
3/17/2008 2:49:14 AM
|
|
On Mar 16, 4:37 am, James Kanze <james.ka...@gmail.com> wrote:
> On 15 mar, 17:42, c...@mailvault.com wrote:
>
> > On Mar 15, 3:54 am, James Kanze <james.ka...@gmail.com> wrote:
> > > And a specialize transport layer only knows a little. It
> > > doesn't take much specialization to ensure that all of the
> > > objects transported have a common base (i.e.
> > > TransportableObject?), and are polymorphic.
> > I don't think there is anything gained from doing it that way
> > and with virtual functions there is probably some perf. hit.
>
> There's a definite advantage in that you state up front which
> objects are transportable. In many cases, such objects may have
> to fulfill additional requirements as well. Deriving from a
> common base allows some additional compile time checking.
>
What I suggest states up front what your intentions are as well.
On the other hand, the slln.net site says, "Serialization support can
often be added to types without modifying those types, or them even
being aware they are playing along. For example ...
Out of the box it supports all STL containers (and workalikes), nested
arbitrarily deep. By extension, we can (rightfully) assume that...
Clients can easily extend it to support their own types using, often
non-intrusively."
I'm closer to you in terms of approach than I am to the s11n author,
but I don't know of any C++ serialization framework that takes the
approach you suggest. S11n doesn't. Boost doesn't. RCF doesn't.
What I advocate doesn't.
> As for the performance hit, be serious. You're going through an
> extra layer; that will cost something in terms of runtime. More
> than any virtual function call, anyway.
>
Doesn't what you propose also use an extra layer? I thought
it was all other things being equal.
>
> > > Maybe, maybe not. There's not necessarily a wire; it's sending
> > > objects between to components. There are many different types
> > > of transport layer. (And even if there is a wire, many
> > > protocols will have all transportable objects derive from some
> > > common base type, or a small set of common base types.)
> > > > or at most doing some kind of byte-order or line-ending
> > > > conversion.
> > > I think you're confusing the transport layer of OSI network
> > > protocols (TCP, etc.) with the transport layer of OO design.
> > > Why? If you want an object to be transportable, you say so. It
> > > seems to me to be part of the basic principles of static type
> > > checking.
> > The way I do it, you "say so" by adding Send/Receive function
> > prototypes to a class. The compiler will complain if it finds
> > implementations for those functions, but the class doesn't have
> > prototypes for them.
>
> Which can work too, but is less sure than if the programmer
> explicitly states that he implements the TransportableObject
> contract. And at least in C++, doing this way means using
> templates, with the resulting increase in coupling, code bloat
> and explosion of compile times. (This would be partially
> mitigated if your compiler supports export, but the code bloat
> and some additional coupling is inevitable.)
I don't guess you are advocating writing all marshalling code
by hand, but if you aren't using code generation/templates, I'm not
sure what choice you have. All the serialization libraries use some
form of code generation. Those that rely heavily on templates usually
are slow build-wise and produce bloated executables.
http://www.webebenezer.net/comparison.html.
>
> > > You're transporting message objects between higher
> > > level components which have connected to the transport layer.
> > > You don't (and can't) transport just anything.
> > There has to be action from the programmer indicating what
> > types are going to be used in messages, but I don't think that
> > translates to inheritance being required. Using inheritance
> > like that seems just to get in the way and makes multiple
> > inheritance much more likely.
>
> Inhertance remains the simplest and most efficient means of
> specifying conformance to a contract. It does get in the way of
> the programmer accidentally creating a class which conforms to
> the superficial aspects (presence of such and such a function),
> but being unaware of the actual contract, and not implementing
> the desired semantics, yes.
>
That would result in a compile-time error in what I advocate.
If functions aren't implemented (generated) and calls are made to
those functions, the compiler will complain.
Brian Wood
|
|
0
|
|
|
|
Reply
|
coal (257)
|
3/17/2008 4:41:02 AM
|
|
lbonafide@yahoo.com wrote:
> On Mar 16, 6:42 pm, Juha Nieminen <nos...@thanks.invalid> wrote:
>
>> The issue is that some derived classes might have features which do
>> not belong to the base class at all. (And, in some cases, couldn't even
>> be added there.)
>
> Which is why I wouldn't generally put those Derived in a collection of
> Base *, but in a collection of Derived *.
Maybe you haven't read the problem which has been posed several times
now? I'm getting tired of repeating it.
You may have several types derived from the base class, and they are
all stored in a common data container, and their *order* inside that
data container is crucial. Storing each type in their own type-specific
data container doesn't work because this ordering is lost.
Some operations need to be performed to the objects in the order in
which they are stored.
The majority of such operations can be performed by having a virtual
function in the base class, so there's no problem. However, there may be
some operations which cannot be put in the base class (or even
operations which cannot be performed by the objects at all, eg. because
they don't have access to something needed).
|
|
0
|
|
|
|
Reply
|
nospam270 (2853)
|
3/17/2008 10:52:17 AM
|
|
On 16 Mar, 23:42, Juha Nieminen <nos...@thanks.invalid> wrote:
> Daniel T. wrote:
Juha, you snip rather generously!
> > Excellent post!
> All I understood from his post was: If you have a feature which is
> common to *all* the objects, put that feature in the base class.
expect that ean't the issue. It was if a feature is common to all su-
objects
of a particular sub-type- then make it a static property. Sounds quite
cool
to me
> Well, duh. Hello? That isn't the issue here at all. (*Of course* I do
> that every time I can. You don't have to tell me that.)
>
> The issue is that some derived classes might have features which do
> not belong to the base class at all. (And, in some cases, couldn't even
> be added there.)
Read The Code
this is the "excellent" post
***
the request
should be dealt with much more abstractly -- something more like
"highlight interfaces", using a relatively abstract description of
the
desired action instead of directly describing its physical
manifestation.
Although I can't say I've seen a use for this specifically in UML,
let's
assume for the moment that it really was something you wanted. In
that
case, I think stepping through the collection of all the objects and
setting the interfaces to highlighted is only a minor improvement
over
stepping through them and setting rectangles to yellow.
Instead, if we want to be able to highlight all the interfaces (or
whatever) we'd share the "highlighted" vs. "normal" state (or perhaps
the current color) among all objects of that type:
struct UML_object {
int x_, y_;
virtual void draw(surface_t &) = 0;
};
class UML_interface : public UML_object {
static color_t color;
public:
static void highlight(bool state=true) {
static color_t colors[] = {
RGB(0,0,0),
RGB(255, 0,0)
};
color = colors[state];
// code to force re-draw goes here.
}
square(int x, int y) : x_(x), y_(y) {}
virtual void draw(surface_t &s) {
// draw "interface" on specified surface
}
};
class UML_class : public UML_object {
public:
UML_class(int x, int y) : x_(x), y_(y) {}
virtual void draw(surface_t &s) {
// draw "class" on specified surface
}
};
color_t UML_interface::color;
***
--
Nick Keighley
|
|
0
|
|
|
|
Reply
|
nick_keighley_nospam (4574)
|
3/17/2008 11:20:29 AM
|
|
On 15 Mar, 17:51, Jerry Coffin <jcof...@taeus.com> wrote:
> In article <9ce9997f-1a62-4120-8eb4-e21b54a5a8e6
> @e6g2000prf.googlegroups.com>, nick_keighley_nos...@hotmail.com says...
> > 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!
>
> As I noted elsethread, I'd start from the fact that as we're postulating
> the design, we're using a shared action among all objects of a given
> type to simulate sharing state among all objects of a given type.
>
> If we want objects to act like they're sharing state, I think we should
> express that directly rather than simulating it by sharing an action
> across those objects.
yes, good point
> My earlier post contained an implementation that
> answered the question that was asked, but wasn't (IMO) particularly
> extensible.
I think we had some posts cross. I hadn't seen you post until today.
> Realistically, we know that if you want to be able to highlight objects
> of one type, you probably also want to be able to highlight objects of
> other types as well -- in fact, you _probably_ want to be able to
> highlight objects of _any_ type.
yes
> The approach I used elsethread _could_ do that, but would require code
> duplicated across all the descendants of 'shape' to do so (to create and
> manipulate a static variable in each). Realistically, we also know that
> there's more to the UI of an object than just its current color. That
> implies that there would be quite a lot of code duplicated across the
> hierarchy, something we'd generally much rather avoid.
>
> To accomplish this, we probably want to start by creating a UI class
> that describes the current UI state of an object. We create an instance
> of this UI class for each class of object in our hierarchy. When we
> create each object in the hierarchy, we include a pointer (or reference)
> to the appropriate UI state object that's shared among all objects of
> that type. When we want to (for example) highlight objects of a
> specified type, we do NOT manipulate the individual objects of that type
> -- rather, we manipulate the UI state object shared by all objects of
> that type:
oh yes, much better
> class UI_state {
> =A0 =A0 =A0 =A0 enum { NORMAL, HIGHLIGHTED } highlight_state;
> =A0 =A0 =A0 =A0 color colors[2];
> =A0 =A0 =A0 =A0 int x_, y_;
> =A0 =A0 =A0 =A0 /* other UI state here */
>
> public:
> =A0 =A0 =A0 =A0 highlight() { highlight_state =3D HIGHLIGHTED; }
> =A0 =A0 =A0 =A0 normal() { highlight_state =3D NORMAL; }
>
> =A0 =A0 =A0 =A0 color current_color() const { return colors[highlight_stat=
e]; }
>
> };
>
> enum UML_objects {
> =A0 =A0 =A0 =A0 UML_CLASS,
> =A0 =A0 =A0 =A0 UML_INTERFACE,
> =A0 =A0 =A0 =A0 UML_COMPONENT,
> =A0 =A0 =A0 =A0 /* ... */
> =A0 =A0 =A0 =A0 UML_OBJECT_LAST
>
> };
>
> std::vector<UI_state> UI_states(UML_object_last);
>
> class UML_object {
> =A0 =A0 =A0 =A0 UI_state &ui_;
> public:
> =A0 =A0 =A0 =A0 virtual void draw(surface_t &surface) const =3D 0;
>
> }
>
> class UML_class : public UML_object {
> public:
> =A0 =A0 =A0 =A0 UML_class(int x, int y)
> =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 : x_(x), y_(y), ui_(UI_states[UML_CLASS])
> =A0 =A0 =A0 =A0 {}
> =A0 =A0 =A0 =A0 virtual void draw(surface_t &surface) const {
> =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 // draw self onto surface using ui
> =A0 =A0 =A0 =A0 }
>
> };
>
> class UML_interface : public UML_object {
> public:
> =A0 =A0 =A0 =A0 UML_interface(int x, int y)
> =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 : x_(x), y_(y), ui(UI_states[UML_INTERFACE=
])
> =A0 =A0 =A0 =A0 {}
>
> =A0 =A0 =A0 =A0 virtual void draw(surface_t &surface) const {
> =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 // draw self onto surface using ui
> =A0 =A0 =A0 =A0 }
>
> };
>
> Now, to highlight all interfaces, we do NOT look through our collection
> of objects looking for interface objects, and then manipulate the color
> of each. Instead, 'UI_states[UML_INTERFACE].highlight()' does the whole
> job at once. Highlighting objects of a different type obviously requires
> nothing more than choosing the appropriate constant. In any case, we're
> manipulating a single shared state instead of searching for a set of
> objects with duplicated state.
>
> I should add that I doubt this design would really apply to a UML
> editor. I can't remember ever having wanted to highlight all objects of
> a given type.
I was having trouble in all the theory talk (hetrogenous collections)
and wanted something more concrete. Squares and Circles *still* seemed
a bit unreal. So I thought a noddy UML editor might be better.
The trouble with noddy examples is they remain noddy.
> Rather, highlighting would typically be based on a
> relationship, such as "everything that inherits from X", or "everything
> that implements Y", or "everything that depends on Z". As such, you're
> probably going to determine the highlight state based on the
> relationships defined in the model, not simply the type of an object.
>
> As such, I think the design is probably for something nobody has ever
> (and probably will ever) want, at least as presented. OTOH, I can
> imagine situations where such a thing really would be useful, and for
> such a situation, I think this is a much cleaner implementation than
> anything using dynamic_cast.
>
> IMO, this design is much more adaptable to a real-world scenario. If I
> was designing a UML editor, I don't think I'd use a static class
> hierarchy to represent the UML elements. Rather, I'd put almost
> everything related to each UML element into initalization files of some
> sort, and simply load those up during program startup. For example, an
> implementation on Window would load a series of metafiles, and when it
> needed to draw an object, it would use PlayMetafile or PlayEnhMetafile
> to do the job.
>
> Using this design, all the UML objects might easily be of precisely the
> same type. Sharing the UI state would remain easy, because when we
> create an object we know what kind of object we're creating, and we only
> need to use a suitable value to relate that object to the appropriate UI
> state. In this situation, dynamic_cast wouldn't work at all -- it would
> do nothing to distinguish between one UML element and another, since
> from the viewpoint of C++ they would all just be objects of a single
> type.
>
thanks very helpful
--
Nick Keighley
|
|
0
|
|
|
|
Reply
|
nick_keighley_nospam (4574)
|
3/17/2008 11:35:11 AM
|
|
kwikius <a...@servocomm.freeserve.co.uk> wrote:
[warning, libral snippage]
> FWIW Using dynamic_cast is fine in my book.
>
> dynamic polymorphism is a theory beloved of academics.
>
> outside the ideal world of academia and textbooks, dynamic_cast is
> required to discriminate.
It might be approprate to remember what started this thread in the
first place. Andy Champ found the dynamic_cast syntax to be ugly and
asked how often people use it. I said that I have never used it in 10
years of writing professional code.
Now here you are saying that it is required? All I can say is that our
experiences differ.
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/17/2008 5:15:36 PM
|
|
Daniel T. wrote:
> I am a video game programmer, so most of my
> work is in the simulation domain... often on systems that are much
> like what embedded programmers have to deal with.
> OO programming's roots are in simulation work so I sometimes think
> that I'm more steeped in the paradigm than many others.
Hmm, that's interesting. Does modelling a highly dynamic environment
such as a game world work well with a rigid object system such as C++'?
I could imagine that such applications in particular would benefit from
a more dynamic system and a more communicative rather than
type-hierarchical approach. Kindof like an agent world vs. a static
object tree.
|
|
0
|
|
|
|
Reply
|
mkb (996)
|
3/17/2008 8:37:45 PM
|
|
Matthias Buelow wrote:
> Hmm, that's interesting. Does modeling a highly dynamic environment
> such as a game world work well with a rigid object system such as C++'?
No. This is why some games ship with the upper 2/3rds of their code in Lua.
> Kindof like an agent world vs. a static
> object tree.
Right. "Alternate hard and soft layers".
--
Phlip
|
|
0
|
|
|
|
Reply
|
phlip2005 (2147)
|
3/17/2008 8:51:35 PM
|
|
Daniel T. wrote:
> It might be approprate to remember what started this thread in the
> first place. Andy Champ found the dynamic_cast syntax to be ugly and
> asked how often people use it. I said that I have never used it in 10
> years of writing professional code.
>
> Now here you are saying that it is required? All I can say is that our
> experiences differ.
Well actually I was providing a way to make it less ugly - but obviously
that's of no use to you in your style of working; others may be able to
use it.
|
|
0
|
|
|
|
Reply
|
no.way7055 (86)
|
3/17/2008 9:08:19 PM
|
|
Phlip wrote:
> Matthias Buelow wrote:
>
>> Hmm, that's interesting. Does modeling a highly dynamic environment
>> such as a game world work well with a rigid object system such as C++'?
>
> No. This is why some games ship with the upper 2/3rds of their code in Lua.
>
>> Kindof like an agent world vs. a static
>> object tree.
>
> Right. "Alternate hard and soft layers".
That's interesting, and not what I would have expected. I do a lot of
hardware simulation that I take to be very similar to video game
programming, and have better success with C++ as the language for almost
all code that gets shipped. I find dynamic languages useful for testing
and managing the in-house code-base. I looked into Lua at one point,
but didn't pursue it; is it worth checking out? Have you tried
embedding Python or Ruby in your application, or do they just look like
they would be prohibitively heavy-weight?
|
|
0
|
|
|
|
Reply
|
jeff34 (1594)
|
3/17/2008 9:09:09 PM
|
|
On Mar 17, 5:15=A0pm, "Daniel T." <danie...@earthlink.net> wrote:
> kwikius <a...@servocomm.freeserve.co.uk> wrote:
>
> [warning, libral snippage]
>
> > FWIW =A0Using dynamic_cast is fine in my book.
>
> > dynamic polymorphism is a theory beloved of academics.
>
> > outside the ideal world of academia and textbooks, dynamic_cast is
> > required to discriminate.
>
> It might be approprate to remember what started this thread in the
> first place. Andy Champ found the dynamic_cast syntax to be ugly and
> asked how often people use it. I said that I have never used it in 10
> years of writing professional code.
>
> Now here you are saying that it is required? All I can say is that our
> experiences differ.
I use currently extensively in a so called DOM lib. Or call it a
directory tree or "duck typing" or whatever.
Firstly I use it for discriminating branches and leaves. If a
dynamic_cast to a branch from a node to a branch fails ( for say
looking up a path), then it throws an exception. Second I use it to
retrieve data from leaves.
In this type of tree you can put any type of data you like in any leaf
node at runtime. You can use various methods to structure the tree.
The simplest is just knowing what type of data lives at
"root.my_stuff.stuff". Data Access is via a function templated on the
type of data. Again if a dynamic_cast to the expected data type fails
it throws an exception.
Its a very versatile and useful library. You can prototype very
quickly with it.
regards
Andy Little
|
|
0
|
|
|
|
Reply
|
andy199 (808)
|
3/17/2008 11:37:19 PM
|
|
On Mar 7, 11:39=A0am, Michael DOUBEZ <michael.dou...@free.fr> wrote:
> Juha Nieminen a =E9crit :
>
> > Greg Herlihy wrote:
> >> On Mar 5, 3:05 pm, Andy Champ <no....@nospam.com> wrote:
> >>> How many times have you written
>
> >>> someType* var =3D dynamic_cast<someType*>(someOtherPtr);
>
> >>> and wondered WTH you have to quote the type twice?
> >> The C++ cast operators, including dynamic_cast<>, were deliberately
> >> designed to have an unwieldy syntax precisely to discourage C++
> >> programmers from using them.
>
> > =A0 Do you have any reference to corroborate this?
>
> I guess it is one of those urban legend.
It is IIRC specifically stated in either The C++ programming language
book, or in the Design and Evolution of C++ book, (both by Bjarne
Stroustruup) I can't remember which, probably TCPPL .
regards
Andy Little
|
|
0
|
|
|
|
Reply
|
andy199 (808)
|
3/17/2008 11:50:31 PM
|
|
On Mar 7, 8:24=A0pm, "Daniel T." <danie...@earthlink.net> wrote:
> On Mar 7, 2:58=A0pm, Ian Collins <ian-n...@hotmail.com> wrote:
>
> > Daniel T. wrote:
>
> > > Because I program using OO idioms, not representational ones. Or to pu=
t
> > > it another way, I specifically design my programs such that I don't ne=
ed
> > > to use dynamic_cast... even occasionally.
>
> > There are occasions when the interface forces your hand. =A0For example
> > when implementing the W3C Document Object Model, where all of the
> > container types are collections of the the base object (Node). =A0Node i=
s
> > seldom used, most containers end up storing derived objects (Elements or=
> > Attributes) that extend the functionality of Node.
>
> All I can say to that is that I have never had my hand forced. :-) The
> question with the above is, do you know statically (i.e., at compile
> time) the types of the objects?
You generally read 'em in from a stream at runtime.
regards
Andy Little
|
|
0
|
|
|
|
Reply
|
andy199 (808)
|
3/18/2008 12:13:42 AM
|
|
kwikius wrote:
> On Mar 7, 11:39 am, Michael DOUBEZ <michael.dou...@free.fr> wrote:
>> Juha Nieminen a �crit :
>>
>>> Greg Herlihy wrote:
>>>> On Mar 5, 3:05 pm, Andy Champ <no....@nospam.com> wrote:
>>>>> How many times have you written
>>>>> someType* var = dynamic_cast<someType*>(someOtherPtr);
>>>>> and wondered WTH you have to quote the type twice?
>>>> The C++ cast operators, including dynamic_cast<>, were deliberately
>>>> designed to have an unwieldy syntax precisely to discourage C++
>>>> programmers from using them.
>>> Do you have any reference to corroborate this?
>> I guess it is one of those urban legend.
>
> It is IIRC specifically stated in either The C++ programming language
> book, or in the Design and Evolution of C++ book, (both by Bjarne
> Stroustruup) I can't remember which, probably TCPPL .
It's in D&E, and in Bjarne's FAQ. He has also addressed it in the past
in c.l.c++.
|
|
0
|
|
|
|
Reply
|
jeff34 (1594)
|
3/18/2008 12:40:00 AM
|
|
Matthias Buelow <m...@incubus.de> wrote:
> Daniel T. wrote:
>
> > I am a video game programmer, so most of my
> > work is in the simulation domain... often on systems that are much
> > like what embedded programmers have to deal with.
> > OO programming's roots are in simulation work so I sometimes think
> > that I'm more steeped in the paradigm than many others.
>
> Hmm, that's interesting. Does modelling a highly dynamic environment
> such as a game world work well with a rigid object system such as C++'?
Absolutely. IMHO, the only real difference between them is how much of
the design is exposed code.
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/18/2008 12:48:18 AM
|
|
Juha Nieminen <nospam@thanks.invalid> wrote:
> Daniel T. wrote:
> > > > 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...
> >
> > You don't know how to make a virtual function in a base class?
>
> You want me to add a virtual function named "changeSquareColor" to
> the Shape base class if I want to be able to change the color of
> squares?
Not at all, that would be a silly name.
> That breaks object-oriented design quite horribly. For one, it
> breaks the "is-a" relationship. More precisely, it breaks the
> "behaves like" property of an "is-a" relationship: If you specify
> that "Shape" supports the functionality "changeSquareColor", you
> are effectively saying that *all* classes derived from Shape
> support that functionality. However, only one does, the others
> don't.
Is that really the case? Let's ignore the silly name for a bit and ask
something more important... What is the post-condition of this function?
I would expect the post-condition to be simply "true", i.e., the
function guarantees to return, but nothing else. Since no behavior is
prescribed, I don't see how any "behaves like" property could be broken.
So, why would a client call this function on the shapes in question? To
let them know of a particular event, *not* in order to tell them what to
do. When designing an OO system, it is better to decentralize your
thinking, try to treat each object has a semi-autonomous,
anthropomorphic entity, instead of a billiard ball you are banging
around. The is the basic design behind the observer pattern, notify
others of your state changes rather than telling them what state to
change to.
Is this particular design the best solution for this particular problem?
Probably not, but it does suit other problems where dynamic_cast is used.
> The base class would be cluttered with functions specific to some
> derived classes.
Absolutely not. I don't recommend putting functions in the base class
that relate to derived classes. I recommend putting functions in the
base class (interface) that relate to client classes.
> And besides, in some cases you *can't* modify the base class (eg.
> because it's in a library).
As I said more than once, if you are forced by the current design to use
dynamic_cast, then by all means use it.
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/18/2008 2:53:55 AM
|
|
Juha Nieminen <nospam@thanks.invalid> wrote:
> You may have several types derived from the base class, and they are
> all stored in a common data container, and their *order* inside that
> data container is crucial. Storing each type in their own type-specific
> data container doesn't work because this ordering is lost.
>
> Some operations need to be performed to the objects in the order in
> which they are stored.
Like what?
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/18/2008 2:56:20 AM
|
|
Daniel T. wrote:
>> Hmm, that's interesting. Does modelling a highly dynamic environment
>> such as a game world work well with a rigid object system such as C++'?
>
> Absolutely. IMHO, the only real difference between them is how much of
> the design is exposed code.
I'm not sure I understand what you mean.
|
|
0
|
|
|
|
Reply
|
mkb (996)
|
3/18/2008 3:13:41 PM
|
|
Daniel T. wrote:
> Juha Nieminen <nospam@thanks.invalid> wrote:
>
>> You may have several types derived from the base class, and they are
>> all stored in a common data container, and their *order* inside that
>> data container is crucial. Storing each type in their own type-specific
>> data container doesn't work because this ordering is lost.
>>
>> Some operations need to be performed to the objects in the order in
>> which they are stored.
>
> Like what?
Like for example saving the settings of those objects to a file. (The
order in which they are saved into the file determines the order in
which they are loaded, which then restores the original order.)
And no, it may not be possible to simply have a "virtual void
saveSettings()" function in the base class.
|
|
0
|
|
|
|
Reply
|
nospam270 (2853)
|
3/18/2008 4:42:32 PM
|
|
In article <47dff18c$0$8170$4f793bc4@news.tdc.fi>, nospam@thanks.invalid
says...
> Daniel T. wrote:
> > Juha Nieminen <nospam@thanks.invalid> wrote:
> >
> >> You may have several types derived from the base class, and they are
> >> all stored in a common data container, and their *order* inside that
> >> data container is crucial. Storing each type in their own type-specific
> >> data container doesn't work because this ordering is lost.
> >>
> >> Some operations need to be performed to the objects in the order in
> >> which they are stored.
> >
> > Like what?
>
> Like for example saving the settings of those objects to a file. (The
> order in which they are saved into the file determines the order in
> which they are loaded, which then restores the original order.)
>
> And no, it may not be possible to simply have a "virtual void
> saveSettings()" function in the base class.
If you could tell us more about _why_ it's not possible, that would
likely provide a lot more chance of somebody coming up with a real
solution for the problem. Right now, you simply haven't told us enough
for anybody to make an intelligent suggestion for how you should
proceed.
--
Later,
Jerry.
The universe is a figment of its own imagination.
|
|
0
|
|
|
|
Reply
|
jcoffin (2240)
|
3/19/2008 2:26:31 AM
|
|
Jerry Coffin wrote:
> If you could tell us more about _why_ it's not possible, that would
> likely provide a lot more chance of somebody coming up with a real
> solution for the problem. Right now, you simply haven't told us enough
> for anybody to make an intelligent suggestion for how you should
> proceed.
Since this is related to my work, and my contract agreement is
extremely restrictive about what I can divulge about my job, I'm not
sure how much I can describe what I'm doing in detail. (It may be ok to
give more details, but better safe than sorry, I suppose.)
|
|
0
|
|
|
|
Reply
|
nospam270 (2853)
|
3/19/2008 8:15:04 AM
|
|
Juha Nieminen <nospam@thanks.invalid> wrote:
> Jerry Coffin wrote:
> > If you could tell us more about _why_ it's not possible, that would
> > likely provide a lot more chance of somebody coming up with a real
> > solution for the problem. Right now, you simply haven't told us enough
> > for anybody to make an intelligent suggestion for how you should
> > proceed.
>
> Since this is related to my work, and my contract agreement is
> extremely restrictive about what I can divulge about my job, I'm not
> sure how much I can describe what I'm doing in detail. (It may be ok to
> give more details, but better safe than sorry, I suppose.)
So what we are left with is you demanding concrete code for a very
nebulous problem. A problem which you have basically defined as, "what
if I need to use dynamic_cast and there is no other way to do it?"
I've answered that question more than once.
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/19/2008 10:47:11 AM
|
|
On Mar 12, 10:55 am, "Daniel T." <danie...@earthlink.net> wrote:
> James Kanze <james.ka...@gmail.com> wrote:
> > "Daniel T." <danie...@earthlink.net> wrote:
> > > The idea is that you shouldn't be telling the object to do
> > > X, you shouldn't be telling it to change its state to X,
> > > you should be telling it about changes in its environment.
> > Agreed. You tell it to do something. In practice, however,
> > objects may pass through many layers of intermediate
> > software, which knows nothing about (or should know nothing
> > about) what services the provider actually offers, and what
> > services the consumer needs. In practice, however, real
> > software often has to deal with different versions: you must
> > query whether your partner supports some new functionality,
> > and be prepared to use an alternate strategy if it doesn't.
> > Those are, IMHO, two cases where dynamic_cast is called
> > for---in the first case, it actually helps design (by
> > encapsulating the functionality at the relevant level).
> That may be the practice in poor designs, but it is not the
> general case.
So explain the alternatives, and show how they're better. For
the moment, all you've said is "bad design", with no explination
as to why, or what would be better. Sort of like a child at a
playground.
--
James Kanze (GABI Software) email:james.kanze@gmail.com
Conseils en informatique orient=EF=BF=BDe objet/
Beratung in objektorientierter Datenverarbeitung
9 place S=EF=BF=BDmard, 78210 St.-Cyr-l'=EF=BF=BDcole, France, +33 (0)1 30 2=
3 00 34
|
|
0
|
|
|
|
Reply
|
james.kanze (9594)
|
3/19/2008 11:30:20 AM
|
|
Matthias Buelow <mkb@incubus.de> wrote:
> Daniel T. wrote:
>
> > > Hmm, that's interesting. Does modelling a highly dynamic environment
> > > such as a game world work well with a rigid object system such as C++'?
> >
> > Absolutely. IMHO, the only real difference between them is how much of
> > the design is exposed code.
>
> I'm not sure I understand what you mean.
When looking at a bunch of code, one of the questions I ask myself is,
"what types (classes) can be used in this context?" The answer to that
is in the code with static typed languages, but not dynamic types.
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/19/2008 10:00:59 PM
|
|
James Kanze <james.kanze@gmail.com> wrote:
> "Daniel T." <danie...@earthlink.net> wrote:
> > James Kanze <james.ka...@gmail.com> wrote:
> > > "Daniel T." <danie...@earthlink.net> wrote:
> > >
> > > > The idea is that you shouldn't be telling the object to do
> > > > X, you shouldn't be telling it to change its state to X,
> > > > you should be telling it about changes in its environment.
>
> > > Agreed. You tell it to do something. In practice, however,
> > > objects may pass through many layers of intermediate
> > > software, which knows nothing about (or should know nothing
> > > about) what services the provider actually offers, and what
> > > services the consumer needs. In practice, however, real
> > > software often has to deal with different versions: you must
> > > query whether your partner supports some new functionality,
> > > and be prepared to use an alternate strategy if it doesn't.
> > > Those are, IMHO, two cases where dynamic_cast is called
> > > for---in the first case, it actually helps design (by
> > > encapsulating the functionality at the relevant level).
>
> > That may be the practice in poor designs, but it is not the
> > general case.
>
> So explain the alternatives, and show how they're better. For
> the moment, all you've said is "bad design", with no explination
> as to why, or what would be better. Sort of like a child at a
> playground.
Alternatives have been discussed throughout this thread. As for a
specific solution when no specific problem has been presented... I can't
do that.
The entire discussion with you has been me saying "try not to do that
(but sometimes you have to)", and you saying "sometimes you have do it
(but try not to.)" Maybe we aren't really on different sides of the
fence at all...
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/19/2008 10:05:24 PM
|
|
James Kanze <james.kanze@gmail.com> wrote:
> So explain the alternatives, and show how they're better. For
> the moment, all you've said is "bad design", with no explination
> as to why, or what would be better. Sort of like a child at a
> playground.
A related issue came up at work today...
Our framework has a class "Graphic", we have several classes derived
from it including "Image" and "Animation".
Graphics have rotate functionality, but they rotate about their center,
and what I needed was to be able to rotate the graphic about any
arbitrary point along the vertical center of the graphic. This meant
doing some math to change the x, y of the graphic as I was rotating it.
In addition, Graphics have a "bool hit( int x, int y ) const" function.
If the 'x' and 'y' are in the hit area of the graphic, then it returns
true. This function also needs to be modified so that it returns true if
a particular region of the graphic was hit based on the current rotation.
(To help picture the situation, imagine a picture of a crank. Normally,
for such an image, the fulcrum would be at the center of the crank, and
the hit region would be anywhere on the image. What I needed was for the
fulcrum to be at one end of the image, and the hit region to be at the
other.)
So, in my estimation, I need a decorator that changes what the rotate
and hit functions do.
class RotateableGraphic : public Graphic {
Graphic* graphic;
// other data to keep track of the sweep radius.
public:
RotateableGraphic( Graphic* g ): graphic( g ) { }
void rotate( int degrees ) {
// do math that rotates 'graphic' and changes its 'x' and 'y'
}
bool hit( int x, int y ) const {
// do the math to figure out if 'x' and 'y' are in the hit region
}
};
Part of the problem is that I also want to be able to call the
additional functions in Image and Animation even after adding it to the
RotateableGraphic.
It seems that some here would argue that the best thing to do would be
to provide a "getGraphic" function and then dynamic_cast it in order to
access that extra functionality. What would you do?
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/19/2008 10:56:03 PM
|
|
On Mar 18, 12:42=A0pm, Juha Nieminen <nos...@thanks.invalid> wrote:
> Daniel T. wrote:
> > Juha Nieminen <nos...@thanks.invalid> wrote:
>
> >> You may have several types derived from the base class, and they are
> >> all stored in a common data container, and their *order* inside that
> >> data container is crucial. Storing each type in their own type-specific=
> >> data container doesn't work because this ordering is lost.
>
> >> Some operations need to be performed to the objects in the order in
> >> which they are stored.
>
> > Like what?
>
> =A0 Like for example saving the settings of those objects to a file. (The
> order in which they are saved into the file determines the order in
> which they are loaded, which then restores the original order.)
>
> =A0 And no, it may not be possible to simply have a "virtual void
> saveSettings()" function in the base class.
I've just gone back and re-read most of this thread to ensure that the
crucial points are clear in my mind. However, it's entirely possible
I've missed something.
It seems to me that you've elevated a property of shapes - "being a
square" - from the status of a property to the status of a type. C+
+'s support for predicate classes, a language feature that would make
this kind of specialization natural, is practically nonexistant, so
this may not have been a sensible way to model the problem domain.
That is: I would not have made squares into a class of their own, but
rather expressed them via a predicate function that can be applied to
shapes. By not elevating the predicate to the class hierarchy,
querying and selection against the predicate is much more natural.
You could, for example, have
vector<Shape *> squares =3D filter (shapes.begin (),
shapes.end (),
&is_square);
for some reasonable definition of the filter function.
In fact, this lends itself to compositional filters - you could write
is_square (Shape*) in terms of other queries against the number of
sides the shape has, how long each side is, and what the angle at each
vertex is, or any other combination of properties that defines a
square.
The tradeoff here is that the Shape class is reduced to holding
properties and completely generic behaviour - most of the behaviour
that would be specific to some types of shapes would end up being
implemented in helpers that accept Shape* (which would in turn have to
protect themselves from being passed non-square Shape*s).
I think this line of reasoning applies to the UML/UML_Interface/
UML_Class variation of this design point as well: "being an interface"
is not a natural fit for a C++ class distinction, but rather a
property of the underlying UML object.
-o
|
|
0
|
|
|
|
Reply
|
angrybaldguy (338)
|
3/20/2008 5:17:43 AM
|
|
Owen Jacobson a �crit :
> On Mar 18, 12:42 pm, Juha Nieminen <nos...@thanks.invalid> wrote:
>> Daniel T. wrote:
>>> Juha Nieminen <nos...@thanks.invalid> wrote:
>>>> You may have several types derived from the base class, and they are
>>>> all stored in a common data container, and their *order* inside that
>>>> data container is crucial. Storing each type in their own type-specific
>>>> data container doesn't work because this ordering is lost.
>>>> Some operations need to be performed to the objects in the order in
>>>> which they are stored.
>>> Like what?
>> Like for example saving the settings of those objects to a file. (The
>> order in which they are saved into the file determines the order in
>> which they are loaded, which then restores the original order.)
>>
>> And no, it may not be possible to simply have a "virtual void
>> saveSettings()" function in the base class.
>
> I've just gone back and re-read most of this thread to ensure that the
> crucial points are clear in my mind. However, it's entirely possible
> I've missed something.
>
> It seems to me that you've elevated a property of shapes - "being a
> square" - from the status of a property to the status of a type. C+
> +'s support for predicate classes, a language feature that would make
> this kind of specialization natural, is practically nonexistant, so
> this may not have been a sensible way to model the problem domain.
>
> That is: I would not have made squares into a class of their own, but
> rather expressed them via a predicate function that can be applied to
> shapes. By not elevating the predicate to the class hierarchy,
> querying and selection against the predicate is much more natural.
> You could, for example, have
>
> vector<Shape *> squares = filter (shapes.begin (),
> shapes.end (),
> &is_square);
>
> for some reasonable definition of the filter function.
>
> In fact, this lends itself to compositional filters - you could write
> is_square (Shape*) in terms of other queries against the number of
> sides the shape has, how long each side is, and what the angle at each
> vertex is, or any other combination of properties that defines a
> square.
>
> The tradeoff here is that the Shape class is reduced to holding
> properties and completely generic behaviour - most of the behaviour
> that would be specific to some types of shapes would end up being
> implemented in helpers that accept Shape* (which would in turn have to
> protect themselves from being passed non-square Shape*s).
The only gain is that you don't apply a dynamic_cast<> on all elements
of the collection. This is a gain somewhat depending on the complexity
of the predicate but it doesn't remove the need for dynamic_cast<>.
The subject here was "How strong an indicator of bad design is
dynamic_cast<>() ?".
The answer I think is there are cases where you can find alternatives to
dynamic_cast<>() like visitor, delegation, messaging system ... But the
trade-off it creates doesn't really make it worth.
> I think this line of reasoning applies to the UML/UML_Interface/
> UML_Class variation of this design point as well: "being an interface"
> is not a natural fit for a C++ class distinction, but rather a
> property of the underlying UML object.
I don't understand here. There are two main ways to make it an interface
in c++:
- duck typing
- strong typing
"being an interface" is a natural fit I would say.
What is missing at the language level is the messaging system such as in
smalltalk (that you retrieve in dynamic typing language like python or
ruby). That can be emulated in C++ but it is heavier/uglier to
manipulate (I don't want to make this thread bigger by a static/dynamic
typing holy war so let it lie :) ).
Michael
|
|
0
|
|
|
|
Reply
|
michael.doubez (922)
|
3/20/2008 8:18:31 AM
|
|
Michael DOUBEZ wrote:
> What is missing at the language level is the messaging system such as in
> smalltalk (that you retrieve in dynamic typing language like python or
> ruby). That can be emulated in C++ but it is heavier/uglier to
> manipulate (I don't want to make this thread bigger by a static/dynamic
> typing holy war so let it lie :) ).
Maybe what C++ needs is a kind of "type conditionals" (I don't know
what kind of paradigm this would fall into). Something along the lines of:
void foo(Shape* ptr)
{
switch(the_real_type_of(ptr))
{
case <Square*>(sPtr):
// Do something with sPtr
break;
case <Circle*>(cPtr):
// Do something with cPtr
break;
}
}
The advantage of this is that the compiler could generate code which
directly casts the pointer to the actual type it's pointing to, thus
saving the overhead and ugliness of repeated dynamic_casts, which may
fail. (After all, it feels useless to have to try different
dynamic_casts until you find the correct type.)
It would also avoid the overhead related to a true
messaging/delegation system.
|
|
0
|
|
|
|
Reply
|
nospam270 (2853)
|
3/20/2008 9:13:22 AM
|
|
Juha Nieminen a �crit :
> Michael DOUBEZ wrote:
>> What is missing at the language level is the messaging system such as in
>> smalltalk (that you retrieve in dynamic typing language like python or
>> ruby). That can be emulated in C++ but it is heavier/uglier to
>> manipulate (I don't want to make this thread bigger by a static/dynamic
>> typing holy war so let it lie :) ).
>
> Maybe what C++ needs is a kind of "type conditionals" (I don't know
> what kind of paradigm this would fall into). Something along the lines of:
>
> void foo(Shape* ptr)
> {
> switch(the_real_type_of(ptr))
> {
> case <Square*>(sPtr):
> // Do something with sPtr
> break;
>
> case <Circle*>(cPtr):
> // Do something with cPtr
> break;
> }
> }
>
> The advantage of this is that the compiler could generate code which
> directly casts the pointer to the actual type it's pointing to, thus
> saving the overhead and ugliness of repeated dynamic_casts, which may
> fail. (After all, it feels useless to have to try different
> dynamic_casts until you find the correct type.)
In fact, it would be the same as a dynamic cast. The compiler would have
to perform a search, at runtime, in the inheritance tree of the
underlying object in order to find a match.
The only advantage of such a construct would be compared to
if(dynamic_cast<Square*>(sPtr))
{
}
else if(dynamic_cast<Circle*>(sPtr))
{
}
else
{
}
And that kind of construct is truly ugly from the design point of view.
Foo should go in the base class or if you want to make it
method-extensible, you should go to the overhead of a visitor pattern or
another construct.
Michael
|
|
0
|
|
|
|
Reply
|
michael.doubez (922)
|
3/20/2008 11:04:08 AM
|
|
On Thu, 20 Mar 2008 11:13:22 +0200, Juha Nieminen wrote:
> Maybe what C++ needs is a kind of "type conditionals" (I don't know
> what kind of paradigm this would fall into).
It is manual dispatch (implemented by comparing types tags).
> Something along the lines of:
>
> void foo(Shape* ptr)
> {
> switch(the_real_type_of(ptr))
> {
> case <Square*>(sPtr):
> // Do something with sPtr
> break;
>
> case <Circle*>(cPtr):
> // Do something with cPtr
> break;
> }
> }
In order to do this in a sane way you have first to separate "real"
(=specific) and "unreal" (=class-wide) types of objects. Otherwise the
above becomes ambiguous. It is unclear whether <Square *> denotes only
pointers to Square, or also pointers to all possible descendants of Square.
> (After all, it feels useless to have to try different
> dynamic_casts until you find the correct type.)
It is same as with catching exceptions. Consider:
switch (*Ptr)
{
case (Square& Obj) { ... // deal with Obj }
case (Rectangle& Obj) { ... }
(I have changed the syntax to make it more obvious). Considering Square
derived from Rectangle, which alternative to select? When Square& and
Rectangle& are treated class-wide, then Square& is a part of Rectangle& (a
subclass). When Square& and Rectangle& are considered specific types, then
they are distinct.
> It would also avoid the overhead related to a true
> messaging/delegation system.
Better than manual dispatch are models where all arguments of a subprogram
are dispatching (virtual), including the result. In C++ only the first,
hidden argument is (when the subprogram is declared virtual).
--
Regards,
Dmitry A. Kazakov
http://www.dmitry-kazakov.de
|
|
0
|
|
|
|
Reply
|
mailbox2 (6354)
|
3/20/2008 11:05:29 AM
|
|
Owen Jacobson wrote:
<snip leaving only enough for context>
> That is: I would not have made squares into a class of their own, but
> rather expressed them via a predicate function that can be applied to
> shapes. By not elevating the predicate to the class hierarchy,
> querying and selection against the predicate is much more natural.
> You could, for example, have
>
Owen,
I don't see what that gains you over dynamic_cast, and I can see it
could give problems.
Under the hood, I expect dynamic_cast<Square*>(someShapePtr) does
something like:
if (&(someShapePtr->vtbl) == &vbtl_Square)
{
return someShapePtr;
}
else
{
return 0;
}
This isn't a complicated bit of code.
Your function would have to be declared on Shape - and a similar
function for every derived class - and implemented to return false. Or
if it isn't a member, you have to implement a static function that takes
a pointer and somehow without dynamic_cast discovers if it's a Shape*.
This is a lot of (simple) code, if you have lots of classes, and because
it is so simple you won't concentrate when duplicating it for a new
class - and so you'll have two classes with the same "type".
What *would* be useful - and I've thought of doing it in our project -
is a virtual function on each class that returns an ID. You could then
switch on the ID. But once again, it's error prone, and has been
pointed out, what is the position for a multi-level inheritance
hierarchy - is a square a rectangle? A Rhombus? A Parallelogram? A
Quadrilateral? A Polygon? Or just a square?
Andy
|
|
0
|
|
|
|
Reply
|
no.way7055 (86)
|
3/20/2008 8:22:27 PM
|
|
On Mar 20, 3:22=A0pm, Andy Champ <no....@nospam.com> wrote:
> what is the position for a multi-level inheritance
> hierarchy - is a square a rectangle? =A0A Rhombus? A Parallelogram? A
> Quadrilateral? A Polygon? =A0Or just a square?
Depends on how you define the interface.
http://www.parashift.com/c++-faq-lite/proper-inheritance.html#faq-21.6
|
|
0
|
|
|
|
Reply
|
dave_mikesell (189)
|
3/20/2008 8:37:47 PM
|
|
Michael DOUBEZ wrote:
> In fact, it would be the same as a dynamic cast. The compiler would have
> to perform a search, at runtime, in the inheritance tree of the
> underlying object in order to find a match.
The point is that the compiler has to make it only *once*. When it has
found the correct type, it can directly jump to the proper "case" block.
This would not only be more efficient (because there's no "try dynamic
cast again and again against different types until you find the correct
one), the code becomes cleaner (basically the only dynamic cast is done
behind the scenes, and failure simply means that the 'default' block is
executed, or nothing at all if there's no such block).
|
|
0
|
|
|
|
Reply
|
nospam270 (2853)
|
3/20/2008 8:59:42 PM
|
|
Daniel T. wrote:
> So, in my estimation, I need a decorator that changes what the rotate
> and hit functions do.
>
> class RotateableGraphic : public Graphic {
> Graphic* graphic;
> // other data to keep track of the sweep radius.
> public:
> RotateableGraphic( Graphic* g ): graphic( g ) { }
>
> void rotate( int degrees ) {
> // do math that rotates 'graphic' and changes its 'x' and 'y'
> }
>
> bool hit( int x, int y ) const {
> // do the math to figure out if 'x' and 'y' are in the hit region
> }
> };
>
> Part of the problem is that I also want to be able to call the
> additional functions in Image and Animation even after adding it to the
> RotateableGraphic.
>
> It seems that some here would argue that the best thing to do would be
> to provide a "getGraphic" function and then dynamic_cast it in order to
> access that extra functionality. What would you do?
Personally I'd use the cast, but I'd like to know how you get around not
using it.
|
|
0
|
|
|
|
Reply
|
lilburne718
|
3/21/2008 12:06:45 PM
|
|
lilburne <lilburne@godzilla.npspam.net> wrote:
> Daniel T. wrote:
> > So, in my estimation, I need a decorator that changes what the rotate
> > and hit functions do.
> >
> > class RotateableGraphic : public Graphic {
> > Graphic* graphic;
> > // other data to keep track of the sweep radius.
> > public:
> > RotateableGraphic( Graphic* g ): graphic( g ) { }
> >
> > void rotate( int degrees ) {
> > // do math that rotates 'graphic' and changes its 'x' and 'y'
> > }
> >
> > bool hit( int x, int y ) const {
> > // do the math to figure out if 'x' and 'y' are in the hit region
> > }
> > };
> >
> > Part of the problem is that I also want to be able to call the
> > additional functions in Image and Animation even after adding it to the
> > RotateableGraphic.
> >
> > It seems that some here would argue that the best thing to do would be
> > to provide a "getGraphic" function and then dynamic_cast it in order to
> > access that extra functionality. What would you do?
>
> Personally I'd use the cast, but I'd like to know how you get around not
> using it.
First solution... Note that the code I wrote above has no destructor, so
what happens to the graphic passed in when the RotaeableGraphic object
is destroyed? That is a clue. The object that passes in the Graphic
still has it and can call any functions it wants on it. So, for example,
an object can create an Image and pass it to a RotateableGraphic object,
then call the additional Image methods on it anytime he wants. In the
mean time, he can pass the RotateableGraphic to any generic Graphic
containers and they will ultimately modify the state of the Image
contained.
Next solution... Make RotateableGraphic a template:
template < typename T >
class RotateableGraphic : public T
{
public:
// as above except 'g' is absent.
};
With this solution, RotateableGraphic can inherit from any type derived
from graphic and doesn't need to hold a pointer to a Graphic, since it
is the Graphic.
In the first case, if some other object wants to manipulate the graphic
as the derived type, then the creator of the object needs to pass a
reference to the object inside the RotateableGraphic, instead of the
RotateableGraphic object. This makes sense because the first object
created it, so knows its type, and the second object wants to use that
particular type so everything is obvious.
In the second case, there is no object inside the RotateableGraphic, but
it can be used as the appropriate type by simply *up-casting* one level,
(an operation that is guaranteed to succeed.)
In neither case would the RotateableGraphic class even have a
"getGraphic" member, so there is no question about pulling it out and
wondering what sub-type it may be.
|
|
0
|
|
|
|
Reply
|
daniel_t (1960)
|
3/21/2008 11:10:19 PM
|
|
On Mar 21, 11:10=A0pm, "Daniel T." <danie...@earthlink.net> wrote:
> lilburne <lilbu...@godzilla.npspam.net> wrote:
> > Daniel T. wrote:
> > > So, in my estimation, I need a decorator that changes what the rotate
> > > and hit functions do.
>
> > > class RotateableGraphic : public Graphic {
> > > =A0 =A0Graphic* graphic;
> > > =A0 =A0// other data to keep track of the sweep radius.
> > > public:
> > > =A0 =A0RotateableGraphic( Graphic* g ): graphic( g ) { }
>
> > > =A0 =A0void rotate( int degrees ) {
> > > =A0 =A0 =A0 // do math that rotates 'graphic' and changes its 'x' and =
'y'
> > > =A0 =A0}
>
> > > =A0 =A0bool hit( int x, int y ) const {
> > > =A0 =A0 =A0 // do the math to figure out if 'x' and 'y' are in the hit=
region
> > > =A0 =A0}
> > > };
>
> > > Part of the problem is that I also want to be able to call the
> > > additional functions in Image and Animation even after adding it to th=
e
> > > RotateableGraphic.
>
> > > It seems that some here would argue that the best thing to do would be=
> > > to provide a "getGraphic" function and then dynamic_cast it in order t=
o
> > > access that extra functionality. What would you do?
>
> > Personally I'd use the cast, but I'd like to know how you get around not=
> > using it.
>
> First solution... Note that the code I wrote above has no destructor, so
> what happens to the graphic passed in when the RotaeableGraphic object
> is destroyed? That is a clue. The object that passes in the Graphic
> still has it and can call any functions it wants on it. So, for example,
> an object can create an Image and pass it to a RotateableGraphic object,
> then call the additional Image methods on it anytime he wants. In the
> mean time, he can pass the RotateableGraphic to any generic Graphic
> containers and they will ultimately modify the state of the Image
> contained.
>
> Next solution... Make RotateableGraphic a template:
>
> =A0 =A0template < typename T >
> class RotateableGraphic : public T
> {
> public:
> =A0 =A0// as above except 'g' is absent.
>
> };
>
> With this solution, RotateableGraphic can inherit from any type derived
> from graphic and doesn't need to hold a pointer to a Graphic, since it
> is the Graphic.
>
> In the first case, if some other object wants to manipulate the graphic
> as the derived type, then the creator of the object needs to pass a
> reference to the object inside the RotateableGraphic, instead of the
> RotateableGraphic object. This makes sense because the first object
> created it, so knows its type, and the second object wants to use that
> particular type so everything is obvious.
>
> In the second case, there is no object inside the RotateableGraphic, but
> it can be used as the appropriate type by simply *up-casting* one level,
> (an operation that is guaranteed to succeed.)
>
> In neither case would the RotateableGraphic class even have a
> "getGraphic" member, so there is no question about pulling it out and
> wondering what sub-type it may be.
The functionality in the first option seems somewhat fragile
because validity of the proxy RotatableViewOfObject depends on the
validity of the core 'graphic' object.
There seems to be no functionality provided to set the centre of
rotation or rotation angle angle as a graphic, this being additional
functionality, so whatever needs to manipulate a graphic as a
RotateableGraphic needs to ascertain it Is, the simplest solution for
that being AFAICS...dynamic_cast.
The cleverness or otherwise of the owner is irrelevant AFAICS.
regards
Andy Little
|
|
0
|
|
|
|
Reply
|
andy199 (808)
|
3/25/2008 11:49:42 PM
|
|
In message
<58cc0707-53db-4f56-9b74-39630644eac0@n58g2000hsf.googlegroups.com>,
James Kanze <james.kanze@gmail.com> writes
>On Mar 12, 10:55 am, "Daniel T." <danie...@earthlink.net> wrote:
>> James Kanze <james.ka...@gmail.com> wrote:
>> > "Daniel T." <danie...@earthlink.net> wrote:
>
>> > > The idea is that you shouldn't be telling the object to do
>> > > X, you shouldn't be telling it to change its state to X,
>> > > you should be telling it about changes in its environment.
>
>> > Agreed. You tell it to do something. In practice, however,
>> > objects may pass through many layers of intermediate
>> > software, which knows nothing about (or should know nothing
>> > about) what services the provider actually offers, and what
>> > services the consumer needs. In practice, however, real
>> > software often has to deal with different versions: you must
>> > query whether your partner supports some new functionality,
>> > and be prepared to use an alternate strategy if it doesn't.
>> > Those are, IMHO, two cases where dynamic_cast is called
>> > for---in the first case, it actually helps design (by
>> > encapsulating the functionality at the relevant level).
>
>> That may be the practice in poor designs, but it is not the
>> general case.
>
>So explain the alternatives, and show how they're better. For
>the moment, all you've said is "bad design", with no explination
>as to why, or what would be better. Sort of like a child at a
>playground.
>
I understand the correct unanswerable put-down in these circumstances is
"won't pass code review in any place I've ever worked in"
;-/
--
Richard Herring
|
|
0
|
|
|
|
Reply
|
Richard
|
3/26/2008 3:00:08 PM
|
|
On 26 mar, 16:00, Richard Herring <junk@[127.0.0.1]> wrote:
[...]
> I understand the correct unanswerable put-down in these
> circumstances is "won't pass code review in any place I've
> ever worked in"
That can be a problem. If the local coding guidelines forbid
dynamic_cast, then you have a problem. (In practice, I've found
the reverse to be true: before dynamic_cast was part of the
language, we developed techniques to emulate it. Because
although it can easily be abused, it's a necessary part of OO
design.)
--
James Kanze (GABI Software) email:james.kanze@gmail.com
Conseils en informatique orient=E9e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S=E9mard, 78210 St.-Cyr-l'=C9cole, France, +33 (0)1 30 23 00 34
|
|
0
|
|
|
|
Reply
|
james.kanze (9594)
|
3/26/2008 9:12:46 PM
|
|
Daniel T. wrote:
> lilburne <lilburne@godzilla.npspam.net> wrote:
>> Daniel T. wrote:
>
>>> So, in my estimation, I need a decorator that changes what the rotate
>>> and hit functions do.
>>>
>>> class RotateableGraphic : public Graphic {
>>> Graphic* graphic;
>>> // other data to keep track of the sweep radius.
>>> public:
>>> RotateableGraphic( Graphic* g ): graphic( g ) { }
>>>
>>> void rotate( int degrees ) {
>>> // do math that rotates 'graphic' and changes its 'x' and 'y'
>>> }
>>>
>>> bool hit( int x, int y ) const {
>>> // do the math to figure out if 'x' and 'y' are in the hit region
>>> }
>>> };
>>>
>>> Part of the problem is that I also want to be able to call the
>>> additional functions in Image and Animation even after adding it to the
>>> RotateableGraphic.
>>>
>>> It seems that some here would argue that the best thing to do would be
>>> to provide a "getGraphic" function and then dynamic_cast it in order to
>>> access that extra functionality. What would you do?
>> Personally I'd use the cast, but I'd like to know how you get around not
>> using it.
>
> First solution... Note that the code I wrote above has no destructor, so
> what happens to the graphic passed in when the RotaeableGraphic object
> is destroyed? That is a clue. The object that passes in the Graphic
> still has it and can call any functions it wants on it. So, for example,
> an object can create an Image and pass it to a RotateableGraphic object,
> then call the additional Image methods on it anytime he wants. In the
> mean time, he can pass the RotateableGraphic to any generic Graphic
> containers and they will ultimately modify the state of the Image
> contained.
>
The problem is that in the cases I have its the reading code that has
created the objects, and built up topologies of different types of
surfaces, stitched together by shared edge curves and faces. So the
application sees
Topology->Face[]->Surface->Edges[]->Curve
But ignore most of that and just consider the Curve types
Curve -
| TransformedCurve
| Bezier
| Nurb
| Arc
| Polyline
| CompositeCurve
| ProjectedCurve
| SurfaceCurve
| ReversedCurve
CompositeCurve(Curve[])
TransformedCurve(Curve, Xform)
ProjectedCurve(Curve, Plane)
SurfaceCurve(Surface)
Lets say I want to extract all the edge curves from a topology and
perform some analysis on them. Because certain types of geometric
analysis can be performed more quickly if one knows what the type is,
for example arc-line intersection, or arc-arc intersection, one also
wants to avoid wrapping if possible.
So whilst a method that involved transforming all the input may just
wrap the input curves in a TransformCurve as that wouldn't require a
deep copy of the curves (you wouldn't simple physically transform the
curves themselves because that would require transforming the topology).
it may well decide to retain any Arcs or Polylines in the input, so one
may well dynamically cast for those type, and deep copy and physically
transform just those types.
I was hoping your solution would help in situations like that, but
thanks anyway.
|
|
0
|
|
|
|
Reply
|
lilburne718
|
3/27/2008 10:34:13 AM
|
|
lilburne a �crit :
> Daniel T. wrote:
>> lilburne <lilburne@godzilla.npspam.net> wrote:
>>> Daniel T. wrote:
>>
>>>> So, in my estimation, I need a decorator that changes what the
>>>> rotate and hit functions do.
>>>>
>>>> class RotateableGraphic : public Graphic {
>>>> Graphic* graphic;
>>>> // other data to keep track of the sweep radius.
>>>> public:
>>>> RotateableGraphic( Graphic* g ): graphic( g ) { }
>>>>
>>>> void rotate( int degrees ) {
>>>> // do math that rotates 'graphic' and changes its 'x' and 'y'
>>>> }
>>>>
>>>> bool hit( int x, int y ) const {
>>>> // do the math to figure out if 'x' and 'y' are in the hit region
>>>> }
>>>> };
>>>>
>>>> Part of the problem is that I also want to be able to call the
>>>> additional functions in Image and Animation even after adding it to
>>>> the RotateableGraphic.
>>>>
>>>> It seems that some here would argue that the best thing to do would
>>>> be to provide a "getGraphic" function and then dynamic_cast it in
>>>> order to access that extra functionality. What would you do?
>>> Personally I'd use the cast, but I'd like to know how you get around
>>> not using it.
>>
>> First solution... Note that the code I wrote above has no destructor,
>> so what happens to the graphic passed in when the RotaeableGraphic
>> object is destroyed? That is a clue. The object that passes in the
>> Graphic still has it and can call any functions it wants on it. So,
>> for example, an object can create an Image and pass it to a
>> RotateableGraphic object, then call the additional Image methods on it
>> anytime he wants. In the mean time, he can pass the RotateableGraphic
>> to any generic Graphic containers and they will ultimately modify the
>> state of the Image contained.
>>
>
> The problem is that in the cases I have its the reading code that has
> created the objects, and built up topologies of different types of
> surfaces, stitched together by shared edge curves and faces. So the
> application sees
>
> Topology->Face[]->Surface->Edges[]->Curve
>
> But ignore most of that and just consider the Curve types
>
> Curve -
> | TransformedCurve
> | Bezier
> | Nurb
> | Arc
> | Polyline
> | CompositeCurve
> | ProjectedCurve
> | SurfaceCurve
> | ReversedCurve
>
> CompositeCurve(Curve[])
> TransformedCurve(Curve, Xform)
> ProjectedCurve(Curve, Plane)
> SurfaceCurve(Surface)
>
>
> Lets say I want to extract all the edge curves from a topology and
> perform some analysis on them. Because certain types of geometric
> analysis can be performed more quickly if one knows what the type is,
> for example arc-line intersection, or arc-arc intersection, one also
> wants to avoid wrapping if possible.
>
> So whilst a method that involved transforming all the input may just
> wrap the input curves in a TransformCurve as that wouldn't require a
> deep copy of the curves (you wouldn't simple physically transform the
> curves themselves because that would require transforming the topology).
> it may well decide to retain any Arcs or Polylines in the input, so one
> may well dynamically cast for those type, and deep copy and physically
> transform just those types.
>
> I was hoping your solution would help in situations like that, but
> thanks anyway.
Your problem is different: you have a limited number of class to
consider (there is only so much curve classes ...) but you might want to
extend the procedures applied to you topology (rendering,
searching,finding a path ...) .
This is typically the case where a visitor pattern is IMO a good option.
Michael
|
|
0
|
|
|
|
Reply
|
michael.doubez (922)
|
3/27/2008 11:19:17 AM
|
|
Michael DOUBEZ wrote:
> lilburne a �crit :
>>
>> The problem is that in the cases I have its the reading code that has
>> created the objects, and built up topologies of different types of
>> surfaces, stitched together by shared edge curves and faces. So the
>> application sees
>>
>> Topology->Face[]->Surface->Edges[]->Curve
>>
>> But ignore most of that and just consider the Curve types
>>
>> Curve -
>> | TransformedCurve
>> | Bezier
>> | Nurb
>> | Arc
>> | Polyline
>> | CompositeCurve
>> | ProjectedCurve
>> | SurfaceCurve
>> | ReversedCurve
>>
>> CompositeCurve(Curve[])
>> TransformedCurve(Curve, Xform)
>> ProjectedCurve(Curve, Plane)
>> SurfaceCurve(Surface)
>>
>>
>> Lets say I want to extract all the edge curves from a topology and
>> perform some analysis on them. Because certain types of geometric
>> analysis can be performed more quickly if one knows what the type is,
>> for example arc-line intersection, or arc-arc intersection, one also
>> wants to avoid wrapping if possible.
>>
>> So whilst a method that involved transforming all the input may just
>> wrap the input curves in a TransformCurve as that wouldn't require a
>> deep copy of the curves (you wouldn't simple physically transform the
>> curves themselves because that would require transforming the
>> topology). it may well decide to retain any Arcs or Polylines in the
>> input, so one may well dynamically cast for those type, and deep copy
>> and physically transform just those types.
>>
>> I was hoping your solution would help in situations like that, but
>> thanks anyway.
>
> Your problem is different: you have a limited number of class to
> consider (there is only so much curve classes ...)
There are 'only so much' in most other domains too.
> but you might want to
> extend the procedures applied to you topology (rendering,
> searching,finding a path ...) .
One wouldn't want to pollute the geometry classes with graphical
knowledge, these underlaying classes describe the geometry
mathematically. But you've highlight another example of the problem: it
is far more efficient to render a trimmed plane or cylinder than it is
to render a swoopy surface. So the graphical rendering engine also
dynamically casts to optimize its handling of the simple cases.
> This is typically the case where a visitor pattern is IMO a good
> option.
>
For some methods each curve needs to be processed in strict order
regardless of its type. I can't for example drive a plotter to draw all
the lines, then all the arcs, then all the other bits. Well I could but
the users will get rather upset as the pen lifts off the paper and jumps
around not drawing anything.
|
|
0
|
|
|
|
Reply
|
righteous1
|
3/27/2008 11:56:10 AM
|
|
Hang Dog a �crit :
> Michael DOUBEZ wrote:
>> lilburne a �crit :
>>>
>>> The problem is that in the cases I have its the reading code that has
>>> created the objects, and built up topologies of different types of
>>> surfaces, stitched together by shared edge curves and faces. So the
>>> application sees
>>>
>>> Topology->Face[]->Surface->Edges[]->Curve
>>>
>>> But ignore most of that and just consider the Curve types
>>>
>>> Curve -
>>> | TransformedCurve
>>> | Bezier
>>> | Nurb
>>> | Arc
>>> | Polyline
>>> | CompositeCurve
>>> | ProjectedCurve
>>> | SurfaceCurve
>>> | ReversedCurve
>>>
>>> CompositeCurve(Curve[])
>>> TransformedCurve(Curve, Xform)
>>> ProjectedCurve(Curve, Plane)
>>> SurfaceCurve(Surface)
>>>
>>>
>>> Lets say I want to extract all the edge curves from a topology and
>>> perform some analysis on them. Because certain types of geometric
>>> analysis can be performed more quickly if one knows what the type is,
>>> for example arc-line intersection, or arc-arc intersection, one also
>>> wants to avoid wrapping if possible.
>>>
>>> So whilst a method that involved transforming all the input may just
>>> wrap the input curves in a TransformCurve as that wouldn't require a
>>> deep copy of the curves (you wouldn't simple physically transform the
>>> curves themselves because that would require transforming the
>>> topology). it may well decide to retain any Arcs or Polylines in the
>>> input, so one may well dynamically cast for those type, and deep
>>> copy and physically transform just those types.
>>>
>>> I was hoping your solution would help in situations like that, but
>>> thanks anyway.
>>
>> Your problem is different: you have a limited number of class to
>> consider (there is only so much curve classes ...)
>
>
> There are 'only so much' in most other domains too.
But in other domains, the set of method is limited and it is the
hierarchy that tends to evolve.
In your case, adding another kind of curve is less likely to happen than
adding another operation on your lattice.
>
>
>> but you might want to extend the procedures applied to you topology
>> (rendering, searching,finding a path ...) .
>
> One wouldn't want to pollute the geometry classes with graphical
> knowledge, these underlaying classes describe the geometry
> mathematically.
That is why the visitor patten fits quite well. Your classes can be
dedicated to describing your topology while you can extend the
application with new visitors.
> But you've highlight another example of the problem: it
> is far more efficient to render a trimmed plane or cylinder than it is
> to render a swoopy surface. So the graphical rendering engine also
> dynamically casts to optimize its handling of the simple cases.
The visitor can choose the hierarchy depth at which it will work, there
are usually default visitor for unhandled cases: no-op, delegation to
inherited class' visitation function, assert ...
>
> > This is typically the case where a visitor pattern is IMO a good
> > option.
> >
>
> For some methods each curve needs to be processed in strict order
> regardless of its type. I can't for example drive a plotter to draw all
> the lines, then all the arcs, then all the other bits. Well I could but
> the users will get rather upset as the pen lifts off the paper and jumps
> around not drawing anything.
Yes but the order of visitation is another concern. Usually, one pass a
visitor and a visitation strategy in order to determine how the tree is
spanned.
Michael
|
|
0
|
|
|
|
Reply
|
michael.doubez (922)
|
3/27/2008 12:21:33 PM
|
|
|
251 Replies
154 Views
(page loaded in 1.644 seconds)
Similiar Articles: improve strlen - comp.lang.asm.x86int xstrlen(const char* text) { const char* p = text; unsigned int a = reinterpret_cast ... Oh, they might do it on the first pass, but then they'd see how ugly the result ... c++ - Avoiding dynamic_cast/RTTI - Stack OverflowOk when you have a handful of methods that will generally be implemented, but plain ugly when only needed for a single derived class. #2 is just dynamic_cast<> in a polka ... c++ - How bad is dynamic casting? - Stack OverflowWhen you have simple containers, a lot of algorithms and you want to keep your implementation hidden, dynamic_cast offers an easy and ugly solution. 7/23/2012 9:04:40 AM
|