c/c++already

chapter4 解决 内存泄露: 智能指针

2022-06-18  本文已影响0人  my_passion
SP 
    internal ptr 指向 heap 内存
    
    `控制 lifetime` 
        
        确保 离开 SP scope 时, 自动 delete internal heap 内存, 防止内存泄露 
    
    通用技术        
    
        `引用计数 RC`
            
            SP 每 使用 1次, 其 internal RC 加 1;
                
            每 析构 1次, 其 internal RC 减 1; 减为 0 时, delete 所指 heap 内存                                           
    
    <memory>        

4.1 std::shared_ptr

(1) 初始化 
    
    ——————————————————————————————————————————————————
    [1] Ctor                        
    ——————————————————————————————————————————————————
    [2] std::make_shared        高效      
    ——————————————————————————————————————————————————
    [3] reset()             shared_ptr 未初始化 => RC = 1       
                            shared_ptr 已初始化 => RC - 1
    ——————————————————————————————————————————————————
    
        template <class T, class... Args>
        shared_ptr<T> 
        make_shared (Args&&... args);
                                    
        1] ::new 分配 T 对象 memory + args 作 参数 调 T Ctor
                        |
                        |   ptr 作 SP internal ptr 
                        |/
        2] 分配 shared_ptr 对象 memory 
        
        3] RC = 1
    
        std::shared_ptr<int> sp1 = std::make_shared<int> (1);
        
        std::shared_ptr<int> sp2( new int(1) );
        
        std::shared_ptr<int> sp3; 
        sp3.reset( new int(1) );

        不能将 原始指针 赋值给 SP, 编译报错: 语法错
        std::shared_ptr<int> sp4 = new int(1); 
    
    std::make_shared 相比 std::shared_ptr Ctor 的 好处 
    
    优点
        [1] std::shared_ptr Ctor: 2次内存分配
        
            Resource <- - - -
                             |
                             |
            control block: resPtr + strongPtrNum/RCNum + weakPtrNum + Deleter + Allocator  
                |
                |   make_shared: 1次内存分配 => 时 空 效率更高
                |/
            Resource + control block: strongPtrNum/RCNum + weakPtrNum + ...
        
        [2] 异常安全
        
            潜在的资源泄露 
            f1(std::shared_ptr<A>(new A), f2() );
            
                可能的执行顺序 
                    new A
                    f2() 
                    std::shared_ptr Ctor
                若 f2() 抛出异常, 则 第1步 new 的 内存泄露   
                |
                |   异常安全
                |/
            f1(std::make_shared<A>(), f2() );
        
    缺点 
        
        [1] resource 的 Ctor private/protected 时, make_shared 不能用
        
        [2] 延迟了 resource 内存 回收 的时间
        
            last weak_ptr 出 scope 时, resource 内存才被回收 
            
            原本 强引用(RC) 减为 0 时, resource 内存回收
                现在变为了 强/弱引用 都减为 0 时, resource 内存回收

(2) operator=
        
    Replaces the managed object with the one managed by r.
        
    对 原 resource 的 RC 减 1, 减到 0 则 delete 原 resource 
    对 新 resource, 若存在, 其 RC 加 1
    
(3) 获取 internal ptr: get()
    
    int* p = sp.get();

(4) 到 bool 的 类型转换运算符: operator bool() 

    <=> get() != nullptr;
    
    #include <memory>
    #include <iostream>

    void f(std::shared_ptr<int> sp)
    {
        if (sp) // operator bool(): 
            std::cout << "*sp=" << *sp << "\n";
    }

    int main()
    {
        std::shared_ptr<int> sp = std::make_shared<int>(1);
        f(sp);
    }

(5) 指定 deleter(删除器)
    
    resource's RC = 0 时, 自动调用 deleter
    
    [1] 
        // class A;
        std::shared_ptr<A> sp(new A);
                                        |
                                        |   等价 
                                        |
        std::shared_ptr<A> sp(new A, [](A* p) { delete p; } )
        
            |
            |   问题: std::shared_ptr 的默认 deleter 不支持 数组对象 
            |/
    [2] 用 std::default_delete
        
        std::shared_ptr<A> sp(new A[2], [](A* p) { delete[] p; } )
                                            |
                                            |   等价 
                                            |
        std::shared_ptr<A> sp(new A[2], std::default_delete<A[]> );
            |
            |   封装 
            |/
        template <typename T>
        std::shared_ptr<T>
        make_shared_array(size_t n)
        {
            return std::shared_ptr<T>(new T[n], std::default_delete<T[]>() );
        }

        #include <memory>

        // === 不支持 internal ptr 指向 1维数组 
        template <typename T>
        class Deleter
        {
        public:
            void operator()(T* p) const
            {
                delete p;
            }
        };

        template <typename T>
        std::shared_ptr<T>
        make_shared_array(size_t n)
        {
            return std::shared_ptr<T>(new T[n], std::default_delete<T[]>() );
        }

        int main()
        {
            // std::shared_ptr<int> sp(new int[2], Deleter<int[] >()); // compile error

            std::shared_ptr<int> sp1(new int, Deleter<int>() ); // ok
            std::shared_ptr<int> sp2(new int[2], std::default_delete<int[]>() );
            std::shared_ptr<int> sp3 = make_shared_array<int>(2);
        }
    
