zoukankan      html  css  js  c++  java
  • std::move的原理与实现,右值引用的深入理解

    这次我真的懂了。。。。

    首先C++11引入了右值引用 &&

    ‘&&’这个要连起来看,是一个整体,C++多了一个关键字而已。

    不是引用的引用。是船新的一种语法。那有什么用呢?

    额,参数的类型又多了一种!

    void fun(int T)

    void fun(int& T)

    void fun(int && T)

    void fun(int* t)

    之前的参数,值传递,引用,指针。现在呢?多了一个叫 “右值引用”的玩意,多了一种参数类型的选择。仅此而已。

    那他们号称的右值引用速度快,代价小呢?

    额,这个需要库作者自己去实现的,跟C++语言本身无关。

    举两个例子

    void fun(int & t)

    {

        t= 2;

    }

    void fun(int && t)

    {

        cout<<"int &&"<<endl;

        int x = t;

       x++;

      x--;

    }

    这个右值引用的fun函数就更复杂了嘛,没有说一定要简单啊,完全由库作者决定的。

    当然,现在的库作者对右值引用的函数往往做了内存转移的操作(尤其是移动构造函数与移动赋值函数)

    class A
    {
        A(int num)
        {
            p = new int(num);
        }
        A(A& a)
        {
            p = new int(*a.p);
        }
        A(A&& a)
        {
            p = a.p;
            a.p = nullptr;
        }
        ~A()
        {
            delete p;
        }
    private:
        int * p=nullptr;
    };

    如上,对于右值引用构造函数,仅仅是转移了内存,并让被转移的指针置空。当然,这个右值引用构造函数具体的实现还是由库作者决定的。

    另外,如果没有右值引用构造函数,会自动调用拷贝构造函数。

    这里说到了转移,嗯,翻译下就是move。move这个函数看上去是专门转移内存的。实际上是错误的。。

    move仅仅是进行了一个 右值引用 的强制转换。

    对于强制转换,你可能会写

    template<typename T>
    T && make_move(T&& t)  //当然真正的是std::move,我这里取名实现类似的move。make_move跟make_love没有关系哈,纯粹的偶然。。
    {
        return static_cast<T&&>(t);
    }

    额,这是啥,T &&转换成T&& ,看上去啥都没做嘛。

    首先:对于make_move(T&& t)中的 t,说明make_move的函数参数是右值引用,但不代表t是右值引用。t可能是左值。额,越来越头大了。

    想起了“书越读越厚,然后越读越薄”。其实我自己对这个的理解过程也超过了2年多,这次真的搞懂了!!

    上例子缓缓

    int x =10;

    make_move(x)  //此时x是左值,什么叫左值,就是可以取地址的变量。&x有意义的变量。

    make_move(20) //20是真正的右值。

    看上去这个时候make_move体现出了意义,把t强转成右值引用了。

    但读过  模板类型推倒、auto推导 后,我们知道,左值(或引用)的强制右值转换返回是个左值引用。简单的如下:

    于是,经过make_move函数后返回的是int & 而不是int &&。

    那怎么才能得到真正的int && 呢。需要加上traits。

    template<typename T>
    typename remove_reference<T>::type && make_move(T&& t)
    {
        using Rtype = typename remove_reference<T>::type &&;
        return static_cast<Rtype>(t);
    }

    typename 是为了告诉编译器type是一个类型,这个在stl很常见。

    举个例子

    struct A

    {

      typedef unsigned size_t;

      static size_t value;

    }

     我们访问value      使用A::value

    我们访问size_t      使用A::size_t  那么size_t到底是值还是类型,编译器不明白。

    所以我们会用 typename A::size_t ;  (typename 翻译类型名字,就是表明该变量是个类型)

    remove_reference<T>::type 就是去掉T的引用后的类型,再加上&&

    就是真的T的右值引用了。

    如你所见,这个也基本是std::move干的事。因此move并没有转移内存还是啥的,甚至没有转移的语义。只是一种类型的强制转换。所以如果命名为rvalue_cast,我也能早点懂得。

    std::vector<std::string> ve;

    std::string str="msg";

    ve.push_back(str);

    ve.push_back(std::move(str)); //内部实现可能是这样子的

    void push_back(str)  

    {

        T temp (str);  //调用值拷贝构造

        __insert(temp);

    }

    ve.push_back(std::move(str));

    {

      T temp (t) ;  //调用右值拷贝构造

        __insert(temp);

    }

      确实会比ve.push_back(str)快一点点,std::string的右值拷贝构造直接转移了内存。

      最终看起来像是move的功劳,也实现了转移的语义。

      但实际上是std::string的右值拷贝构造直接转移了内存。当然感谢move,但str真的从左值变成了右值引用。

      the  end

  • 相关阅读:
    C/C++中的堆、栈和队列
    网卡工作状态检测
    Delphi 2007 的midas程序注册问题
    使用SQL语句备份与恢复数据库
    C/C++中的堆、栈和队列
    Delphi 2007 的midas程序注册问题
    如何使用VirtualBox的共享文件夹(转)
    如何使用VirtualBox的共享文件夹(转)
    C语言struct的使用
    printf常用格式
  • 原文地址:https://www.cnblogs.com/xuhuajie/p/11491924.html
Copyright © 2011-2022 走看看