0

After constructing an object with a moving constructor, the new object should "steal" the resources of the "source" object, which is then left in an indefinite (but valid) state.

For example:

#include <iostream>
#include <vector>

template <class T>
void print(const std::vector<T>& v)
{
    std::cout << "size = " << v.size() << " vector = ";
    for (const auto& x : v)
        std::cout << x << " ";
    std::cout << std::endl;
}

int main()
{
    std::vector<int> data(10, 3);
    std::cout << "data:" << std::endl;
    print(data);

    std::vector<int> data2(std::move(data));
    std::cout << "data2:" << std::endl;
    print(data2);
    std::cout << "data after moving:" << std::endl;
    print(data);

    return 0;
}

See it live on Coliru.

As far as I know the standard does not specify the content of data after having called the moving constructor, however one would expect that the resources of data have been stolen by data2. And indeed, the output the program above shows that:

data:
size = 10 vector = 3 3 3 3 3 3 3 3 3 3 
data2:
size = 10 vector = 3 3 3 3 3 3 3 3 3 3 
data after moving:
size = 0 vector = 

Now consider a slight variation of the above program:

#include <iostream>
#include <vector>

class A {
    std::vector<int> m_data;
public:
    A(std::vector<int>&& data) : m_data{data} { }
    const std::vector<int>& data() const { return m_data; }
};

template <class T>
void print(const std::vector<T>& v)
{
    std::cout << "size = " << v.size() << " vector = ";
    for (const auto& x : v)
        std::cout << x << " ";
    std::cout << std::endl;
}

int main()
{
    std::vector<int> data(10, 3);
    std::cout << "data:" << std::endl;
    print(data);

    A x{std::move(data)};
    std::cout << "x.data():" << std::endl;
    print(x.data());
    std::cout << "data after moving:" << std::endl;
    print(data);

    return 0;
}

See it live on Coliru.

I am surprised by output of the program:

data:
size = 10 vector = 3 3 3 3 3 3 3 3 3 3 
x.data():
size = 10 vector = 3 3 3 3 3 3 3 3 3 3 
data after moving:
size = 10 vector = 3 3 3 3 3 3 3 3 3 3 

It looks like the vector data has been just copied to A::m_data rather then being moved.

If I substitute the moving constructor of A with

A(std::vector<int>&& data) : m_data{std::move(data)} { }

(see it live on Coliru)

or with

A(std::vector<int>&& data) : m_data{std::forward<std::vector<int>&&>(data)} { }

(see it live on Coliru)

then the output of the program resembles that of the first code

data:
size = 10 vector = 3 3 3 3 3 3 3 3 3 3 
x.data():
size = 10 vector = 3 3 3 3 3 3 3 3 3 3 
data after moving:
size = 0 vector = 

In other words, it seems that either std:move or std::forward are necessary to effectively call the moving constructor of A::m_data. Both std::move and std::forward return a static_cast to std::vector<int>&&, but the argument of the moving constructor of A is already a rvalue.

Why is an additional std::move or std::forward necessary?

francesco
  • 7,189
  • 7
  • 22
  • 49
  • 1
    or https://stackoverflow.com/questions/21321145/why-does-this-need-an-explicit-stdmove or etc. – underscore_d Jan 09 '20 at 10:12
  • 1
    `A(std::vector&& data) : m_data{data}, m_data2{data} {}` is perfectly legal, so that first thing had better not obliterate `data`!! – David Schwartz Jan 09 '20 at 10:16
  • 2
    When you use `data` as an expression, the type of this expression is adjusted to be a non-reference type `std::vector`. No `&`, no `&&`. How should the compiler know which constructor to call? Value category. `data` has lvalue category and cannot bind to `std::vector&&` in a move constructor. To change the value category you use move: `std::move(data)` has xvalue category and can bind to `std::vector&&`. It will be a better match than copy ctor. See [this question](https://stackoverflow.com/questions/56716647/rvalues-lvalues-and-formal-definitions) if this sounds confusing. – Evg Jan 09 '20 at 10:28

1 Answers1

1

First your should know that A::A(std::vector<int>&& data) means that data is rvalue reference to std::vector<T>, just means reference not a call to move constructor,

and A(std::vector<int>&& data) : m_data{data} { } just calls copy constructor of m_data because data itself is a named value not a r value temporary in scope of A::A hence a std::move or std::forward is necessary.

Gaurav Dhiman
  • 1,163
  • 6
  • 8