cpp11移动语义完美转发

TODO

左右值引用,完美转发,RVO,重新修改文章

前言

目前还是初稿,文章有待更改,参考资料都为好文,学习自用,有错麻烦提一下

左右值

左右值判断

之所以要认识左右值是因为后面要使用移动语义,而我们不需要那么精确的左右值定义,只需要知道变量是左值,其他的:函数返回,字面值等很容易看出来是右值

一些观念

  • 等号左边是左值,等号右边是右值

    这是最初的定义

  • 看能不能对表达式取地址,如果能,则为左值,否则为右值

移动语义

右值是可以很安全的被"移动"的,operator=赋值的时候调用移动构造函数,比左值赋值时候调用的拷贝构造函数更高效

移动语义就是

移动语义的原理

略,只是类似于简单的交换指针一样

std::move作用:

将左值转换为右值,从而方便应用移动语义

使用场景和方法:

  1. 成员函数中shared_ptr的传参并复制给类成员变量

    用shared_ptr要拷贝,一个shared_ptr对象,因为shared_ptr对象是线程不安全的,在这里用到shared_ptr会有问题(在构造函数时线程不安全吗),用移动构造函数就不会了(为什么呢TODO)

  2. unique_ptr,见下面[不可复制对象用移动]部分

  3. void push_back( const T& value ); // (1)
    void push_back( T&& value ); // 定义赋值函数
    vector<vector<string>> vv; 
    vector<string> v = {"123", "456"}; 
    v.push_back("789"); // 临时构造的string类型右值被移动进容器v
    vv.push_back(move(v)); // std::move的用法,显式将v移动进vv
  4. 同样是使用vector的情况

    class Mystring;//详细定义略,假定他的占用很大
    MyString(MyString&& str) noexcept{};//移动构造函数,详细内容略
    vector<MyString> vecStr;
        vecStr.reserve(1000); //先分配好1000个空间,不这么做,调用的次数可能远大于1000
        for(int i=0;i<1000;i++){
            vecStr.push_back(MyString("hello"));
        }
    //这里使用移动语义的应用就是定义一个移动构造函数,
    //这样编译器会自动优化,利用移动构造函数而不是拷贝构造来传入vector中

移动语义的作用

使用移动来代替构造更高的效率

情况一:

vector<string> str_split(const string& s) {
  vector<string> v;
  // ...
  return v; // v是左值,但优先移动,不支持移动时仍可复制。
}

情况二:

//同理上面,用移动构造函数来代替拷贝构造函数,用移动赋值代替复制赋值
vector<string> str_split(const string& s); 
vector<string> v = str_split("1,2,3"); //1
vector<string> v2; //2
v2 = str_split("1,2,3");//3

对于上面这段代码,没有移动语义的时候应该是:

  • 第一句,栈上构造个v(原答案说是堆上,为什么),然后用拷贝构造函数构造临时值后,拷贝构造给v,再析构临时对象

  • 第三句,拷贝构造临时对象后,先清理v2原本的数据,再拷贝赋值给v2,析构释放临时对象内存

如果使用移动语义后:

  • 第一句,函数返回的vector用以移动构造对象v。而v不需要额外申请空间,直接取走临时对象的堆上内存(临时对象可以是堆上的吗?),之后临时对象成为空壳,析构时也无需释放堆内存。

  • 第三句,返回值移动构造给v2,v2原本的数据先被清理掉,然后不用释放内存

情况三:

std::vector的增长

当vector的存储容量需要增长时,通常会重新申请一块内存,并把原来的内容一个个复制过去并删除。复制并删除,这里改用移动语义就够了。对vector<string>这样的容器带来性能改变巨大

不可复制对象用移动

不可复制对象某些情况下可以使用移动语义,std::thread和unqiue_ptr

unique_ptr<SomeObj> create_obj(/*...*/) {return unique_ptr<SomeObj>(new SomeObj(/*...*/));
}//返回unqiue_ptr,因为函数传参返回要用到拷贝构造,只能移动语义
  • 比如把unque_ptr放入vector中,因为vector重新分配空间时会复制对象到另一块内存,所以只能移动

额外知识

回值优化(RVO/NRVO, RVO, Return Value Optimization 返回值优化,或者NRVO, Named Return Value Optimization)

可见参考资料5,TODO找出inside cpp object model的例子

完美转发std::forword

简单的说,与std::move区别就是,move会把参数转换成右值,而std::forward是把参数保留左右值

应用场景:

template <typename T>//(1)
void relay(T&& t) {    func(t);}
////////
template <typename T>//(2)
void relay(T&& t) {    func(std::forward<T>(t));}

以上这个realay函数作用是把参数传进func并调用,在一版本中,因为t是具名的,所以编译器认为他是一个左值,调用的时候会传入左值

TODO:给个回调函数的例子

参考资料

  1. 简书的80赞

  2. csdn的,参考资料看看

  3. 左右值区分

  4. 清华的高赞

  5. [RVO/NRVO优化](https://blog.csdn.net/rlyhaha/article/details/80381170)

  6. [完美转发](https://blog.csdn.net/suchto/article/details/54947998)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章