item13 以对象管理资源
:::tips
通过new
开辟出来的对象需要主动调用delete
来销毁
人为的管理可能会因为种种原因导致未释放(e.g.异常、提前的返回等等) ::: :::info 以对象管理资源:
获得资源后立即放入对象,这个观念又被称为:资源取得时机便是初始化时机,即 RAII(Resource Acquisition Is Initialization)
管理对象运用析构函数确保资源被释放 :::
std::auto_ptr<T> pInv(xx);
std::shared_ptr<T> pInv(xx);
通过智能指针可以实现资源的管理;
可以使用
auto_ptr
来管理资源,该对象析构时释放资源。需要注意auto_ptr
被复制时,原指针将变成 NULL,保证同时只有一个指针指向资源。若需要支持正常复制,则需要使用shared_ptr
需要注意
delete
和delete[]
,对应new
和new[]
item14 在资源管理类中小心copying行为
:::info 当一个RAII对象被复制时,会有以下两种可能: :::
禁止复制 :::info 比如对于锁(lock),会把copying操作声明为私有; :::
对底层资源祭出“引用计数法”
比如std::shared_ptr<template>
当然,也可以又别的选择:
- 复制底部资源
- 转移底部资源所有权
如auto_ptr
:::tips
- 复制RAII对象必须一并复制它管理的资源,资源的copying行为决定了RAII对象的copying行为
普遍而常见的RAII class copying行为是:抑制copying、施行引用计数法。 :::
item15 在资源管理类中提供对原始资源的访问
在有些情况下,有的API需要直接指涉资源,这时需要资源管理类能够提供对原始资源的访问 :::tips 两种做法:
显式转换
隐式转换 :::
class Font
{
public:
FontHandle get() const { return f; }
private:
FontHandle f;
}
class Font
{
public:
operator FontHandle() const { return f; }
private:
FontHandle f;
}
void changeFontSize(FontHandle f,int newSize); //函数
Font f(getFont());
changeFontSize(f.get(),newFontSize); //显示调用
changeFontSize(f,newFontSize); //隐式:
//changeFontSize(FontHandle(f),newFontSize);//相当于这样
:::tips 一般而言显式转换更安全,隐式转换客户用起来更舒服; :::
item16 成对使用new和delete时要采取相同的形式
:::info 使用new会发生什么?
通过operator new函数获得分配的内存(p.s.实际上还是同malloc分配的,malloc又是通过系统调用_brk和mmap开辟内存空间)
针对此内存,会有一个或是多个构造函数被调用。 ::: :::tips 同理,调用delete也会执行两件事:析构&释放内存空间,但是delete不知道是否是在处理一个数组; :::
调用
new[]
,必须在相应的delete
表达式中也使用[]
,反之如果没有用[]
,也不要在delete
中用[]
item17 以独立语句将newed对象置入智能指针
考虑这样一个函数
void processWidget(shared_ptr<Widget> pw,int priority);
int priority();
//wrong!
processWidget(new Widget,priority());
:::tips shared_ptr构造函数需要一个原始指针,但是该构造函数是个explicit构造函数,上述的传参方式会发生隐式转换:
Widget*->shared_ptr<Widget>
:::processWidget(nshared_ptr<Widget>(new Widget),priority());
编译器在调用processWidget之前要计算各个实参,所以需要创建下面的代码:
调用
priority
- 执行
new Widget
- 调用
std::shared_ptr
构造函数
编译器能够保证 new Widget
在 std::shared_ptr
之前完成,因为前者作为参数被后者调用,但是对于 priority
编译器却可以排在第一、第二甚至第三执行,假设执行顺序如下:
- 执行
new Widget
- 调用
priority
调用
std::shared_ptr
构造函数 :::danger 当 priority 出错时,new Widget 返回的指针将会遗失,因为此时它还没被置入 std::shared_ptr 中,而造成内存泄露,解决方法也很简单,使用分离语句 :::以独立语句将newed对象存储在智能指针内,否则一旦发生异常,可能导致难以察觉的资源泄露。