Oliv
Oliv

Reputation: 10822

Destructor calls

I'm trying to understand vectors of classes. First the class:

class A {
public:
    A() {   cout << "A ctor" << endl; }
    ~A() { cout << "A dtor" << endl; }
};

Then the code:

int main() {
    vector<A> v;
    cout << "after vector created" << endl;
    v.push_back(A());
    cout << "after one A added" << endl;
}

If I run the program, the output is:

after vector created
A ctor
A dtor   <<-- why this??
after one A added
A dtor

I do not understand, what is the additional destructor call. Shouldn't the constructor and destructor calls be paired?

Secondly, if I pass the vector by value to a function, why are the constructors are not called?

void f(vector<A> v);

I checked, that f has copy of A, since if I modify it's internal state, it's not modified in the original vector and &v[0] returns different value inside f.

Upvotes: 3

Views: 313

Answers (6)

Niall
Niall

Reputation: 30624

Shouldn't the constructor and destructor calls be paired?

Yes, they are "paired"; for every object created, its destructor will be called. Essentially what you are doing is because you are not tracing out the implicitly generated copy constructor here. It is generated by the temporary A() and since the class A has no move associated with it, a copy of this temporary is inserted into the vector. If the class A had a move constructor, it would be used to "move" the object into the vector and hence you would see the move constructor being used instead of the copy constructor.

Once C++11 and up is included in the mix, there are a few members that may be involved here; copy constructors, move constructors and assignment operators are included.

#include <iostream>
#include <vector>

using namespace std;

class A {
public:
    A() { cout << "A ctor" << endl; }
    ~A() { cout << "A dtor" << endl; }
    A(const A&) { cout << "A copy ctor" << endl; }
    A(A&&) { cout << "A move ctor" << endl; }
    A& operator=(const A&) { cout << "An assignment" << endl; return *this; }
    A& operator=(A&&) { cout << "A move assignment" << endl; return *this; }
};

int main() {
    vector<A> v;
    cout << "after vector created" << endl;
    v.push_back(A());
    cout << "after one A added" << endl;
}

With the output;

after vector created
A ctor
A move ctor
A dtor
after one A added
A dtor

With respect to the second query;

...if I pass the vector by value to a function, why are the constructors are not called?

They are, the copy constructors would be used to copy the contents of the vector. If using C++11, you could also move the entire vector when call the function (if the vector would no longer be required in the calling function).

Upvotes: 3

VP.
VP.

Reputation: 16775

If you see the definition of push_back of the vector:

// [23.2.4.3] modifiers
 /**
  *  @brief  Add data to the end of the %vector.
  *  @param  __x  Data to be added.
  *
  *  This is a typical stack operation.  The function creates an
  *  element at the end of the %vector and assigns the given data
  *  to it.  Due to the nature of a %vector this operation can be
  *  done in constant time if the %vector has preallocated space
  *  available.
  */
 void
 push_back(const value_type& __x)
 {
   if (this->_M_impl._M_finish != this->_M_impl._M_end_of_storage)
     {
       _Alloc_traits::construct(this->_M_impl, this->_M_impl._M_finish,
                                __x);
       ++this->_M_impl._M_finish;
     }
   else
#if __cplusplus >= 201103L
     _M_emplace_back_aux(__x);
#else
     _M_insert_aux(end(), __x);
#endif
 }

#if __cplusplus >= 201103L
    void
    push_back(value_type&& __x)
    { 
        emplace_back(std::move(__x)); 
    }
#endif

Here it is easy to see what is push_back doing:

  1. In older standards (less than C++11) it just passes an object by copy thus vector creates a copy of the object you passed to the push_back.
  2. In newer standards (>= C++11) it simply uses move-constructor which avoids the copying but still creates an object by using move-constructor.

To avoid this you may use the emplace_back:

class A {
public:
    A(int i = 0) {   cout << "A ctor" << endl; }
    ~A() { cout << "A dtor" << endl; }
};

int main() {
    vector<A> v;
    cout << "after vector created" << endl;
    v.emplace_back();
    cout << "after one A added" << endl;
}

This will just create an object of class A inside vector's memory space by passing an arguments of the method to the constructor of the class object, so it would not be copied or moved:

after vector created
A ctor
after one A added
A dtor

Test emplace_back

P.S. About what you got:

after vector created
A ctor
A dtor
after one A added
A dtor

You don't see ctor-dtor paired here because in std::vector::push_back as I mentioned above does not construct an object of class A through the default constructor (in which you print A ctor) but a copy-constructor or a move-constructor (depends on a standard with what you compile and implementation details).

So, it is actually paired but you just don't see it because you did not implement (override implicitly-declared) move and copy constructors. If you'll do this, you'll see something like this:

A default constructor
A copy/move constructor
A destructor
A destructor

Upvotes: 2

Pumkko
Pumkko

Reputation: 1593

In the line v.push_back(A()); the argument of type A you put in the method is a result of the constructor call A(), I think you can easily understand why the constructor is called. Now keep in mind that the passed argument will live until the push_back method return. So at the end of the push_back method, your object will be destroyed. Here's a simple example

if( ... )
{
     int a = 42;
}
a = 12; //error: use of undeclared identifier 'a'

a live in the scope of the if statement, so when you live the statement a is destroyed. Same thing for your argument.

Secondly why your constructor is not called here void f(vector<A> v); ? It's because a copy constructor is called. You pass your already constructed object to the method by value, so a copy is made. But you did not define a copy constructor by yourself, so the compiler created one for you and call it.

If you want to see how it works check this Live example i made.

If you don't know what a copy constructor is check this

Upvotes: 1

TartanLlama
TartanLlama

Reputation: 65770

You are not getting the full picture because you are not tracing the copy constructor:

class A {
public:
    A() {   cout << "A ctor" << endl; }
    ~A() { cout << "A dtor" << endl; }
    //trace copy constructor
    A(const A&) { cout << "A copy ctor" << endl; }
};

Now the output is:

after vector created
A ctor
A copy ctor
A dtor
after one A added
A dtor

You create a temporary object of type A to pass in to push_back. This is then copied using the copy constructor, the temporary is destroyed, then the copy is destroyed along with the std::vector at the end of the main function.

Upvotes: 4

MSalters
MSalters

Reputation: 180303

Yes, they're paired. For every dtor call, there is one ctor call. That may be a default ctor (which prints "A ctor") or a copy ctor (which prints nothing - it's compiler generated).

Upvotes: 1

Samuel Navarro Lou
Samuel Navarro Lou

Reputation: 1198

Because the A object is copied into the vector, and therefore the temporary object that you pass in the call to push_back() is destroyed right after the copy.

This is what happens:

vector<A> v;
cout << "after vector created" << endl;
v.push_back(A()); 
// <<-- A temporary A object is created by calling A(). Then, the copy constructor of A is called to insert it into the vector.
// The temporary A object is destroyed.
cout << "after one A added" << endl;
// The A object in the vector is destroyed

Upvotes: 0

Related Questions