Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

avoid cast to derived

4 views
Skip to first unread message

Grizlyk

unread,
Dec 26, 2006, 9:41:35 PM12/26/06
to

There is example below and answer on it

>Your code has a poor design. If the userUser class can only work with
>userBase objects, then define it as such. If it can work with all Base
>objects, then it shouldn't need to dynamic_cast the Base object it has.

Can anybody say the way to avoid cast to derived in the case?

Example:
// *** library module

#define adjust_list
#define adjust_list1
#define adjust_list2
#define adjust_list3

//object
struct Base{ virtual void method()=0; };

//Most parts of user code
struct User
{
virtual void user1(Base& obj){ obj.method(); }
virtual void user2(Base& obj){ obj.method(); }
};

struct Creator
{
virtual Base* create_base()=0;
virtual User* create_user()=0;
};


// *** user module
struct userBase: public Base
{
void adjust(adjust_list){}
void method();
};


struct userUser: public User
{
//to get extended interface of derived via pointer to its base class
void user2(Base& obj)
{
userBase *const tmp=dynamic_cast<userBase*>(&obj);
if(!tmp){ obj.method(); return; }

tmp->adjust(adjust_list1);
obj.method();

tmp->adjust(adjust_list2);
obj.method();
}
};


struct userCreator: public Creator
{
Base* create_base(){ return new userBase;}
User* create_user(){ return new userUser;}
};


// *** another user module

extern Creator& extCreator();

//Most parts of user code
int main()
{
Base &base= *extCreator().create_base();
User &user= *extCreator().create_user();
// ...
user.user1(base);
// ...
{
userBase tmp ;
tmp.adjust(adjust_list3);
user.user1(tmp);
}

// ...
user.user2(base);
}

H. S. Lahman

unread,
Dec 27, 2006, 12:34:32 PM12/27/06
to
Responding to Grizlyk...

>>Your code has a poor design. If the userUser class can only work with
>>userBase objects, then define it as such. If it can work with all Base
>>objects, then it shouldn't need to dynamic_cast the Base object it has.
>
>
> Can anybody say the way to avoid cast to derived in the case?

Yes, several depending on what the real problem is. However, your code
already hard-wires several design decisions so it is difficult to
determine what are problem requirements and what is design. My first
suggestion would be that just because misbegotten OOPLs like C++
encourage one to write C programs with strong typing and dynamic
dispatch doesn't mean one should do so. However, given what is here...

First, note that when one passes an object reference to a behavior
method one is instantiating a temporary relationship that exists only
for the scope of the called method. Thus

1 R1 invokes 1 1 R2 invokes 1
[A] --------------------- [B] ----------------------- [C]
+ methodB(&C)

When [A] invokes B::methodB passing a C as an argument, [A] is
instantiating the R2 relationship between B and C. That means that [A]
is responsible for knowing exactly which member of [C] the B in hand
should collaborate with in the current context. This is important
because...

Fundamentally what we have here is:

[Client]
| 1
|
| R1
|
| invokes
| 1 1 R2 invokes 1
[User] -------------------------- [Base]
| 1 + method()
| A
| | R4
| R3 +--------+--------+
| | |
+--------------------- [userBase] ???
invokes 0..1 + adjust()

If [User] wants to invoke method() for all members of [Base] it must
navigate the R2 relationship. But if it wants to invoke the adjust()
specialization, it must navigate the R3 relationship. So there /must/
be two separate relationships if one wants to access all members of
[Base] AND some members of [Base] from User.user2.

So the real problem is that [Client] is instantiating two relationships
here when passing a Base reference, one of which is conditional (i.e.,
when the Base in hand is not a member of [userBAse]). IOW, there isn't
enough information here for [User] to navigate R1 and R2 properly. If
the client is going to pass a [Base] member, it has the responsibility
of knowing which relationship is being instantiated. In particular,
[Client] needs to tell [User] whether the [Base] reference happens to be
a member of [userBAse]. So one needs code like:

