zoukankan      html  css  js  c++  java
  • C++ 对象资源管理惯用法

    原文链接http://blog.csdn.net/breakerzy/article/details/7593137

    关于 C++ 对象资源管理的惯用法,note-to-self + keynote + idiom case + cross-reference 式笔记

    keyword: RAII, deleter, Two-stage Initialization, pimpl, Reference Counting (RC), Copy on Write (COW), Smart Pointer (SP)

    目录


    C 语言的资源管理方法^

    见 [CPP LANG] 14.4 Resource Management

    e.g. Very Beginning

    1. void copy_file(const char* src, const char* dst)  
    2. {  
    3.     FILE* srcFile = fopen(src, "r");  
    4.     if (srcFile == NULL)  
    5.         goto _RET;  
    6.   
    7.     FILE* dstFile = fopen(src, "w");  
    8.     if (dstFile == NULL)  
    9.         goto _CLOSE_SRC;  
    10.   
    11.     // read source file, and transform it's content.  
    12.     if (HANDLE_FAILED)  
    13.         goto _CLOSE_DST;  
    14.   
    15.     // end processing  
    16. _CLOSE_DST:  
    17.     fclose(dstFile);  
    18. _CLOSE_SRC:  
    19.     fclose(srcFile);  
    20. _RET:  
    21.     return;  
    22. }  

    引出 Resource Management 的基本要求:

    1. 离开时释放, Release before returning.
    2. 按照资源的申请的相反顺序释放资源, Resources are released in the reverse order of their acquisition.

    其它的资源释放手法(不建议):

    1. do-while-break 式:教条地避免使用上述 goto 式的变形
    2. throw-catch 式:throw 内建类型,通常是 int, char*,效率低、代码乱

    RAII^

    RAII (Resource Acquisition Is Initialization) 资源申请即初始化,是 C++ 资源管理的主流技术和基石

    见 [CPP LANG] 14.4.1; [EFFECT CPP] Item 13, 14; wiki: RAII
    见 Stack Unwinding 堆栈回退, wiki: Call Stack

    注意:

    1. RAII 式类要求其组成部分(基类和成员)也是 RAII 式的,对于那些非 RAII 的部分需要手动管理资源(在析构函数中释放),如:

      • stdlib 类大多是 RAII 式的,如 iostream, string, STL container
      • MFC 的某些类是 RAII 式的,如 CWnd, CGdiObject
      • COM 接口不是 RAII 式的,需手动调用 Release 方法,但可用 CComPtr 等 SP 封装,使其成为 RAII
    2. 不要让析构函数抛出异常,见 [CPP LANG] 14.4.7; [EFFECT CPP] Item 8

    Sample:

    deleter^

    见 [EFFECT CPP] Item 14; [BOOST TUTORIAL] 3.4.8

    deleter 删除器:如果资源不限于内存分配型,则需要用一种灵活的、统一的方法指定资源的释放操作,如 TR1/Boost 的 shared_ptr 的构造函数第二个参数指定 deleter

    Sample:

    • a batch of deleters: 可配置 Check(检查是已释放)和 Zeroed(释放后置零),释放操作包括 delete, delete[], free, Release (COM), CloseHandle, fclose

    Two-stage Initialization^

    见 Two Stage Construction in C++ versus Initializing ConstructorsRAII in C++Google C++ Style Guide: Doing Work in Constructors 中文翻译

    Two-stage Initialization/Construction (abbr. 2-stage init) 两阶段初始化/构造:

    1. stage 1, 调用构造函数,初始化对象本体
    2. stage 2, 调用对象的 init 方法,初始化对象涉及的资源,这里的 init 是形式名,例如 fstream::open, auto_ptr::reset, CWnd::Create 是 init 的具现名。CWnd::Create 的 2-stage 是强制的(MFC 风格),而 fstream 和 auto_ptr 可用 init,也可用构造函数

    Why 2-stage init? 或者说它能带来什么好处:

    1. 可复用对象本体

    2. 对象数组初始化。因为没有语法指定初始化数组时,每个单元该如何构造(POD 例外,可用 {} 初始化每个单元),只能统一地用默认构造函数初始化,然后对每个单元调用 init 初始化

      替代方法:用放置式 placement new + 构造函数代替 init,std::allocator::construct 使用这种手法,更多 placement new 见 [EFFECT CPP] Item 52; [MEFFECT CPP] Item 8

    3. 如何说明对象初始化后的状态,如报告初始化过程的错误:

      1. init 方法可用返回值表示。Google C++ Style 倾向使用这种方法:构造函数 (init stage 1) 只做简单初始化,称为 trivial init。真正有意义的 non-trivial init 在 init 方法中进行。这是因为 Google C++ Style 不建议使用异常系统

      2. 构造函数通常用抛出异常的方法。我一般用这种方法,而不用 2-stage init,我觉得 2-stage init fucked RAII。当然,当初始化错误的预期较高,并且是效率敏感处的大量对象初始化时,2-stage init 是优选

      3. 设置对象内的状态描述变量,适用于构造函数和 init。不建议用设置对象状态变量的方法 3,除非对象本身有较强的 state-driven 特点

    More about 2-stage init:

    1. public init vs. private init

      只有 public init 是为了 2-stage init 的目的,而 private init 是另外一个东西,它多半是为了将多个重载的构造函数之公共部分抽成一个 init 函数以减少代码

    2. init vs. re-init

      当用户使用 init 时,其实际的语义是 re-init 吗?即执行过程:

      1. 先判断对象是否已分配资源,如果是,则需释放
      2. 申请新的资源

      于是:RAII 让资源管理变简单,而使用 2-stage init 又让事情变复杂

    3. init vs. ctor

      考虑两种方法的效率 (pseudocode):

      1. 使用构造函数:

        1. // d 是 for 的 scoped 型对象, 下面情况都会销毁 d  
        2. // 1. 条件退出 2. break 退出  
        3. // 3. 每次迭代结束 4. continue 结束本次迭代  
        4. for (int i = 0; i < NUM; i++) {  
        5.     Data d(dataFile);  
        6.     d.process();  
        7. }  
      2. 使用 init:

        1. // 设 init 是 re-init 式的  
        2. Data d;  
        3. for (int i = 0; i < NUM; i++) {  
        4.     d.init(dataFile);  
        5.     d.process();  
        6. }  

      init 赚不了什么便宜,而使用构造函数的版本可籍由 拖延构造对象手法 获利,见 [EFFECT CPP] Item 26

    pimpl^

    见 [EFFECT CPP] Item 14, 25, 29, 31

    pimpl (Pointer to Implementation) 实质是实现 (Implementation) 和封装 (Wrapper) 分离,Bjarne 习惯把 Implementation 叫做 表示 (Representation)

    注意:

    1. 开销:

      空间开销变小:只有 pimpl 指针
      时间开销变大:多了解引操作

    2. 施行拷贝 (assgin, copy ctor) 的方式:

      1. Shallow Copy 浅拷贝:仅拷贝 pimpl 指针,例如标准 SP:auto_ptr, share_ptr
        浅拷贝使 wrapper 具有引用语义 (Reference Semantics),如果想使浅拷贝具有值语义,可用 RC + COW (Copy on Write) 写时复制手法

      2. Deep Copy 深拷贝:拷贝 pimpl 所指物
        深拷贝使 wrapper 具有值语义 (Value Semantics),又拷贝语义

    Reference Counting^

    见 [EFFECT CPP] Item 13, 14; [MEFFECT CPP] Item 17, 29

    Reference Counting (RC) 引用计数

    一般不用从零写 RC 类,只要类中包含 RC 的组成部分即可,如利用 share_ptr 成员

    Sample:

    • class Person: RC + COW 简单示例

    • class String: 改自 [CPP LANG] 11.12 A String Class

      技术:pimpl, RC, COW, Proxy class。Proxy class 用以区分 operator[] 的读写语义:R-value Usage vs. L-value Usage,见 [MEFFECT CPP] Item 17, 30

      功能:值语义的 string 类示例,没有模板化之 CharType、CharTraits 和 Allocator

      Bjarne: That done, we can throw away our exercises and use the standard library string. (Ch.20)

    Smart Pointer^

    见 [CPP LANG] 11.10 Dereferencing, 14.4.2 auto_ptr; [EFFECT CPP] Item 13, 14, 15, 17, 18; [MEFFECT CPP] Item 28; [BOOST TUTORIAL] 3.1~3.7

    Smart Pointer (SP) 智能指针是一种 Delegation of Raw Pointer 机制,技术上等于:RAII + pimpl + Ownership Semantics 所有权语义

    常用 SP 助记(来自 stdlib/Boost/TR1):

    1. _ptr vs. _array 后缀

      ptr 型托管单体对象 (new),array 型托管对象数组 (new[])
      array 型有 [] 操作(不检查下标范围),没有 *, -> 操作
      array 型可用 vector 或 ptr 型 + vector 代替

    2. scoped 型

      所有权:Monopolize 独占,又 Noncopyable
      注意:不能用于值语义之 STL 容器的元素

    3. auto 型

      所有权:Transfer 转移,又 Distructive Copy 破坏性拷贝
      注意:不能用于值语义之 STL 容器的元素

    4. shared 型

      所有权:Share 共享,又 RCSP (Reference-Counting Smart Pointer) 引用计数型智能指针
      特点:支持 deleter, Cross-DLL, Raw Pointer 访问
      注意:Cycles of References 环状引用问题

      见 <boost/make_shared.hpp> 的 make_shared

    5. weak_ptr

      所有权:Observe 观察
      特点:伪 SP,不独立使用,而是配合 shared_ptr 使用以解决环状引用问题

      见 <boost/enable_shared_from_this.hpp> 的 shared_from_this

    6. intrusive_ptr

      特点:size 和 raw pointer 相同,利用被托管对象已有的 RC 机制(侵入式)

    7. CComPtr, CComQIPtr

      ATL 中托管 COM 接口的 SP,均派生自 CComPtrBase,自动执行 COM 引用计数 AddRef 和 Release。CComQIPtr 使用 QueryInterface (QI) 根据指定 IID 查询并获得 COM 接口。

    Sample:

    参考书籍^

      • [CPP LANG] "C++ Programming Language, Special Ed", Bjarne Stroustrup
      • [EFFECT CPP] "Effective C++, 3Ed", Scott Meyers
      • [MEFFECT CPP] "More Effective C++", Scott Meyers
      • [BOOST TUTORIAL]《Boost 程序库完全开发指南》,罗剑锋
  • 相关阅读:
    权限管理
    书城项目第五阶段---book表的curd
    大话设计模式学习
    数据绑定流程分析
    GO 解决使用bee工具,报 bash: bee: command not found
    VScode插件:Todo Tree
    ant design pro如何实现分步表单时,返回上一步值依然被保存
    React开发流程
    为什么函数式组件没有生命周期?
    html2canvas@^1.0.0-rc.1
  • 原文地址:https://www.cnblogs.com/wangkangluo1/p/2783897.html
Copyright © 2011-2022 走看看