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