What is std::move(), and when should it be used?

后端 未结 8 1975
粉色の甜心
粉色の甜心 2020-11-22 08:27
  1. What is it?
  2. What does it do?
  3. When should it be used?

Good links are appreciated.

相关标签:
8条回答
  • 2020-11-22 08:53

    std::move itself doesn't really do much. I thought that it called the moved constructor for an object, but it really just performs a type cast (casting an lvalue variable to an rvalue so that the said variable can be passed as an argument to a move constructor or assignment operator).

    So std::move is just used as a precursor to using move semantics. Move semantics is essentially an efficient way for dealing with temporary objects.

    Consider Object A = B + C + D + E + F;

    This is nice looking code, but E + F produces a temporary object. Then D + temp produces another temporary object and so on. In each normal "+" operator of a class, deep copies occur.

    For example

    Object Object::operator+ (const Object& rhs) {
        Object temp (*this);
        // logic for adding
        return temp;
    }
    

    The creation of the temporary object in this function is useless - these temporary objects will be deleted at the end of the line anyway as they go out of scope.

    We can rather use move semantics to "plunder" the temporary objects and do something like

     Object& Object::operator+ (Object&& rhs) {
         // logic to modify rhs directly
         return rhs;
     }
    

    This avoids needless deep copies being made. With reference to the example, the only part where deep copying occurs is now E + F. The rest uses move semantics. The move constructor or assignment operator also needs to be implemented to assign the result to A.

    0 讨论(0)
  • 2020-11-22 08:54

    You can use move when you need to "transfer" the content of an object somewhere else, without doing a copy (i.e. the content is not duplicated, that's why it could be used on some non-copyable objects, like a unique_ptr). It's also possible for an object to take the content of a temporary object without doing a copy (and save a lot of time), with std::move.

    This link really helped me out :

    http://thbecker.net/articles/rvalue_references/section_01.html

    I'm sorry if my answer is coming too late, but I was also looking for a good link for the std::move, and I found the links above a little bit "austere".

    This puts the emphasis on r-value reference, in which context you should use them, and I think it's more detailed, that's why I wanted to share this link here.

    0 讨论(0)
  • 2020-11-22 08:56

    "What is it?" and "What does it do?" has been explained above.

    I will give a example of "when it should be used".

    For example, we have a class with lots of resource like big array in it.

    class ResHeavy{ //  ResHeavy means heavy resource
        public:
            ResHeavy(int len=10):_upInt(new int[len]),_len(len){
                cout<<"default ctor"<<endl;
            }
    
            ResHeavy(const ResHeavy& rhs):_upInt(new int[rhs._len]),_len(rhs._len){
                cout<<"copy ctor"<<endl;
            }
    
            ResHeavy& operator=(const ResHeavy& rhs){
                _upInt.reset(new int[rhs._len]);
                _len = rhs._len;
                cout<<"operator= ctor"<<endl;
            }
    
            ResHeavy(ResHeavy&& rhs){
                _upInt = std::move(rhs._upInt);
                _len = rhs._len;
                rhs._len = 0;
                cout<<"move ctor"<<endl;
            }
    
        // check array valid
        bool is_up_valid(){
            return _upInt != nullptr;
        }
    
        private:
            std::unique_ptr<int[]> _upInt; // heavy array resource
            int _len; // length of int array
    };
    

    Test code:

    void test_std_move2(){
        ResHeavy rh; // only one int[]
        // operator rh
    
        // after some operator of rh, it becomes no-use
        // transform it to other object
        ResHeavy rh2 = std::move(rh); // rh becomes invalid
    
        // show rh, rh2 it valid
        if(rh.is_up_valid())
            cout<<"rh valid"<<endl;
        else
            cout<<"rh invalid"<<endl;
    
        if(rh2.is_up_valid())
            cout<<"rh2 valid"<<endl;
        else
            cout<<"rh2 invalid"<<endl;
    
        // new ResHeavy object, created by copy ctor
        ResHeavy rh3(rh2);  // two copy of int[]
    
        if(rh3.is_up_valid())
            cout<<"rh3 valid"<<endl;
        else
            cout<<"rh3 invalid"<<endl;
    }
    

    output as below:

    default ctor
    move ctor
    rh invalid
    rh2 valid
    copy ctor
    rh3 valid
    

    We can see that std::move with move constructor makes transform resource easily.

    Where else is std::move useful?

    std::move can also be useful when sorting an array of elements. Many sorting algorithms (such as selection sort and bubble sort) work by swapping pairs of elements. Previously, we’ve had to resort to copy-semantics to do the swapping. Now we can use move semantics, which is more efficient.

    It can also be useful if we want to move the contents managed by one smart pointer to another.

    Cited:

    • https://www.learncpp.com/cpp-tutorial/15-4-stdmove/
    0 讨论(0)
  • 2020-11-22 08:58

    Wikipedia Page on C++11 R-value references and move constructors

    1. In C++11, in addition to copy constructors, objects can have move constructors.
      (And in addition to copy assignment operators, they have move assignment operators.)
    2. The move constructor is used instead of the copy constructor, if the object has type "rvalue-reference" (Type &&).
    3. std::move() is a cast that produces an rvalue-reference to an object, to enable moving from it.

    It's a new C++ way to avoid copies. For example, using a move constructor, a std::vector could just copy its internal pointer to data to the new object, leaving the moved object in an moved from state, therefore not copying all the data. This would be C++-valid.

    Try googling for move semantics, rvalue, perfect forwarding.

    0 讨论(0)
  • 2020-11-22 08:58

    Q: What is std::move?

    A: std::move() is a function from the C++ Standard Library for casting to a rvalue reference.

    Simplisticly std::move(t) is equivalent to:

    static_cast<T&&>(t);
    

    An rvalue is a temporary that does not persist beyond the expression that defines it, such as an intermediate function result which is never stored in a variable.

    int a = 3; // 3 is a rvalue, does not exist after expression is evaluated
    int b = a; // a is a lvalue, keeps existing after expression is evaluated
    

    An implementation for std::move() is given in N2027: "A Brief Introduction to Rvalue References" as follows:

    template <class T>
    typename remove_reference<T>::type&&
    std::move(T&& a)
    {
        return a;
    }
    

    As you can see, std::move returns T&& no matter if called with a value (T), reference type (T&), or rvalue reference (T&&).

    Q: What does it do?

    A: As a cast, it does not do anything during runtime. It is only relevant at compile time to tell the compiler that you would like to continue considering the reference as an rvalue.

    foo(3 * 5); // obviously, you are calling foo with a temporary (rvalue)
    
    int a = 3 * 5;
    foo(a);     // how to tell the compiler to treat `a` as an rvalue?
    foo(std::move(a)); // will call `foo(int&& a)` rather than `foo(int a)` or `foo(int& a)`
    

    What it does not do:

    • Make a copy of the argument
    • Call the copy constructor
    • Change the argument object

    Q: When should it be used?

    A: You should use std::move if you want to call functions that support move semantics with an argument which is not an rvalue (temporary expression).

    This begs the following follow-up questions for me:

    • What is move semantics? Move semantics in contrast to copy semantics is a programming technique in which the members of an object are initialized by 'taking over' instead of copying another object's members. Such 'take over' makes only sense with pointers and resource handles, which can be cheaply transferred by copying the pointer or integer handle rather than the underlying data.

    • What kind of classes and objects support move semantics? It is up to you as a developer to implement move semantics in your own classes if these would benefit from transferring their members instead of copying them. Once you implement move semantics, you will directly benefit from work from many library programmers who have added support for handling classes with move semantics efficiently.

    • Why can't the compiler figure it out on its own? The compiler cannot just call another overload of a function unless you say so. You must help the compiler choose whether the regular or move version of the function should be called.

    • In which situations would I want to tell the compiler that it should treat a variable as an rvalue? This will most likely happen in template or library functions, where you know that an intermediate result could be salvaged.

    0 讨论(0)
  • 2020-11-22 08:58

    Here is a full example, using std::move for a (simple) custom vector

    Expected output:

     c: [10][11]
     copy ctor called
     copy of c: [10][11]
     move ctor called
     moved c: [10][11]
    

    Compile as:

      g++ -std=c++2a -O2 -Wall -pedantic foo.cpp
    

    Code:

    #include <iostream>
    #include <algorithm>
    
    template<class T> class MyVector {
    private:
        T *data;
        size_t maxlen;
        size_t currlen;
    public:
        MyVector<T> () : data (nullptr), maxlen(0), currlen(0) { }
        MyVector<T> (int maxlen) : data (new T [maxlen]), maxlen(maxlen), currlen(0) { }
    
        MyVector<T> (const MyVector& o) {
            std::cout << "copy ctor called" << std::endl;
            data = new T [o.maxlen];
            maxlen = o.maxlen;
            currlen = o.currlen;
            std::copy(o.data, o.data + o.maxlen, data);
        }
    
        MyVector<T> (const MyVector<T>&& o) {
            std::cout << "move ctor called" << std::endl;
            data = o.data;
            maxlen = o.maxlen;
            currlen = o.currlen;
        }
    
        void push_back (const T& i) {
            if (currlen >= maxlen) {
                maxlen *= 2;
                auto newdata = new T [maxlen];
                std::copy(data, data + currlen, newdata);
                if (data) {
                    delete[] data;
                }
                data = newdata;
            }
            data[currlen++] = i;
        }
    
        friend std::ostream& operator<<(std::ostream &os, const MyVector<T>& o) {
            auto s = o.data;
            auto e = o.data + o.currlen;;
            while (s < e) {
                os << "[" << *s << "]";
                s++;
            }
            return os;
        }
    };
    
    int main() {
        auto c = new MyVector<int>(1);
        c->push_back(10);
        c->push_back(11);
        std::cout << "c: " << *c << std::endl;
        auto d = *c;
        std::cout << "copy of c: " << d << std::endl;
        auto e = std::move(*c);
        delete c;
        std::cout << "moved c: " << e << std::endl;
    }
    
    0 讨论(0)
提交回复
热议问题