Effective C++ 笔记

3 资源管理


条款 13 :以对象管理资源

// 资源种类: 内存、文件描述器、互斥锁、图形界面中的字型和笔刷、数据库连接、网络套接字
// 当不再使用时,需归还系统

class Investment // 塑模投资行为
{
	...
};
// 工厂函数生成 Investment对象
Investment* createInvestment(); // 返回指针,指向 Investment 继承体系内的动态分配对象

// 以下为使用示例
void f()
{
	Investment* pInv = createInvestment();
	...
	delete pInv;	// 释放pInv 所指对象
}
// 该函数在若干情况下可能无法删除 pInv 所指对象资源
// 在“...” 区域 出现return语句 或者该区域抛出异常等

// 策略是把资源放进对象内,依赖 C++ 的析构函数自动调用机制确保资源的释放
// 示例1 使用 auto_ptr 避免潜在的资源泄露可能性
void f()
{
	std::auto_ptr<Investment> pInv(createInvestment());

	...					// 一如既往地使用 pInv, 经由 auto_ptr 析构函数自动删除 pInv
}
// 这个例子引出两个想法:
// 1. 获得资源后立刻放进管理对象内
// 2. 管理对象运用析构函数确保资源被释放

// RCSP(reference-counting smart pointer),即引用计数型智慧指针,持续追踪共有多少对象指向某笔资源,并在
// 无人指向它时自动删除该资源,但无法打破环状引用

// 示例2 使用 tr1::shared_ptr重写刚才用例
void f()
{
	std::tr1:shared_ptr<Investment> pInv(createInvestment());
	...
}

请记住:

  • 为防止资源泄露,请使用RAII对象,它们在构造函数中获得资源并在析构函数中释放资源。
  • 两个常被使用的RAII classes 分别是 tr1::shared_ptr 和 auto_ptr 。 前者通常是较佳选择,因为其copy行为比较直观。若选择 auto_ptr, 复制动作会使它(被复制物)指向null。 

条款 14 :在资源管理类中小心 copying行为

// RAII : Resource Acquisition Is Initialization ,资源取得时机便是初始化时机

// C API 处理类型为 Mutex 互斥器对象
void lock(Mutex *pm);
void unlock(Mutex *pm);

// 建立 class 类管理机锁
class Lock
{
public:
	explicit Lock(Mutex *pm)
	: mutexPtr(pm)
	{ lock(mutexPtr); }
	~Lock() { unlock(mutexPtr); }
private:
	Mutex *mutexPtr;
};

Mutex m;
...
{
	Lock m1(&m); // 锁定互斥器
	...
}

Lock ml1(&m); // 锁定 m
Lock ml2(ml1); // 将 ml1 复制到 ml2 身上,这会发生什么事?

// 可能的选择
// 1. 禁止复制
class Lock : private Uncopyable		// 禁止复制,见条款 6
{
public:								// 如前
	...
};

// 2. 对底层资源祭出“引用计数法”
class Lock
{
public:
	explicit Lock(Mutex *pm)	// 以某个 Mutex 初始化 shared_ptr
	: mutexPtr(pm, unlock)		// 并以 unlock 函数为删除器
	{
		lock(mutexPtr.get());
	}

private:
	std::tr1::shared_ptr<Mutex> mutexPtr;
};

// 3. 复制底部资源: “深度拷贝”

// 4. 转移底部资源: 资源的拥有权从被复制物转移到目标物 

请记住:

  • 复制 RAII 对象必须一并复制它所管理的资源,所以资源的 copying 行为决定 RAII 对象的 copying 行为。
  • 普遍而常见的 RAII class copying 行为是: 抑制 copying、施行引用计数法。不过其他行为也都可能被实现。 

条款 15 :在资源管理类中提供对原始资源的访问

std::tr1::shared_ptr<Investment> pInv(createInvestment()); // 见条款 13

// daysHeld 返回投资天数
int daysHeld(const Investment* pi);

