什么是 std::move(),什么时候应该使用它?

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

提问人:Basilevs 提问时间:8/5/2010 最后编辑:Jan SchultkeBasilevs 更新时间:9/21/2023 访问量:539544

问:

  1. 这是什么?
  2. 它有什么作用?
  3. 什么时候应该使用?

良好的链接是值得赞赏的。

C++ 11 移动语义 C++-常见问题 stdmove

评论

63赞 DumbCoder 8/5/2010
Bjarne Stroustrup 在 A Brief Introduction to Rvalue References 中解释了这一举动
2赞 Basilevs 9/11/2014
移动语义
22赞 josaphatv 10/18/2016
这个问题指的是;还存在一个与 相关的算法。我指出这一点是为了让其他人不会像我第一次面对三个论点时那样困惑。en.cppreference.com/w/cpp/algorithm/movestd::move(T && t)std::move(InputIt first, InputIt last, OutputIt d_first)std::copystd::move
2赞 Karan Singh 6/25/2020
如果您不太了解 lvalue 和 rvalue 引用的含义,建议阅读本文 internalpointers.com/post/......
1赞 Adrian McCarthy 4/18/2022
澄清一下,这个问题是关于 from ,而不是 from 。std::move<utility>std::move<algorithm>

答:

422赞 Scharron 8/5/2010 #1

