harrison@necssd.NEC.COM (Mark Harrison) (02/02/91)
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? Thanks in advance, Mark. -- Mark Harrison harrison@necssd.NEC.COM (214)518-5050 {necntc, cs.utexas.edu}!necssd!harrison standard disclaimers apply...
tilo@bernina.ethz.ch (Tilo Levante) (02/05/91)
The g++, tc++, ... compilers are correct. You missed the (default) copy constructor. If you write your program this way, you will see it: #include<stream.h> class bug { public: bug() { cout << "in constructor\n"; } bug(const bug& a) { cout << "in copy constructor\n"; } ~bug() { cout << "in destructor\n"; } }; foo(bug a, bug b) { cout << "in foo\n"; } main() { bug a,b; foo(a,b); } The output is: in constructor in constructor in copy constructor in copy constructor in foo in destructor in destructor in destructor in destructor I assume that cfront has a bug in this case. It has to destruct the value parameters. Tilo
jimad@microsoft.UUCP (Jim ADCOCK) (02/12/91)
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.]
rmartin@clear.com (Bob Martin) (02/15/91)
In article <70619@microsoft.UUCP> jimad@microsoft.UUCP (Jim ADCOCK) writes: >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; " [which invokes a copy constructor not the default constructor] > >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. > I would only add to this that the SUN compiler spit out the following warnings when I tried this program: "bug.cc", line 10: warning: a not used "bug.cc", line 10: warning: b not used "bug.cc", line 13: warning: no value returned from foo() This is in reference to the fact the 'foo' does not use the parameters. In this case it appears that the compiler chose not to construct them since they were not used. -- +-Robert C. Martin-----+:RRR:::CCC:M:::::M:| Nobody is responsible for | | rmartin@clear.com |:R::R:C::::M:M:M:M:| my words but me. I want | | uunet!clrcom!rmartin |:RRR::C::::M::M::M:| all the credit, and all | +----------------------+:R::R::CCC:M:::::M:| the blame. So there. |