Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
401 views
in Technique[技术] by (71.8m points)

c++11 - C++: std::move with rvalue reference is not moving contents

Sample program:

#include <iostream>
#include <string>
#include <vector>

template <typename T>
void print(const T& _vec)
    {        
        for( auto c: _vec )
            std::cout << c << ",";
    }

typedef std::vector<std::string> vecstr_t;

struct Trade
{
    explicit Trade(vecstr_t&& vec) : _vec(vec )
    {       
    }  

     vecstr_t _vec;
};


int main()
{   
    vecstr_t tmpV = {"ONE", "TWO", "THREE", "FOUR"};    
    std::cout << "size 1:" << tmpV.size() << ""; print(tmpV); std::cout <<  "
" ;    
    Trade t(std::move(tmpV));    
    std::cout << "size 2:" << tmpV.size() << "";  print(tmpV); std::cout <<  "
" ; // expted tmpV should be e,pty but it has original contents    
    print(t._vec);    
}

I expect size 2: should be ZERO but output is:

size 1:4    ONE,TWO,THREE,FOUR,
size 2:4    ONE,TWO,THREE,FOUR,
ONE,TWO,THREE,FOUR,
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)
explicit Trade(vecstr_t&& vec) : _vec(vec)
{}

In the constructor above, even though vec is of type rvalue reference to vecstr_t, it is itself an lvalue. The basic rule to remember is - if it has a name, it's an lvalue.

There are very few contexts where an lvalue may automatically be moved from (such as the return statement of a function that returns an object by value), but a constructor's mem-initializer list is not one of them.

In your example, _vec is copy constructed from vec. If you want it to be move constructed instead, use std::move.

explicit Trade(vecstr_t&& vec) : _vec(std::move(vec))
{}

Now the second call to print will not print anything. Note that technically the second call could print a non-zero size because the contents of a moved from vector are unspecified. But on most (probably all) implementations, you'll see an empty vector.

Live demo


Your comment below says your intent is to accept both rvalues and lvalues, move only in the case of the former, and copy the argument otherwise. As currently written, your constructor will only accept rvalues, and not lvalues. There are a few different options to achieve what you want.

The easiest probably is to change the parameter so that it's taking the argument by value, and then unconditionally move.

explicit Trade(vecstr_t vec) : _vec(std::move(vec))
{}

The drawback with this approach is that you may incur an additional move construction of the vector, but move constructing a vector is cheap, and you should go with this option in most cases.

The second option is to create two overloads of the constructor

explicit Trade(vecstr_t&&      vec) : _vec(std::move(vec)) {}
explicit Trade(vecstr_t const& vec) : _vec(vec)            {}

The drawback with this one is that the number of overloads will increase exponentially as the number of constructor arguments increases.

The third option is to use perfect forwarding.

template<typename V>
explicit Trade(V&& vec) : _vec(std::forward<V>(vec)) {}

The code above will preserve the value category of the argument passed to the constructor when it forwards it to construct _vec. This means that if vec is an rvalue, the vecstr_t move constructor will be called. And if it is an lvalue, it will be copied from.

The drawback with this solution is that your constructor will accept any type of argument, not just a vecstr_t, and then the move/copy construction in the mem-initializer list will fail if the argument is not convertible to vecstr_t. This may result in error messages that are confusing to the user.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...