以对象管理资源
当我们向系统申请某一笔资源后, 应该立刻在同一语句内用它来初始化某个管理对象. 如果有下面这个工厂函数用来动态分配一个资源, 并返回指向资源的指针:
1
Resource* create_resource();
大部分时候我们可以通过智能指针很好的管理资源, 例如下面的代码:
1
std::shared_ptr<Resource> ptr(create_resource());
这样做的好处是将返回的指针放入智能指针对象中进行管理, 这样当该智能指针对象的生存期结束后, 将会在其析构函数中自动释放其持有的资源. 如果不这么做, 用户就需要自己通过 delete
来释放资源, 这会明显增加内存泄漏的风险.
另外值得注意的是, 不要试图用智能指针去管理动态分配的数组, 例如下面的代码:
1
std::shared_ptr<int> ptr(new int[1024]);
这行代码中试图用 shared_ptr
来管理一个包含 1024 个元素的整型数组, 这不会导致编译错误. 但是在 shared_ptr
等智能指针的析构函数中使用的是 delete
操作而非 delete[]
, 这会导致有一部分资源没有被释放.
如果在
new
表达式中使用了[]
, 那么一定要在相应的delete
表达式中也使用[]
. 反之亦然.
复制资源管理类时要小心
由于在资源管理类中往往持有指向资源的指针, 因此在对其进行复制时需要谨慎应对. 通常我们应该考虑该资源是否能被复制, 该如何被复制, 这直接决定了资源管理类的复制方式. 常用的处理手段有如下几种:
- 禁止复制
- 采用引用计数法, 类似
shared_ptr
- 复制底部资源(深拷贝)
- 转移底部资源的所有权, 类似
unique_ptr
将 new
ed 对象放入智能指针时应使用一条独立的语句
考虑下面这两个函数, 前者可以为后者提供 prio
参数:
1
2
int priority();
void process_widget(std::shared_ptr<Widget> pw, int prio);
如果我们用下面的方式来调用后者:
1
process_widget(std::shared_ptr<Widget>(new Widget), priority());
在调用该函数之前, 编译器首先要生成函数的实参, 这个过程中包含三个步骤:
- 执行
new Widget
- 调用
std::shared_ptr
构造函数 - 调用
priority()
函数
而 C++ 以什么顺序来完成这三个步骤是不能确定的, 如果编译器采用了下面的方式:
- 执行
new Widget
- 调用
priority()
函数 - 调用
std::shared_ptr
构造函数
在这种情况下, 一旦 priority()
函数中出现异常, 那么第一步中得到的指针将会遗失, 这将导致内存泄漏. 为了避免这种问题, 只需要将生成智能指针的部分代码分离成一条单独的语句即可:
1
2
std::shared_ptr<Widget> pw(new Widget);
process_widget(pw, priority());
这样做是因为编译器只有在 同一条语句 内才有重新排列各项操作的自由.