void user2 (Base& obj, int type)
{
if (type == USER_BASE)
obj.method(); // navigate just R2
else
{
obj.adjust(adjust_list1); // navigate R3
obj.method(); // navigate R2
obj.adjust(adjust_list2); // navigate R3
obj.method(); // navigate R2
}
}

However, the real problem is that the sibling to [UserBase] is an
instance of [Base]. IOW, some [Base] members are defined as NOT
[userBase] members. In OOA/D there is a rule that the union of subclass
members must be a complete set of superclass members, precisely to
eliminate that sort of ambiguity for subsequent maintenance. So what
one should have is:

[Client]
| 1
|
| R1
|
| invokes
| 1 1
[User] ---------------------+
| 1 |
| |
| R2 | R3
| |
| invokes | invokes
| 0..1 | 0..1
[userBase] [plainBase]
+ adjust() |
| |
+-----------+-------------+
| R4
_
V
[Base]
+ method()

Now [Client] must instantiate exactly one relationship and User.user2
uses the conditionality to navigate the right one:

void user2 (userBase& obj1, otherBase& obj2)
{
if (otherBase != NULL))
obj2.method(); // navigate just R2
else
{
obj1.adjust(adjust_list1); // navigate R3
obj1.method(); // navigate R2
obj1.adjust(adjust_list2); // navigate R3
obj1.method(); // navigate R2
}
}

where [Client] passes a NULL reference for the one that isn't in play.

As a practical matter one wouldn't normally instantiate the relationship
by passing an object reference, especially in this situation. [User]
would have referential attributes for R2 and R3 and [Client] would
simply set the appropriate one prior to invoking User.user2. The
reason is that it separates the concerns of relationship instantiation
(who participates) from those of collaboration (when they collaborate).
So any object that understands the context determining which specific
[Base] to use can instantiate the relationship (and deinstantiate it
when the context no longer prevails). Thus that object may not be the
one that needs to collaborate with [User] by invoking User.user2 in the
overall flow of control (i.e., in this example [Client] might not be the
object to instantiate R2/R3).

This segues to my opening point above. The fact that object references
are being passed to behavior methods suggests more fundamental problems
in the design. However, without a description of the problem actually
being solved I can't speculate on exactly what those might be.

[Having said all this, let me note that languages like C++ do not
provide facilities for processing a stream of arbitrary objects. In
that case one must use dynamic_cast to map the type of the object in
hand. (OOPLs like Ada and those derived from scripting languages
usually do provide such facilities in a disciplined fashion.) However,
as a practical matter such situations are pretty few and far between so
there is rarely any reason to use dynamic_cast.]


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

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

Daniel T.

unread,
Dec 27, 2006, 4:34:57 PM12/27/06
to
"Grizlyk" <griz...@yandex.ru> wrote:

> There is example below and answer on it
>
> > Your code has a poor design. If the userUser class can only work
> > with userBase objects, then define it as such. If it can work with
> > all Base objects, then it shouldn't need to dynamic_cast the Base
> > object it has.
>
> Can anybody say the way to avoid cast to derived in the case?

I didn't want to be the first to respond to this because Grizlyk and I
have been discussing it over in comp.lang.c++. Thank you HS for your
extensive coverage.


Part of the problem with the code is the fact that userUser::user2()
treats different sub-types differently. The one acceptable use of
dynamic_cast that we have talked about is when it's "all or nothing"
i.e., either the object is the proper sub-type or the method doesn't
deal with it at all. This code, however, opens up the possibility of the
userUser::user2() method needing to be modified when some other sub-type
of Base is created that userUser must treat differently as well.

Fundamentally though, most of the problems in the code comes from the
fact that the "User" class is incomplete. It simply cannot stand on its
own, and needs a client to provide a Base object with every message
send. How can this be fixed? Well, HS Lahman has discussed this before,
separate the relationship instantiation from the method invocation.
Following this rule, and using RCMs "Intelligent Child" pattern, we end
up with something like this:

