Hari
Hari

Reputation: 4612

Destructor not called when object on stack is overwritten

Today I was wondering about c++ destructors so I wrote a small test program. That answered my original question but raised a new one which is:
The following program:

#include "stdafx.h"
#include <vector>
#include <iostream>
using namespace std;
class test
{
public:
    int id;
    vector<test> collection;
    test(){}
    test(int id_in){id = id_in;}
    ~test(){cout << "dying: " << id << "\n";}
};

int _tmain(int argc, _TCHAR* argv[])
{
    {
        test obj(1);
        obj.collection.push_back(test(2));
        obj.collection.push_back(test(3));
        cout << "before overwrite\n";
        obj = test(4);
        cout << "before scope exit\n";
    }
    int x;
    cin >> x;
}

produces the following output:

dying: 2
dying: 2
dying: 3
before overwrite
dying: 2
dying: 3
dying: 4
before scope exit
dying: 4

Why don't I see a destructor for my test object with id 1? If its destructor isn't called when it is overwritten, than what calls the destructors of the instances in its vector?

Upvotes: 7

Views: 1406

Answers (5)

Cheers and hth. - Alf
Cheers and hth. - Alf

Reputation: 145239

Regarding the in-practice behavior, the statement

    obj = test(4);

changes the value of the id member to 4. Consequently, when that object is destroyed, it reports that the object with id 4 is destroyed. The assignment performs a memberwise assignment because you haven't defined the copy assignment operator.

Regarding the formally guaranteed behavior, you only have that (for a hosted implementation) if the non-standard "stdafx.h" header in your code defines the macros _tmain and _TCHAR such that preprocessing yields a standard main function as required by the standard:

C++11 §3.6.1/1:
“A program shall contain a global function called main, which is the designated start of the program. It is implementation-defined whether a program in a freestanding environment is required to define a main function.”

Although unlikely, this means that if the header does not define those macros appropriately, then you can, in principle, get the output you see regardless of the rest of the code.

One way to ensure that that undefined behavior does not happen, is to simply use a standard main.

After all, as of 2012 there is absolutely no advantage in using those Microsoft macros that are designed to support Windows 9x, especially considering that Microsoft, with the Layer for Unicode, made those macros obsolete already in 2001.

I.e., continuing to use them over 10 years after, is simply meaningless obfuscation and added work, including that you can't formally say that your program must produce any particular result.

Upvotes: 1

Timbo
Timbo

Reputation: 28050

You violate the Rule of Three by creating a destructor, but no assignment operator.

From reading that, you can interpret your code as follows:

When the line

obj = test(4);

is compiled, a temporary instance of test is created with id 4.

Then, the assignment operator is called. Since you did not provide one, the compiler generated one for you that looks like this:

test& operator=(const test& other)
{
    id = other.id;
    collection = other.collection;
    return *this;
}

The id 1 is simply overwritten with the 4 from the temporary, and for the collection assignment, the assignment operator of std::vector is called.

std::vector's assignment operator deletes all previously contained elements, which is why you see

dying: 2
dying: 3

in your output. Finally, the temporarily created obj instance with id 4 is deleted, causing

dying: 4

to appear for the first time. When obj goes out of scope, you see the

dying: 4

output once more.

Upvotes: 6

klm123
klm123

Reputation: 12865

You don't see 1 because you destruct obj at the very end. And before you have rewritten it by test(4). Therefore 1 is rewritten by 4.

Upvotes: 0

Component 10
Component 10

Reputation: 10487

obj is not destructed when you do this:

obj = test(4);

All that's happening is that a test(4) is being created and assigned onto the existing object so the id of 1 will be overwritten with 4, which is why you see the last one as:

dying: 4

Upvotes: 3

sharptooth
sharptooth

Reputation: 170489

That's because you don't have assignment operator implemented and so a memberwise assignment is done instead. So this line:

obj = test(4);

causes id in the very first object (test obj(1)) to be overwritten to 4. The last line dying: 4 is from destroying that very object.

Upvotes: 6

Related Questions