首页 文章

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

提问于
浏览
468
  • 这是什么?

  • 它做了什么?

  • 什么时候应该使用?

赞赏良好的链接 .

6 回答

  • 123

    1.“这是什么?”

    虽然 std::move() 在技术上是一个功能 - 我会说 it isn't really a function . 它是编译器考虑表达式值的方式之间的转换器 .

    2.“它做了什么?”

    首先要注意的是 std::move() doesn't actually move anything .

    如果你曾经看过动画系列Bleach--它相当于Quincy Seele Schneider的Reishi软化 .

    但是,严肃地说,它将一个表达式转换为lvalue or pure rvalue(例如你可能已经使用很长一段时间的变量,或者暂时分别传递一段时间)作为xvalue . xvalue告诉编译器:

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

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

    3.“应该何时使用?”

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

    典型的用法是从一个对象到另一个对象的资源而不是复制 . @Guillaume链接到this page,它有一个简单的简短例子:用较少的复制交换两个对象 .

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

  • 202

    上面已经解释了 What is it?What does it do? .

    我举一个 when it should be used. 的例子

    例如,我们有一个包含大数组资源的类 .

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

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

    引:

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

  • 109

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

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

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

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

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

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

  • 32

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

    这个链接真的帮助了我:

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

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

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

  • 26

    问:什么是std :: move?

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

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

    static_cast<T&&>(t);
    

    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
    

    std :: move()的实现在N2027: "A Brief Introduction to Rvalue References"中给出,如下所示:

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

    如您所见,无论是使用值( T ),引用类型( T& )还是右值引用( T&& )调用, std::move 都会返回 T&& .

    问:它做了什么?

    答:作为演员,它在运行时没有做任何事情 . 只有在编译时才能告诉编译器您希望继续将引用视为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)`
    

    它的作用 not 做:

    • 制作参数的副本

    • 调用复制构造函数

    • 更改参数对象

    问:什么时候应该使用?

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

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

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

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

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

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

  • 1

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

    • 在C 11中,除了复制构造函数外,对象还可以有移动构造函数 .
      (除了复制赋值运算符,它们还有移动赋值运算符 . )

    • 如果对象的类型为"rvalue-reference"( Type && ),则使用移动构造函数而不是复制构造函数 .

    • std::move() 是一个转换器,它生成对象的rvalue引用,以便从中移动 .

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

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

相关问题