Below is code that has different kinds of push backs. By that, I mean push backs with different inputs. An example is accounts.push_back(five)
. I am not sure why the constructor printouts are different for each kind of push back. Not only are the orders different, but the number of times, as an example, the deep copy constructor used is different.
#include <iostream>
#include <vector>
class account
{
private:
std::string *name_ptr;
double *total_ptr;
public:
std::string get_name() { return *name_ptr; }
void set_name(std::string new_name) { *name_ptr = new_name; }
double get_total() { return *total_ptr; }
void set_total(double new_total) { *total_ptr = new_total; }
void deposit(double amount) { *total_ptr += amount; }
void withdraw(double amount) { *total_ptr -= amount; }
account(std::string name, double total = 0)
{
name_ptr = new std::string {name};
total_ptr = new double {total};
std::cout << "Constructor for " << *name_ptr << std::endl;
}
account(const account &source)
: account {*(source.name_ptr), *(source.total_ptr)}
{ std::cout << "Deep copy constructor for " << *name_ptr << std::endl; }
account(account &&source) noexcept
: name_ptr(source.name_ptr), total_ptr(source.total_ptr)
{
source.name_ptr = nullptr;
source.total_ptr = nullptr;
std::cout << "Move constructor for " << *name_ptr << std::endl;
}
~account()
{
if(name_ptr == nullptr) std::cout << "Destructor for nullptr" << std::endl;
else std::cout << "Destructor for " << *name_ptr << std::endl;
delete name_ptr;
delete total_ptr;
}
};
int main()
{
std::vector<account> accounts;
account one {"Jonathon Stevenson"};
accounts.push_back( account(one) );
account two {"Cliffard Stevenson"};
accounts.push_back( account(two) );
account three {"Tom Stevenson"};
accounts.push_back( account(three) );
std::cout << std::endl;
account four {"Jonathon Richardson"};
accounts.push_back(four);
account five {"Cliffard Richardson"};
accounts.push_back(five);
account six {"Tom Richardson"};
accounts.push_back(six);
std::cout << std::endl;
accounts.push_back( account {"Jonathon Watson"} );
accounts.push_back( account {"Cliffard Watson"} );
accounts.push_back( account {"Tom Watson"} );
std::cout << std::endl;
accounts.push_back( account("Jonathon Sullivanson") );
accounts.push_back( account("Cliffard Sullivanson") );
accounts.push_back( account("Tom Sullivanson") );
std::cout << std::endl;
return 0;
}
Output:
Constructor for Jonathon Stevenson
Constructor for Jonathon Stevenson
Deep copy constructor for Jonathon Stevenson
Move constructor for Jonathon Stevenson
Destructor for nullptr
Constructor for Cliffard Stevenson
Constructor for Cliffard Stevenson
Deep copy constructor for Cliffard Stevenson
Move constructor for Cliffard Stevenson
Move constructor for Jonathon Stevenson
Destructor for nullptr
Destructor for nullptr
Constructor for Tom Stevenson
Constructor for Tom Stevenson
Deep copy constructor for Tom Stevenson
Move constructor for Tom Stevenson
Move constructor for Jonathon Stevenson
Move constructor for Cliffard Stevenson
Destructor for nullptr
Destructor for nullptr
Destructor for nullptr
Constructor for Jonathon Richardson
Constructor for Jonathon Richardson
Deep copy constructor for Jonathon Richardson
Constructor for Cliffard Richardson
Constructor for Cliffard Richardson
Deep copy constructor for Cliffard Richardson
Move constructor for Jonathon Stevenson
Move constructor for Cliffard Stevenson
Move constructor for Tom Stevenson
Move constructor for Jonathon Richardson
Destructor for nullptr
Destructor for nullptr
Destructor for nullptr
Destructor for nullptr
Constructor for Tom Richardson
Constructor for Tom Richardson
Deep copy constructor for Tom Richardson
Constructor for Jonathon Watson
Move constructor for Jonathon Watson
Destructor for nullptr
Constructor for Cliffard Watson
Move constructor for Cliffard Watson
Destructor for nullptr
Constructor for Tom Watson
Move constructor for Tom Watson
Move constructor for Jonathon Stevenson
Move constructor for Cliffard Stevenson
Move constructor for Tom Stevenson
Move constructor for Jonathon Richardson
Move constructor for Cliffard Richardson
Move constructor for Tom Richardson
Move constructor for Jonathon Watson
Move constructor for Cliffard Watson
Destructor for nullptr
Destructor for nullptr
Destructor for nullptr
Destructor for nullptr
Destructor for nullptr
Destructor for nullptr
Destructor for nullptr
Destructor for nullptr
Destructor for nullptr
Constructor for Jonathon Sullivanson
Move constructor for Jonathon Sullivanson
Destructor for nullptr
Constructor for Cliffard Sullivanson
Move constructor for Cliffard Sullivanson
Destructor for nullptr
Constructor for Tom Sullivanson
Move constructor for Tom Sullivanson
Destructor for nullptr
Destructor for Tom Richardson
Destructor for Cliffard Richardson
Destructor for Jonathon Richardson
Destructor for Tom Stevenson
Destructor for Cliffard Stevenson
Destructor for Jonathon Stevenson
Destructor for Jonathon Stevenson
Destructor for Cliffard Stevenson
Destructor for Tom Stevenson
Destructor for Jonathon Richardson
Destructor for Cliffard Richardson
Destructor for Tom Richardson
Destructor for Jonathon Watson
Destructor for Cliffard Watson
Destructor for Tom Watson
Destructor for Jonathon Sullivanson
Destructor for Cliffard Sullivanson
Destructor for Tom Sullivanson
That is all about how std::vector
works.
Whenever it runs out of memory, upon the next std::vector::push_back
it simply allocates a new chunk, copies (or moves, if possible) all the data and then deletes the old one. Since it can't really know how much space do you need, automatic reallocations rely on some strategy, the most common of which is to allocate N * 2
where N
is the current capacity. Now let's look at what's going on in main
.
std::vector<account> accounts;
- this creates an empty vector (no allocations).
account one {"Jonathon Stevenson"};
- this creates an object of your class by invoking your, let's call it string-constructor, that's where you get the very first output into console.
accounts.push_back(account(one));
- this one is responsible for the next three messages (account(one)
creates a temporary using copy-constructor, which works by invoking string-constructor; after that said temporary gets moved into the vector). Here the capacity of accounts
is 1;
The next "iteration" of creation and pushing works the same way, except you get an additional move: that's vector
moving data to a new location. Here the capacity of accounts
is 2;
Same goes for the next one. Here the capacity of accounts
is 4;
Then, at accounts.push_back(four);
- you do not longer create an excessive temporary, and thus don't see move-constructor working. Here the capacity of accounts
is 4;
At the next push_back
another reallocation happens, and you can see vector
moving all the contents (4 elements) to a new location.
At last, when you try to accounts.push_back( account("Tom Sullivanson") );
your capacity is 8 and the number of elements inside accounts
is also 8, so you can see vector
yet again moving its contents to a new location - exactly 8 times. Now capacity is 16 and no reallocations will occur in this program anymore.