0

I read a Microsoft tutorial (located here: https://learn.microsoft.com/en-us/cpp/cpp/move-constructors-and-move-assignment-operators-cpp?view=msvc-170) about the move constructor and the move assignment operator and I didn't quite understand when the move ctor is invoked.

Below the code of the MemoryBlock class

class MemoryBlock
{
public:
    explicit MemoryBlock(size_t length)
        : m_length(length),
          m_data(new int[length])
    {
        std::cout << "In MemoryBlock(size_t). length = "
                  << m_length << "." << std::endl;
    }

    virtual ~MemoryBlock()
    {
        std::cout << "In ~MemoryBlock(). length = "
                  << m_length << ".";

        if (m_data != nullptr)
        {
            delete[] m_data;
            m_data = nullptr;
        }

        std::cout << std::endl;
    }

    // Copy ctor
    MemoryBlock(const MemoryBlock& other)
        : m_length(other.m_length),
          m_data(new int[other.m_length])
    {
        std::cout << "In MemoryBlock(const MemoryBlock&). length = "
                        << other.m_length << ". Copying resource." << std::endl;

        std::copy(other.m_data, other.m_data + m_length, m_data);
    }

    // Copy assignment operator
    MemoryBlock& operator=(const MemoryBlock& other)
    {
        std::cout << "In operator=(const MemoryBlock&). length = "
                        << other.m_length << ". Copying resource." << std::endl;

        if (this != &other)
        {
            delete[] m_data;

            m_length = other.m_length;
            m_data = new int[m_length];

            std::copy(other.m_data, other.m_data + m_length, m_data);
        }

        return *this;
    }

    // Move ctor
    MemoryBlock(MemoryBlock&& other) noexcept
        : m_length(0),
          m_data(nullptr)
    {
        std::cout << "In MemoryBlock(MemoryBlock&&). length = "
                     << other.m_length << ". Moving resource." << std::endl;

        m_data = other.m_data;
        m_length = other.m_length;

        other.m_data = nullptr;
        other.m_length = 0;
    }

    // Move assignment
    MemoryBlock& operator=(MemoryBlock&& other) noexcept
    {
        // Avoid assigning the object to itself
        if (this != &other)
        {
            // Free the existing resource
            delete[] m_data;

            m_data = other.m_data;
            m_length = other.m_length;

            other.m_data = nullptr;
            other.m_length = 0;
        }

        return *this;
    }

private:
    size_t m_length;
    int *m_data;
};

What I don't I understand is in the main. Understand why the move ctor is invoked rather than the copy ctor

int main()
{
    std::vector<MemoryBlock> v;

    // In this way the copy ctor is called
    MemoryBlock m(10);
    v.push_back(m);

    // And in this way the move ctor is called, why if std::move is not invoked?
    v.push_back(MemoryBlock(25));

    return 0;
}

I compiled and built the code and tried to understand, also searching for information in internet without any result

Jesper Juhl
  • 30,449
  • 3
  • 47
  • 70
Franco
  • 3
  • 7

3 Answers3

1

MemoryBlock&& is an rvalue reference. So the move constructor:

MemoryBlock(MemoryBlock&& other) noexcept

Is invoked whenever you pass it an rvalue. There are lots of ways to make an rvalue. std::move is only one of those ways. Others, like in your case, are creating an unnamed temporary. This can also be done with a function:

MemoryBlock foo() { ... }

...

MemoryBlock m{foo()};

All of these are rvalues, so the move constructor is called.

Fantastic Mr Fox
  • 32,495
  • 27
  • 95
  • 175
1

This is because when you wrote v.push_back(m);, the expression m has the value category of lvalue and so the lvalue reference version push_back( const T& ) of std::vector::push_back is used which uses copy initialization.

On the other hand, when you wrote v.push_back(MemoryBlock(25));, the expression MemoryBlock(25) is an rvalue and so the push_back( T&& value ) version of std::vector::push_back is used. So this version uses the move constructor.

From std::vector::push_back:

void push_back( const T& value );      (1)
void push_back( T&& value );           (2)

Appends the given element value to the end of the container.

  1. The new element is initialized as a copy of value.
  2. value is moved into the new element.
Jason
  • 36,170
  • 5
  • 26
  • 60
0

In general:

  • Move constructor is invoked when rvalues is used to create a new object
  • Copy constructor is invoked when lvalue is used to create a new object
  • Move assignment operator is invoked when left right-hand-side object is of rvalues
  • Assignment operator is invoked when left right-hand-side object is of lvalues

Coming to your question: What I don't I understand is in the main. Understand why the move ctor is invoked rather than the copy ctor

In line v.push_back(m);, m is an lvalue so copy-constructor is called. While in line v.push_back(MemoryBlock(25));, MemoryBlock(25) is an rvalue so move-constructor is called.

Following are some useful reading in this context:

cse
  • 4,066
  • 2
  • 20
  • 37