[comp.std.c++] protected != public-to-derived

wkd@asc.slb.com (Bill Duttweiler) (03/20/91)

I posted this to comp.lang.c++ a week ago, and haven't gotten any
feedback. Since I neglected to post to comp.std.c++, I thought I'd try
THAT before giving up on it...

Is it illegal to call a protected member function on an instance other
than 'this'? Consider the following simple example:

class A
{
public:
    A();
protected:
    void SetState();
};

class B : public A
{
public:
    B(A* a) { MyA = a; }
    void Foo() { MyA->SetState(); }
protected:
    A *MyA;
};

When run through g++ (1.37.1), I get the following error message:

test.cc: In method void B::Foo ():
test.cc:13: method `void A::SetState ()' is protected

When run through Sun C++ (based on cfront 2.0), I get

"test.cc", line 13: error:  B::Foo() cannot access A::SetState(): protected  member

So, these compilers agree. But I don't: B is publicly derived from A
and should have access to protected member functions from within its
own member functions. I can call SetState() on 'this', why not on
MyA???
-- 
William K. Duttweiler                   Schlumberger Austin Systems Center
(512) 331-3000                          PO Box 200015
wkd@asc.slb.com                         Austin, TX  78720-0015

davidm@uunet.UU.NET (David S. Masterson) (03/23/91)

>>>>> On 19 Mar 91 16:53:33 GMT, wkd@asc.slb.com (Bill Duttweiler) said:

Bill> Is it illegal to call a protected member function on an instance other
Bill> than 'this'? Consider the following simple example:

[...example deleted...]

Bill> So, these compilers agree. But I don't: B is publicly derived from A
Bill> and should have access to protected member functions from within its
Bill> own member functions. I can call SetState() on 'this', why not on
Bill> MyA???

This is a difficult question to answer in a short and sweet way.

So, lets turn it around.  Why should B have access to protected members of MyA
since MyA is not in the inheritance chain of B (as opposed to A which is)?
What differentiates this access from B attempting to access the protected
members of some object MyC when B does not derive from C?
--
====================================================================
David Masterson					Consilium, Inc.
(415) 691-6311					640 Clyde Ct.
uunet!cimshop!davidm				Mtn. View, CA  94043
====================================================================
"If someone thinks they know what I said, then I didn't say it!"

gyro@kestrel.edu (Scott Layson) (03/24/91)

In article <1991Mar19.165333.23908@asc.slb.com> wkd@asc.slb.com (Bill Duttweiler) writes:
>
>I posted this to comp.lang.c++ a week ago, and haven't gotten any
>feedback. Since I neglected to post to comp.std.c++, I thought I'd try
>THAT before giving up on it...
>
>Is it illegal to call a protected member function on an instance other
>than 'this'? Consider the following simple example:
>
>class A
>{
>public:
>    A();
>protected:
>    void SetState();
>};
>
>class B : public A
>{
>public:
>    B(A* a) { MyA = a; }
>    void Foo() { MyA->SetState(); }
>protected:
>    A *MyA;
>};
>
>When run through g++ (1.37.1), I get the following error message:
> [...]

The second sentence of section 11.5 of E&S (p. 253) reads:

   A friend or a a 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 obejct of the derived class (or any
   class derived from that class).

I have bumped my head on this rule a couple of times, and frankly
don't agree with the rationale given in E&S for its existence (see the
commentary starting on the bottom of p.254).  The last sentence of
that rationale, which describes a bug someone once had which the rule
would have prevented, reads:

   Naturally, the use of a virtual function for performing the update
   would have avoided the problem.

Seems to me that this sentence admits that the bug really had quite
another cause than the mistaken access of a protected member.

Since the issue has come up, I am very much interested to hear the
committee's current feeling about this rule.  Unless that feeling
turns out to be one of utter attachment, I may consider submitting a
formal proposal and argument that it be rescinded (which would be a
compatible change, affecting no previously legal code).

-- Scott Layson Burson

daves@ex.heurikon.com (Dave Scidmore) (03/24/91)

In article <1991Mar24.010442.27298@kestrel.edu> gyro@kestrel.edu (Scott Layson) writes:

>The second sentence of section 11.5 of E&S (p. 253) reads:
>
>   A friend or a a 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 obejct of the derived class (or any
>   class derived from that class).
>
>I have bumped my head on this rule a couple of times, and frankly
>don't agree with the rationale given in E&S for its existence (see the
>commentary starting on the bottom of p.254).  The last sentence of
>that rationale, which describes a bug someone once had which the rule
>would have prevented, reads:
>
>   Naturally, the use of a virtual function for performing the update
>   would have avoided the problem.
>
>Seems to me that this sentence admits that the bug really had quite
>another cause than the mistaken access of a protected member.

I think you may be missing the point. The text which you neglected to
include stated:

    There are examples where the restriction appears to provide protection
    against accidents. For example suppose I am running a bank, and have
    many different kinds of accounts. I would like to have a base class
    "Account", that has information common to all kinds of accounts,
    including including the account balance, so a friend of "Account" can
    walk the list of all "Accounts" and tell me the financial position of
    my bank at any time. Each kind of "Account", however, has its own rules
    for updating the balance (for example, what rate of interest is paid,
    when it is compounded, what types of deposits are accepted, special
    penalties for early withdrawal, and so on), so the derived classes which
    are all the different kinds of accounts contain code for updating the
    balance. The member functions of "checking_accounts", however should
    not be able to access the balance in an "AutoLoan_account." The
    restriction prevents that.

It is hard to deny that a measure of protection has been afforded by the
restriction that some form of the derived object be used to access protected
base information. Any time accidents can be avoided by introducing a reasonable
protection mechanism I fail to see why someone would oppose it. Especially since
their is a way to bypass the protection, as stated on page 254 when talking
about easing the restriction:

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

So it can be seen that a cast can be used to bypass this rule. The second
grounds on which I feel the rule is reasonable is elequently stated just
after the above:

    ...without the use of an explicit cast. This would be the only place
    where the language allowed this.

In other words it is consistent with, and a logical extension of, existing
protection mechanisms. If you were big on logical consistency then you might
easily be fooled by the relaxation of this rule, expecting errors to be
generated if you attempted to use the wrong class to change protected base
class data. You might spend many days or even weeks hunting down such an
obscure and subtle error in a large program.

By contrast I am rarely fooled by compiler error messages. If I can not find
anything in the program to indicate a syntactical error my first impulse is
to grab a language reference and see if I am doing something wrong. Once I
find the mistake I made I rarely make it again. Ultimately, the whole process
may only take a few minutes to a few hours to eliminate the compilation
error. If, like the bug created by the relaxed rules, it takes days or weeks
for you to eliminate a compilation error that results form a less than full
understanding of the language, then you might consider a different line of
work. At the very least you would have trouble explaining it to your boss.

In other words I would rather spend a few minutes looking through E&S than
a few days hunting down an obscure bug. In that sense I think the protection
is worth the cost, especialy when I can bypass it and at the same time make
it quite clear that I intended to by using a cast.
--
Dave Scidmore, Heurikon Corp.
dave.scidmore@heurikon.com

wmm@world.std.com (William M Miller) (03/25/91)

gyro@kestrel.edu (Scott Layson) writes:
> I have bumped my head on this rule a couple of times, and frankly
> don't agree with the rationale given in E&S for its existence (see the
> commentary starting on the bottom of p.254).  The last sentence of
> that rationale, which describes a bug someone once had which the rule
> would have prevented, reads:
>
>    Naturally, the use of a virtual function for performing the update
>    would have avoided the problem.
>
> Seems to me that this sentence admits that the bug really had quite
> another cause than the mistaken access of a protected member.
>
> Since the issue has come up, I am very much interested to hear the
> committee's current feeling about this rule.

I can't speak for the rest of the committee, but I personally think the
rationale is pretty reasonable.  It might very well be that Derived1 and
Derived2 classes have different conventions for managing the protected
members of their common base class.  If a Derived1 member function were able
to access the base class protected members via a pointer or reference to the
base class, because of the conversion rules, it might actually be
manipulating a Derived2 object in an invalid fashion.  By restricting access
to use only a Derived1 pointer or reference, you at least have the type
system working in your favor to avoid this kind of bug.

-- William M. Miller, Glockenspiel, Ltd.; vice chair, X3J16
   wmm@world.std.com

gyro@kestrel.edu (Scott Layson) (03/26/91)

In article <1991Mar24.205156.10206@world.std.com> wmm@world.std.com (William M Miller) writes:
>gyro@kestrel.edu (Scott Layson) writes:
>> Since the issue has come up, I am very much interested to hear the
>> committee's current feeling about this rule.
>
>I can't speak for the rest of the committee, but I personally think the
>rationale is pretty reasonable.  It might very well be that Derived1 and
>Derived2 classes have different conventions for managing the protected
>members of their common base class.  If a Derived1 member function were able
>to access the base class protected members via a pointer or reference to the
>base class, because of the conversion rules, it might actually be
>manipulating a Derived2 object in an invalid fashion.  By restricting access
>to use only a Derived1 pointer or reference, you at least have the type
>system working in your favor to avoid this kind of bug.

Let's look at the example given at the top of p. 255 of E&S:

   For example, suppose I am running a bank, and I have many different
   kinds of accounts.  I would like to have a base class, `Account',
   that has information common to all kinds of accounts, including the
   account balance, so a friend of `Account' can walk the list of all
   Accounts and tell me the financial position of my bank at any time.
   Each kind of `Account', however, has its own rules for updating the
   balance ([examples omitted]), so the derived classes which are all
   the different kinds of accounts contain the code for updating the
   balance.  The member functions of checking_accounts, however,
   should not be able to access the balance in an `AutoLoan_account'.
   The restriction prevents that.

An easy way to implement this, in the absence of the restriction, is
that `Account' should not own the `balance' member variable, but that
`Account' should provide a private virtual function for retrieving the
balance.

Put another way, why do you want to make `balance' a member variable
of `Account' if you *don't* want the derived classes of `Account' to
be able to access each other's balance?