(6) operator*   operator->

(7) use_count() : resource's RC 

(8) Note 

    [1] 1 个 native ptr 不能 初始化 多个 shared_ptr
                                            |
                                            |/
                                        RC 不正确 
        int* p = new int;
        std::shared_ptr<int> sp1(p);
        std::shared_ptr<int> sp2(p); // logic error 
        
    [2] 不要在 `函数实参` 中 `创建 shared_ptr`                
    
        void f(shared_ptr<int>(new int(1) ), g() );8
            
        编译器 重排 同一语句内 各基本操作的 顺序 
                
        => new 与 shared_ptr 间 其他 操作 抛出异常    
        => 内存泄露 
                    |
                    |   解决 
                    |/
                先创建 shared_ptr, 再 函数实参 
            
        f1(std::shared_ptr<A>(new A), f2() ); 
            |
            |/
        std::shared_ptr<A> sp1(new A);
        f1(sp1, f2() );
            
    [3] this 指针 ( 作实参 构造的 shared_ptr ) 不能直接 return 
            |
            |   本质是 native ptr 
            |/
        同一 this 指针 构造 2个 shared_ptr 没有关联
                            
            this 被 delete 2次 
                |
                |   解决 
                |/
        std::enable_shared_from_this<> 内部的 
            weak_ptr 通过 已构造的 sp 构造 newSp 返回
        
            => weak_ptr 关联起 已构造的 sp 与 newSp 
            
        // 运行报错
        #include <memory>
        #include <iostream>

        struct A
        {
            std::shared_ptr<A> getSelfSp()
            {
                return std::shared_ptr<A>(this); // don't do it 
            }
        };

        int main()
        {
            std::shared_ptr<A> sp1(new A);
            std::shared_ptr<A> sp2 = sp1->getSelfSp();
        }
        |
        |/
        #include <memory>
        #include <iostream>

        struct A: public std::enable_shared_from_this<A>
        {
            std::shared_ptr<A> getSelfSp()
            {
                return shared_from_this();
            }

            ~A() { std::cout << "A dtor \n"; }
        };

        int main()
        {
            std::shared_ptr<A> sp1(new A);

            // 获取 自身智能指针的函数 仅在 sp Ctor 被调用之后才能用
            std::shared_ptr<A> sp2 = sp1->getSelfSp();
        }   
            
    [4] 避免 循环引用 
        
        2 个 struct (A B)
            |
            |   都有1个 `指向对方类型(A B) 的 shared_ptr` 成员 
            |                           |
            |/                          |
        分别被                             |/
        `2 个 shared_ptr` 管理     实际 也指向 `对方 obj`
                
        => 2个 resource 的 RC 均= 2
            
        2 个 shared_ptr 离开 scope 时, 2份 resource’s RC 只减 到 1, 而不是 0 
        => 2 份 resource(A B) 都不会被 析构: 内存泄露
            |
            |   解决 
            |/
        A B 之一 的 成员 shared_ptr 改为 weak_ptr 
            如 B 内部用 weak_ptr
            => resourceA/spA 的 RC = 1, resourceB/spB 的 RC = 2
            
            spA 出 scope 时, spA 的 RC 减 1 变为 0 => resourceA 析构
                => 调 成员 sp<B> 的 dtor => resourceB/spB 的 RC 减 1 变为 1 
            
            spB 出 scope 时, spB 的 RC 减 1 变为 0 => resourceB 析构
        
        #include <memory>
        #include <iostream>

        struct A;
        struct B;

        struct A
        {
            std::shared_ptr<B> spB;
            ~A() { std::cout << "A dtor \n"; }
        };
        struct B
        {
            std::shared_ptr<A> spA;
            ~B() { std::cout << "B dtor \n"; }
        };

        void test()
        {
            {
                std::shared_ptr<A> spA(new A);
                std::shared_ptr<B> spB(new B);

                spA->spB = spB;
                spB->spA = spA;
            }
        }

        int main()
        {
            test();
        }
        // print: 无
        |
        |/
        #include <memory>
        #include <iostream>

        struct A;
        struct B;

        struct A
        {
            std::shared_ptr<B> spB;
            ~A() { std::cout << "A dtor \n"; }
        };
        struct B
        {
            std::weak_ptr<A> wpA;
            ~B() { std::cout << "B dtor \n"; }
        };

        void test()
        {
            {
                std::shared_ptr<A> spA(new A);
                std::shared_ptr<B> spB(new B);

                spA->spB = spB;
                spB->wpA = spA;
            }
        }

        int main()
        {
            test();
        }
        // print 
        A dtor
        B dtor

4.2 std::unique_ptr 独占的 SP

与 shared_ptr 区别 

