6

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?

razlebe
  • 7,134
  • 6
  • 42
  • 57
Hari
  • 4,514
  • 2
  • 31
  • 33

5 Answers5

6

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.

sharptooth
  • 167,383
  • 100
  • 513
  • 979
  • That was my first thought. But if thats the case than why and how does the destructors of object 2 and 3 (the instances in the collection of obj with id 1) are called when upon the assignment? – Hari Aug 17 '12 at 11:02
  • @Hari: Those objects are created, *copied* and then destroyed, and this object is *assigned to*. – sharptooth Aug 17 '12 at 11:05
  • That is definetely the case for the first three destructor calls. But to me it appears that the re-assignment of obj 1 somehow triggers the destructor of its vector and therefore the destruction of obj 2 and 3 (the fourth and fifth destructor call in the output) if that is the case than I would like to know how is the vector's destructed and if this isn't the case than I still don't understand what is going on. – Hari Aug 17 '12 at 11:12
  • @Hari: The `vector` inside is reassigned too and its contents is erased this is where 4th and 5th output comes from. – sharptooth Aug 17 '12 at 11:24
  • That explains everything. Thank you for your answer. – Hari Aug 17 '12 at 11:29
6

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.

Community
  • 1
  • 1
Timbo
  • 27,472
  • 11
  • 50
  • 75
3

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
Component 10
  • 10,247
  • 7
  • 47
  • 64
0

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.

klm123
  • 12,105
  • 14
  • 57
  • 95
0

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.

Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331