Slajni
Slajni

Reputation: 115

Why destructor runs before object is being deleted

When I run this code destructor starts before object removal.

Code is here:

#include <string>
#include <vector>

using namespace std;

class Testi {
public:

    string name;
    Testi(string a) : name(a) {
        cout << "Im alive: " << name << endl;
    }
    ~Testi() {
        cout << "Im no longer alive: " << name << endl;
    }

};
int main() {

    vector <Testi> a;

    a.push_back(Testi("John"));
    a.push_back(Testi("Jack"));
    a.push_back(Testi("Jake"));

    cout << a[1].name;

    cin.get();
    return 0;
}

When i run program output is:

Im alive: John
Im no longer alive: John
Im alive: Jack
Im no longer alive: John
Im no longer alive: Jack
Im alive: Jake
Im no longer alive: John
Im no longer alive: Jack
Im no longer alive: Jake

Jack

And after input:

Im no longer alive: John
Im no longer alive: Jack
Im no longer alive: Jake

So after every push_back() all destructors runs. The output operation works good so objects still exist. For the 1st one destructor runs 4 times! Why?

Upvotes: 1

Views: 126

Answers (4)

Teivaz
Teivaz

Reputation: 5665

If you would overload the copy and move constructors (and track instances for convenience) you will see what is going on there

struct Testi {
    static int instanceCount;
    
    std::string name;
    int instanceIndex;
    
    Testi(Testi&& other) : name(other.name), instanceIndex(++instanceCount) {
        std::cout <<other.instanceIndex << " => " << instanceIndex << " move constructor " << name << std::endl;
    }
    
    Testi(const Testi& other) : name(other.name), instanceIndex(++instanceCount) {
        std::cout <<other.instanceIndex << " => " << instanceIndex << " copy constructor " << name << std::endl;
    }
    Testi(std::string a) : name(a), instanceIndex(++instanceCount) {
        std::cout << instanceIndex << " Im alive: " << name << std::endl;
    }
    ~Testi() {
        std::cout << instanceIndex << " Im no longer alive: " << name << std::endl;
    }
};

int Testi::instanceCount = 0;

1 Im alive: John
1 => 2 move constructor John
1 Im no longer alive: John
3 Im alive: Jack
3 => 4 move constructor Jack
2 => 5 copy constructor John
2 Im no longer alive: John
3 Im no longer alive: Jack
6 Im alive: Jake
6 => 7 move constructor Jake
5 => 8 copy constructor John
4 => 9 copy constructor Jack
5 Im no longer alive: John
4 Im no longer alive: Jack
6 Im no longer alive: Jake
Jack

8 Im no longer alive: John
9 Im no longer alive: Jack
7 Im no longer alive: Jake

1 - temporary object that is being passed as an argument to push_back()
2 - object stored in the vector created by moving (or copying)
3 - another temporary object
4 - object that is stored in a vector
5 - copy of the object 2 because vector needs to reallocate
And so on. But this will give you general idea of implementation (and why one might want to reserve()). Standard does not restrict what exactly should be used. Here we should also talks about versions:

  • Before C++11 contained type should be copy-constructible and copy-assignable
  • Starting from C++11 restrictions are applied to a specific function; for push_back() contained type should be CopyInsertable and MoveInsertable

This should give you general expectations of implementation.

Upvotes: 0

Vlad from Moscow
Vlad from Moscow

Reputation: 310930

To make it more clear add the copy constructor for the class for example the following way as it is shown in this demonstrative program

#include <iostream>
#include <vector>
#include <string>

using namespace std;

class Testi {
public:

    string name;
    Testi(const string &a) : name(a) {
        cout << "Im alive: " << name << endl;
    }
    Testi( const Testi &t ) : name(t.name + "_copy") {
        cout << "Im alive: " << name << endl;
    }
    ~Testi() {
        cout << "Im no longer alive: " << name << endl;
    }

};


int main()
{
    {
        vector <Testi> a;

        a.push_back(Testi("John"));
        a.push_back(Testi("Jack"));
        a.push_back(Testi("Jake"));

        cout << "---------------------" << endl;

        for (const auto &item : a) cout << item.name << ' ';
        cout << endl << endl;
    }
    {
        cout << "---------------------" << endl;
        vector <Testi> a;
        a.reserve(3);

        a.emplace_back("John");
        a.emplace_back("Jack");
        a.emplace_back("Jake");

        cout << "---------------------" << endl;

        for (const auto &item : a) cout << item.name << ' ';
        cout << endl << endl;
    }

    return 0;
}

Its output might look like

Im alive: John
Im alive: John_copy
Im no longer alive: John
Im alive: Jack
Im alive: John_copy_copy
Im no longer alive: John_copy
Im alive: Jack_copy
Im no longer alive: Jack
Im alive: Jake
Im alive: John_copy_copy_copy
Im alive: Jack_copy_copy
Im no longer alive: John_copy_copy
Im no longer alive: Jack_copy
Im alive: Jake_copy
Im no longer alive: Jake
---------------------
John_copy_copy_copy Jack_copy_copy Jake_copy

Im no longer alive: John_copy_copy_copy
Im no longer alive: Jack_copy_copy
Im no longer alive: Jake_copy
---------------------
Im alive: John
Im alive: Jack
Im alive: Jake
---------------------
John Jack Jake

Im no longer alive: John
Im no longer alive: Jack
Im no longer alive: Jake

So in this statement

a.push_back(Testi("John"));

there is created a temporary object as result of the expression Testi("John"). Then this object is copied to the vector and the vector stores a copy of the temporary object. At the end of the statement the temporary object is deleted.

Im alive: John
Im alive: John_copy
Im no longer alive: John

When this statement is executed

a.push_back(Testi("Jack"));

the same operations are performed except that the vector needs to reallocate memory that to accommodate the new element.

Im alive: Jack
Im alive: John_copy_copy
Im no longer alive: John_copy
Im alive: Jack_copy
Im no longer alive: Jack

the first message corresponds to creating the temporary object that corresponds to the argument Testi("Jack"). Then the current element of the vector is copied to the new extent of memory due to the memory reallocation

Im alive: John_copy_copy
Im no longer alive: John_copy

then the new element is copied and the temporary object is deleted

Im alive: Jack_copy
Im no longer alive: Jack

and so on.

If you will reserve enough memory in the vector then there will no memory reallocation. Also if you will use emplace_back instead of push_back there will not be created temporary objects. In this case the output will be

Im alive: John
Im alive: Jack
Im alive: Jake

and

Im no longer alive: John
Im no longer alive: Jack
Im no longer alive: Jake

Upvotes: 2

Adrian McCarthy
Adrian McCarthy

Reputation: 47954

Here's the relevant piece of code:

vector <Testi> a;
a.push_back(Testi("John"));
  1. The Testi("John") creates a new, temporary Testi object.
  2. push_back copies that object into the vector.
  3. Then the temporary object is deleted.

So the unexpected constructor and destructor calls come from the creation and deletion of the temporary. You can avoid the extra temporaries and copies by using emplace_back which will construct the object directly in the vector.

Upvotes: 4

SomeWittyUsername
SomeWittyUsername

Reputation: 18338

You're passing a temporary object as a parameter to push_back. the temporary is being copied and after that its lifetime expires, thus it's destroyed.

Upvotes: 0

Related Questions