Since there's another way to achieve the protection you want, and
since the restriction is demonstrably counterintuitive to a lot of
people -- a) it contradicts the simple rule "protected = public-to-
derived" and b) it goes against the grain of "the unit of protection
is the class, not the object" (I'm not saying it literally contradicts
that philosophy, only that it seems to) -- I oppose it.

-- Scott Layson Burson
Gyro@Reasoning.COM

wmm@world.std.com (William M Miller) (03/26/91)

gyro@kestrel.edu (Scott Layson) writes:
> Put another way, why do you want to make `balance' a member variable
> of `Account' if you *don't* want the derived classes of `Account' to
> be able to access each other's balance?

I'd put "balance" into the base class as a simple matter of factoring: every
Account will have one, so it's most convenient to put it once and for all
into the base class instead of having to declare it over and over again in
each derived class.  If I had a situation in which derived classes needed to
be able to manipulate each other's data, I'd probably make them mutual
friends; I don't think it would have occurred to me to put the data into the
protected section of the base class.

>              b) it goes against the grain of "the unit of protection
> is the class, not the object" (I'm not saying it literally contradicts
> that philosophy, only that it seems to)

I'm afraid I don't see the relationship here.  You're not restricted to
"this" -- *any* pointer to, reference to, or object of the correct class or
any class derived therefrom is permissible; the restriction only applies to
pointers or references to or objects of a base or sibling class.

> Since there's another way to achieve the protection you want...

This sounds like the argument over passive restraints versus seatbelts.
It's nice to have that airbag in the steering column in case I forget to
buckle up or in case I decide that it's only a short, safe trip -- but it's
not free.

-- William M. Miller, Glockenspiel, Ltd.
   wmm@world.std.com

daves@ex.heurikon.com (Dave Scidmore) (03/26/91)

gyro@kestrel.edu (Scott Layson) writes:
>wmm@world.std.com (William M Miller) writes:
>>gyro@kestrel.edu (Scott Layson) writes:
>>> Since the issue has come up, I am very much interested to hear the
>>> committee's current feeling about this rule.
>>
>>I can't speak for the rest of the committee, but I personally think the
>>rationale is pretty reasonable.
>
>Let's look at the example given at the top of p. 255 of E&S:
>
>   For example, suppose I am running a bank, and I have many different
>   kinds of accounts.  I would like to have a base class, `Account',
>   that has information common to all kinds of accounts, including the
>   account balance, so a friend of `Account' can walk the list of all
>   Accounts and tell me the financial position of my bank at any time.
>   Each kind of `Account', however, has its own rules for updating the
>   balance ([examples omitted]), so the derived classes which are all
>   the different kinds of accounts contain the code for updating the
>   balance.  The member functions of checking_accounts, however,
>   should not be able to access the balance in an `AutoLoan_account'.
>   The restriction prevents that.
>
>An easy way to implement this, in the absence of the restriction, is
>that `Account' should not own the `balance' member variable, but that
>`Account' should provide a private virtual function for retrieving the
>balance.

Again I think you are missing the point. The point is not whether you can
design your way around a hole in the protection mechanisms of the language,
but whether relaxation of this rule creates the possibility for programmer
error that need not exist. Using the above rational I could say that GOTOs
are a good idea because anyone using good programming practices won't require
them.

>Since there's another way to achieve the protection you want, and
>since the restriction is demonstrably counterintuitive to a lot of
>people -- a) it contradicts the simple rule "protected = public-to-
>derived"

Which would you rather have a rule that is counterintuitive or one that
is not logicaly consistent with the rest of the type checking system. I
think it is only counterintuitive to those that have not given it much
thought. One way of thinking about it that makes it more intuitive is to
think of those protected members who are accessed through the derived type
as conceptually belonging to that derived type.

The need to treat protected members of the base class this way stems from
the fact that protected members already represent a breach of the protection
mechanism. This breach is made for convience sake, but when combined with
implicit conversion of pointers to objects of a derived class to pointers
to objects of a public base class, you suddenly have the ability for
unrelated derived objects to get at each others data. The result is that
all the protections afforded by the class heirarchy go out the window for
protected data. The simple solution was the obvious one, since protected
data in essence is an extension of the private data of the derived type,
why not simply make the same rules apply to private data and protected
data inherited from a base class.

> b) it goes against the grain of "the unit of protection is the class,
> not the object" (I'm not saying it literally contradicts that philosophy,
> only that it seems to) -- I oppose it.

Huh? The rule does *nothing* in any way to shift the unit of protection from
that of the class to the object. Unless I am grossly mistaken *any* object
of the derived class can access protected base class data in any other object
of the same class. If anything this rule modifies the rule about converting
pointers to objects to pointers to one of their public base classes so that
the conversion can not be done in order to access protected data. To me this
seems completely logical and completely consistent.
--
Dave Scidmore, Heurikon Corp.
dave.scidmore@heurikon.com

gyro@kestrel.edu (Scott Layson Burson) (03/26/91)

In article <1991Mar25.210620.13970@world.std.com> wmm@world.std.com (William M Miller) writes:
>gyro@kestrel.edu (Scott Layson) writes:
>> Put another way, why do you want to make `balance' a member variable
>> of `Account' if you *don't* want the derived classes of `Account' to
>> be able to access each other's balance?
>
>I'd put "balance" into the base class as a simple matter of factoring: every
>Account will have one, so it's most convenient to put it once and for all
>into the base class instead of having to declare it over and over again in
>each derived class.  If I had a situation in which derived classes needed to
>be able to manipulate each other's data, I'd probably make them mutual
>friends; I don't think it would have occurred to me to put the data into the
>protected section of the base class.

