Effective C++ 3: Resorce Managem

2021-07-21  本文已影响0人  my_passion

1 用 object 管理 资源: 资源管理 class / RAII class

    1   `手动 释放 资源`
    
        Factory Method 提供 A 继承体系 的 `动态分配对象`, caller 负责 释放 对象管理的资源 

            A* createA();
        
        问题: 3 种 场景 + 2 个 泄露 
        
            ————————————————————————————————————————————————————
            1) 抛出异常 
            
            2) 过早 return 

            3) 循环内 delete 因某个 continue / goto 而 过早退出
            ————————————————————————————————————————————————————    
            
            ————————————————————————————
            `泄露  
                [1] 对象内存
                [2] 对象 所保存的 资源`
            ————————————————————————————

            |
            |   解决
            |/
        
    2   `RAII: 用 对象 管理 资源 / put 资源 to 对象` + 依赖 C++ 的 `dtor 自动调用机制` 确保 `资源被释放`
           |
           |/
          init. 初始化 -> 实际 也可以是 `赋值` 
    
        `SP ( smart pointer )`
            ————————————————————————————————————————————————————————————————————————————————————————————————
            std::auto_ptr   |   [1] 多 auto_ptr 不能同时指向 同一 资源
                            |   [2] `copying 函数 复制` auto_ptr 时, 转移所有权, `源 auto_ptr 变 null`
                            |           `STL 容器 容不得 auto_ptr`
                            |               `STL 容器 要求其 elem` 有 `not 转移所有权 的 复制行为`
            ————————————————————————————————————————————————————————————————————————————————————————————————                                    
            std::shared_ptr |   [1] RC( 引用计数 )
                            |   [2] 可启用第2参数 deleter 
            ————————————————————————————————————————————————————————————————————————————————————————————————    
            相同          |   `dtor 内 delete 单个 元素, 而不是 delete 数组 / delete []`
            ————————————————————————————————————————————————————————————————————————————————————————————————
            
    3   令 factory Method 返回 SP 而不是 raw pointer` 
                    
            `强迫 client 用 存储 raw pointer 的 SP`

                std::shared_ptr<A> createA();

2 在 资源管理 class 中 小心 copying 行为

    1   用 C API 处理 mutex

            2 个 函数
                ——————————————————————————————————————————————
                void lock(Mutex* pm);   // 锁住 pm 所指 mutex
                void unlock(Mutex* pm); // 解锁 mutex
                ——————————————————————————————————————————————
    
    2   `client 按 RAAI 方式 使用 Lock`

            Mutex m;            // 1) 定义 mutex
            ...
            {                   // 2) 设 block 来定义 critical section ( `临界区` )
                Lock lk(&m);    // 3) 锁住 mutex
                ...             // 4) 执行 `临界区` 内 操作
            }                   // 5) block 末尾, 自动解除 mutex 锁定

    3   `资源管理(RAII) 类`
        
        ——————————————————————————————————————————————————————————————————————————————————————————————
        资源管理 class / resource handler
            
            ——————————————————————————————————————————————————————————————————————————————————————————
            脊柱: RAAI
            ——————————————————————————————————————————————————————————————————————————————————————————
                [1] 管理 heap-based 资源        |   auto_ptr / shared_ptr
                ——————————————————————————————————————————————————————————————————————————————————————  
                [2] 管理 non-heap-based 资源    |   自建 `资源管理 class + shared_ptr 作 internal ptr` 
                            |                   |               |
                            |   如           |               |   如
                            |/                  |               |/
                            mutex               |               Lock
        ——————————————————————————————————————————————————————————————————————————————————————————————
        
        internal raw ptr: Mutex* => 管理的资源 是 Mutex 

            RAAI 对象 被 copy 时, 发生什么 ?
        
                答: 4 种需求 & 处理

                    (1) 禁止 copy 

                            `copy 行为` 对 RAAI class `不合理`

                                应用: Lock + private 继承 Uncopyable 

                    (2) resource RC

                            想保持 resource, 直到 最后1个 RAAI object 被 销毁

                                RAAI class 
                                    `internal ptr 由 native ptr 改为 RCSP` 
                                                        |             |
                                                        |/            |/
                                                    Mutex*          shared_ptr<Mutex>
                                                                    |
                                                                    |   问题: `shared_ptr 所指 Resource 的 RC = 0 时, 默认动作` 是
                                                                    |/
                                                        delete 其 所指物`,
                                                            而我们目标 `释放动作` 是 `解除锁定`
                                                                |
                                                                |   解决: 启用 shared_ptr 第 2 参数
                                                                |/
                                                            deleter
                                                                function 或 function object ( unlock() )
                                                                    RC = 0 时, 被 调用
                                                                        Note: auto_ptr 无次机能

                        Note    
                            该 RAAI class 不必 explicitly 声明 dtor
                                compiler implicitly 自动生成 的 dtor 版本 
                                    会 自动调用 其 成员 RCSP/shared_ptr 的 dtor
                                        在 RC = 0 时, 自动调 deleter

                    (3) deep copy

                        copy RAAI 对象 + copy 底层 resource 同时进行 
                            |                       |
                            |   如                  |
                            |/                      |/
                        某种 `字符串`        `internal ptr 指向 heap 内存`
                            
                    (4) `move ownership of 底层 resource`

                        希望 `只有 1 个 RAAI 对象` 指向 raw resource`
                                        |
                                        |   如
                                        |/
                                    auto_ptr
                                        resource 的 ownership 转移: 从 源对象 转移到 目标对象

3 在 资源管理 class 中 访问 原始 resource

    引入
        `C API 通过 RAII class(标准库 或 自建) 的 explicit / implicit conversion 
    
            get() 成员函数 / operator-> 或 operator* 或 类型转换运算符 

                将 RAAI object 转换为 internal ptr/Handler to raw resource
                
                    进而访问 raw resource

    (1) 资源管理 class / RAII 类 转换为 internal raw handle/ptr to resource
    
            ——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
            [1] explicit conversion |                   |   get() 成员函数      |   [1]优: 安全            
                                    |                   |                       |   [2]缺: 麻烦
            ————————————————————————    shared_ptr      ——————————————————————————————————————————————————————————————————————————————————————————————
                                    |   auto_ptr        |   2种 解引用 运算符  |   std::shared_ptr<A> sp1( createA() ); // A* createA();
                                    |                   |       operator->      |   bool isTrue  = !( sp1->isF() );      // A::isF()    
                                    |                   |       operator*       |   bool isTrue2 = !( (*sp2).isF() );   
            [2] implicit conversion ——————————————————————————————————————————————————————————————————————————————————————————————————————————————————
                                    |                   |                       |   [1]优: 方便 C API 调用       
                                    |   自建 RAII 类   |    类型转换运算符        |   [2]缺: 易出错 
                                    |                   |                       |       copy RAII 对象, 误写为 隐式转换为 其 internal Handle 的 copy,                                       
                                    |                   |   X::operator T()     |           RAII对象 销毁时, resource 释放 + `目标 handle/ptr 悬挂` 
                                    |                   |                       |               RAIIClass rAIIObj( getAHandle() );
                                    |                   |                       |               InternalHandle internalHandle = rAIIObj;                            
            ——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

