Fork me on GitHub

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

 

posted @ 2019-09-09 15:25  烟波--钓徒  阅读(7756)  评论(0编辑  收藏  举报