Path: utzoo!utgpu!news-server.csri.toronto.edu!cs.utexas.edu!uunet!microsoft!jimad From: jimad@microsoft.UUCP (Jim ADCOCK) Newsgroups: comp.std.c++ Subject: Re: Calling constructors for parms passed by value Message-ID: <70619@microsoft.UUCP> Date: 12 Feb 91 01:26:39 GMT References: <631@necssd.NEC.COM> Reply-To: jimad@microsoft.UUCP (Jim ADCOCK) Organization: Microsoft Corp., Redmond WA Lines: 140 In article <631@necssd.NEC.COM> harrison@necssd.NEC.COM (Mark Harrison) writes: |A question arose in our internal C++ class. The instructor was |describing a bug to look out for in some C++ compilers. The |code was something like this: | |class bug { | bug() { cout << "in constructor" } | ~bug() { cout << "in destructor" } |} |main() |{ | bug a,b; | foo(a,b); |} |foo(bug a, bug b) |{ | cout << "in foo"; |} | |The output of a cfront based compiler was | |in constructor |in constructor |in foo |in destructor |in destructor | |While on some other compilers (TC++, Zortec, G++, versions unknown) the |output was | |in constructor |in constructor |in foo |in destructor <-- apparently destructed in foo() |in destructor <-- apparently destructed in foo() |in destructor |in destructor | |We agreed that it was a bug that #constructors != #destructors, but had |a disagreement about whether the number of constructor/destructor pairs |should have been 2 or 4. | |So my question: Should a constructor/destructor be called for a formal |parameter that has been passed by value? Some people said yes, because |it is coming into/out of scope. Some people said no, because it had |already been copied onto the stack and therefore existed. Our instructor |said it was implementation defined, but I can't believe that this is |true. Any references that anyone can give me? The appropriate reference would seem to be ARM, bottom of page 288: "The initialization that occurs in argument passing and function return is equivalent to the form T x = a; " -- whatever that means. People can and do argue about exactly what this statement means. The interpretation I'd give is that your main is equivalent to : main() { bug a, b; // pretending that foo is inline expanded.... { bug parm_a = a; bug parm_b = b; cout << "in foo"; } } which in turn is about equivalent to the "C" code: main() { struct bug a, b; foo_default_construct(&a); // prints "in constructor" foo_default_construct(&b); // prints "in constructor" // pretending that foo is inline expanded.... { struct bug parm_a, parm_b // assuming some gratuitous temporaries are optimized out: foo_copy_construct(&parm_a, &a); // doesn't print anything foo_copy_construct(&parm_b, &b); // doesn't print anything cout << "in foo" foo_destruct(&parm_b); // prints "in destructor" foo_destruct(&parm_a); // prints "in destructor" } foo_destruct(&a); // prints "in destructor" foo_destruct(&b); // prints "in destructor" } So, I'd claim in the situations where you appear to be getting more destructors than constructors, in fact the compilers are doing the right thing. The question then remains, are compilers free to "optimize away" the parameters a and b of foo? I claim they are not free to do so. Compilers *are* free to optimize away unnamed temporaries. But parameters a and b are not unnamed temporaries, but rather named declared variables in the scope of foo. Therefore they need to be copy_constructed on entry to foo, and properly destructed on exit from foo. Note specifically page 22 of ARM states: "A named local object may not be destoyed before the end of its block nor may a local named object with a constructor or destructor with side effects be eliminated even if it appears to be unused." I claim that parameters a and b of foo are exactly such named local objects. Side effects of their constructors and destructors must be preserved. One way to double check your other compiler is to explicitly define a copy constructor. Do two more destructors then suddenly appear? -- If so, it would seem to be a compiler bug -- the fact that constructors don't have side effects doesn't dismiss the compiler from honoring any side-effect in the destructors! [Therefore, adding an explicit copy constructor should in no way change any side-effects of any destructors.] [Note that if you fail to declare a copy constructor and/or assignment operator, the compiler will automatically generate one for you. Which is what happened in your case. Since you only put a print statement in the default constructor, but not in the [automatically generated] default constructor, your constructors/destructors *appeared* to be unbalanced. Its problems like these that cause long-time C++ hacks to always insist on explicitly defining copy constructors and assignments. Another thing to note: it that it is almost always a bad idea to generate constructors/destructors inline, since an inordinate amount of compiler- generated hidden code almost always results. -- Not to mention this makes it really hard to track down what the compiler is doing to you.]