4 new / delete pair 形式要相同

    1   new/delete 背后: 3/2 条 基本操作

    2   delete
            
            (1) 问题
                
                若用错了 delete 形式 => undefined behavior
            
            (2) Note    
                    single object 和 object array `内存布局` 不同
                    
                        object array 的 内存 含 `array size record`
                        
    3   `class 的 internal ptr 指向 动态分配内存` + 有 `多个 ctor` 时
                
                所有 ctor 中 要用 `同形式 new` 初始化 internal ptr 
                                        |
                                        |/
                                    都 new / 都 new []

                    原因: dtor 只有 1 个 => 只能有 1 种 delete 形式

    4   `不要用 array 形式 做 typedef`

            |   避免 用错 delete 形式 
            |
            |   替换为
            |/
        标准库中 `string / vector 等`
            可 `将 array 需求 降为 几乎为 0`

5 以 独立语句 存 new 的 object 到 智能指针

    `compiler` 有 `重排 同一 statement` 各 `基本操作` (的 执行次序) 的 自由
                            |                   |
                            |                   |   
                            |/                  |/
        f( shared_ptr<Widget>(              [1] new Widget
            new Widget ), getVal() );       [2] shared_ptr ctor
                                            [3] getVal() 
        void 
        f(std::shared_ptr<Widget> spw,          |
          int val);                             |
                                                |
                                                |/
                若 编译器 重排 函数调用语句 f(.) 为 [1] -> [3] -> [2] 
                    
                    + [3] 抛出异常 
                        
                        => `new 返回的 ptr 丢失 / 未被置入 shared_ptr 内`
                        => `资源泄漏`
                            |
                            |   解决 
                            |/
                    `分离 2个操作 于 不同 statement` 
                    
                    compiler 对 `跨 statement 的 各操作` 没有 `重排 其 基本操作` 的自由
                    
                        std::shared_ptr<Widget> pw( new Widget );
                        f( pw, getVal() );
