C++对象资源管理惯用法(一)

2014-11-24 12:03:47 · 作者: · 浏览: 2

关于 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 语言的资源管理方法
RAII
deleter
Two-stage Initialization
pimpl
Reference Counting
Smart Pointer
参考书籍
C 语言的资源管理方法^
见 [CPP LANG] 14.4 Resource Management

e.g. Very Beginning

[cpp]
void copy_file(const char* src, const char* dst)
{
FILE* srcFile = fopen(src, "r");
if (srcFile == NULL)
goto _RET;

FILE* dstFile = fopen(src, "w");
if (dstFile == NULL)
goto _CLOSE_SRC;

// read source file, and transform it's content.
if (HANDLE_FAILED)
goto _CLOSE_DST;

// end processing
_CLOSE_DST:
fclose(dstFile);
_CLOSE_SRC:
fclose(srcFile);
_RET:
return;
}
引出 Resource Management 的基本要求:

离开时释放, Release before returning.
按照资源的申请的相反顺序释放资源, Resources are released in the reverse order of their acquisition.
其它的资源释放手法(不建议):

do-while-break 式:教条地避免使用上述 goto 式的变形
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

注意:

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

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

Sample:

class File: FILE 的浅封装
class ScopedBuf: scoped 型缓冲区类 (Use vector and resize to expand buffer instead.)
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 Constructors, RAII in C++, Google C++ Style Guide: Doing Work in Constructors 中文翻译

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

stage 1, 调用构造函数,初始化对象本体
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 或者说它能带来什么好处:

可复用对象本体

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

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

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

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

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

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

More about 2-stage init:

public init vs. private init

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

init vs. re-init

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

先判断对象是否已分配资源,如果是,则需释放
申请新的资源 www.2cto.com
于是:RAII 让资源管理变简单,而使用 2-stage init 又让事情变复杂

init vs. ctor

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

使用构造函数:

[cpp]
// d 是 for 的 scoped 型对象, 下面情况都会销毁 d
// 1. 条件退出 2. break 退出
// 3. 每次迭代