class User {
protected:
virtual Base& base() = 0;
public:
virtual void user1() {
base().method();
}
virtual void user2() {
base().method();
}
};

class userUser : public User {
userBase* itsUserBase;
protected:
Base& base() {
return *itsUserBase;
}
public:
void setBase( userBase* b ) {
itsUserBase = b;
}

void user2() {
itsUserBase->adjust();
itsUserBase->method();
itsUserBase->adjust();
itsUserBase->method();
}
};

'userUser' no longer needs to wonder if he has a userBase, or some other
sort of Base.

Now, who is to call setBase? Obviously the code that knows what kind of
Base to give the User, that would be the factory function. You see, in
the OPs code, the User class was incomplete, so the factory was creating
and returning an incomplete object. The client was then forced to use
the factory again to create and return the rest of the User object (or
otherwise knowing what kind of User it has so it can create a Base
object on its own.) Instead of the client building the object from parts
provided by the factory, the factory itself should be turned into a
builder.

struct Creator {
virtual User* createUser() = 0;
};

struct userCreator: public Creator
{
User* createUser() {
userUser* result = new userUser;
result->setBase( new userBase );
return result;
}
};

Yes, this disallows some client's ability to change the Base object
being used, but this is a good thing. Imagine a situation where User
objects contain state and Base objects contain state, and these states
interacted with each other such that the client needed to know what
state each was in before passing a Base to a User. That kind of
intrusive need from the client is exactly what OO paradigm tries to
avoid.

Grizlyk

unread,
Dec 29, 2006, 1:41:13 AM12/29/06
to

Daniel T wrote:

> This code, however, opens up the possibility
> of the userUser::user2() method needing to be modified
> when some other sub-type of Base is created
> that userUser must treat differently as well.

Yes. The fact is not advantage of the desing way, but the
userUser:: code do not use private interface of userBase.

If we use only public interface of userBase, we can hope
to have nearly constant interface of userBase, as interface
of Base do.

The second disadvantage is theoretical unlimited growing
number of interfaces till total number of classes, but in
practical cases, we need new interface of derived very
rarely.

So, we do not make each class with his own interface, and
do not expect big number of interfaces, that is why, your
"possibility to be modified" we can not treat as obstacle
to get derived interface, if we need it.

There is another argument, that we can do not fear of
"possibility to be modified", becase the modified classes
developed together (see below).

> The one acceptable use of dynamic_cast that we have talked
> about is when it's "all or nothing" i.e., either the object
> is the proper sub-type or the method doesn't deal with it
> at all.

There are no resctrictions or reasons to do that. What is the
evident sence of the resctrictions? Nothing.

> most of the problems in the code comes from the fact that
> the "User" class is incomplete. It simply cannot stand on
> its own, and needs a client to provide a Base object with
> every message send

There are no resctrictions or reasons to minimizing number of
invokes of methods of external objects.

Any method, in general, encapsulates access to a state and
method is more abstracted than direct accsess to state,
and it does not matter who will provide method as access
to state.

And we can not consider class as state, it is much better
to do that as implementation of class's interface.

> and using RCMs "Intelligent Child" pattern, we end up
> with something like this:

Describe, please, the design pattern "Intelligent Child"
at least in three steps (context, goal, implementation).

> 'userUser' no longer needs to wonder if he has a userBase,
> or some other sort of Base.

Of course, you are statically links the Base and User, but
your User class can not work with external Base, (later
developed, for example, which no need userBase interface),
see below.

> Imagine a situation where User objects contain state
> and Base objects contain state, and these states

> interacted with each other ...

Understand.

> ... such that the client needed to know what


> state each was in before passing a Base to a User.

Yes. But any upper level code, which is using some objects
of any classes, must know not only all its invoking methods
and parameters, but the exact sequence of methods to call.

