c++ 什么是std::move(),什么时候应该使用它,它实际上会移动任何东西吗?

2ul0zpep  于 2023-05-13  发布在  其他
关注(0)|答案(9)|浏览(119)

1.是什么东西?
1.它有什么作用?
1.什么时候应该使用?
好的链接值得赞赏。

iezvtpos

iezvtpos1#

1.“什么事?“

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

2.“它能做什么?“

首先要注意的是std::move()实际上并不移动任何东西。它将表达式从lvalue(例如命名变量)更改为xvalue。xvalue告诉编译器:
你可以掠夺我,移动我持有的任何东西并在其他地方使用它(因为我很快就会被摧毁)。
换句话说,当你使用std::move(x)时,你允许编译器蚕食x。因此,如果x在内存中有自己的缓冲区,那么在std::move()编译之后,编译器可以让另一个对象拥有它。
您也可以从prvalue(例如您正在传递的临时)移动,但这很少有用。

3.“什么时候用?“

问这个问题的另一种方式是“我为什么要占用现有对象的资源?”“如果你在写应用程序代码,你可能不会在编译器创建的临时对象上浪费太多时间。所以主要是在构造函数、操作符方法、类似标准库算法的函数等地方这样做。在那里对象被自动地创建和销毁了很多。当然,这只是经验法则。
典型的用法是将资源从一个对象“移动”到另一个对象,而不是复制。@Guillaume链接到this page,其中有一个简单的例子:用较少的复制来交换两个对象。

template <class T>
swap(T& a, T& b) {
    T tmp(a);   // we've made a second copy of a
    a = b;      // we've made a second copy of b (and discarded a copy of a)
    b = tmp;    // we've made a second copy of tmp (and 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是大小为n的vector<int>时会发生什么。在第一个版本中,你读取和写入3*n个元素,在第二个版本中,你基本上只读取和写入指向向量缓冲区的3个指针,加上3个缓冲区的大小。当然,T类需要知道如何移动;你的类应该有一个move-assignment操作符和一个move-constructor类T,这样才能工作。

3hvapo4f

3hvapo4f2#

Wikipedia关于C11 R值引用和移动构造函数的页面
1.在C
11中,除了复制构造函数,对象可以有移动构造函数。
(And除了复制赋值运算符外,它们还有移动赋值运算符。)
1.如果对象的类型为“rvalue-reference”(Type &&),则使用移动构造函数而不是复制构造函数。

  1. std::move()是一个强制转换,它产生一个对对象的右值引用,以允许从它移动。
    这是一种新的C避免副本的方法。例如,使用移动构造函数,std::vector可以只将其指向数据的内部指针复制到新对象,使移动的对象处于moved from状态,因此不会复制所有数据。这将是C有效的。
    尝试谷歌移动语义,右值,完美转发。
khbbv19g

khbbv19g3#

当需要将对象的内容“转移”到其他地方时,可以使用move,而不需要进行复制(即内容是不重复的,这就是为什么它可以用在一些不可复制的对象上,比如unique_ptr)。使用std::move,对象也可以在不进行复制的情况下获取临时对象的内容(并保存大量时间)。
这个链接真的帮到我了:
http://thbecker.net/articles/rvalue_references/section_01.html
如果我的回答来得太晚了,我很抱歉,但我也在寻找一个好的std::move链接,我发现上面的链接有点“简朴”。
这把重点放在了r值引用上,在这种情况下你应该使用它们,我认为它更详细,这就是为什么我想在这里分享这个链接。

b4qexyjb

b4qexyjb4#

Q:什么是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中给出:“R值引用简介”如下:

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

正如您所看到的,std::move返回T&&,无论是使用值(T)、引用类型(T&)还是右值引用(T&&)调用。

Q:它有什么作用?

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

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

*********:

  • 复制一份论点
  • 调用复制构造函数
  • 更改参数对象

Q:什么时候使用?

答:如果你想调用支持move语义的函数,参数不是右值(临时表达式),你应该使用std::move
这就引出了以下问题:

  • 什么是move semantics?与复制语义相反,移动语义是一种编程技术,其中对象的成员通过“接管”而不是复制另一个对象的成员来初始化。这种“接管”只对指针和资源句柄有意义,通过复制指针或整数句柄而不是底层数据,可以廉价地传输这些句柄。
  • 什么样的类和对象支持移动语义?作为开发人员,如果这些类将受益于转移其成员而不是复制它们,则由您在自己的类中实现移动语义。一旦实现了移动语义,您将直接受益于许多库程序员的工作,他们添加了对有效处理具有移动语义的类的支持。
  • 为什么编译器不能自己解决呢?编译器不能只调用函数的另一个重载,除非你这么说。您必须帮助编译器选择应调用函数的常规版本还是移动版本。
  • 在什么情况下我会告诉编译器它应该把一个变量当作右值?这很可能发生在模板或库函数中,在这些函数中,您知道可以回收中间结果(而不是分配新示例)。
tez616oj

tez616oj5#

std::move本身并没有做太多的事情。我以为它调用了一个对象的移动构造函数,但它实际上只是执行了一个类型转换(将左值变量转换为右值,以便该变量可以作为参数传递给移动构造函数或赋值运算符)。
所以std::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。其余部分使用移动语义。还需要实现移动构造函数或赋值运算符,以将结果赋值给A。

egmofgnx

egmofgnx6#

“是什么?““它是做什么的?”上面已经解释过了。
我将给予一个
“何时使用”的例子。**
例如,我们有一个类,里面有很多资源,比如大数组。

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

我们可以看到std::movemove constructor可以轻松转换资源。
std::move在哪里有用?
std::move在对元素数组进行排序时也很有用。许多排序算法(如选择排序和冒泡排序)通过交换元素对来工作。以前,我们不得不求助于复制语义来进行交换。现在我们可以使用移动语义,这更有效。
如果我们想将一个智能指针管理的内容移动到另一个智能指针,它也很有用。
引用:

sirbozc5

sirbozc57#

std::move本身不做任何事情,而不是static_cast。根据cppreference.com
它完全等效于将static_cast转换为右值引用类型。
因此,它取决于你在move之后赋值的变量的类型,如果类型有constructorsassign operators,它可能会也可能不会 * 窃取 * 原始变量的内容,所以,它可能会将原始变量留在unspecified state中:
除非另外指定,否则所有已从放置在有效但未指定状态中移动的标准库对象。
因为没有专门的move constructormove assign operator用于内置的文字类型,如整数和原始指针,所以,它只是这些类型的简单副本。

f5emj3cl

f5emj3cl8#

下面是一个完整的示例,使用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;
}
ryhaxcpt

ryhaxcpt9#

std::move只是将变量转换为右值引用。这个右值引用用&&表示。假设你有一个类Foo,你示例化了一个像这样的对象

Foo foo = Foo();

如果你写

Foo foo2 = std::move(foo);

这和如果我写

Foo foo2 = (Foo&&) foo;

std::move将此强制转换替换为右值引用。之所以要编写前面两行代码中的任何一行,是因为如果编写

Foo foo2 = foo;

将调用复制构造函数。假设Foo示例有一个指向它们拥有的堆上的一些数据的指针。在Foo的析构函数中,堆上的数据被删除。如果你想区分从堆中复制数据和获取该数据的所有权,你可以编写一个接受const Foo&的构造函数,该构造函数可以执行深度复制。然后你可以写一个接受右值引用(Foo&&)的构造函数,这个构造函数可以简单地重新连接指针。这个接受Foo&&的构造函数将在你写

Foo foo2 = std::move(foo);

当你写作时

Foo foo2 = (Foo&&) foo;

相关问题