[comp.std.c++] Calling constructors for parms passed by value

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.     |