=== 详细 
    概述 
        1 资源
        
            `使用后必须还给系统` 的 东西

                C++ 中 常用资源

                    ——————————————————————————————————————————
                    `动态分配 的 内存`: 
                        `分配了 却 未曾归还`, 导致 `内存泄露`

                    file descriptor

                    mutex lock

                    数据库连接

                    网络 socket
                    ——————————————————————————————————————————
    
        2 `手动 归还资源` 的 问题
              |
              |/
            非 compiler 自动 调 dtor 等 方式

            1) 异常
            
            2) 提前 return / 跳出 (因 循环内的 continue / goto)

                等 导致 归还未被执行
        
        3 以 对象 管理资源

            以 ctor / dtor / copying 函数

                严格遵守这些做法, 可以几乎消除 资源关联问题

1 用 object 管理 资源: 资源管理 class / RAII class

    1 手动 释放 资源
    
        Factory Method 提供 A 继承体系 的 `动态分配对象`, caller 负责 释放 对象管理的资源 

            A* createA();
        
                void f()
                {
                    A* createA();
                    ...
                    delete pInv;
                }
                
        问题: 3种场景 + 2个泄露 
        
            ————————————————————————————————————————————————————
            1) 抛出异常 
            
            2) 过早 return 

            3) 循环内 delete 因某个 continue / goto 而 过早退出
            ————————————————————————————————————————————————————
            
            
            ————————————————————————————
            `泄露  
                [1] 对象内存
                [2] 对象 所保存的 资源`
            ————————————————————————————

            |
            |   解决
            |/
        
    `2 RAII: 用 对象 管理 资源 / put 资源 to 对象` + 依赖 C++ 的 `dtor 自动调用机制` 确保 `资源被释放`
          |
          |/
         init. 初始化 -> 实际 也可以是 `赋值` 
    
        `SP ( smart pointer )`

            (1) std::auto_ptr

                限制

                    1> `auto_ptr 销毁时, 自动删除 其 所指之物` 
                        
                        => 多 auto_ptr 不能同时指向 同一 资源
                            
                            否则, delete 多次 => undefined behavior
                        
                    2> `用 copying 函数 复制` auto_ptr 时, 
                            
                            `源 auto_ptr 变 null`, `目的 auto_ptr 获得 资源 唯一拥有权`

                            `STL 容器 容不得 auto_ptr`
                            
                                `STL 容器 要求其 elem` 有 `not 转移所有权 的 复制行为`

                            std::auto_ptr<A> 
                                pInv1( createA() );
                                
                            std::auto_ptr<A> 
                                pInv2(pInv1); // pInv2 指向 object, pInv1 = null

                            pInv1 = pInv2;    // pInv1 指向 object, pInv2 = null

                    |
                    |   引入 
                    |/

                RC(reference-counting)
                    
                    追踪 有多少个 RCSP object 指向 资源,` 并在 `最后1个 RCSP 销毁时, 自动删除 资源`
                    |
                    |   如 
                    |/
            (2) std::shared_ptr
            
                    void f()
                    {
                        std::shared_ptr<A> 
                            pInv1( createA() );
                        
                        std::shared_ptr<A> 
                            pInv2(pInv1);  // pInv2/pInv1 均指向 object

                        pInv1 = pInv2;     // 同上
                        
                    } // pInv1/pInv2 两者 最后一个销毁时, 其 所指对象 被自动销毁

            Note
                auto_ptr 和 shared_ptr `相同点`

                    `dtor 内 delete 单个 元素, 而不是 delete 数组 / delete []`

                        => `不该` 用于 `动态分配数组`

                                std::auto_ptr/shared_ptr<int> sp(new int(10) );

                    标准库 没 设计 `动态分配 数组` 的 SP
                            |
                            |   reason
                            |/
                        `vector 和 string 几乎` 总是可以 `取代 动态分配数组`

    3 conclusion

        (1) 手动 释放资源 易出错

        (2) 资源管理 class
                标准 库 std::auto_ptr / std::shared_ptr 
                    往往能 轻松满足 本条款 忠告

                有时需自己定制

        `(3) 令 factory Method 返回 SP 而不是 raw pointer` 
                
                `强迫 client 使用 存储 raw pointer 的 SP`

                    std::shared_ptr<A> createA ();

2 在 资源管理 class 中 小心 copying 行为

    `1  non-heap-based 资源管理: 自建 资源管理类`

        (1) 用 C API 处理 mutex object

            2 个 函数:
                void lock(Mutex* pm);   // 锁住 pm 所指 mutex
                void unlock(Mutex* pm); // 解锁 mutex

        (2) 设计 RAII 类 Lock 来 管理 Mutex
                
                确保 被锁住 的 Mutex 定会被 解锁

                    class Lock
                    {
                    public:
                        explicit Lock(Mutex* pm)
                            : mPtr(pm) 
                            { lock(mPtr);}
                        
                        ~Lock() { unlock(mPtr); }
                    private:
                        Mutex* mPtr;
                    };


        (3) `client 按 RAAI 方式 使用 Lock`

            Mutex m;  // 1) 定义 mutex
            ...
            {   // 2) 设 block 来定义 critical section (临界区)
                Lock lk(&m);  // 3) 锁住 mutex
                ...           // 4) 执行 critical section (临界区) 内 操作
            }                 // 5) block 末尾, 自动解除 mutex 锁定

        (4) 但, Lock 对象 被 copy 时, 会 发生什么 ?

            Lock lk1(&m);
            Lock lk2(lk1);

            问题一般化为: `RAAI 对象 被 copy, 会发生什么 ?`

    `2  RAAI object 被 copy 时 的 需求 和 处理`

        (1) 禁止 copy
        
            class Lock: private Uncopyable // 禁止 copy
            {
            public:
                ...    // 如前 
            };

        (2) resource RC + 启用 shared_ptr 第 2 参数 deleter 去 执行 释放操作
        
            class Lock
            {
            public:
                explicit Lock(Mutex* pm)
                    : mSp(pm, unlock)
                    { lock( mSp.get() ); /* get: 获得 internal raw ptr */ }
            private:
                std::shared_ptr<Mutex> mSp;
            };

3 在 资源管理 class 中 访问 原始 resource

        自定义 RAII class + internal raw pointer/Handle to resource

            resource: 字体 <- internal Handle: AHandle <- RAAI class: A

            C API
                void changeASIze(AHandle ah, int size);     

            explicit conversion
                get() 成员函数
                    AHandle get() const { return ah; };
                        
                client 每次 用 API, 就要调 get() -> 到处显式转换 -> 麻烦
                    changeASIze(A.get(), newASize);

            implicit conversion
                A::operator AHandle() const
                    { return ah; }

                client 调 C API 时 轻松自然
                    changeASIze(A, newASize); // A 隐式转换为 AHandle obj

                但 隐式转换 增加错误发生机会

                    RAIIClass rAIIObj( getAHandle() );
                    // ...
                    InternalHandle internalHandle = rAIIObj; 

                    client 原意是 copy RAII object, 却 误写了:
                        RAII object 隐式转换为 其 internal Handle 的 copy, 
                            该 copy 再 assignment 给 新 internalHandle

                    => 新 internalHandle 被 销毁时, resource  被释放
                    => 新 internalHandle 成 `dangle/悬挂`

    `1 资源管理 class` 可对抗 `资源泄漏`

        [1] 通常 `必须 用 资源管理 class 处理 与 resource 间 交互`

            `API 不应 直接 访问 raw resource`

        [2] 有时 `需要 用 API 处理 resource`
        
            int dayHeld( const A* pI );
        
            `SP 如 auto_ptr 和 shared_ptr 可用于 
                保存 factory Method return 的 raw pointer to resource`
                    std::shared_ptr<A> pInv( createA() );

                    只要 用1个 函数 可 
                        将 RAAI class object 转换为 其 internal raw ptr to resource

                    auto_ptr 和 shared_ptr 用 `get() 成员函数 / operator-> operator*`
                        可实现该 explicit / implicit conversion

                int days = dayHeld(pInv.get() );

                class A
                {
                public:
                    bool isF() const;
                };

                A* createA();

                std::auto_ptr<A>
                std::shared_ptr<A> sp1(createA() ); // A* createA();
                bool isTrue  = !( sp1->isF() );         // A::isF()
                bool isTrue2 = !( (*sp2).isF() );

    2 自建 RAAI class

        类型转换运算符 
            `将 class type 值` 转换为 `其他 type 值`
            
            X::operator T()
                mem func
                return_type 即 operator 后的 type
                paraList 空

        ```
        // 不准确实现
        
        (1) Resource raw Handle/ptr
            
            class AHandle {};

        (2) C API 
            
            AHandle getAHandle() { return AHandle(); }

            void releaseResource(AHandle ah){ }

            void changeASize(AHandle ah, int newSize){ }

        (3) RAAI 类 
        
            class A                                       // RAII class
            {
            private:
                AHandle ah;                               // raw Handle/pointer to resource
            public:
                explicit A(AHandle ah_)                   // 获得资源, pass by value 因为 C API 也这么做
                    : ah(ah_) {}
                    
                ~A() { releaseResource(ah); }             // 释放资源

                AHandle get() const { return ah; }        // 显式转换
                
                operator AHandle () const  { return ah; } // 隐式转换
            };

            int main()
            {
                A a( getAHandle() );
                int newASize = 10;

                changeASize(a.get(), newASize);
                
                changeASize(a, newASize);
            }

    3 conclusion

        `(1) C API 往往要 访问 raw resource
            
            => `RAAI class 应` 提供 `access 其 所管理资源` 的 方法/interface

        `(2) acess resource 可经由 explicit conversion ( 较 安全 ) 或 implicit conversion ( 对 client 较 方便 )`

4 new / delete pair 形式要相同

image.png
    `1 new/delete 背后: 3/2 条 基本操作`

        new
            1) 分配内存     operator new
            2) 强转 为 class type
            3) 针对此内存 调 class ctor

        delete
            1) 针对此内存 调用 class dtor
            2) 释放内存     operator delete

    `2 delete`
        
        (1) 问题
            
            若用错了 delete 形式 => undefined behavior
        
                (1) 若对 strPtr1 用 delete 数组 形式 ( delete [] p )
            
                    delete 读 若干 memory 解释为 array size = n + 调 n 次 析构 + 释放 n 块 object memory => undefined behavior

                (2) 若对 strPtr2 用 delete 元素 形式 (delete p ) => 只析构1次 => undefined behavior
            
        (2) Note    
                single object 和 object array `内存布局` 不同
                
                    object array 的 内存 含 `array size record`
                        
                        用于告诉 delete, 应该 
                            `调   多少次 dtor`
                            `释放 多少个 object 的 memmory`
                            
                        对 `client` 来说, 只需要 `用 delete 数组的形式: delete [] ptr,
                                        
                            std::string* strPtr1 = new std::string;
                            std::string* strPtr2 = new std::string[100];
                            ...
                            delete strPtr1; 
                            delete [] strPtr2;

    3   `class 的 internal ptr 指向 动态分配内存` + 有 `多个 ctor` 时
            
            所有 ctor 中 要用 `同形式 new` 初始化 internal ptr 

    4   `不要用 array 形式 做 typedef`

            typedef std::string arr[3];

            std::string* strPtr = new arr; // <=> new string[3]

            delete strPtr;    // undefined behavior

            delete [] strPtr; // ok

        arr 类型 可设为 vector<string>

5 以 独立语句 store newed object in 智能指针

上一篇下一篇

猜你喜欢

热点阅读