chris@mimsy.UUCP (Chris Torek) (04/21/88)
In article <6590039@hplsla.HP.COM> jima@hplsla.HP.COM (Jim Adcock) writes: >If you refuse to stick to your original definition of blah, >and you make blah public, and someone uses blah, and then you change >your definition of what blah means, then that user of your class is >going to be hosed. > >BUT this is true whether or not blah is an access function or a public >instance variable. If you change your definition of what a public >anything means, you will hose your users. This is true. The point, however, is that a variable is always a variable, while a function is arbitrary. So if you declare that henceforth and forever, anyone can take complexvar.realpart() and complexvar.imaginarypart(), and you decide you prefer polar coordinates, people can still take the two, while if you declare that anyone can simply take .realpart and .imaginarypart, you are stuck with rectangular. To make the example slightly more interesting, consider a system in which complex numbers are needed in rectangular form about half the time, and in polar about half, but `clustered'. Then if you used access functions you can write something like class complex { union { struct { double re, im; } r; // if rectangular struct { double r, theta; } p; // if polar } u; boolean polar; // true => polar, else rectangular void flip(); // guess... public: inline double re() { if (this->polar) flip(); return (this->u.r.re); } inline double theta() { if (!this->polar) flip(); return (this->u.p.theta); } // etc } which has the nice side effect of leaving it in the last-used format, eliminating unnecessary conversions. If you never need the polar representation, this is just excess baggage. The question becomes `do I want to leave myself the option of using polar representation?' If you intend to reuse the implmentation later, perhaps it would be wise to do so. -- In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7163) Domain: chris@mimsy.umd.edu Path: uunet!mimsy!chris
fox@convent.columbia.edu (David Fox) (04/21/88)
In article <11152@mimsy.UUCP> chris@mimsy.UUCP (Chris Torek) writes: >The point, however, is that a variable is always >a variable, while a function is arbitrary. So if you declare that >henceforth and forever, anyone can take complexvar.realpart() and >complexvar.imaginarypart(), and you decide you prefer polar coordinates, >people can still take the two, while if you declare that anyone >can simply take .realpart and .imaginarypart, you are stuck with >rectangular. There is really no need for the parentheses after a function that takes no arguments. Envision a preprocessor which builds the symbol table for the program and, when it runs across an identifier which is defined as a function taking no arguments, adds the parentheses. This could also be added to a c++ compiler (or even to a C compiler). Then you could start with a public member and later change it to an access function without hosing your clients. Some might say that the parens are a clue to the object's type, but is this important? I say that a variable really *is* a restricted form of a function that takes no arguments. If something is called 'imaginary_part' then it *is* (or ought to be) the imaginary_part, regardless of what machinations occur during its evaluation. David Fox fox@heathcliff.columbia.edu
lindsay@K.GP.CS.CMU.EDU (Donald Lindsay) (04/22/88)
In article <11152@mimsy.UUCP> chris@mimsy.UUCP (Chris Torek) writes: >The point, however, is that a variable is always >a variable, while a function is arbitrary. So if you declare that >henceforth and forever, anyone can take complexvar.realpart() and >complexvar.imaginarypart(), and you decide you prefer polar coordinates, >people can still take the two, while if you declare that anyone >can simply take .realpart and .imaginarypart, you are stuck with >rectangular. The functional approach has other virtues. Various speed/space optimizations become possible. The functional approach allows you to use hidden booleans with names like "initted", "allocated", "swappedout", "compressed", "shared", "spin_locked", "readonly", "InSymbolicForm", and so on. In particular, the functional approach allows you to start with a straightforward implementation, and then, later, add hidden cleverness. A library could contain several compatible implementations. For example, imagine instrumenting a program, to gather usage statistics. An access function can be recoded so that it increments a counter. Without the functional approach, the recode must touch the using program at every place where the variable is referenced. (Yes, some compilers can do simple instrumenting for you .. but can they count how many times a datum was zero, or was "more than half full" ? ) -- Don lindsay@k.gp.cs.cmu.edu CMU Computer Science
karl@haddock.ISC.COM (Karl Heuer) (04/22/88)
In article <5547@columbia.edu> fox@convent.columbia.edu (David Fox) writes: >There is really no need for the parentheses after a function that >takes no arguments. [If the parens were supplied by the compiler,] >then you could start with a public member and later change it to an >access function without hosing your clients. Only if the clients have been treating the variable as if it were an access function. `zi = z.imag' works fine, but anyone who used `z.imag = zi' would be hosed. (Using an lfunction (i.e. having imag() return a reference) doesn't solve it either; in the most general case you need separate access and store functions. In the complex-number example, the store function is usually just the constructor.) Karl W. Z. Heuer (ima!haddock!karl or karl@haddock.isc.com), The Walking Lint
jima@hplsla.HP.COM ( Jim Adcock) (04/23/88)
> >Again, not true -- the issue is public vs non-public, it is not an issue > >of access functions or not access functions. Change the meaning of > >any part of your public interface, either instance variable or function, > >and your class's users will be hosed. > > It is easy to overlook a subtle point. When you make available a > value through a member function, the only thing you are putting into > the public interface is access to a value. When you make it > available as a data member you are also putting into the interface > the operation of changing that value. Further you are committing > yourself to the implmentation of this operation as a simple > assignment. Could be a valid point. What I've seen in practice is lots of programmers writing TWO access routines for "every" instance variable in their object. One to "put" the value, and one to "get" the value. And then these people think they're doing "real programming." My concern is that I think its important to emphasize "engineering judgement" in the writing of C++ programs -- as opposed to the spouting of religious dogma. I've seen too many programmers in other "object oriented" languages get hung up on "religious" issues -- while ignoring whether or not their end product solves a real world need by the time they're done programming. If in doubt, wrap it in access functions. If NOT in doubt, there's no need to wrap it in access functions. If C++ is to succeed, it will be because C++ programmers write great programs! ---- > This is true. The point, however, is that a variable is always > a variable, while a function is arbitrary. So if you declare that > henceforth and forever, anyone can take complexvar.realpart() and > complexvar.imaginarypart(), and you decide you prefer polar coordinates, > people can still take the two, while if you declare that anyone > can simply take .realpart and .imaginarypart, you are stuck with > rectangular. True statement. [Boy am I sorry I ever started using complex numbers as an example!] Lets go over this again. If you have made public .real and .imag of your object, then you the class writer are committed to making: theRealPart = aComplex.real; theImagPart = aComplex.imag; aComplex.real = aRealPart; aComplex.imag = aImagPart; work the way it obviously looks like it should. If you decide later that you would like .real to really mean "mag" as in "mag-and-phase" then you have a problem -- namely what you told the user means "real" now actually means "mag." And this means you have failed in your commitment not to change the meaning of anything in your public interface! Now lets go over the analogous situation using access functions: theRealPart = aComplex.real(); theImagPart = aComplex.imag(); aComplex.real(aRealPart); aComplex.imag(aImagPart); Now lets pretend you decide that like in the above example, for SOME unbeknown reason, you decide you want .real() to really mean the "mag" of the data, -- then the user is still hosed, 'cause his/her program still won't work. AGAIN, you have failed in your commitment to keep the meaning of everything in your public interface the same. It doesn't matter whether or not you use access functions. IF you keep your committment to keep the MEANING of the various parts of your public interface constant, then your class user's programs will continue to run correctly. If you do not keep your committment to keep the MEANING of the various parts of your public interface constant, then your class user's programs will be hosed. If you know [using your "engineering" judgment] that you need to use access functions to meet this commitment of not changing the meaning of anything in your public interface, then please, use access functions! If you know [using your "engineering" judgment] that you do not need to use access functions to meet this commitment of not changing the meaning of anything in your public interface, then possibly your time and effort can be spent more productively than on "redundant" access functions! I just feel that this issue is one of judgement, not of the religious dogma: "use access functions everywhere."
chris@mimsy.UUCP (Chris Torek) (04/23/88)
>In article <11152@mimsy.UUCP> I wrote the following key line (but probably did not highlight it sufficiently): >>The point, however, is that a variable is always >>a variable, while a function is arbitrary. In article <1490@pt.cs.cmu.edu> lindsay@K.GP.CS.CMU.EDU (Donald Lindsay) writes: >The functional approach has other virtues. [various virtues deleted] Agreed. jima has a point, however: perhaps one should declare that *all* object variables shall be retrieved through access functions, but that argument-free functions should look like variables. This leaves one with the question of how to implement assignments to these. One possible syntax: class c { int var0; int nv0; // counts var0 refs public: int var1; // freely accessible /* * Define access (read & write) functions for var0 that * make it act like var1 without actually being the same * as var1, and incidentally count references too. If * we defined only a read function, or only a write function, * var1 would be a read-only or write-only variable. */ inline int var0() { this->nv0++; return this->var0; } // I never did like implicit references :-) inline int var0=(x) { this->nv0++; return this->var0 = x; } // Here the type of x is implicitly int. // x is any identifier, but there may be only one. c() { var0=0; nv0=0; var1=0; } // `local color' :-) }; Note that cfront already gives a `sorry, not implemented' error for declarations of the form class c { int v; public: int v() { return this->v; } }; -- In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7163) Domain: chris@mimsy.umd.edu Path: uunet!mimsy!chris
jima@hplsla.HP.COM ( Jim Adcock) (04/26/88)
| I should also mention that there is no performance penalty for access | functions. If they are expanded inline the generated code is exactly | the same as reading a public instance variable. Not strictly true. It is not uncommon for the compiler to assign ["unnecessarily"] to an intermediate variable in order to "make sure" that the value of the underlaying variable that the access function protects does not "accidentally" get changed. The compiler seems to do this whenever it is unsure whether or not that underlaying variable might get clobbered. Again, my only point is that I do not believe one should say uncategorically that access functions should be used everywhere. Someone try to explain to me what great advantages access functions have in the following [trivialized to the ridiculous] example ???: class IntVal { private: int val; public: void setValue(int aval) {val = aval;} int value() {return val;} }; verses class IntVal { public: value; }; But if even this one example shows that "use access functions everywhere" statement is overreaching, haven't I shown its converse, namely that there ARE situations where one should consider NOT using access functions ??? [I still don't like the use of mag/phase rep for complex numbers :-]
mike@turing.UNM.EDU (Michael I. Bushnell) (05/05/88)
In article <6590046@hplsla.HP.COM> jima@hplsla.HP.COM ( Jim Adcock) writes: >Someone try to explain >to me what great advantages access functions have in the following >[trivialized to the ridiculous] example ???: > >class IntVal >{ > private: > int val; > public: > void setValue(int aval) {val = aval;} > int value() {return val;} >}; > >verses > >class IntVal >{ > public: > value; >}; Nothing, obviously. This code is better written as struct IntVal { public: value; }; Get the picture? If you think that you really NEED access functions, you might as well just make it a struct. N u m q u a m G l o r i a D e o Michael I. Bushnell HASA - "A" division 14308 Skyline Rd NE Computer Science Dept. Albuquerque, NM 87123 OR Farris Engineering Ctr. OR University of New Mexico mike@turing.unm.edu Albuquerque, NM 87131 {ucbvax,gatech}!unmvax!turing.unm.edu!mike
nevin1@ihlpf.ATT.COM (00704a-Liber) (05/06/88)
In article <6590046@hplsla.HP.COM> jima@hplsla.HP.COM (Jim Adcock) writes: |Not strictly true. It is not uncommon for the compiler to assign |["unnecessarily"] to an intermediate variable in order to "make sure" |that the value of the underlaying variable that the access function |protects does not "accidentally" get changed. The compiler seems to |do this whenever it is unsure whether or not that underlaying variable |might get clobbered. Yes, but in this case I would expect the underlying C compiler to optimize this out. For the moment, let us say that given a very good optimizing compiler, there should be no extra run-time performance penalty for providing access functions for simple assignments. |Again, my only point is that I do not believe one should say uncategorically |that access functions should be used everywhere. Someone try to explain |to me what great advantages access functions have in the following |[trivialized to the ridiculous] example ???: |class IntVal |{ | private: | int val; | public: | void setValue(int aval) {val = aval;} | int value() {return val;} |}; |verses |class IntVal |{ | public: | value; |}; Suppose that, after writing a couple of applications using IntVal, you decide that you want more precision, say 9*sizeof(long) bytes worth of precision. With your method, this cannot be done without rewriting all the code dependent on IntVal. If access functions were used for the assignments, none of the code dependent on IntVal (except for the actual coding of the access functions themselves, of course) would need to be changed. |But if even this one example shows that "use access functions everywhere" |statement is overreaching, haven't I shown its converse, namely that there |ARE situations where one should consider NOT using access functions ??? I don't think so. By not using access functions, you lock yourself into a single specific implementation of the class, and this defeats the whole point of programming using data abstraction techniques. If you are going to do this, what do you gain by using C++ instead of C? -- _ __ NEVIN J. LIBER ..!ihnp4!ihlpf!nevin1 (312) 510-6194 ' ) ) "The secret compartment of my ring I fill / / _ , __o ____ with an Underdog super-energy pill." / (_</_\/ <__/ / <_ These are solely MY opinions, not AT&T's, blah blah blah
mwette@gauss.ucsb.edu (Matt Wette) (05/17/88)
In article <1027@unmvax.unm.edu> mike@turing.UNM.EDU.UUCP (Michael I. Bushnell) writes: > >Nothing, obviously. This code is better written as >struct IntVal >{ > public: > value; >}; >Get the picture? If you think that you really NEED access functions, >you might as well just make it a struct. > N u m q u a m G l o r i a D e o > Michael I. Bushnell What about ... class IntVal { int val; public: int value() { return val; } void value(int aval) { val = aval; } }; Matt Wette _____________________________________________________________________________ Matthew R. Wette | ARPA: mwette%gauss@hub.ucsb.edu Scientific Computation Lab | UUCP: ucbvax!ucsbhub!gauss!mwette Dept. Elec. and Comp. Eng. | University of California | Santa Barbara, CA 93106 | -----------------------------------------------------------------------------