Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

vector push_back calling copy_constructor more than once?

Tags:

c++

stdvector

I am a bit confused with the way vector push_back behaves, with the following snippet I expected the copy constructor to be invoked only twice, but the output suggest otherwise. Is it a vector internal restructuring that results in this behaviour.

Output:

Inside default

Inside copy with my_int = 0

Inside copy with my_int = 0

Inside copy with my_int = 1
class Myint
{
private:
    int my_int;
public:
    Myint() : my_int(0) 
    {
        cout << "Inside default " << endl;
    }
    Myint(const Myint& x) : my_int(x.my_int)
    {
        cout << "Inside copy with my_int = " << x.my_int << endl;
    }

    void set(const int &x)
    {
        my_int = x;
    }
}

vector<Myint> myints;
Myint x;

myints.push_back(x);
x.set(1);
myints.push_back(x);
like image 491
Anandan Avatar asked Nov 04 '14 16:11

Anandan


People also ask

What is a copy constructor in C++?

A copy constructor of class T is a non-template constructor whose first parameter is T&‍, const T&‍, volatile T&‍, or const volatile T& ‍, and either there are no other parameters, or the rest of the parameters all have default values. // Typical declaration of a copy constructor. // Forcing a copy constructor to be generated by the compiler.

How to push an element into a vector from the back?

vector::push_back () push_back () function is used to push elements into a vector from the back. The new value is inserted into the vector at the end, after the current last element and the container size is increased by 1.

What is the difference between vector push_back and pop_back in C++ STL?

vector::push_back() and vector::pop_back() in C++ STL. Vectors are same as dynamic arrays with the ability to resize itself automatically when an element is inserted or deleted, with their storage being handled automatically by the container. vector::push_back() push_back() function is used to push elements into a vector from the back.

How many times “copied” is printed in a vector?

Hence, “ Copied ” is printed thrice as the copy constructor is called thrice, once for each element inserted. However, if the size of the vector is fixed, then before inserting any element, the number of times “Copied” will be printed is one.


2 Answers

What happens:

  1. x is inserted via push_back. One copy occurs: The newly created element is initialized with the argument. my_int is taken over as zero because xs default constructor initialized it so.

  2. The second element is push_back'd; The vector needs to reallocate the memory since the internal capacity was reached.
    As no move constructor is implicitly defined for Myint1 the copy constructor is chosen; The first element is copied into the newly allocated memory (its my_int is still zero... so the copy constructor shows my_int as 0 again) and then x is copied to initialize the second element (as with the first in step 1.). This time x has my_int set to one and that's what the output of the copy constructor tells us.

So the total amount of calls is three. This might vary from one implementation to another as the initial capacity might be different. However, two calls are be the minimum.

You can reduce the amount of copies by, in advance, reserving more memory - i.e. higher the vectors capacity so the reallocation becomes unnecessary:

myints.reserve(2); // Now two elements can be inserted without reallocation.

Furthermore you can elide the copies when inserting as follows:

myints.emplace_back(0);

This "emplaces" a new element - emplace_back is a variadic template and can therefore take an arbitrary amount of arguments which it then forwards - without copies or moves - to the elements constructor.

1 Because there is a user-declared copy constructor.

like image 93
Columbo Avatar answered Oct 24 '22 03:10

Columbo


You got it...it was the resizing. But I'll just point out that if you're doing some bean counting on your constructors, you might be interested in "emplacement":

#include <iostream>
#include <vector>
using namespace std;

class Myint
{
private:
    int my_int;
public:
    explicit Myint(int value = 0) : my_int(value) 
    {
        cout << "Inside default " << endl;
    }
    Myint(const Myint& x) : my_int(x.my_int)
    {
        cout << "Inside copy with my_int = " << x.my_int << endl;
    }
    Myint(const Myint&& x) noexcept : my_int(x.my_int) {
        cout << "Inside move with my_int = " << x.my_int << endl;
    } 
};

int main() {
    vector<Myint> myints;
    myints.reserve(2);

    myints.emplace_back(0);
    myints.emplace_back(1);

    // your code goes here
    return 0;
}

That should give you:

Inside default 
Inside default

And, due to the noexcept on the move constructor...if you delete the reserve you'd get a move, not a copy:

Inside default 
Inside default 
Inside move with my_int = 0

There's no real advantage with this datatype of a move over a copy. But semantically it could be a big difference if your data type was more "heavy weight" and had a way of "moving" its members that was more like transferring ownership of some pointer to a large data structure.

like image 36
HostileFork says dont trust SE Avatar answered Oct 24 '22 03:10

HostileFork says dont trust SE