Search code examples
c++vectormovecopy-constructoremplace

Unable to avoid copying while pushing objects with copy-construcor into a vector


I'm trying to avoid copying with emplace_back() and reserve(). But when I've tried to do it, i caught myself getting 3 copies for reason i cannot really understand. reserve() actually helps to avoid copying, but emplace_back() actually does nothing with it (works the same way as push_back() in this case). Here's the code:

struct Vertex
{
    size_t x, y, z;

    Vertex(size_t x, size_t y, size_t z)
    {
        this->x = x;
        this->y = y;
        this->z = z;
    }

    Vertex(const Vertex& v)
        : x(v.x), y(v.y), z(v.z)
    {
        std::cout << "\nCOPIED!\n";
    }
};

int main()
{
    std::vector<Vertex> vert;

    vert.reserve(3);
    vert.emplace_back(Vertex(1, 2, 3));
    vert.emplace_back(Vertex(4, 5, 6));
    vert.emplace_back(Vertex(7, 8, 9));

    return 0;
}

The output is 3 times 'COPIED!'. Well i tried to something like this:

    vert.emplace_back(std::move(Vertex(1, 2, 3)));
    vert.emplace_back(std::move(Vertex(4, 5, 6)));
    vert.emplace_back(std::move(Vertex(7, 8, 9)));

to convert my objects into r-values, but again i got 3 times 'COPIED!'.

Then i tried to push the same object 3 times with std::move and without and again got the same result:

    Vertex vertex(1, 2, 3);
    vert.emplace_back(vertex);
    vert.emplace_back(vertex);
    vert.emplace_back(vertex);

or

    Vertex vertex(1, 2, 3);
    vert.emplace_back(std::move(vertex));
    vert.emplace_back(std::move(vertex));
    vert.emplace_back(std::move(vertex));

I don't understand what am i doing wrong. I use MSVS 2022 Preview C++14. Also i tried to do it on C++14/17/20 and same result. Is there a way to get rid of all of the copies? Or maybe i understand situation wrong?


Solution

  • std::move isn't useful here. The temporary Vertex object is already a prvalue, so casting it to an xvalue doesn't change anything. The class has no move constructor, so copy initialisation cannot move; it has to copy. The implicit move constructor has been inhibited by the user defined copy constructor. Although, the move constructor couldn't be any faster than the copy constructor for this class anyway.

    The way that emplace_back works is that it forwards the arguments to the constructor of the element. If the argument that you pass is an object of the element type, then you invoke the constructor that accepts another instance of the class - that is the copy constructor (or the move constructor for classes that have it).

    Instead of creating a temporary Vertex object, and passing it as an argument to emplace_back, you should pass the three size_t objects that can be forwarded to the constructor Vertex(size_t, size_t, size_t). This way you can avoid copying (and moving) of the Vertex object entirely:

    vert.emplace_back(1, 2, 3);
    vert.emplace_back(4, 5, 6);
    vert.emplace_back(7, 8, 9);