For example, we must "open" file, "seek" to position and
"read" data of "size", and at each point we can lost state
or call "read" befor "seek".

Interface of any object can not encapsulate sequence or
parameters. If the sequence is hard or too many parameters,
we must do wrapper with more simple interface.

> Well, HS Lahman has discussed this before, separate the
> relationship instantiation from the method invocation.

I am still translating his post (too many new english
words and new terms), but for the first view I did not
see any obstacles to do the class design with
dynamic_cast<>.

Outcom:

I saw your objections, but found, that they not only do not
prohibit the design of classes, but has many disadvantages,
which were not listed by you.

1.
The first of disadvantage in your design is hard to
develop Base and User independently each from other.

The most used way in OO design - guess what can be changed
in future and encapsulating all that.

Dividing to Base and User has been done due to the condition.
Separated Base and User can easy be inherited independently

Base -> concreteBase1, concreteBase2, ...
User -> concreteUser1, concreteUser2, ...

Due to polymorphic interfaces any concreteBase and any
concreteUser can be linked each other at runtime and
class Creator encapsulates the behavior. Creator can easy
be inherited independently too.

Creator -> concreteCreator1, concreteCreator2, ...

The reason to have concreteCreator - to make concrete
pair (Base,User). Creator in fact do "dynamic inheritance"
of pair (Base,User).

You, of course, have divided the classes Base and User too,
but the reason of the dividing is not so easy to find.

Because your userUser can work only with userBase, but
separated Base and User must be able to work with any
derived from Base and User. This is the target of dividing.

I think that most desing patterns of classes can be
implemented with the help of separated classes, for
example bridge and factory can be implemented
exactly as example with dynamic_cast<>.

In other words, Base and User must be separated.

2.
Concerning dynamic_cast<>.

In most cases we no need any cast from Base to userBase,
due to polymorphic interface of separated classes.

But for perfomace reasons or any other reasons we can
extend interface of base class.

In oder to use extented interface we can make new
classes with new interfaces:

Base_new -> concreteBase_new1, concreteBase_new2, ...
User_new -> concreteUser_new1, concreteUser_new2, ...

But the classes is incompatible with already written
code, so we lost the reason of dividing Base and
User.

We can use adapter

Base-> Base_adapter(Base_new)
User-> User_adapter(User_new)

or just inherit new interface from old

Base-> Base_new
User-> User_new

Now new code can use advantages of new interface, and
old code still can work, but already without advantages
of new interface, so we lost the reason of dividing
Base and User againg.

At this point all depends only from concrete conditions,
and concrete classes.

But in general I do not see any obstacles, if implemented
in derived methods of base classes will switched to new
interface if it is possible. In other words, will do
dynamic_cast<>-depended code.

Using dynamic_cast<> allows some previously separated
classes ( but developed together for cooperative work
for any reasons, and linked via pointer to its base
class) use its extended interfaces.

Dynamic_cast<> allows to make concrete pair (Base,User)
at runtime in each call, hide the creation from all
other parts of program.

3.
One can see, that dynamic_cast<> acts as if-else
operator. Can compiler do the if-else?

I heard that exists OO languages with more than one
"virtual condition", if C++ allows do more then one
dispath per method, then yes.

Borland C++ 3.x was able use int as second dispath
selector (with many limitations). In our case,
instead of int we must use type of parameter
as second dispath selector.

//for all obj do obj.method();
void user::user2(Base& obj)[*]{ obj.method(); }

//for obj == Base_new&
void user_new::user2(Base& obj)[Base_new& obj]{ exit(); }
//for all other obj do obj.method();
void user_new::user2(Base& obj)[*]{ obj.method(); }

or we must use dynamic_cast<>

Dynamic_cast<> allows to have separated classes for
development and joined classes for usage.

There are many cases of improper usage of
dynamic_cast<>.

I you have any objections to me, i will be glad to see them.

0 new messages