[comp.lang.c++] reference parameters for overloaded operators

chris@ctk1.UUCP (Chris Old) (08/13/90)

When should one use references rather than passing by value to
overloaded operators?

I realise that references will save some overhead for large structures,
but the rather scanty manual I have, seemed to indicate that there is
another reason. It (the manual) also hinted at situations when it would
be preferable to pass one of the parameters by value and the other by
reference. Can anyone help?
--------------------
Chris Old  (C.t.K.)               : chris@ctk1.UUCP
Tran Systems Ltd                  : ddsw1!olsa99!ctk1!chris

steve@taumet.com (Stephen Clamage) (08/16/90)

chris@ctk1.UUCP (Chris Old) writes:

|When should one use references rather than passing by value to
|overloaded operators?

|I realise that references will save some overhead for large structures,
|but the rather scanty manual I have, seemed to indicate that there is
|another reason.

The overhead can be significant even for a small structure, since the
copy constructor must be called to initialize the formal parameter.

The return value must be a reference if you want an lvalue, such as
for operator[].
-- 

Steve Clamage, TauMetric Corp, steve@taumet.com

chip@tct.uucp (Chip Salzenberg) (08/17/90)

According to chris@ctk1.UUCP (Chris Old):
>When should one use references rather than passing by value to
>overloaded operators?

I have found references useful as parameters when the objects passed
as parameters cannot be copied cheaply (or at all).

jimad@microsoft.UUCP (Jim ADCOCK) (08/21/90)

In article <26CBE285.4EC2@tct.uucp> chip@tct.uucp (Chip Salzenberg) writes:
>According to chris@ctk1.UUCP (Chris Old):
>>When should one use references rather than passing by value to
>>overloaded operators?
>
>I have found references useful as parameters when the objects passed
>as parameters cannot be copied cheaply (or at all).

Also, whenever doing polymorphic programming [as in vtables and virtual
functions, not polymophic via parameter overloading] one should call
by reference to avoid the slicing problem.

fkuhl@mitre.org (F. S. Kuhl) (08/21/90)

In article <56749@microsoft.UUCP> jimad@microsoft.UUCP (Jim ADCOCK) writes:
>Also, whenever doing polymorphic programming [as in vtables and virtual
>functions, not polymophic via parameter overloading] one should call
>by reference to avoid the slicing problem.

OK, I give.  What's the slicing problem?  C++ already looks to my
uninitiated mind like a Swiss Army knife.

--
Frederick Kuhl			fkuhl@mitre.org
Civil Systems Division
The MITRE Corporation

mckenney@sparkyfs.istc.sri.com (Paul Mckenney) (08/21/90)

In article <117703@linus.mitre.org> fkuhl@ralph.mitre.org (F. S. Kuhl) writes:
>In article <56749@microsoft.UUCP> jimad@microsoft.UUCP (Jim ADCOCK) writes:
>>Also, whenever doing polymorphic programming [as in vtables and virtual
>>functions, not polymophic via parameter overloading] one should call
>>by reference to avoid the slicing problem.

>OK, I give.  What's the slicing problem?  C++ already looks to my
>uninitiated mind like a Swiss Army knife.

Suppose you have a function expecting a base-class object for its parameter:

	class B
		{
		int		a;
	public:
		virtual void	q() { cout << "B::q\n"; }
		};

	class D : public B
		{
		int		b;
	public:
		void		q() { cout << "D::q\n"; }
		};

	void f(B x);

Then a call to ``f'' passing in a derived object:

	D y;
	f(y);

will coerce from class D to class B in the process of initializing formal
parameter object ``x'' from object ``y''.  Conceptually, this
involves ``slicing'' off the definitions that class D adds to class B,
in this case the member variable ``b''.  In addition, if ``f'' invokes
member function ``q'':

	y.q();

class B's definition of ``q'' will be used (in other worded, ``B::q'' will
be printed).  Formal parameter ``x'' is in all ways an object of class ``B''.

Suppose that another function uses a reference rather than value parameter:

	void g(B &z);

Then a call to ``g'' passing in a derived object:

	g(y);

would pass in a pointer to the instance ``y''.  No ``slicing'' would occur,
member variable ``z.b'' would be intact, and if ``g'' invoked virtual member
function ``z.q()'', ``D::q'' would be printed.  None of the ``D''-ness of
instance ``y'' has been lost.

				Hope this helps, Paul