Making the two classes mutual friends doesn't address the situation in
question, which is that B and some set of other classes are all
derived from A, and a member function of B has got hold of some object
other than *this which is some kind of A, but it doesn't know anything
else about it, and it wants to have access to that A just as it would
to the A within *this.  What you would have to do is make each derived
class of A be a friend of A.

I think this observation pinpoints what is so surprising about the
restriction.  People want to think of friendship and derivation in the
same way, and here's a case where they turn out to be different.

I was at one point trying to generate a proposal for unifying the two,
in fact, more or less along these lines: let friendship grant only
protected member access, and let it be inherited and transitive.  I
didn't think about that long enough to decide whether I really
considered it a good idea, but there's no question it would make the
language simpler to think about.

Keep in mind that the current distinctions between inheritance and
friendship vis-a-vis access rights are in part historical: when
friendship was invented, there were no protected members.  It's true
that the unification I propose would eliminate some access
restrictions (including the one under discussion), but it would also
introduce a whole new set, making it possible for a class to restrict
the interface its friends, as well as its derived classes, see.

>>              b) it goes against the grain of "the unit of protection
>> is the class, not the object" (I'm not saying it literally contradicts
>> that philosophy, only that it seems to)
>
>I'm afraid I don't see the relationship here.

Only that when I first bumped my head against the rule in question, I
scratched my head and thought "what's going on here -- the unit of
protection is the class".  I'm agreeing with you that this is a
confused association -- that's what I was trying to say -- but in my
experience it's a likely confusion.  But no matter.

