zoukankan      html  css  js  c++  java
  • Lambda表达式和智能指针

    Lambda表达式

    • 匿名函数

    • Capture list (外部变量访问方式说明符)

      • 可以是=或&,表示{}中用到的、定义在{}外面的变量在{}中是否允许被改变

      • =表示只读,&表示可修改。在[]为空时,不使用定义在外面的变量

      • ->可以省略,让编译器自行推导

        [capture list] (parameter list) -> return type declaration
        {
            lambda body
        }
        
      • []表示外部变量不可见

      • [=, &x, &y]表示外部变量x、y的值可以被修改,其余外部变量不能被修改,但是可见

      • [&, x, y]表示除x、y以外的外部变量,值都可以被修改

      #include <iostream>
      #include <vector>
      
      int main() {
          size_t a = 5;
          std::vector<size_t> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
          auto b = [&a, v](std::vector<size_t> vc) -> void {
              while (a != 0) {
                  std::cout << vc[a] << " ";
                  --a;
              }
          };
          b(v);
          return 0;
      }
      
      //输出结果:6 5 4 3 2
      

    智能指针unique_ptr、shared_ptr和weak_ptr

    ​ 帮助我们管理内存,避免内存泄露。因为new和delete并不是有机整合在一起的操作,所以易造成内存泄露。

    ​ 智能指针管理内存的方法原理时像类的构造函数和析构函数一样,内存分配和回收有机整合,使用构造函数和析构函数包含指针,在不需要时自动将指针析构。其中unique_ptr对应构造函数的move语义(move构造函数),而share_ptr对应构造函数的copy语义(拷贝构造函数)。

    • unique_ptr

      在类unique_ptr中,可以看到 unique_ptr(const unique_ptr&) = delete; unique_ptr& operator=(const unique_ptr&) = delete; unique_ptr类的拷贝构造方法和复制构造方法被禁用。unique_ptr的独一性就是靠将其拷贝构造函数和复制构造函数禁用来实现的。

    #include <iostream>
    #include <memory>
    using namespace std;
    
    class A {
    public:
        void foo() {
            cout << "foo() called." << endl;
        }
    };
    
    int main(){
        //A* nakePtr = new A;  这样不利于内存管理,需要手动释放nakePtr
        unique_ptr<A> r1 {make_unique<A>()};  //声明一个unique_ptr的智能指针指向一个A对象
        auto r2 {make_unique<A>()};    //同上
        //unique_ptr具有独一性,有一个指针指向该对象,不能同时有别的指针指向该对象,
        //auto r3 = r1;    unique_ptr类中拷贝构造函数被禁用,故该操作是非法的
        //auto r3(r1);     unique_ptr类中复制构造函数被禁用,故该操作非法
        auto r3 = move(r1);  //合法,因为unique_ptr的move构造函数并未被禁用
        r3->foo();
        return 0;
    }
    

    gdb调试如下:

    在执行auto r3 = move(r1)之前

    image-20210425181011713

    在执行auto r3 = move(r1)之后

    image-20210425181120463

    • shared_ptr

    share_ptr允许有多个指针指向同一对象,同时会有一个use count,来表示有多少个指针同时指向该对象,当该对象的use count为0时,该对象会被自动析构。

    #include <iostream>
    #include <memory>
    using namespace std;
    
    class A {
    public:
        int a;
        A(int _a = 0) : a(_a){};
        ~A() { cout << a << ":destructor called.
    "; }
    };
    
    int main() {
        //A* nakePtr = new A;  这样不利于内存管理,需要手动释放nakePtr
        shared_ptr<A> r1{make_shared<A>(1)}; //声明一个shared_ptr的智能指针指向一个A对象
        shared_ptr<A> r2 = r1;               //unique_ptr类中拷贝构造函数被禁用,故该操作是非法的
        r1.reset(new A(2));                  //将r1重新指向一个新的A对象
        r2.reset(new A(3));                  //将r2重新指向一个新的A对象
        return 0;
    }
    

    执行结果:

    1:destructor called.
    3:destructor called.
    2:destructor called.
    

    r1r2被重设为指向新的对象后,值为1的对象的use count变为0,随即被析构掉,然后函数返回,按栈上顺序依次析构值为3和值为2的对象。

    在执行shared_ptr<A> r2 = r1 之前

    image-20210426143814291

    在执行shared_ptr<A> r2 = r1之后

    image-20210426143841915

    执行r1.reset(new A(2));后,use count减1,变为1

    image-20210426144037198

    再次执行r2.reset(new A(3));后,use count减1,变为0,析构值为1的对象。

    • weak_ptr

    前言

    weak_ptr这个指针天生一副“小弟”的模样,也是在C++11的时候引入的标准库,它的出现完全是为了弥补它老大shared_ptr天生有缺陷的问题,其实相比于上一代的智能指针auto_ptr来说,新进老大shared_ptr可以说近乎完美,但是通过引用计数实现的它,虽然解决了指针独占的问题,但也引来了引用成环的问题,这种问题靠它自己是没办法解决的,所以在C++11的时候将shared_ptrweak_ptr一起引入了标准库,用来解决循环引用的问题

    weak_ptr本身也是一个模板类,但是不能直接用它来定义一个智能指针的对象,只能配合shared_ptr来使用,可以将shared_ptr的对象赋值给weak_ptr,并且这样并不会改变引用计数的值。查看weak_ptr的代码时发现,它主要有lockswapresetexpiredoperator=use_count几个函数,与shared_ptr相比多了lockexpired函数,但是却少了get函数,甚至连operator*operator->都没有,可用的函数数量少的可怜,下面通过一些例子来了解一下weak_ptr的具体用法。

    使用环境

    VS2015 + Windows7(应该是C++11标准)
    头文件#include <memory>
    命名空间using namespace std;

    测试过程

    1.weak_ptr解决shared_ptr循环引用的问题
    定义两个类,每个类中又包含一个指向对方类型的智能指针作为成员变量,然后创建对象,设置完成后查看引用计数后退出,看一下测试结果:

    class CB;
    class CA{
    public:
        CA() { cout << "CA() called! " << endl; }
        ~CA() { cout << "~CA() called! " << endl; }
        void set_ptr(shared_ptr<CB>& ptr) { m_ptr_b = ptr; }
        void b_use_count() { cout << "b use count : " << m_ptr_b.use_count() << endl; }
        void show() { cout << "this is class CA!" << endl; }
    private:
        shared_ptr<CB> m_ptr_b;
    };
    
    class CB{
    public:
        CB() { cout << "CB() called! " << endl; }
        ~CB() { cout << "~CB() called! " << endl; }
        void set_ptr(shared_ptr<CA>& ptr) { m_ptr_a = ptr; }
        void a_use_count() { cout << "a use count : " << m_ptr_a.use_count() << endl; }
        void show() { cout << "this is class CB!" << endl; }
    private:
        shared_ptr<CA> m_ptr_a;
    };
    
    void test_refer_to_each_other(){
        shared_ptr<CA> ptr_a(new CA());
        shared_ptr<CB> ptr_b(new CB());
        cout << "a use count : " << ptr_a.use_count() << endl;
    	cout << "b use count : " << ptr_b.use_count() << endl;
    	ptr_a->set_ptr(ptr_b);
    	ptr_b->set_ptr(ptr_a);
    	cout << "a use count : " << ptr_a.use_count() << endl;
    	cout << "b use count : " << ptr_b.use_count() << endl;
    }
    

    测试结果如下:

    CA() called!
    CB() called!
    a use count : 1
    b use count : 1
    a use count : 2
    b use count : 2
    

    通过结果可以看到,最后CACB的对象并没有被析构,其中的引用效果如下图所示,起初定义完ptr_aptr_b时,只有①③两条引用,然后调用函数set_ptr后又增加了②④两条引用,当test_refer_to_each_other这个函数返回时,对象ptr_aptr_b被销毁,也就是①③两条引用会被断开,但是②④两条引用依然存在,每一个的引用计数都不为0,结果就导致其指向的内部对象无法析构,造成内存泄漏。

    image-20210426134302685

    解决这种状况的办法就是将两个类中的一个成员变量改为weak_ptr对象,因为weak_ptr不会增加引用计数,使得引用形不成环,最后就可以正常的释放内部的对象,不会造成内存泄漏,比如将CB中的成员变量改为weak_ptr对象,代码如下:

    class CB{
    public:
        CB() { cout << "CB() called! " << endl; }
        ~CB() { cout << "~CB() called! " << endl; }
        void set_ptr(shared_ptr<CA>& ptr) { m_ptr_a = ptr; }
        void a_use_count() { cout << "a use count : " << m_ptr_a.use_count() << endl; }
        void show() { cout << "this is class CB!" << endl; }
    private:
        weak_ptr<CA> m_ptr_a;
    };
    

    测试结果如下:

    CA() called!
    CB() called!
    a use count : 1
    b use count : 1
    a use count : 1
    b use count : 2
    ~CA() called!
    ~CB() called!
    

    通过这次结果可以看到,CACB的对象都被正常的析构了,引用关系如下图所示,流程与上一例子相似,但是不同的是④这条引用是通过weak_ptr建立的,并不会增加引用计数,也就是说CA的对象只有一个引用计数,而CB的对象只有2个引用计数,当test_refer_to_each_other这个函数返回时,对象ptr_aptr_b被销毁,也就是①③两条引用会被断开,此时CA对象的引用计数会减为0,对象被销毁,其内部的m_ptr_b成员变量也会被析构,导致CB对象的引用计数会减为0,对象被销毁,进而解决了引用成环的问题。

    image-20210426134612911

    2.测试weak_ptr对引用计数的影响
    其实weak_ptr本身设计的很简单,就是为了辅助shared_ptr的,它本身不能直接定义指向原始指针的对象,只能指向shared_ptr对象,同时也不能将weak_ptr对象直接赋值给shared_ptr类型的变量,最重要的一点是赋值给它不会增加引用计数

    void test1(){
        // 编译错误 // error C2665: “std::weak_ptr<CA>::weak_ptr”: 3 个重载中没有一个可以转换所有参数类型
        // weak_ptr<CA> ptr_1(new CA());
    
        shared_ptr<CA> ptr_1(new CA());
        
        cout << "ptr_1 use count : " << ptr_1.use_count() << endl; // 输出:ptr_1 use count : 1
        
        shared_ptr<CA> ptr_2 = ptr_1;
        
        cout << "ptr_1 use count : " << ptr_1.use_count() << endl; // 输出:ptr_1 use count : 2
        cout << "ptr_2 use count : " << ptr_2.use_count() << endl; // 输出:ptr_1 use count : 2
        
        weak_ptr<CA> wk_ptr = ptr_1;
        
        cout << "ptr_1 use count : " << ptr_1.use_count() << endl; // 输出:ptr_1 use count : 2
        cout << "ptr_2 use count : " << ptr_2.use_count() << endl; // 输出:ptr_1 use count : 2
        
        // 编译错误
        // error C2440 : “初始化”: 无法从“std::weak_ptr<CA>”转换为“std::shared_ptr<CA>”
        // shared_ptr<CA> ptr_3 = wk_ptr;
    
    }
    

    3.测试weak_ptr常用函数的用法
    weak_ptr中只有函数lockexpired两个函数比较重要,因为它本身不会增加引用计数,所以它指向的对象可能在它用的时候已经被释放了,所以在用之前需要使用expired函数来检测是否过期,然后使用lock函数来获取其对应的shared_ptr对象,然后进行后续操作:

    void test2(){
        shared_ptr<CA> ptr_a(new CA());     // 输出:CA() called!
        shared_ptr<CB> ptr_b(new CB());     // 输出:CB() called!
        cout << "ptr_a use count : " << ptr_a.use_count() << endl; // 输出:ptr_a use count : 1
    	cout << "ptr_b use count : " << ptr_b.use_count() << endl; // 输出:ptr_b use count : 1
    
    	weak_ptr<CA> wk_ptr_a = ptr_a;
    	weak_ptr<CB> wk_ptr_b = ptr_b;
    
    	if (!wk_ptr_a.expired()){
       		wk_ptr_a.lock()->show();        // 输出:this is class CA!
    	}
    
    	if (!wk_ptr_b.expired()){
        	wk_ptr_b.lock()->show();        // 输出:this is class CB!
    	}
    
    	// 编译错误
    	// 编译必须作用于相同的指针类型之间
    	// wk_ptr_a.swap(wk_ptr_b);         // 调用交换函数
    
    	wk_ptr_b.reset();                   // 将wk_ptr_b的指向清空
    	if (wk_ptr_b.expired()){
        	cout << "wk_ptr_b is invalid" << endl;  // 输出:wk_ptr_b is invalid 说明改指针已经无效
    	}
    
    	wk_ptr_b = ptr_b;
    	if (!wk_ptr_b.expired()){
        	wk_ptr_b.lock()->show();        // 输出:this is class CB! 调用赋值操作后,wk_ptr_b恢复有效
    	}
    
    	// 编译错误
    	// 编译必须作用于相同的指针类型之间
    	// wk_ptr_b = wk_ptr_a;
        // 最后输出的引用计数还是1,说明之前使用weak_ptr类型赋值,不会影响引用计数
    	cout << "ptr_a use count : " << ptr_a.use_count() << endl; // 输出:ptr_a use count : 1
    	cout << "ptr_b use count : " << ptr_b.use_count() << endl; // 输出:ptr_b use count : 1
    }
    

    现象分析

    引用计数的出现,解决了对象独占的问题,但是也带来了循环引用的困扰,使用weak_ptr可以打破这种循环,当你理不清引用关系的时候,不妨采用文中画图的方式来理一理头绪,或许就会有眼前一亮的感觉。

    总结

    1.weak_ptr虽然是一个模板类,但是不能用来直接定义指向原始指针的对象。
    2.weak_ptr接受shared_ptr类型的变量赋值,但是反过来是行不通的,需要使用lock函数。
    3.weak_ptr设计之初就是为了服务于shared_ptr的,所以不增加引用计数就是它的核心功能。
    4.由于不知道什么之后weak_ptr所指向的对象就会被析构掉,所以使用之前请先使用expired函数检测一下。
    ————————————————
    版权声明:本文为CSDN博主「AlbertS」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/albertsh/article/details/82286999

  • 相关阅读:
    NS网络仿真,小白起步版,双节点之间的模拟仿真(基于UDP和CBR流)
    Linux学习,ACL权限管理
    SQL中的注释语句
    C#连接SQL Server数据库小贴士
    C#重写ToString
    C#控制台应用程序之选课系统
    浅谈C、C++及其区别、兼容与不兼容
    C++之客户消费积分管理系统
    A*算法
    HTML标签列表总览
  • 原文地址:https://www.cnblogs.com/bear-Zhao/p/14704620.html
Copyright © 2011-2022 走看看