(1) 独占性 & copy delete 
        
    std::unique_ptr<int> up1(new int);
    std::unique_ptr<int> up2 = up1; // compile error 
    
    可匹配 move ctor/assignment 
        两侧均 左值 => 必须用 std::move() 显式 move 
        1侧为 右值: implicitly move 
        
    std::unique_ptr<int> up2 = std::move(up1);
        
(2) 可 指向 数组 
        
    std::unique_ptr<int []> up(new int[2]); 

    std::shared_ptr<int []> sp(new int[2]); // 编译报错
    
(3) C++11 还没提供 make_unique() 

    自定义不难 
    
    // version1: 支持 普通指针 
    template<typename T, typename... Args>
    typename std::enable_if<!std::is_array<T>::value, std::unique_ptr<T> >::type 
    make_unique(Args&&... args)
    {
        return std::unique_ptr<T>(new T(std::forward<Args>(args)... ) );
    }

(4) 可自定义 Deleter 
    
    ——————————————————————————————————————————————————————————————————————
                    函数对象     |                  lambda                  
    ——————————————————————————————————————————————————————————————————————
                                 |      不捕获         |   捕获 
                                 |  可转换为 函数指针   |
    ——————————————————————————————————————————————————————————————————————              
    第2模板参数  |   Functor 类型 |    函数指针 类型     |   std::function 
    ——————————————————————————————————————————————————————————————————————
    第2实参    |    不需要     |  lambda              |   lambda 
    ——————————————————————————————————————————————————————————————————————  
    
    // (1)      
    std::unique_ptr<int, void(*)(int*)> up(new int(1), [] (int* p) {delete p; } );      
                                  |                           |\
                                  |_ _ _ _ _ _ _ _ _ _ _ _ _ _|
    // (2)                            
    std::unique_ptr<int, std::function<void (int*)> > 
        up(new int(1), 
           [&] (int* p) {delete p; } );     
    
    // (3)
    #include <memory>
    #include <iostream>

    template <typename T>
    class Deleter
    {
    public:
        void operator()(T* p) const
        {
            std::cout << "deleter:\n";
            delete p;
        }
    };

    int main()
    {
        std::unique_ptr<int, Deleter<int> > up(new int(1) );
    }

4.3 std::weak_ptr 弱引用 的 SP

      resource/referenced object - - 
        |           |               |
    own |   mod RC  |               |
        |           |               |
    shared_ptr1-----shared_ptr12    |
          |                         |
          |                         | non-owning: not mod RC, 查 referenced object 的 use_count()
          |                         |
        weak_ptr - - - - - - - -  - -
                             
weak reference
 
    weak 的含义 
        non-owning resource
            不能操作 resource: 没重载 (2种)解引用运运算符 * ->
            其 构造/析构 not modify resource RC 
                
        weak_ptr 必须转换为 shared_ptr 才能 access resource
    
(1) 目的 
    
    observe/track resource 是否被 destory 
        
    => 可 解决 循环引用 
     
(2) 用法 

    ————————————————————————————————————————————
    [0] Ctor 实参: shared_ptr
    
    [1] use_count() 获得 所观测  resource's RC
        
    [2] expired()   <=>             use_count() == 0
    
    [3] lock()      获得 所监视的 shared_ptr
    ————————————————————————————————————————————

    // sp1 赋值给 sp2, 两者的 weak_ptrs 均 == 1, 
    // 全局 weak_ptr 通过 sp1 观察 resource 后, 两者的 weak_ptrs == 2
    #include <iostream>
    #include <memory>

    std::weak_ptr<int> gw;

    void observeRefObj()
    {
        std::cout << "gw.use_count() == " << gw.use_count() << "; ";

        // we have to make a copy of shared pointer before usage:
        if (std::shared_ptr<int> sp = gw.lock())
        {
            std::cout << "gw.use_count() == " << gw.use_count() << "; ";
            std::cout << "*sp == " << *sp << '\n';
        }
        else
            std::cout << "weak_ptr's shared_ptr empty\n";

        if ( gw.expired() )
            std::cout << "resource destoryed\n";
        else
            std::cout << "resource not destoryed\n";
    }

    int main()
    {
        {
            std::shared_ptr<int> sp1 = std::make_shared<int>(10);

            auto sp2 = sp1;

            gw = sp1;

            observeRefObj();
        }
        observeRefObj();
    }
    // print 
    gw.use_count() == 2; gw.use_count() == 3; *sp == 10
    resource not destoryed
    gw.use_count() == 0; weak_ptr's shared_ptr empty
    resource destoryed

4.4 通过 SP 管理第3方库 分配的内存

第 3 方库  
    
    内存分配 -> return native ptr 
    
    内存释放
    
        用 第 3 方接口 
        
        void* p = GetHandle()->Create(); // Factory Method 
        // ...
        GetHandle()->Release(p);
        |
        |   智能指针 
        |/
        void* p = GetHandle()->Create();
        std::shared_ptr<void> sp(p, 
            [this](void* p){ GetHandle()->Release(p); } )
参考
https://zhuanlan.zhihu.com/p/337656226
https://www.jianshu.com/p/03eea8262c11
上一篇 下一篇

猜你喜欢

热点阅读