• C11内存管理之道:智能指针


    http://www.cnblogs.com/ChinaHook/p/7684268.html

    1、shared_ptr共享智能指针

      std::shared_ptr使用引用计数,每个shared_ptr的拷贝都指向相同的内存,在最后一个shared_ptr析构的时候,内存才会释放。

    1.1 基本用法

    1.1.1 初始化

      shared_ptr可以通过make_shared来初始化,也可以通过shared_ptr<T>辅助函数和reset方法来初始化。智能指针的用法和普通指针的用法类似,不过不需要自己管理分配的内存,对于没有初始化的指针,只能通过reset来初始化,当智能指针有值,reset会使计数器减1。智能指针可以通过重载的bool来判断是否为空。

    复制代码
    #include <iostream>
    #include <memory>
    
    using namespace std;
    
    int main()
    {
        //智能指针初始化
        shared_ptr<int> p = make_shared<int>(20);
        shared_ptr<int> p(new int(1));
        shared_ptr<int> p1 = p;
        shared_ptr<int> ptr;
        
        //所指的对象会被重置,不带参数则是销毁
        ptr.reset(new int(5));
        
        if(ptr)
        {
            cout << "ptr is not null" << endl;
        }
        
        return 0;
    }
    复制代码

      智能指针不能通过原始指针来初始化:

    shared_ptr<int> p = new int(1); //编译报错,不能直接赋值

    1.1.2 获取原始指针

      当需要获取原始指针的时候,可以通过get来返回原始指针。不能释放,如果释放会出错。

    shared_ptr<int> ptr(new int(1));
    int* p = ptr.get();
    delete p; //error

    1.1.3 指定删除器

      智能指针支持指定删除器,在指针引用为0的时候自动调用。支持普通函数和lambda表达式。

    复制代码
    //普通函数
    void DeleteIntPtr(int *p) {delete p;}
    shared_ptr<int> p(new int(10), DeleteIntPtr);
    //lambda表达式 shared_ptr<int> p(new int(10), [](int *p) {delete p;});
    复制代码

      当智能指针管理动态数组的时候,默认的删除器不支持数组对象。需要指定删除器,自定义删除器或者使用改善的默认修改器都可以。

    shared_ptr<int> p(new int[10], [](int *p) {delete[] p;}); //lambda
    shared_ptr<int> p1(new int[10], default_delete<int []>); //指定delete []

    1.2 注意问题

      a.避免一个原始指针初始化多个shared_ptr。

    int* p = new int;
    shared_ptr<int> p1(p);
    shared_ptr<int> p2(p);

      b.不要在参数实参中创建shared_ptr。

    func(shared_ptr<int>(new int), g());

      不同的编译器可能有不同的调用约定,如果先new int,然后调用g(),在g()过程中发生异常,但是shared_ptr没有创建,那么int的内存就会泄漏,正确的写法应该是先创建智能指针。

    shared_ptr<int> p(new int);
    f(p, g());

      c.避免循环使用,循环使用可能导致内存泄漏

    复制代码
    #include <iostream>
    #include <memory>
    
    using namespace std;
    
    struct A;
    struct B;
    
    struct A
    {
        shared_ptr<B> bptr;
        ~A() { cout << "A is deleted." << endl; }
    };
    
    struct B
    {
        shared_ptr<A> aptr;
        ~B() { cout << "B is deleted." << endl; }
    };
    
    int main()
    {
        shared_ptr<A> ap(new A);
        shared_ptr<B> bp(new B);
        
        ap->bptr = bp;
        bp->aptr = ap;
        
        return 0;
    }
    复制代码

      这个最经典的循环引用的场景,结果是两个指针A和B都不会删除,存在内存泄漏。循环引用导致ap和bp的引用计数为2,离开作用域之后,ap和bp的引用计数为1,并不会减0,导致两个指针都不会析构而产生内存泄漏。

      d.通过shared_from_this()返回this指针。不要将this指针作为shared_ptr返回出来,因为this指针本质是一个裸指针,这样可能导致重复析构。

    复制代码
    #include <iostream>
    #include <memory>
    
    using namespace std;
    
    struct A
    {
        shared_ptr<A> GetSelf()
        {
            return shared_ptr<A>(this);
        }
        
        ~A() { cout << "A is deleted." << endl; }
    };
    
    int main()
    {
        shared_ptr<A> ap(new A);
        shared_ptr<A> ap2 = ap->GetSelf();
        
        return 0;
    }
    
    //执行结果
    A is deleted.
    A is deleted.
    复制代码

      这个例子中,由于同一指针(this)构造了两个只能指针ap和ap2,而他们之间是没有任何关系的,在离开作用域之后this将会被构造的两个智能指针各自析构,导致重复析构的错误。当然,也有解决办法,解决办法在之后的weak_ptr介绍。

    2、unique_ptr独占智能指针

    2.1 初始化

      unique_ptr是一个独占型智能指针,它不允许其他的智能指针共享其内部的指针,不允许通过赋值将一个unique_ptr赋值给另一个unique_ptr。只能通过函数来返回给其它的unique_ptr,比如move函数,但是转移之后,不再对之前的指针具有所有权。

    unique_ptr<int> uptr(new int(10));
    unique_ptr<int> uptr2 = uptr;       //error
    unique_ptr<int> uptr3 = move(uptr); //uptr将变为null

    2.2 特点

    2.2.1 数组

      unique_ptr和shared_ptr相比除了独占之外,unique_ptr还可以指向一个数组。

    unique_ptr<int []> ptr(new int[10]);   //ok
    ptrp[1] = 10;
    
    shared_ptr<int []> ptr2(new int[10]); //error

    2.2.2 删除器

      unique_ptr必须指定删除器类型,不像shared_ptr那样直接指定删除器。

    shared_ptr<int> ptr(new int(1), [](int *p){delete p;});           //ok
    unique_ptr<int> ptr2(new int(1), [](int *p){delete p;});           //error
    unique_ptr<int, void(*)(int *)> ptr2(new int(1), [](int *p){delete p;}); //ok

      通过指定函数类型,然后通过lambda表达式实现是可以,但是如果捕获了变量将会编译报错,因为lambda表达式在没有捕获变量的情况下可以直接转换为函数指针,但是捕获了变量就无法转换。如果要支持,可以通过std::function来解决。

    unique_ptr<int, void(*)(int *)> ptr2(new int(1), [&](int *p){delete p;});            //error
    unique_ptr<int, std::function<void(int*)>> ptr2(new int(1), [&](int *p){delete p;}); //ok

      unique_ptr支持自定义删除器。

    复制代码
    #include <iostream>
    #include <memory>
    #include <functional>
    
    using namespace std;
    
    struct DeleteUPtr
    {
        void operator()(int* p)
        {
            cout << "delete" << endl;
            delete p;
        }
    };
    
    int main()
    {
        unique_ptr<int, DeleteUPtr> p(new int(1));
        
        return 0;
    }
    复制代码

    3、weak_ptr弱引用智能指针

    弱引用智能指针weak_ptr用来监视shared_ptr,不会使引用技术加1,也不管理shared_ptr内部的指针,主要是监视shared_ptr的生命周期。weak_ptr不共享指针,不能操作资源,它的构造和析构都不会改变引用计数。

    3.1 基本用法

    3.1.1 观测计数

      通过use_count()方法来获得当前资源的引用计数。

    shared_ptr<int> sp(new int(10));
    weak_ptr<int> wp(sp);
    
    cout << wp.use_count() << endl; //输出1

    3.1.2 观察是否有效

    复制代码
    shared_ptr<int> sp(new int(10));
    weak_ptr<int> wp(sp);
    
    if(wp.expired())
    {
        cout << "sp 已经释放,无效" << endl;
    }
    else
    {
        cout << "sp 有效" << endl;
    }
    复制代码

    3.1.3 监视

      可以通过lock方法来获取所监视的shared_ptr。

    复制代码
    #include <iostream>
    #include <memory>
    
    using namespace std;
    
    weak_ptr<int> gw;
    
    void f()
    {
        //监听是否释放
        if(gw.expired()) 
        {
            cout << "gw is expired." << endl;
        }
        else
        {
            auto spt = gw.lock();
            cout << *spt << endl;
        }
    }
    
    int main()
    {
        {
            auto p = make_shared<int>(20);
            gw = p;
            f();
        }
        f();
        
        return 0;
    }
    
    //执行结果
    20
    gw is expired.
    复制代码

    3.2 返回this指针

      sharerd_ptr不能直接返回this指针,需要通过派生std::enable_shared_from_this类,并通过其方法shared_from_this来返回智能指针,因为std::enable_shared_from_this类中有一个weak_ptr,这个weak_ptr用来观测this指针,调用shared_from_this方法时,调用了内部的weak_ptr的lock()方法,将所观测的sharerd_ptr返回。

    复制代码
    #include <iostream>
    #include <memory>
    
    using namespace std;
    
    struct A:public enable_shared_from_this<A>
    {
        shared_ptr<A> GetSelf()
        {
            return shared_from_this();
        }
        
        ~A()
        {
            cout << "A is deleted." << endl;
        }
    };
    
    int main()
    {
        shared_ptr<A> spy(new A);
        shared_ptr<A> p = spy->GetSelf(); //ok
        
        return 0;
    }
    
    //执行结果
    A is deleted.
    复制代码

      在外面创建A对象的智能指针通过该对象返回this的智能指针是安全的,因为shared_from_this()是内部weak_ptr调用lock()方法之后返回的智能指针,在离开作用域之后,spy的引用计数为0,A对象会被析构,不会出现A对象被析构两次的问题。

      需要注意的是,获取自身智能指针的函数仅在share_ptr<T>的构造函数调用之后才能使用,因为enable_shared_from_this内部的weak_ptr只有通过shared_ptr才能构造。

    3.3 解决循环引用问题

      shared_ptr的循环引用可能导致内存泄漏,之前的例子不再赘述,通过weak_ptr可以解决这个问题,怎么解决呢?答案是,将A或者B任意一个成员变量改为weak_ptr即可。

    复制代码
    #include <iostream>
    #include <memory>
    
    using namespace std;
    
    struct A;
    struct B;
    
    struct A
    {
        shared_ptr<B> bptr;
        ~A() { cout << "A is deleted." << endl; }
    };
    
    struct B
    {
        weak_ptr<A> aptr;
        ~B() { cout << "B is deleted." << endl; }
    };
    
    int main()
    {
        shared_ptr<A> ap(new A);
        shared_ptr<B> bp(new B);
        
        ap->bptr = bp;
        bp->aptr = ap;
        
        return 0;
    }
    
    //执行结果
    A is deleted.
    B is deleted.
    复制代码

      这样在对B成员赋值时,即bp->aptr = ap,由于aptr是weak_ptr,并不会增加引用计数,所以ap的计数仍然是1,在离开作用域之后,ap的引用计数会减为0,A指针会被析构,析构之后,其内部的bptr引用计数会减1,然后离开作用域之后,bp引用计数从1减为0,B对象也被析构,所以不会发生内存泄漏。

  • 相关阅读:
    【LeetCode每天一题】Combinations(组合)
    【算法】字符串匹配算法
    【LeetCode每天一题】Edit Distance(编辑距离)
    【LeetCode每天一题】Set Matrix Zeroes(设置0矩阵)
    SpringIOC和DI
    SpringMVC基础
    SpringMVC框架简介
    Spring配置JDBCTemplate
    java自定义注解
    KTV项目之3个ListView的跳转和加载歌手图片
  • 原文地址:https://www.cnblogs.com/mwl523/p/10818146.html
Copyright © 2020-2023  润新知