// tr1::shared_ptr 和 auto_ptr 都提供一个 get 成员函数,用来执行显示转换,返回智能指针内部原始指针(的复件)
int days = daysHeld(pInv.get());

// RAII class 设计 提供一个隐式转换函数
FontHandle getFont();
void releaseFont(FontHandle fh);
class Font {
public:
	explicit Font()
	: f(fh)
	{}
	~Font() { releaseFont(f); }
private:
	FontHandle f;
};

// 提供显示转换函数,比如get,但此举较为麻烦
class Font
{
public:
	...
	FontHandle get() const {return f;}
	...
};

void changeFontSize(FontHandle f, int newSize);
Font f(getFont());
int newFontSize;
...
changeFontSize(f.get(), newFontSize); // 明白地将 Font 转换为 FontHandle

// 提供隐式转换函数,转型为 FontHandle
class Font
{
public:
	...
	operator FontHandle() const 	// 隐式转换函数
	{ return f; }
	...
};

Font f(getFont());
int newFontSize;
...
changeFontSize(f, newFontSize); // 将 Font 隐式转换为 FontHandle
// 但这个隐式转换可能增加错误发生机会
Font f1(getFont());
...
FontHandle f2 = f1; // 原意是要拷贝一个 Font 对象,却反而将 f1 隐式转换为底部 FontHandle,然后才复制它。
// 当 f1 被销毁,字体被释放,而 f2 因此成为“虚吊的”

请记住:

  • APIs 往往要求访问原始资源,所以每个 RAII class 应该提供一个“取得其所管理之资源”的办法。
  • 对原始资源的访问可能经由显式转换或隐式转换。一般而言显式比较安全,但隐式专函对客户比较方便。

条款 16 :成对使用 new 和 delete 时要采取相同形式

/*
new : 1. 内存被分配(operator new 函数); 2. 针对此内存会有一个或多个构造函数被调用。
delete : 1. 针对此内存会有一个或多个析构函数被调用; 2. 内存被释放(operator delete 函数)
*/

// 示例1
std::string* stringArray = new std::string[100];
...
delete stringArray;	// 结果程序行为未有定义

// 示例2
typedef std::string AddressLines[4];	// 每个人的地址有4行,每行是一个 string
// 由于 AddressLines 是一个数组,所以返回结果等价于 new string[4]
std::string* pal = new AddressLines;

//释放
delete pal;		// 行为未有定义
delete [] pal;	// 很好

// 正确示例3
std::string* stringPtr1 = new std::string;
std::string* stringPtr2 = new std::string[100];
...
delete stringPtr1;
delete [] stringptr2;

请记住:

  • 如果你在 new 表达式中使用 [ ], 必须在相应的 delete 表达式中也使用 [ ]. 如果你在 new 表达式中不使用 [ ], 一定不要在相应的 delete 表达式中使用 [ ].

条款 17 :以独立语句将 newed 对象置入智能指针

// 举例说明处理程序的优先权
int priority();
void processWidget(std::tr1::shared_ptr<Widget> pw, int priority);

// 调用形式
processWidget(std::tr1::shared_ptr<Widget>(new Widget), priority());
/*
在调用 processWidget 之前,编译器必须创建代码,做以下三件事:
1. 调用 priority
2. 执行 “new Widget”
3. 调用 tr1::shared_ptr 构造函数
*/

// 问题: C++ 编译器以怎样的次序完成这些事情呢?

/*
其中可能的顺序:
1. 执行 “new Widget”
2. 调用 priority
3. 调用 tr1::shared_prt 构造函数
*/

// 如果对 priority 的调用导致异常,则会导致资源泄漏的可能
// 避免方法: 1. 创建 Widget 2. 将它置入一个智能指针内; 3. 传给 processWidget
std::tr1::shared_ptr<Widget> pw(new Widget);
processWidget(pw, priority());

请记住:

  • 以独立语句将 newed 对象存储于(置入)智能指针内。如果不这样做,一旦异常被抛出,有可能导致难以察觉的资源泄漏。 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值