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

Bas*_*evs 581 c++ c++-faq move-semantics c++11 stdmove

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

赞赏良好的链接.

Sch*_*ron 249

关于C++ 11 R值参考和移动构造函数的Wikipedia页面

  1. 在C++ 11中,除了复制构造函数之外,对象还可以具有移动构造函数.
    (除了复制赋值运算符,它们还有移动赋值运算符.)
  2. 如果对象具有类型"rvalue-reference"(Type &&),则使用移动构造函数而不是复制构造函数.
  3. std::move() 是一个转换器,它生成对象的rvalue引用,以便从中移动.

这是一种避免复制的新C++方法.例如,使用移动构造函数,std::vector可以将其内部指针复制到新对象,使移动的对象保持不正确状态,从而避免复制所有数据.这将是C++ - 有效.

尝试谷歌搜索移动语义,右值,完美转发.

  • 移动语义要求移动的对象保持*有效*,这不是一个不正确的状态.(理由:它仍然必须破坏,使它工作.) (35认同)
  • -1*"std :: move()是使用移动语义的C++ 11方式"*请修复它.`std :: move()`不是使用移动语义的方法,移动语义是对程序员透明地执行的.`move`它只是一个强制转换,可以将值从一个点传递到另一个点,而不再使用原始左值. (22认同)
  • 我会走得更远.`std :: move`本身没什么" - 它没有副作用.它只是向编译器发出信号,表示程序员不再关心该对象会发生什么.即,它允许软件的其他部分*权限*从对象移动,但它不要求它被移动.实际上,右值引用的接收者不必对它将对数据做什么或不做什么做出任何承诺. (13认同)
  • @GMan:嗯,它必须处于可以安全破坏的状态,但是,AFAIK,它不必用于其他任何东西. (12认同)
  • 我猜它会"交换"它的指针,而不是复制它. (9认同)
  • @ZanLynx:对.请注意,标准库还要求移动对象可以分配,但这仅适用于stdlib中使用的对象,而不是一般要求. (7认同)
  • 哦,我多么感叹 C++ 必须发明一些可笑的东西来取代 C 的简单性!`struct mystruct * newstruct = oldstruct;` <== 看,我刚刚通过重新分配指针将 `oldstruct` 的内容移到了 C 中的 `newstruct`! (5认同)

ein*_*ica 192

"它是什么?"

虽然std::move() 在技术上是一个功能-我会说这是不是真正的功能.它是编译器考虑表达式值的方式之间的转换器.

"它做了什么?"

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

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

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

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

"什么时候应该使用?"

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

典型的用法是将资源从一个对象"移动"到另一个对象而不是复制.@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)
}
Run Code Online (Sandbox Code Playgroud)

使用move允许您交换资源而不是复制它们:

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

想想当T是T大小为n 时会发生什么.在第一个版本中,您读取和写入3*n个元素,在第二个版本中,您基本上只读取和写入向量缓冲区的3个指针.当然,T级需要知道如何进行移动; 你应该有一个移动赋值运算符和一个T类的移动构造函数,以便它可以工作.

  • _(cont.)_ 如果我们允许这个定义(而且我更喜欢它),那么@Zebrafish 的观察并没有错,只是稍微不完整。 (10认同)
  • 很长一段时间以来,我都听说过这些移动语义,但我从未研究过它们。从你给出的这个描述来看,它似乎是一个浅拷贝而不是一个深拷贝。 (9认同)
  • @TitoneMaurice:除了它不是副本 - 因为原始值不再可用. (5认同)
  • @rubenvb Zebra并非完全错误。的确,通常会故意破坏原始的对象以避免混乱的错误(例如,将其指针设置为nullptr以表示其不再拥有指针),但整个动作是通过简单地从源中复制指针来实现的确实到达目的地(并故意避免与pointe做任何事情)确实让人联想到浅表副本。实际上,我什至可以说是一个浅拷贝,然后可选地是对源进行部分自毁。_(续)_ (4认同)
  • @rubenvb 在更高的抽象层次上是正确的,但如果我们谈论它是如何实现的,那么基本的相似性就不能被忽略,至少如果我们的目标是在实际中解释 C++ 移动语义真正_are_ 的话。即便如此,我相信源的部分自毁涵盖了所有权的丧失。 (4认同)
  • @Zebrafish您再也不会错了。浅拷贝使原件保持完全相同的状态,移动通常会导致原件为空或处于其他有效状态。 (2认同)

