• c++ 右值引用,move关键字


    c++ move关键字

    move的由来:在 c++11 以前存在一个有趣的现象:T&  指向 lvalue (左传引用), const T& 既可以指向 lvalue 也可以指向 rvalue。但却没有一种引用类型,可以限制为只指向 rvalue。

    c++11 中的 move() 是这样一个函数,它接受一个参数,然后返回一个该参数对应的右值引用.
    

    就这么简单!你甚至可以暂时想像它的原型是这样的(当然是错的)

    T&& move(T& val);
    

    &&的由来:在函数体中,程序员无法分辨传进来的参数到底是不是 rvalue,我们缺少一个 rvalue 的标记。为了解决这个问题,c++11 中引入了一个新的引用类型: some_type_t &&,这种引用指向的变量是个 rvalue。

    由于&和&&是属于不同的类型,所以用于各种函数(构造函数,赋值函数)的重载

    holder(holder& other)
    
    holder(holder&& other)
    

    上面是2个重载函数

    holder& operator=(holder& other)
    
    holder& operator=(holder&& other)
    

    上面是2个重载函数

    具体看下面的例子:假设我们有一个类,它包含了一些资源。我们的愿望是:当调用拷贝构造函数或者赋值语句时,我们不想再new一个Resource的对象,因为要new一个Resource对象,即浪费空间,有浪费时间。

    解决办法:利用右值引用。

    右值,本质上是一个临时的内存空间,使用过后,系统马上就会释放掉它,有了右值引用后,就可以延长这个临时空间的生命周期,相当于有了左值的效果,所以可以使用这个临时空间了。回到上面提出的问题,我们不想再new一个Resource的对象,这时我们就可以利用右值引用,去引用一个临时的并且马上要被释放的空间。

    为了调用&&的拷贝构造函数,必须使用std::move,转化成右值引用.

    holder h2(std::move(get_holer()))
    

    但是,h1 = get_holer(),即使不使用std::move,也会调用&&的赋值函数

    h1 = get_holer()
    

    完整代码:

    #include <iostream>
    using namespace std;
    
    class Resource{};
    
    class holder{
    public:
      //构造函数                                                       
      holder(){res = new Resource();}
      //析构函数                                                       
      ~holder(){
        //res不为NULL,就释放res                                     
        if (res)delete res;
      }
      //拷贝构造函数                                                   
      holder(const holder& other){
        cout << "holder&" << endl;
        res = new Resource(*other.res);
      }
      holder(holder& other){
        cout << "holder&1" << endl;
        res = new Resource(*other.res);
      }
    
      //右值                                                           
      holder(holder&& other){
        cout << "holder&&" << endl;
        res = other.res;
        other.res = nullptr;
      }
    
      //赋值                                                           
      holder& operator=(const holder& other){
        cout << "operator" << endl;
        delete res;
        res = new Resource(*other.res);
        return *this;
      }
      holder& operator=(holder& other){
        cout << "operator1" << endl;
        delete res;
        res = new Resource(*other.res);
        return *this;
      }
    
      //右值                                                           
      holder& operator=(holder&& other){
        cout << "operator &&" << endl;
        std::swap(res, other.res);
        return *this;
      }
    
    private:
      Resource* res;
    };
    
    holder get_holer(){
      holder h;
      return h;
    }
    
    int main(void){
      holder h1,h11;
      holder h2(std::move(get_holer()));//调用holder(holder&& other)
      holder h3(get_holer());//编译器自动优化了,没有调用拷贝构造函数  
      holder h4(h11);
    
      h1 = h2;//调用operator(holder&);                                 
      h1 = get_holer();//调用operator(holder&&);                        
    
    }
    

    上面的例子有个需要注意的地方,就是下面这行代码

    holder h3(get_holer());//编译器自动优化了,没有调用拷贝构造函数  
    

    这行代码乍一看,应该调用拷贝构造函数。但是实际用GDB,断点调试时,发现并没有调用拷贝构造函数。

    个人的猜测:如果编译器不优化,在函数get_holer()的return一行处就应该有一次拷贝,调用拷贝构造函数,把h拷贝一份返回给调用测,然后由于holder h3(get_holer())的写法,又要调用一次拷贝构造函数,把get_holer()返回值拷贝给h3。这样一来就多了2次不必要的拷贝,所以编译器自动优化,把这2次拷贝构造函数的调用都省略掉了,直接让h3 = 在get_holer()创建的对象

    65        holder h2(std::move(get_holer()));                       
    (gdb) n  
    holder&& 
    66        holder h3(get_holer());//编译器自动优化了,没有调用拷贝构造函数 
    (gdb) s  
    get_holer () at rvalue_move.cpp:59                                 
    59        holder h;//调用一次构造函数,在下面的9行可以看到                                                
    (gdb) s                                                            
    holder::holder (this=0x7fffffffe180) at rvalue_move.cpp:9 
    9         holder(){res = new Resource();}  //调用构造函数,创建h对象
    (gdb) s 
    get_holer () at rvalue_move.cpp:60 
    60        return h; //返回h对象
    (gdb) p h  //查看h对象里面res的内存地址
    $12 = {res = 0x603070} 
    (gdb) p &h //查看h的内存地址
    $13 = (holder *) 0x7fffffffe180  
    (gdb) n       
    61      } 
    (gdb) n  
    main () at rvalue_move.cpp:67  
    (gdb) p h3 //查看h3对象里面res的内存地址,发现h3的res的内存地址和在函数get_holer()里创建的h对象的res的内存地址相同
    $14 = {res = 0x603070}                                             
    (gdb) p &h3 //查看h3的内存地址后,发现h3的内存地址和在函数get_holer()里创建的h对象的内存地址相同
    $15 = (holder *) 0x7fffffffe180
    
  • 相关阅读:
    SpringBoot入门系列
    日志收集系统-多线程消息队列
    阿里云ecs 服务器配置
    MySQL 表分区详解MyiSam引擎和InnoDb 区别(实测)
    Redis 3.2 Linux 环境集群搭建与java操作
    Java
    多线程编程-工具篇-BlockingQueue
    java常见面试题及答案 11-20(JVM篇)
    28.function_score自定义相关度分数算法
    27.四种常见的相关度分数优化方法
  • 原文地址:https://www.cnblogs.com/xiaoshiwang/p/9582325.html
Copyright © 2020-2023  润新知