美文网首页
Effective C++ 3: Resorce Managem

Effective C++ 3: Resorce Managem

作者: my_passion | 来源:发表于2021-07-21 23:23 被阅读0次

    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 智能指针

    相关文章

      网友评论

          本文标题:Effective C++ 3: Resorce Managem

          本文链接:https://www.haomeiwen.com/subject/eywrmltx.html