>> Since there's another way to achieve the protection you want...
>
>This sounds like the argument over passive restraints versus seatbelts.
>It's nice to have that airbag in the steering column in case I forget to
>buckle up or in case I decide that it's only a short, safe trip -- but it's
>not free.

Now *I'm* confused -- it sounds like you're making my argument for me.
That is, I identify the airbag with the access restriction in question
-- it's there whether I want it or not, and you're quite right, it
does cost me something.

-- Scott Layson Burson
Gyro@Reasoning.COM

jimad@microsoft.UUCP (Jim ADCOCK) (03/27/91)

In article <1991Mar19.165333.23908@asc.slb.com> wkd@asc.slb.com (Bill Duttweiler) writes:
|Is it illegal to call a protected member function on an instance other
|than 'this'? 

See ARM section 11.5

wmm@world.std.com (William M Miller) (03/27/91)

gyro@kestrel.edu (Scott Layson Burson) writes:
> Making the two classes mutual friends doesn't address the situation in
> question, which is that B and some set of other classes are all
> derived from A, and a member function of B has got hold of some object
> other than *this which is some kind of A, but it doesn't know anything
> else about it, and it wants to have access to that A just as it would
> to the A within *this.  What you would have to do is make each derived
> class of A be a friend of A.

No, what I meant was that I would put any information that needed to be
shared among the derived classes into a private member of each derived class
and make the derived classes mutual friends.  Depending on the exact
situation, I'm sure there are a number of other ways to address this type of
problem without changing the semantics of "protected."

