2

I noticed that whenever I push_back() an object into a vector, all existing objects in the vector seem to be "re-created".

To test this I made the following test program down below. What the program does is to push_back() a few instances of MyClass into a vector and print the adresses of the current objects in the vector after each push_back(). The constructor and destructor prints a message with the adress of the object whenever an instance is created/destroyed.

#include "stdafx.h"
#include <vector>
#include <iostream>

using namespace std;

class MyClass {

public:
    MyClass() {
        cout << "Constructor: " << this << endl;
    }
    ~MyClass() {
        cout << "Destructor: " << this << endl;
    }

};

void printAdresses(const vector<MyClass> & v) {
    for (int i = 0; i < v.size(); i++) {
        cout << &v[i] << endl;
    }
}

int main()
{
    vector<MyClass> v;

    for (int i = 0; i < 4; i++) {
        v.push_back(MyClass());
        cout << "Contains objects: " << endl;
        printAdresses(v);
        cout << endl;
    }

    system("pause");
    return 0;
}

Down below is the output I got from running the program. Every block is the output from one iteration of the loop.

Constructor: 00EFF6AF
Destructor: 00EFF6AF
Contains objects:
034425A8

Constructor: 00EFF6AF
Destructor: 034425A8
Destructor: 00EFF6AF
Contains objects:
034426F8
034426F9

Constructor: 00EFF6AF
Destructor: 034426F8
Destructor: 034426F9
Destructor: 00EFF6AF
Contains objects:
034424E8
034424E9
034424EA

Constructor: 00EFF6AF
Destructor: 034424E8
Destructor: 034424E9
Destructor: 034424EA
Destructor: 00EFF6AF
Contains objects:
034423F8
034423F9
034423FA
034423FB

Appart from the constructor and destructor of the item that was given as argument for the push_back(), destructors of other objects are called during a push_back() (but no corresponding constructors?). When taking a closer look at the adresses we see that the destructors belong to elements in the vector prior to the push_back(). Afterwards the vector contains other objects with different adresses, but the same number of elements (plus the newly push_backed).

What all this tells me is that all objects in the vector are destroyed and re-created after each push_back. If this is the case, why is that? It seems really inefficient when a vector gets too large. If it's not the case, then what exactly happens?

6
  • 4
    Look up capacity and reserve. Commented Dec 3, 2017 at 23:05
  • Vectors offer amortized constant back insertion time, so no, it often doesn't get really inefficient at large sizes. It's not okay for everyone, but it's fine for most general use. Commented Dec 3, 2017 at 23:09
  • 1
    You should also instrument the move constructor MyClass(MyClass&&) noexcept; and/or the copy constructor MyClass(const MyClass&); to see what's really happening - these are being used to create the vector elements, which is why you don't see your default constructor called for them. This shouldn't happen every time through the loop for larger values, but your limit might be too small to observe that. Commented Dec 3, 2017 at 23:09
  • This is down to your implementation - on MinGW GCC I see different behaviour. Commented Dec 3, 2017 at 23:15
  • 1
    @OP: Change compiler options, such as optimization level, and you may see a different result. Commented Dec 3, 2017 at 23:21

1 Answer 1

1

Vector pre-allocates some space.

For example, it can have an initial size of 2 elements. When you try to push the 3rd one, it re-allocates for 4 elements. When you try to push the 5th element, it re-allocates for 8 elements and so on.

That's why you have a .size() that reflects how many elements you have and a .capacity() that reflects what is the capacity (how many elements can be stored before a reallocation is needed). You can manually handle that capacity by calling .reserve()

To answer your original question, reallocation requires copying all the elements from the previous array to a bigger one. This requires to destroy the previous elements and to create some new ones. If you saw the destructors calls but not the matching constructor calls, this is because it does not use the default constructor, but the copy constructor:

MyClass(const MyClass&)

This constructor takes one object has a parameter and uses it to construct and initialize a new object.

Moreover, as mentioned in the comments: even though the re-allocation/copy process can be seen as expensive, on average it is not and actually constitutes a constant time operation. This is because the cost of re-allocation and copy is amortized and future insertion because we do not re-allocate the exact space needed, but more space than necessary.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.