jimad@microsoft.UUCP (Jim ADCOCK) (08/24/90)

In article <117703@linus.mitre.org> fkuhl@ralph.mitre.org (F. S. Kuhl) writes:
>In article <56749@microsoft.UUCP> jimad@microsoft.UUCP (Jim ADCOCK) writes:
>>Also, whenever doing polymorphic programming [as in vtables and virtual
>>functions, not polymophic via parameter overloading] one should call
>>by reference to avoid the slicing problem.
>
>OK, I give.  What's the slicing problem?  C++ already looks to my
>uninitiated mind like a Swiss Army knife.

Well, its certainly true that C++ has three main blades to stick you with
when it comes to pass parameters:

pass-by-value
pass-by-reference
pass-by-pointer

Pass-by-value is as in C.
Pass-by-pointer is as in C, where a pointer to an object is passed to
	allow it to be modified -- although there are other good reasons
	to want to do this in C++ in addition to the historical C reasons.
Pass-by-reference is as in Pascal pass-by-reference, and is typically
	the default in most other OOPL languages -- whereas by default
	C++ is pass-by-value.

The 'Slicing' problem arises in C++ when some "thing2" is passed by
value as a parameter -- where the exact type of "thing2" is not known,
because the "thing2" is actually a derivative of some base class "thing."
But, in deriving thing2, more members are typically added to thing2
than are in a base class thing.  Passing thing2 by value, as-if it
were a "thing", causes those extra members not to be copied -- they
are "sliced off."  This generally leads to undesirable behavior.  The
solution is simple, once recognized:  "never" use pass-by-value where
you'd expect polymorphic behavior.  Use [const] references instead.

