简体   繁体   中英

Move and Forward cases use

I followed this tutorial to start to understand the move semantics and rvalue references in C++11. At some point, he implements these two classes with the std::move in the move constructors explaining that

we pass the temporary to a move constructor, and it takes on new life in the new scope. In the context where the rvalue expression was evaluated, the temporary object really is over and done with. But in our constructor, the object has a name; it will be alive for the entire duration of our function. In other words, we might use the variable other more than once in the function, and the temporary object has a defined location that truly persists for the entire function. It's an lvalue in the true sense of the term locator value

class MetaData
{
public:
    MetaData(int size, const string& name)
        : _name(name)
        , _size(size)
    {}

    MetaData(const MetaData& other)
        : _name(other._name)
        , _size(other._size)
    {
        cout << "MetaData -- Copy Constructor" << endl;
    }

    MetaData(MetaData&& other)
        : _name(move(other._name))
        , _size(other._size)
    {
        cout << "MetaData -- Move Constructor" << endl;
    }

  ~MetaData()
  {
    _name.clear();
  }

    string getName() const { return _name; }
    int getSize() const { return _size; }

private:
    string _name;
    int _size;
};

class ArrayWrapper
{
public:
    ArrayWrapper()
        : _p_vals(new int[64])
        , _metadata(64, "ArrayWrapper")
    {}

    ArrayWrapper(int n)
        : _p_vals(new int[n])
        , _metadata(n, "ArrayWrapper")
    {}

    ArrayWrapper(ArrayWrapper&& other)
        : _p_vals(other._p_vals)
        , _metadata(move(other._metadata))
    {
        cout << "ArrayWrapper -- Move Constructor" << endl;
        other._p_vals = nullptr;
    }

    ArrayWrapper(const ArrayWrapper& other)
        : _p_vals(new int[other._metadata.getSize()])
        , _metadata(other._metadata)
    {
        cout << "ArrayWrapper -- Copy Constructor" << endl;
        for (int i = 0; i < _metadata.getSize(); ++i)
            _p_vals[i] = other._p_vals[i];
    }

    ~ArrayWrapper()
    {
        delete[] _p_vals;
    }

    int* getVals() const { return _p_vals; }
    MetaData getMeta() const { return _metadata; }

private:
    int* _p_vals;
    MetaData _metadata;
};

In the ArrayWrapper move constructor I tried to change std::move with std::forward<MetaData> and the code shows that if I call the ArrayWrapper move constructor this will call the MetaData move constructor, like the example with the std::move .

Of course if I don't use either std::move or std::forward the MetaData copy costructor will be called.

The question is, in this case, is there a difference between using std::move and std::forward ? Why should I use one instead of the other?

is there a difference between using std::move and std::forward? Why should I use one instead of the other?

Yes, std::move returns an rvalue reference of its parameter, while std::forward just forwards the parameter preserving its value category.

Use move when you clearly want to convert something to an rvalue. Use forward when you don't know what you've (may be an lvalue or an rvalue) and want to perfectly forward it (preserving its l or r valueness) to something. Can I typically/always use std::forward instead of std::move? is a question you might be interested in here.

In the below snippet, bar would get exactly what the caller of foo had passed, including its value category preserved:

template <class T>
void foo(T&& t) {
    bar(std::forward<T>(t));
}

Don't let T&& fool you here - t is not an rvalue reference . When it appears in a type-deducing context, T&& acquires a special meaning. When foo is instantiated, T depends on whether the argument passed is an lvalue or an rvalue. If it's an lvalue of type U , T is deduced to U& . If it's an rvalue, T is deduced to U . See this excellent article for details. You need to understand about value categories and reference collapsing to understand things better in this front.

The relevant std::forward and std::move declarations are:

template< class T >
T&& forward( typename std::remove_reference<T>::type& t );

template< class T >
typename std::remove_reference<T>::type&& move( T&& t );

For the former:

std::forward<MetaData>(other._metadata);

std::forward<MetaData> returns MetaData&& .

For the latter:

 std::move(other._metadata);
 //argument derived as lvalue reference due to forwarding reference
 std::move<MetaData&>(other._name);

std::move<MetaData&> returns typename std::remove_reference<MetaData&>::type&& , which is MetaData&& .

So the two forms are identical for your example. However, std::move is the right choice here, as it shows our intent to unconditionally move the argument. std::forward can be used to unconditionally move, but the purpose of it is to perfect-forward its argument.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM