Topic: Old question about access restrictions
Author: hilfingr@tully.CS.Berkeley.EDU (Paul N. Hilfinger)
Date: 4 Nov 1993 06:30:18 GMT Raw View
A question from my archives: A very long while ago, Bill Duttweiler
posted a question concerning access to protected member functions.
After reading the replies, I agree with the responders that the ARM is
clear on the semantics, but I find the rationale for this semantics to
be a bit inscrutable, and wonder if anyone can help me understand it.
Review of the question
------ -- --- --------
The question involved a situation like this (augmented from Desmond D'Souza's
response):
class A {
protected:
virtual void bar();
int i;
};
class B : public A {
public:
void foo (B& bp, A& ap)
{
bp.i = 5; // OK
ap.i = 5; // ERROR
bp.bar(); // OK
ap.bar(); // ERROR
}
};
The legalities are as indicated by the comments:
"A friend or member function of a derived class can access a
protected nonstatic member of one of its base classes only through
a pointer to, reference to, or object of the derived class (or any
class derived from that class)."
The issue is why?
Rationale in ARM
--------- -- ---
The ARM says gives the following rationale [emphasis mine].
"*The* example that caused the restriction to be introduced ...
[involved] device drivers. A driver for one kind of device
accidentally updated the information relating to another kind of
device, and the system mysteriously crashed. The accidentally
updated information was stored in a common base class and accessed
through a pointer to that base class and not the appropriate
derived type. Naturally, the use of a virtual function for
performing the update would have avoided the problem." [ARM, pg
255; my emphasis.]
(The emphasized "the" already raises my eyebrow. In programming-
language design the legal maxim "hard cases make bad law" applies.
Any substantial restriction motivated by AN example is always
suspicious. But this is mere uneasiness, so let's proceed.)
In a previous paragraph, the ARM seems to diagnose the problem as
follows:
"[Relaxing the restriction] would allow a class to access the base
class part of an unrelated class (as if it were its own) without
the use of an explicit cast. This would be the only place where
the language allowed that."
(Here, "unrelated" classes are those that are not related by the
"ancestor of" relation in either direction. Cousins don't count.)
Problems
--------
One problem with this rationale is that it seems not to have been
applied consistently. Precisely the same argument tells me that, in
the example above, BOTH references to member i (ap.i and bp.i) should
be illegal. After all, it's not just unrelated classes that can add
additional invariants on protected members. A class derived from B is
equally capable of doing so; the argument bp doesn't have to come from
a B.
But OK, I'm willing to be cautious and use virtual functions to update
protected member data. Naturally, since the functions I have in mind
are used only for internal implementation purposes, it is appropriate
to keep them out of the public interface of A; hence bar is a
protected virtual member function. Oops! Sorry, this doesn't work (see
above); I can say bp.bar(), but not ap.bar(), even though the
"virtuality" of bar can protect me from gross blunders.
Thus, language-design-wise, we seem to be in somewhat of a cleft
stick. We are allowed, on the one hand, to do things the rationale
labels as dangerous, but are prevented from doing (certain) things the
rationale recommends as safe.
My own inclination is to say the restriction ought to be removed, and
the problem that is cited as motivating it should be diagnosed as the
rationale does: insufficient use of virtual functions. Indeed, I find
it puzzling that having correctly (to my mind) made this diagnosis,
the language designers added the restriction in question rather than
leaving things at "you now see how important virtual functions are."
In any case, comments appreciated.
P. Hilfinger