文章目录
3.资源管理
资源包括:内存、文件描述符、互斥锁、图形界面中的字型和笔刷、数据库连接、网络socket
基于对象的资源管理办法,建立在C++对构造函数、析构函数、copy函数的基础上.
13.以对象管理资源
- 把资源放进对象内,我们便可以来C++的“析构函数”自动调用机制确保资源被释放
- 另一边想法:
void f()
{
Investment* pInv=createInvestment();//调用factory函数
...
delete pInv;
}
考虑如上函数f处理Investment对象,会遇到如下问题而导致delete
被略去
- …区域内过早的return
- delete位于某个循环内,其由于某个continue或goto过早退出
- …区域内抛出异常
“以对象管理资源”的两个关键想法:
- RAII(资源取得时机便是初始化时机):每一笔资源都在获得的同时立刻被放进管理对象中
- 管理对象运用“析构函数”确保资源被释放
auto_ptr
——智能指针
auto_ptr
有如下特性:
- 被销毁时自动删除所指之物
- 若通过
copy assignment
或copy
赋值它们,则复制所得的指针将取得资源的唯一拥有权 - 在析构函数内做
delete
tr1::shared_ptr
——RCSP(reference-counting smart pointer)
特性:
-
记录指向某笔资源对象的数量,并在无人指向该资源时删除它
-
无法打破“环状引用”
-
在析构函数内做
delete
13总结
- 为防止资源泄漏,优先使用RAII对象(其在构造函数中获得资源,并在析构函数中释放资源)
- 注意两种不同指针的copy行为,
auto_ptr
的copy行为会使原指针指向null;
14 在资源管理类中小心copying行为
由于并非所有资源都是heap-based,如Mutex,所以需要创建个性化资源管理类
当一个RAII对象被赋值,会发生什么事情?
禁止复制
将copying操作在base class声明为private。
class Lock:private Uncopyable{}; //派生类继承方式
、
引用计数法——tr1::shared_ptr
tr1::shared_ptr
允许指定deleter
,其为函数或函数对象,当引用次数0时触发。删除器对于tr1::shared_ptr
是可有可无的第二参数
class Lock{
public:
explicit Lock(Mutex* pm)
:mutexPtr(pm,unlock) //unlock 为deleter
{
lock(mutexPtr.get()); //锁定原始资源
}
private:
std::tr1::shared_ptr<Mutex> mutexPtr;
}
//unlock作为删除器,用以解决“释放”而非删除问题
//缘由shared_ptr默认为删除资源
注意:
Lock
不再声明析构函数——class析构函数(不论是编译器生成还是用户自定义的)会自动调用其non-static成员变量的析构函数mutexPtr
的析构函数会在互斥器引用次数0时,自动调用deleter
14总结
-
赋值RAII对象必须一并复制它所管理的资源,所以资源的copying行为决定RAII对象的copying行为
-
普遍而常见的RAII class copying行为:
-
抑制copying(使用
Uncopyable
作为基类) -
引用计数法(
tr1::shared::ptr
)8
-
15 在资源管理类中提供对原始资源的访问
std::tr1::shared_ptr<Investment> pInv(createInvestment());//调用factory函数
int daysHeld(const Investment* pi); //返回投资天数
int d=daysHeld(PInv) //错误!!!
显然daysHeld
的形参为const Investment*
类型,故需RAII对象返回其所内含の原始资源(即Investment*
)
智能指针
- 均提供一个成员函数
get()
,用以返回智能指针内部的原始指针(副本)
int d=daysHeld(PInv.get());
- 几乎均重载了指针取值操作符(
operator ->
和operator *
),其允许隐式转换至底部原始指针
class Investment{
public:
bool isTaxFree() const;
}
Investment* CreateInvestment(); //factory函数生命
std::auto_ptr<Investment*> Ptr1(CreateInvestment());
std::tr1::shared_ptr<Investment*> Ptr2(CreateInvestment());
bool taxable1=Ptr1->isTaxFree();
bool taxable2=(*Ptr2).isTaxFree();
显式隐式获取原始资源
class Font{
private:
FontHandle _f; //原始资源 raw material
public:
explicit Font(FontHandle f)
:_f(f)
{“资源处理”}
~Font(){releaseFont(_f)};
public:
//显式转换函数,返回资源副本
FontHandle get() const
{return _f;}
//隐式转换(Type)函数——转换为其他类型
operator FontHandle() const
{return _f;}
}
补充:
隐式转换函数包括两类:
- 单参数转换构造函数
- 隐式转换函数(
operator Type() const {}
) //此为上述代码使用类型
注: 隐转换函数可能发生“虚吊”问题
Font f1(getFont());
...
FontHandle f2=f1;
//此处编译器编译成如下代码
FontHandle f2=FontHandle(f1);
如上代码造成一个问题:
FontHandle有Font对象f1管理,但也可通过直接使用f2取得,当f1被销毁时,字体被释放,f2因此成为*(dangle)虚吊*的。
15总结
- API往往要求访问原始资源,故每一个RAII CLASS应提供取得其所管理资源的办法
- 对原始资源的访问包括显示转换和隐式转换,一般来说显示转换安全,隐式转换方便
16 成对使用new和delete时要采取相同形式
### 16总结
- 尽量不要对数组形式做typedef动作-无法判定以那种形式delete删除之
- 若使用new[],则对应是哟个delete[]
- 若使用new,则对应使用delete
17 以独立语句将newed
对象置入智能指针
考虑如下代码·
//函数声明
int priority();
void processWidget(std::tr1::shared_ptr<Widget> pw,int priority());
//函数调用方式1
processWidget(std::tr1::shared_ptr<Widget>(new Widget),priority());
//函数调用方式2
std::tr1::shared_ptr<Widget> Ptr(new Widget);
processWidget(Ptr,priority());
调用方式1
在调用processWidget
前,编译器会做如下事项:
- 调用
priority()
- 执行
new Widget
- 调用
tr1::shared_ptr
构造函数
由于C++的执行顺序不确定,可以确定的仅有2位与3之前。故可出现如下执行顺序
- 执行
new Widget
- 调用
priority()
- 调用
tr1::shared_ptr
构造函数
若在2出现异常,1返回的指针就会丢失,因为它尚未被置入资源管理对象tr1::shared_ptr
中
简言之
在对processWidget
调用过程中可能发生资源泄漏,缘由在资源被创建(new widget
)和资源被转换为资源管理对象的两个时间点之间可能发生异常干扰
故可采取调用方式2避免上述事件发生。
17总结
- 以独立语句将
newd对象
存储与智能指针内。
返回的指针就会丢失,因为它尚未被置入资源管理对象tr1::shared_ptr
中
简言之
在对processWidget
调用过程中可能发生资源泄漏,缘由在资源被创建(new widget
)和资源被转换为资源管理对象的两个时间点之间可能发生异常干扰
故可采取调用方式2避免上述事件发生。
17总结
-
以独立语句将
newd对象
存储与智能指针内。若不这样做,一旦异常被抛出,有可能导致难以察觉的资源泄漏