小智 137

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

这个链接真的帮助了我:

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

如果我的答案来得太晚,我很抱歉,但我也在寻找std :: move的良好链接,我发现上面的链接有点"严肃".

这强调了r值参考,在哪种情况下你应该使用它们,我认为它更详细,这就是为什么我想在这里分享这个链接.

  • 不错的链接.我总是发现维基百科的文章,以及我偶然发现的其他链接相当混乱,因为他们只是向你提出事实,留给你来弄清楚实际意义/理由是什么.虽然构造函数中的"移动语义"是相当明显的,但关于传递&& - 值的所有细节都不是......所以教程风格的描述非常好. (24认同)

Chr*_*bek 55

问:什么是std::move

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

简单地std::move(t)相当于:

static_cast<T&&>(t);
Run Code Online (Sandbox Code Playgroud)

rvalue是一个临时值,它不会超出定义它的表达式,例如从不存储在变量中的中间函数结果.

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
Run Code Online (Sandbox Code Playgroud)

N2027中给出了std :: move()的实现:"Rvalue References简介"如下:

template <class T>
typename remove_reference<T>::type&&
std::move(T&& a)
{
    return a;
}
Run Code Online (Sandbox Code Playgroud)

如您所见,无论是使用value(),引用类型()还是rvalue引用()调用,都会std::move返回.T&&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)`
Run Code Online (Sandbox Code Playgroud)

没有做什么:

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

问:什么时候应该使用?

答:std::move如果你想调用支持移动语义的函数,你应该使用不是rvalue(临时表达式)的参数.

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

  • 什么是移动语义?与复制语义相比,移动语义是一种编程技术,其中通过"接管"而不是复制另一个对象的成员来初始化对象的成员.这种"接管"只能通过指针和资源句柄来实现,这可以通过复制指针或整数句柄而不是底层数据来便宜地转移.

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

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

  • 在哪种情况下,我想告诉编译器它应该将变量视为右值?这很可能发生在模板或库函数中,您知道可以挽救中间结果.

  • 带有注释语义的代码示例的大+1。其他最重要的答案使用“ move”本身定义了std :: move-并没有真正弄清楚什么!---我认为值得一提的是,不复制该参数意味着不能可靠地使用原始值。 (2认同)

use*_*404 30

std :: move本身并没有做太多.我认为它为一个对象调用了移动的构造函数,但它实际上只执行一个类型转换(将一个左值变量转换为一个右值,以便所述变量可以作为参数传递给移动构造函数或赋值运算符).

因此std :: 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;
}
Run Code Online (Sandbox Code Playgroud)

在此函数中创建临时对象是无用的 - 当这些临时对象超出范围时,它们将在行尾删除.

我们宁愿使用移动语义来"掠夺"临时对象并执行类似的操作

 Object& Object::operator+ (Object&& rhs) {
     // logic to modify rhs directly
     return rhs;
 }
Run Code Online (Sandbox Code Playgroud)

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

  • "但是E + F会产生一个临时物体" - 操作员```从左到右,而不是从右到左.因此`B + C`将是第一个! (10认同)
  • 你谈到了移动语义.你应该添加你的答案作为如何使用std :: move,因为问题是这样的. (3认同)
  • @Koushik std :: move并没有做太多 - 但用于实现移动语义.如果你不了解std :: move,你可能也不知道移动语义 (2认同)
  • “没有做太多事情”(是的,只是静态转换为右值引用)。它实际上做了什么,它做了什么,这就是OP所要求的。您不需要知道 std::move 是如何工作的,但您必须知道 move 语义的作用。此外,“但是用于实现移动语义”是相反的。知道移动语义,你就会理解 std::move 否则不会。move 只是帮助移动,并且它本身使用移动语义。std::move 除了将其参数转换为右值引用之外什么也不做,这就是移动语义所需要的。 (2认同)

Jay*_*llo 7

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

我将举例说明“何时使用”。

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

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
};
Run Code Online (Sandbox Code Playgroud)

测试代码:

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;
}
Run Code Online (Sandbox Code Playgroud)

输出如下:

default ctor
move ctor
rh invalid
rh2 valid
copy ctor
rh3 valid
Run Code Online (Sandbox Code Playgroud)

我们可以看到,std::move通过with move constructor可以轻松地转换资源。

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

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

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

引用: