Path: utzoo!utgpu!news-server.csri.toronto.edu!cs.utexas.edu!uunet!microsoft!jimad From: jimad@microsoft.UUCP (Jim ADCOCK) Newsgroups: comp.lang.c++ Subject: Re: Default copy constructor not making a copy. Message-ID: <70784@microsoft.UUCP> Date: 19 Feb 91 20:55:41 GMT References: <22023@hydra.gatech.EDU> Reply-To: jimad@microsoft.UUCP (Jim ADCOCK) Organization: Microsoft Corp., Redmond WA Lines: 97 In article <22023@hydra.gatech.EDU> gatech.edu!blsouth!klein (Michael Klein) writes: |Can someone explain why the two classes defined below exhibit different |behavior? The class without the explicit copy constructor does not make |a copy of the *this argument for the + operator. There is a related |comment in Ellis & Stroustrup (12.6.1): "Had no copy constructor been |declared...all would have happened exactly as before because a copy |constructor would have been generated. Again, a good compiler would |eliminate the use of the generated copy constructor." I'm not convinced |that this statement applies in this case. Any enlightenment on this |subject would be welcome. I'm using ATT C++ 2.0 on a Sparc. Part of the confusion arises from the word "good" above -- today's C++ compilers vary greately in how "good" they are at understanding what things in C++ they can or cannot optimize. Thus, C++ compilers can and do give greatly varying and at first glance non-sensical performance when presented with implied or explicit conversion or cast operators. Note that given a class FOO, FOO(x), (FOO)x, and (FOO)(x) all use a FOO constructor to make a unnamed temporary FOO from x. This is to be distinguished from reference casting: (FOO&)x -- which says to consider that glob of bits at x's location as if it were a FOO. This reference cast hack is essentially the same as the common "C" pointer hack: *((FOO*)(&x)) "Consider x's address as if it were a FOO pointer and dereference that FOO pointer." But I digress. Back to that unnamed FOO temporary created by FOO(x). The C++ specs allow compilers to eliminate any and all unnamed temporaries if the only way to tell if the temporary existed is via side-effects of the constructor and destructor. Further, ARM 12.1.2c states that unnamed temporaries can be eliminated when their side effect is to make a copy! Conversely, C++ compilers are free to introduce unnamed temporaries when the compiler considers it necessary or convenient. These rules can be easily summarized from the C++ programmers point of view: "NEVER rely on unnamed temporaries to do anything you want them to do!" How can one tell if one is relying on an unnamed temporary? Simple, ask yourself if that object you think you're relying on has a name. If so, what is its name? If it doesn't have a name, you CANNOT rely on it. How then, can one introduce a NAMED temporary? The most common way is to use a named local "auto" variable: FOO FOO::operator + (int a) { FOO foo(*this); return foo += a; } In this example the introduction of a named local variable "foo" forces a copy of *this. Adding a to foo then cannot modify *this. The value "foo += a" is in turn returned from "operator+" via unnamed temporary -- another issue which I will address below. Another approach to giving a temporary a name is to assign a reference to it. [ARM pg 268] A reference is just another way to give a name to an object: FOO FOO::operator + (int a) { FOO& foo = FOO(*this); return foo += a; } I claim in this example that since the temporary created by FOO(*this) is bound to a name "foo", the compiler is not free to optimize it away. However, this rule is subtle enough that you shouldn't be surprised if many of today's C++ compilers screw it up. Thus, I recommend the first approach. Where the reference-to-a-temporary approach can be important however, is in forcing a valid return object from a function: FOO foo; int a; .... foo + a; // which calls foo.operator+(a) This allows the compiler to "optimize away" the unnamed temporary used to return the results from FOO FOO::operator+(int). Whereas if you say: const FOO& namedvalue = foo + a; then the compiler is forced to generate a valid temporary with the name "namedvalue" which remains valid for the scope that "namedvalue" is valid in. [Note that such use of references to temporaries doesn't keep the temporaries alive indefinitely. Since namedvalue is in the scope of the call to the function, the temporary only stays alive as long as the scope the function "operator+" is called in. Also, function return values aren't lvalues, so the reference assigned to it must be a const.] In Summary: 1) NEVER rely on unnamed temporaries to do anything you want them to do. 2) ALWAYS introduce a named variable to force a copy. 3) COUNT ON finding C++ compilers today that screw up even these simple rules.