是什么? 它能做什么? 什么时候使用?

好的链接是赞赏的。


当前回答

问:什么是std::move?

答:std::move()是c++标准库中用于强制转换为右值引用的函数。

简单来说,std::move(t)等价于:

static_cast<T&&>(t);

右值是一个临时值,它不会在定义它的表达式之外持久存在,比如一个中间函数结果,它永远不会存储在变量中。

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

std::move()的实现在N2027中给出:“右值引用简介”如下所示:

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

如你所见,std::move返回T&&,无论调用时使用值(T)、引用类型(T&)或右值引用(T&&)。

问:它能做什么?

答:作为一个强制转换,它在运行时不做任何事情。只有在编译时告诉编译器您希望继续将引用视为右值才有意义。

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)`

它没有做什么:

复制参数 调用复制构造函数 更改参数对象

问:什么时候使用?

答:如果你想调用一个参数不是右值(临时表达式)的支持move语义的函数,应该使用std::move。

这就引出了以下问题:

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 (rather than allocating a new instance).

其他回答

下面是一个完整的例子,使用std::move来处理一个(简单的)自定义向量

预期的输出:

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

编译:

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

代码:

#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;
}

1. “这是什么?”

虽然std::move()技术上是一个函数-我想说它不是一个真正的函数。它有点像编译器考虑表达式值的方式之间的转换器。

2. “它能做什么?”

首先要注意的是std::move()实际上并不移动任何东西。它将表达式从左值(如命名变量)更改为xvalue。xvalue告诉编译器:

你可以掠夺我,移动我持有的任何东西并在其他地方使用(因为我很快就会被摧毁)”。

换句话说,当你使用std::move(x)时,你是在允许编译器蚕食x。因此,如果x在内存中有自己的缓冲区-在std::move()ing之后,编译器可以让另一个对象拥有它。

您还可以从一个prvalue(例如传递的临时值)中移动,但这很少有用。

3.“什么时候使用?”

问这个问题的另一种方法是“我要为了什么而占用现有对象的资源?”好吧,如果您正在编写应用程序代码,那么您可能不会过多地使用编译器创建的临时对象。主要是在构造函数,操作符方法,标准库算法类函数等地方,在这些地方,对象会自动创建和销毁。当然,这只是一个经验法则。

典型的用法是将资源从一个对象“移动”到另一个对象,而不是复制。@Guillaume链接到这个页面,上面有一个简单的例子:用更少的复制交换两个对象。

template <class T>
swap(T& a, T& b) {
    T tmp(a);   // we now have two copies of a
    a = b;      // we now have two copies of b (+ discarded a copy of a)
    b = tmp;    // we now have two copies of tmp (+ discarded a copy of b)
}

使用move可以让你交换资源,而不是复制它们:

template <class T>
swap(T& a, T& b) {
    T tmp(std::move(a));
    a = std::move(b);   
    b = std::move(tmp);
}

想想当T是,比如说,向量<int>,大小为n。在第一个版本中,你读写了3*n个元素,在第二个版本中,你基本上只读写了3个指向向量缓冲区的指针,加上3个缓冲区的大小。当然,类T需要知道如何移动;你的类应该有一个移动赋值操作符和一个类T的移动构造函数,这样才能工作。

Std::move本身并没有做很多事情。我以为它调用对象的移动构造函数,但它实际上只是执行类型转换(将左值变量转换为右值,以便所述变量可以作为参数传递给移动构造函数或赋值操作符)。

std::move只是用作move语义的前身。Move语义本质上是处理临时对象的一种有效方法。

考虑对象A = B + (C + (D + (E + F)));

这是一个漂亮的代码,但是E + F生成了一个临时对象。然后D + temp生成另一个临时对象,依此类推。在类的每个普通“+”操作符中,都会发生深度复制。

例如

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

在这个函数中创建临时对象是无用的——这些临时对象无论如何都会在行末被删除,因为它们超出了作用域。

我们可以使用move语义来“掠夺”临时对象,并做一些类似的事情

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

这避免了不必要的深度复制。在这个例子中,唯一发生深度复制的部分是E + f,其余部分使用move语义。还需要实现move构造函数或赋值操作符来将结果赋值给A。

“它是什么?”和“它做什么?”已经在上面解释过了。

我将给出一个“什么时候应该使用它”的例子。

例如,我们有一个类,里面有很多资源,比如大数组。

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
};

测试代码:

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;
}

输出如下:

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

我们可以看到带有move构造函数的std::move使得转换资源变得很容易。

还有什么地方是std::move有用?

move在对数组元素排序时也很有用。许多排序算法(如选择排序和冒泡排序)通过交换元素对来工作。以前,我们不得不使用复制语义来进行交换。现在我们可以使用移动语义,这更有效。

如果我们想将一个智能指针管理的内容移动到另一个智能指针上,它也很有用。

引用:

https://www.learncpp.com/cpp-tutorial/15-4-stdmove/

Std::move本身除了static_cast之外什么也不做。根据cppreference.com网站

它完全等价于static_cast到右值引用类型。

因此,它取决于你在移动后赋值给的变量的类型,如果该类型具有接受右值形参的构造函数或赋值操作符,它可能会或可能不会窃取原始变量的内容,因此,它可能会让原始变量处于未指定的状态:

除非另有说明,否则所有移出的标准库对象都处于有效但未指定的状态。

因为对于内置文字类型(如整数和原始指针)没有特殊的移动构造函数或移动赋值操作符,因此,它将只是对这些类型进行简单的复制。