[comp.lang.c++] access functions

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