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!