Bitcopy versus initialization question

Hi, in my quest to learn C++ using Bruce Eckels book, a chapter is dedicated to the copy constructor. While experimenting with the situations presented I triggered some unexpected behaviour I can't explain. I hope one of you can explain why the destructor is triggered twice in the following situation:

Consider the following code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <iostream>
#include <fstream>

using namespace std;

#define out cout
//ofstream out("HowMany.out");

class HowMany {
    static int objectCount;
public:
    HowMany() { objectCount++; }
    static void print(const string& msg = "") {
        if(msg.size() != 0) out << msg << ": ";
        out << "objectCount = "
        << objectCount << endl;
    }
    ~HowMany() {
        objectCount--;
        print("~HowMany()");
    }
};

int HowMany::objectCount = 0;
// Pass and return BY VALUE:

HowMany f(HowMany x) {
    x.print("x argument inside f()");
    return x; 
}

int main(void) {
    HowMany h;
    HowMany::print("after construction of h"); HowMany h2 = f(h);
    //h2 = f(h);    
    HowMany::print("after call to f()");


    HowMany::print("end of main");
    return 0;     // h & h2 are destroyed
} 


output:
after construction of h: objectCount = 1
x argument inside f(): objectCount = 1
~HowMany(): objectCount = 0
after call to f(): objectCount = 0
end of main: objectCount = 0
~HowMany(): objectCount = -1
~HowMany(): objectCount = -2


Ok, according to Bruce this is because C++ is performing a bitcopy rather than calling the constructor. So the first destructor call would apply to "x inside f".

At the end of main "h & h2" are destroyed. So we see 3 destructor calls in this piece of code, and since the constructor was skipped in two occasions the count is now negative: -2.

So far so good.

But now in my experimentation I added one line of code to main, which now looks like this:
1
2
3
4
5
6
7
8
9
10
int main(void) {
    HowMany h;
    HowMany::print("after construction of h"); HowMany h2 = f(h);
    h2 = f(h);    
    HowMany::print("after call to f()");


    HowMany::print("end of main");
    return 0;     // h & h2 are destroyed
} 


I expected this would cause another 'x in f' to be bitcopied and destructed, so in total 4 destructor calls. Yet, somehow it calls the destructor twice more in this situation. it's been hurting my brain for a the past hour, why does it happen, what is it trying to destroy?
Adding this line several times will result in 2 more destructor calls for each line.

In summary:
Why does it call the destructor on "x in f" only once at initialization of h2, but why does it call the destructor twice if h2 already exists?
If you will include a copy constructor you will get a more realistic result

HowMany( const HowMany & ) { objectCount++; }
Last edited on
I would guess it's because the program is doing something along the lines:

1
2
3
4
delete h2
create temporary f(h)
h2 = temporary
delete temporary

There is some redundancy here, and it is likely that changing compilation options (optimization) would remove this redundancy.
@Abramus seems legit, thanks.

@vlad You're right, but the question was not related to why my program wasn't running right, but why it was calling more destructors than I imagined it would call. Just a theoretic question I suppose, I was curious about the way it works.
Last edited on
Note that the copy constructor is a constructor so the number of constructor calls should be the same as the number of destructor calls.
delete h2; that doesn't make sense.
HowMany f(HowMany x);
_ first copy is in creating 'x'.
_ at the end of the function 'x' gets destructed, and a copy is returned. (call it 'z')
_ 'z' goes to the assignment operator (by const reference)
_ at the end of the function 'z' gets destroyed.

HowMany h2 = f(h); only gets one destruction because of RVO. ('z' doesn't destroy, but is 'h2')


according to Bruce this is because C++ is performing a bitcopy rather than calling the constructor
Nope, a constructor is called. The copy constructor provided by the compiler, that doesn't do a bitcopy, but a member by member copy.
It would be equivalent for POD
Last edited on
delete h2; that doesn't make sense.

You are right, my bad.
@ne555
Hmm, I thought to understand it from Abramus' explanation but now I'm confused again.

Do I understand it right if I say:
For what you called 'z' in the example, it means that h2 gets the "same value" as 'z', but both variables are stored at a different address. 'z' is destroyed, h2 is maintained.
While in the example of h2 = f(h) there is never a 'z', but the value is stored directly at the address of h2?
Does it work like that?

(2)
Bruce writes:
The compiler’s assumption is that you want to perform this creation using a bitcopy, and in many cases this may work fine, but in HowMany it doesn’t fly because the meaning of initialization goes beyond simply copying.

You say this is wrong; instead it performs a member-by-member copy. What does that mean ?
f(h) calls the copy constructor. Vlad's first post shows why objectCount is invalidated.

f(h) returns a HowMany, but when you do h2 = f(h) the "=" calls the copy constructor again (also destroying the HowMany it returned).

1
2
HowMany h2 = f(h);
h2 = f(h); 


The h2 in line 1 is destroyed by line 2. Since f(h) also creates and destroys a HowMany, this is why that line makes two more destructor calls.

Like Vlad says, just beef up your copy constructor and you should see things making more sense.
Last edited on
The h2 in line 1 is destroyed by line 2
No, it doesn't. The destructor will only be called when 'h2' goes out of scope (at the end of main())

@Prestissimo:
In HowMany h2 = f(h); there is never 'z', or if you prefer 'z' and 'h2' are the same. That's called RVO http://en.wikipedia.org/wiki/Return_value_optimization

As for (2)
A bitcopy and a member-by-member copy would be the same if we only consider POD.

Now suppose that you've got a std::string member. Internally they hold a pointer where the text is stored.
A bitcopy would simply copy the pointers, resulting in a double delete when the objects go out of scope.
A member-by-member would create another space that holds the same text.
The compiler doesn't have to do RVO.
Ok, thanks a lot for the clarification.
No, it doesn't. The destructor will only be called when 'h2' goes out of scope (at the end of main())

My mistake.
Topic archived. No new replies allowed.