Search code examples
c++c++11vectorbackemplace

Why is the move constructor only called when there is already an element in the vector?


I am trying to learn the new features in C++11. And I am testing the following code in XCode.

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

class CClass
{
    std::string s;
public:
    CClass()
    {
        std::cout<<"Default Constructor"<<std::endl;
    }
    CClass(const std::string v) :s(v) {
        std::cout<<"Constructor"<<std::endl;
    }

    CClass(const CClass& other): s(other.s) {
        std::cout<<"Copy Constructor"<<std::endl;
    }
    CClass(CClass&& a) noexcept
    {
        std::cout<<"Move Constructor"<<std::endl;
        s = std::move(a.s);
    }
    CClass& operator = (const CClass& other)noexcept
    {
        std::cout<<"Copy Assignment"<<std::endl;
        if(this != &other)
        {
            s = other.s;
        }
        return *this;
    }
    CClass& operator = (CClass&& other) noexcept
    {
        std::cout<<"Move Assignment"<<std::endl;
        if(this != &other)
        {
            s = std::move(other.s);
        }
        return *this;
    }
};

int main()
{
    std::vector<CClass> v;
    CClass x("hello");
    //v.push_back(x);
    std::cout<<"--------------------"<<std::endl;
    v.emplace_back("uiuiu");
    std::cout<<"--------------------"<<std::endl;
}

When I uncomment the push back I get the following result:

Constructor
Copy Constructor
--------------------
Constructor
Move Constructor
--------------------

Otherwise, if I comment it, I get:

Constructor
--------------------
Constructor
--------------------

My question is why is the move constructor not being called in second case? It is only being called in the first case when vector is initially not empty.


Solution

  • It's because the one element in the vector needs to be moved to a new memory location. The reason why that happens is that the new size would exceed the vector capacity, so new memory with the new capacity had to be allocated for the vector.

    From std::vector::emplace_back:

    If the new size() is greater than capacity() then all iterators and references (including the past-the-end iterator) are invalidated. Otherwise only the past-the-end iterator is invalidated.

    The iterators and references are invalidated for the same reason: Because the elements are now stored in a new location in memory.

    If you call reserve in the first case, you'll see that no move constructor is called:

    CClass x{"hello"}; // constructor
    v.reserve(2); // make space for 2 elements (you could have also used resize)
    v.push_back(x); // copy constructor
    v.emplace_back("uiuiu"); // constructor