关于 C++11 R 值引用和移动构造函数的维基百科页面

  1. 在 C++11 中,除了复制构造函数之外,对象还可以具有移动构造函数。
    (除了复制赋值运算符之外,它们还有移动赋值运算符。
  2. 如果对象的类型为“rvalue-reference”(),则使用移动构造函数代替复制构造函数。Type &&
  3. std::move()是生成对对象的右值引用的强制转换,以便能够从该对象移动。

这是一种避免复制的新 C++ 方法。例如,使用移动构造函数,可以只将其指向数据的内部指针复制到新对象,使移动的对象处于“移动自”状态,因此不会复制所有数据。这将是 C++ 有效的。std::vector

尝试在谷歌上搜索移动语义、右值、完美转发。

评论

47赞 GManNickG 8/6/2010
移动语义要求移动的对象保持有效,这不是不正确的状态。(理由:它仍然必须破坏,使其工作。
19赞 Zan Lynx 10/5/2011
@GMan:嗯,它必须处于可以安全破坏的状态,但是,AFAIK,它不必可用于其他任何东西。
9赞 GManNickG 10/5/2011
@ZanLynx:对。请注意,标准库还要求移动的对象是可分配的,但这仅适用于 stdlib 中使用的对象,而不是一般要求。
34赞 Manu343726 7/4/2014
-1 “std::move() 是使用 move 语义的 C++11 方式”请修复它。 不是使用移动语义的方式,移动语义对程序员是透明的。 它只是将值从一个点传递到另一个点的强制转换,其中将不再使用原始 lvalue。std::move()move
30赞 Aaron McDaid 8/20/2015
我会走得更远。 本身“什么都不做”——它的副作用为零。它只是向编译器发出信号,表明程序员不再关心该对象会发生什么。也就是说,它允许软件的其他部分从对象中移动,但不要求移动它。事实上,右值引用的接收者不必对它将如何处理数据或不对数据做什么做出任何承诺。std::move
170赞 Guillaume 6/22/2012 #2

当您需要将对象的内容“传输”到其他地方时,可以使用移动,而无需进行复制(即内容不重复,这就是为什么它可以用于某些不可复制的对象,例如unique_ptr)。使用 std::move,对象也可以在不执行复制的情况下获取临时对象的内容(并节省大量时间)。

这个链接真的帮助了我:

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

很抱歉,如果我的答案来得太晚了,但我也在为 std::move 寻找一个好的链接,我发现上面的链接有点“朴素”。

这把重点放在了 r 值参考上,你应该在什么情况下使用它们,我认为它更详细,这就是我想在这里分享这个链接的原因。

评论

31赞 Christian Stieber 7/15/2012
不错的链接。我总是发现维基百科的文章,以及我偶然发现的其他链接相当令人困惑,因为它们只是向你抛出事实,让你弄清楚实际的含义/理由是什么。虽然构造函数中的“移动语义”相当明显,但所有关于传递 &&-值的细节都不是......所以教程式的描述非常好。
47赞 user929404 6/5/2013 #3

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

在此函数中创建临时对象是无用的 - 这些临时对象无论如何都会在行的末尾被删除,因为它们超出了范围。

我们更愿意使用移动语义来“掠夺”临时对象,并执行类似操作

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

这样可以避免不必要的深度复制。参考该示例,现在唯一发生深度复制的部分是 E + F。其余的使用 move 语义。还需要实现移动构造函数或赋值运算符,以便将结果赋给 A。

评论

3赞 Koushik Shetty 6/5/2013
你谈到了移动语义。您应该在答案中添加如何使用 std::move,因为问题会询问这个问题。
2赞 user929404 6/5/2013
@Koushik std::move 并没有做太多事情,但用于实现移动语义。如果你不了解 std::move,你可能也不知道 move 语义
2赞 Koushik Shetty 6/5/2013
“没有多大作用”(是的,只是对右值引用的static_cast)。它实际做了什么,它做了什么,是 OP 所问的。你不需要知道std::move是如何工作的,但你必须知道move语义的作用。此外,“but 用于实现移动语义”则相反。知道移动语义,你就会理解 std::move,否则没有。move 只是帮助移动,并且本身使用 move 语义。std::move 除了将其参数转换为右值引用外,什么都不做,这是 MOVE 语义所需要的。
11赞 Ajay 10/15/2015
“but E + F 产生一个临时对象” - 操作员从左到右,而不是从右到左。因此会是第一个!+B+C
0赞 Ulterior 11/10/2020
只有你的回答向我解释了这一点
575赞 einpoklum 11/20/2014 #4

1.“这是什么?

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

2.“它有什么作用?

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

你可以掠夺我,移动我手中的任何东西,然后在其他地方使用它(因为无论如何我很快就会被摧毁)”。

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

您也可以从 prvalue 移动(例如您正在传递的临时值),但这很少有用。

3.“什么时候应该使用?

问这个问题的另一种方法是“我会蚕食现有对象的资源做什么? 好吧,如果你正在编写应用程序代码,你可能不会对编译器创建的临时对象进行大量处理。因此,您主要在构造函数、运算符方法、类似标准库算法的函数等地方执行此操作,在这些地方,对象经常自动创建和销毁。当然,这只是一个经验法则。

典型的用途是将资源从一个对象“移动”到另一个对象,而不是复制。@Guillaume链接到此页面,该页面有一个简单的简短示例:以较少的复制交换两个对象。

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

想想当大小为 n 时会发生什么。在第一个版本中,您读取和写入 3*n 个元素,在第二个版本中,您基本上只读取和写入指向向量缓冲区的 3 个指针,以及 3 个缓冲区的大小。当然,班级需要知道如何做移动;你的类应该有一个 move-assignment 运算符和一个 move-constructor for class,以便它工作。Tvector<int>TT

评论

22赞 Zebrafish 12/30/2016
很长一段时间以来,我都听说过这些移动语义,但我从未研究过它们。从你给出的这个描述来看,它似乎是一个浅层副本,而不是一个深层副本。
47赞 einpoklum 12/30/2016
@TitoneMaurice:除了它不是副本 - 因为原始值不再可用。
14赞 rubenvb 1/11/2018
@Zebrafish你大错特错了。浅拷贝使原稿处于完全相同的状态,移动通常会导致原稿为空或处于其他有效状态。
92赞 Lightness Races in Orbit 11/2/2018
@rubenvb Zebra 并非完全错误。虽然原始的可复制对象通常被故意破坏以避免混淆错误(例如,将其指针设置为 nullptr 以表示它不再拥有指尖),但整个移动是通过简单地将指针从源复制到目标来实现的(并故意避免对指针做任何事情)确实让人想起浅层复制。事实上,我甚至可以说,移动是一个浅层的复制,然后是源的部分自毁。(续)
29赞 Lightness Races in Orbit 11/2/2018
(续)如果我们允许这个定义(我更喜欢它),那么@Zebrafish的观察并没有错,只是有点不完整。
142赞 Christopher Oezbek 2/20/2017 #5

问:什么是?std::move

答:是 C++ 标准库中的一个函数,用于转换为右值引用。std::move()

简单来说,相当于: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::moveT&&TT&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)`

做什么:

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

问:什么时候应该使用?

答:如果要使用非右值(临时表达式)的参数调用支持移动语义的函数,则应使用。std::move

这为我引出了以下后续问题:

  • 什么是移动语义?与复制语义相比,移动语义是一种编程技术,其中对象的成员通过“接管”而不是复制另一个对象的成员来初始化。这种“接管”只对指针和资源句柄有意义,可以通过复制指针或整数句柄而不是基础数据来廉价地传输这些指针和资源句柄。

  • 什么样的类和对象支持移动语义?作为开发人员,您可以根据自己的类实现移动语义,如果这些语义可以从转移其成员而不是复制它们中受益。一旦你实现了移动语义,你将直接受益于许多库程序员的工作,他们增加了对有效处理具有移动语义的类的支持。

  • 为什么编译器不能自己弄清楚?编译器不能只调用函数的另一个重载,除非你这么说。您必须帮助编译器选择是否应调用函数的常规版本或移动版本。

  • 在哪些情况下,我想告诉编译器它应该将变量视为右值?这很可能发生在模板或库函数中,在这些函数中,您知道可以挽救中间结果(而不是分配新实例)。

评论

15赞 llf 6/8/2018
大 +1 表示注释中带有语义的代码示例。其他热门答案使用“move”本身定义 std::move - 并没有真正澄清任何事情!---我认为值得一提的是,不复制参数意味着无法可靠地使用原始值。
14赞 Jayhello 8/3/2018 #6

“这是什么?”上面已经解释过“它有什么作用?

我将举一个“何时应该使用它”的例子。

例如,我们有一个类,其中包含大量资源,例如大数组。

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

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

引:

-1赞 Goblinhack 5/14/2020 #7

下面是一个完整的示例,将 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;
}
2赞 Tiger Yu 7/1/2020 #8

std::move它本身什么都不做,而不是一个.根据 cppreference.comstatic_cast

它完全等同于右值引用类型的static_cast。

因此,它取决于您在 之后分配给的变量的类型,如果该类型具有或采用右值参数,它可能会也可能不会窃取原始变量的内容,因此,它可能会将原始变量保留在:moveconstructorsassign operatorsunspecified state

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

因为没有特殊的或内置的文字类型,如整数和原始指针,所以,它只是这些类型的简单副本。move constructormove assign operator

-1赞 zast99 2/13/2023 #9

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

Foo foo = Foo();

如果你然后写

Foo foo2 = std::move(foo);

这和我写的是一样的

Foo foo2 = (Foo&&) foo;

std::move 将此强制转换替换为右值引用。 您想要编写前 2 行代码中的任何一行的原因 是不是如果你写

Foo foo2 = foo;

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

Foo foo2 = std::move(foo);

当你写

Foo foo2 = (Foo&&) foo;

评论

0赞 alexpanter 2/21/2023
我认为使用 C 样式转换 “” 可能会产生误导,因为与@ChristopherOezbeck已经建议的使用相比,它提供的编译时保证很少。更多信息请点击此处。否则,这个答案不会增加太多尚未涵盖的内容。(Foo&&)static_cast