From: Bob Hathaway <objsys@netcom.com>
Subject: Re: O.M(...) vs M(...), and is the Real World O-O?
Date: Sun, 27 Sep 92 06:08:05 GMT
Organization: Object Systems

In article <1992Sep23.084548.14002@ucc.su.OZ.AU> maxtal@extro.ucc.su.OZ.AU (John MAX Skaller) writes:
>	class Base {
>	protected: int i;
>	public: int get()const {return i;}
>	};
>
>	class Derived : public Base {
>	public: void set(int j){i=j;}
>	};
>
>	f(B&);
>
>Without downcasting, f cannot change the object. With downcasting,
>and assuming the argument was really a Derived, then f can change
>the object, that is, f can violate the protected interface of
>the static type of its argument.

I'm sorry, but you're mistaken.  f is not a friend and can only access public
members of Base or Derived.  You're mistaken about outside access to
protected base members - this is not allowed in C++.

To take the example further for a real attempt at violation, lets say class
Derived makes protected base member i a declaration specifier and increases
its access to public.  Now f can take a Base reference or pointer (not B and
no slicing) and try checked downcasting.  If it succeeds, the access is Ok
since Derived made the protected base member public.  If the downcast fails,
all is well because f won't be able to access a Base's protected members.  In
other words, all is well because downcasting obviously enforces interface
access specifications.

And with unchecked downcasting, as pointed out above, f could never access
Base's protected members.

>
>>Checked downcasting is like
>>a typecase statement, check the type and then access it in a statically
>>checked (efficient) way.  This is good when a method can do better in certain
>>cases and with certain kinds of objects.  
>
>	I dont follow. A call to a virtual function is certainly more
>efficient than a manually coded type check followed by a static call.

No, its not (in general) any more efficient.  After one check with
downcasting all subsequent calls are static and direct and not thru a
function pointer.  And using virtuals assumes all functions of all derived
classes are moved into the base class.  This is most undesirable with the
most obvious reason being an unnecessary coupling of Base and Derived
classes (and there are many more).

>	Downcasting is usually used when the Derived class is NOT a subtype
>of the Base class, one downcasts to get access to some function Dmem()
>which is NOT a member of the base class.

Not at all.  For example, a device class can be declared.  Certain kinds of
devices may have certain operations and certain kinds may not.  Downcasting
is *one* way of accessing these derived operations without moving all derived
class functions into the base class as virtuals.  Another solution is to
dynamically query if a message is available in a polymorphic object and only
invoke it if it is; this is common in Objective-C.

>>While messaging is usually Ok
>>it doesn't cover these idiosyncratic cases.  
>
>	In other words you want to have a large set of derived classes,
>most of which are subtypes, but a few are not quite subtypes, so you
>put special tests in for these few cases. IMHO there must be a better
>way, for example, tagged pointers.

Not at all.  In the device example there are many kinds of devices and
they do not all share the same operations.  And tagged pointers
can be used to implement downcasting, at least by my use of the term.

>>Not being able to access
>>derived class interfaces is too severe a restriction; I think, and really
>>hurts for the most general cases too.  
>
>	This is a value judgement about a trade off, and you might well
>be right.

Not really.  In (dynamically typed) Smalltalk you can take any object and
pass it any message; this is lost with only static typing (types only
available at compile time) or is at least a restricted mess.

>	class Q {
>	public:
>		void enQ(X&);
>		X deQ();
>		int nQ()const;
>	};
>
>A queue manager routine has calls
>
>	getsome(Q&); .... putsome(Q&);
>
>where 'getsome' and 'putsome' are user supplied source and sink routiines.
>Now the Q manager is supposed to preserve the order of things in the queue.
>But as it stands, getsome or putsome might scramble the order. So we
>change the Q class---
>
>	class Qbase { public: int nQ()const ...}
>	class NQ : public virtual Qbase { public: enQ(X&); };
>	class DQ : public virtual Qbase { public: X deQ(); };
>	class Q : public virtual NQ, public virtual DQ {};
>
>Now I can declare
>
>	getsome(NQ&);
>	putsome(DQ&);
>
>and, without downcasting these routines cannot scramble the Q.
>With downcasting, they can.

I'm sorry, but I don't see this.  Where are these routines declared; in a
class?  Does your point still hold with the new given C++ rules?

bob
objsys@netcom.com