> >This sounds like the argument over passive restraints versus seatbelts.
> >It's nice to have that airbag in the steering column in case I forget to
> >buckle up or in case I decide that it's only a short, safe trip -- but it's
> >not free.
>
> Now *I'm* confused -- it sounds like you're making my argument for me.
> That is, I identify the airbag with the access restriction in question
> -- it's there whether I want it or not, and you're quite right, it
> does cost me something.

My point was that it's a cost-benefit tradeoff -- I personally am quite
willing to pay a couple of percent extra for my car in order to have that
airbag there in case it's needed; others don't think it's worth it.
Similarly here -- I think there's a real benefit offered by the current
definition of "protected" and am willing to put up with whatever minor
inconvenience may result; you obviously draw the cost/benefit line in a
different place.  C'est la vie.

-- William M. Miller, Glockenspiel, Ltd.
   wmm@world.std.com

gyro@kestrel.edu (Scott Layson Burson) (03/27/91)

In article <229@heurikon.heurikon.com> daves@ex.heurikon.com (Dave Scidmore) writes:
>[quote from E&S p. 255 deleted]
>
>It is hard to deny that a measure of protection has been afforded by the
>restriction that some form of the derived object be used to access protected
>base information. Any time accidents can be avoided by introducing a reasonable
>protection mechanism I fail to see why someone would oppose it. Especially since
>their is a way to bypass the protection, as stated on page 254 when talking
>about easing the restriction:
>
>    ...This 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.
>
>So it can be seen that a cast can be used to bypass this rule.

Not in the presence of virtual base classes, which cannot be cast to a
derived class.  (I should have mentioned this before -- this is a
problem I actually encountered.)

But beyond that, in one application I found myself repeatedly
inserting such casts solely for the purpose of bypassing this rule.
(I can't defend myself if you say I must have been doing something
else wrong.  It was months ago and I don't remember all the details
about the situation.)  I will oppose even a reasonable-seeming
protection mechanism if I frequently have to bypass it with a cast,
because I believe that casts should only be used in exceptional
situations, lest people get too used to seeing them.

>							       The second
>grounds on which I feel the rule is reasonable is elequently stated just
>after the above:
>
>    ...without the use of an explicit cast. This would be the only place
>    where the language allowed this.
>
>In other words it is consistent with, and a logical extension of, existing
>protection mechanisms. If you were big on logical consistency then you might
>easily be fooled by the relaxation of this rule, expecting errors to be
>generated if you attempted to use the wrong class to change protected base
>class data. You might spend many days or even weeks hunting down such an
>obscure and subtle error in a large program.

Evidently that happened to someone, and that's why we have the rule.

See a previous posting of mine on the differences between friendship
and derivation.

>By contrast I am rarely fooled by compiler error messages. If I can not find
>anything in the program to indicate a syntactical error my first impulse is
>to grab a language reference and see if I am doing something wrong. Once I
>find the mistake I made I rarely make it again.

In general that's true for me too, but I am evidently not the only
person who had particular trouble finding this restriction given the
error messages I received from the compiler.  Perhaps if nothing else
comes of this conversation, at least compiler authors might be
persuaded to word as specifically as possible the error messages
generated on account of the restriction.  (Hello out there, are you
listening?)

-- Scott Layson Burson
Gyro@Reasoning.COM

gyro@kestrel.edu (Scott Layson Burson) (03/27/91)

In article <264@heurikon.heurikon.com> daves@ex.heurikon.com (Dave Scidmore) writes:
>			    Using the above rational I could say that GOTOs
>are a good idea because anyone using good programming practices won't require
>them.

That's funny.  I would agree with that argument, except I would change
the word `require' to `abuse'.

I have avoided `goto' for many years, but every so often I come across
a situation -- admittedly, not too frequently -- where I have to admit
I don't think I made the code any more readable by using a control
variable rather than a `goto', and I really don't see any better way
to write it in C.  In Scheme I would use tail-recursive local
functions, but the C community doesn't seem to believe in local
functions (and hardly anyone believes in tail recursion).

-- Scott
Gyro@Reasoning.COM