Search code examples
c++c++11copy-constructormove-semanticsmove-constructor

Why the copy and move constructors end up in the same amount of memcopies?


Thee problem here is to understand if the copy or move constructor was called when initializing a vector by a return object of a function. Checking the mallocs with a profiler shows similar memcopies in both cases. Why?

We have a class of type "message". The class provides a function "data_copy" which returns the contents of the "message" as vector.

There are 2 options that I tried. One is to use directly the copy constructor to initialize a new vector.

std::vector<uint8_t> vector1 ( message.data_copy() );

The second option was to try to avoid the extra copy and do

std::vector<uint8_t> vector1 ( std::move( message.data_copy() ) );

For reference I attach what data_copy() does.

std::vector<uint8_t> message::data_copy(void) const
{
    std::vector<uint8_t> d(this->size());
    copy_data_to_buffer(d.data());
    return d;
}
void message::copy_data_to_buffer(uint8_t* buffer) const
{
    DEBUG_LOG("copy_data_to_buffer");
    for(const fragment* p = &head; p != nullptr; p = p->next)
    {
        memcpy(buffer, p->data[0], p->size[0]);
        buffer += p->size[0];

        if(p->size[1])
        {
            memcpy(buffer, p->data[1], p->size[1]);
            buffer += p->size[1];
        }
    }
}

Finally, by using a profiler I compare the amount of malloc calls. While one would expect that the move constructor would avoid the extra memcopy in reality they are the same in both cases.


Solution

  • You are using the move constructor both times. The result of data_copy is a temporary. Then you construct the vector with this temporary argument. So it's a move.

    The second time, you are basically casting the thing that's already an rvalue reference to an rvalue reference, and so the move constructor is used again. There should be absolutely no difference between the behavior of both of them.

    I think, maybe, you are misunderstanding what a temporary is and what an rvalue reference is. It's not often you have to use ::std::move and if you are using it, you should look carefully at the code you're using it in to make sure you're doing the right thing.

    This code on Godbolt is proof that the copy constructor is never called. Here is the code being linked to:

    #include <utility>
    
    struct ICantBeCopied {
        ICantBeCopied(ICantBeCopied const &) = delete;
        ICantBeCopied(ICantBeCopied &&) {}
        ICantBeCopied() {}
    };
    
    ICantBeCopied make_a_copy()
    {
        ICantBeCopied c;
        return c;
    }
    
    void a_function()
    {
        ICantBeCopied a{make_a_copy()};
        ICantBeCopied b{::std::move(make_a_copy())};
    }