[This makes the 'slicing problem' a quick and easy test of new "C++"
books you find at your store -- If an author is talking about
"polymorphic this" and "polymorphic that," but all his/her "C++"
examples use pass-by-value parameters -- Well, then the best one
can say of such authors is that they don't know C++ !!!]

....In the following code, PrintWith_version2 suffers the slicing problem.
PrintWith_version1 avoids the problem by passing by reference -- effectively
allowing different sized objects to be used as its parameter.

extern "C"
{
#include <stdio.h>
}

class THING
{
private:
	const char* name;
public:
	THING(const char* nameT) : name(nameT) {}
	virtual void Print() const;
	virtual void PrintWith_version1(const THING& thing) const;
	virtual void PrintWith_version2(THING thing) const;
};
void THING::Print() const { printf("%s ", name); }
void THING::PrintWith_version1(const THING& thing) const
{ Print(); thing.Print(); putchar('\n'); }
void THING::PrintWith_version2(THING thing) const
{ Print(); thing.Print(); putchar('\n'); }

class THING2 : public THING
{
private:
	const char* name2;
public:
	THING2(const char* name1T, const char* name2T) : 
		THING(name1T), name2(name2T) {}
	virtual void Print() const;
};
void THING2::Print() const { THING::Print(); printf("%s ", name2); }

void main()
{
	THING& thing1 = *new THING("thing1_name");
	THING& thing2 = *new THING2("thing2_firstname", "thing2_lastname");

	thing1.Print(); putchar('\n');
	thing2.Print(); putchar('\n');
	thing1.PrintWith_version1(thing2);
	thing1.PrintWith_version2(thing2);
}

perez@csc.ti.com (Edward Perez) (08/28/90)

In article <56873@microsoft.UUCP> jimad@microsoft.UUCP (Jim ADCOCK) writes:

  The 'Slicing' problem arises in C++ when some "thing2" is passed by
  value as a parameter -- where the exact type of "thing2" is not known,
  because the "thing2" is actually a derivative of some base class "thing."
  But, in deriving thing2, more members are typically added to thing2
  than are in a base class thing.  Passing thing2 by value, as-if it
  were a "thing", causes those extra members not to be copied -- they
  are "sliced off."  This generally leads to undesirable behavior.  The
  solution is simple, once recognized:  "never" use pass-by-value where
  you'd expect polymorphic behavior.  Use [const] references instead.

  ....In the following code, PrintWith_version2 suffers the slicing problem.
  PrintWith_version1 avoids the problem by passing by reference -- effectively
  allowing different sized objects to be used as its parameter.

  <code example deleted>

jim, i understand what you mean by slicing and the problems one could get into.
however, when i compiled the code example using AT&T 2.0 06/30/89, i got the
following output:

thing1_name					<== thing1.Print()
thing2_firstname thing2_lastname		<== thing2.Print()
thing1_name thing2_firstname thing2_lastname	<== thing1.PW_v1(thing2);
thing1_name thing2_firstname thing2_lastname	<== thing1.PW_v2(thing2);

according to your slicing definition, when PrintWith_version2 is called from
main, a temp THING is created using thing2 since thing2 is being passed by
value.  therefore, shouldn't the last line not have "thing2_lastname" because
THING doesnt have a name2 ??  does the fact that the functions are virtual
affect this in any way ??

what do you get when you run your example through your compile <which one>??

---------------------------------------------------------------------
edward roland perez          internet: perez@csc.ti.com             #
computer science center         csnet: perez%ti-csl@relay.cs.net    #
texas instruments, dallas       phone: 214-995-0698(w)              #
---------------------------------------------------------------------

jimad@microsoft.UUCP (Jim ADCOCK) (08/29/90)

In article <PEREZ.90Aug27161734@pottawatomie.csc.ti.com> perez@csc.ti.com (Edward Perez) writes:
>
>jim, i understand what you mean by slicing and the problems one could get into.
>however, when i compiled the code example using AT&T 2.0 06/30/89, i got the
>following output:
>
>thing1_name					<== thing1.Print()
>thing2_firstname thing2_lastname		<== thing2.Print()
>thing1_name thing2_firstname thing2_lastname	<== thing1.PW_v1(thing2);
>thing1_name thing2_firstname thing2_lastname	<== thing1.PW_v2(thing2);
>
>what do you get when you run your example through your compile <which one>??

Interesting!  -- When I use AT&T 2.0 06/30/89 followed by MSC 6.0 -Od 
[optimizations disabled] I get the following output:

>thing1_name					<== thing1.Print()
>thing2_firstname thing2_lastname		<== thing2.Print()
>thing1_name thing2_firstname thing2_lastname	<== thing1.PW_v1(thing2);
>thing1_name thing2_firstname f              	<== thing1.PW_v2(thing2);

-- which I believe represents correct behavior [in this case :-] of
MSC 6.0 based on erroneous code generated from cfront.  I say this based
on manually studying the C code generated from cfront.  I have a theory
about why your compilation appears to "do the right thing", which I will
discuss below.

First of all, what I believe is a cfront error: When value copying thing2
into a temporary THING object, cfront first correctly copies the vtable 
ptr for a THING into the temporary THING's vtable ptr, then requests an
eight-byte structural copy of the first 2/3rds of thing2s stucture on top
of the temporary THING -- wiping out the pointer to the THING vtable, and
replacing it with thing2s pointer to the THING2 vtable.  Thus you end up
with something that is suppose to be a temporary THING, but contains a
THING2 vtable pointer.  The temporary object then uses this erroneous
THING2 vtable pointer to access a THING2 print method, which tries to  
access the second name, which doesn't exist in a THING structure.  Thus,
the garbage "f" is printed.

I believe what cfront should be doing is first do the structural copy, and
then set up the vtable pointer.  Shouldn't a THING temporary object contain
a pointer to the THING vtable?

So how can the "apparently right" [actually wrong] thing be printed with 
your compiler?  Well, my best guess is that your backend compiler is doing 
optimizations.  The backend compiler recognizes via flow analysis that
thing2 is no longer needed after the temp THING is created, and by gosh,
it can actually avoid a lot of structural copying if it defines the temp
THING as living at the same addresses previously occupied by thing2.  So,
by dumb luck, the temp THING gets a vtable pointer corresponding to a 
THING2 object, and by equally dumb luck, that object gets left with the
corresponding extra field left over from the previous thing2 object at that 
address.

Try turning off all C backend optimizations, and see if the code "stops 
working."

Alternatively, maybe its just that I'm crazy.  Can someone else in the know 
confirm or deny my interpretations, please?

What I believe the correct final line of output should be is:

thing1_name thing2_firstname 

-- In any case, pass by reference!