Item13 以对象管理资源
资源:
- 内存、文件描述符、互斥锁、数据库链接和网络socket等,用了需要还给操作系统
- 异常、函数多重返回路径和程序员不恰当的改动,会导致资源没有释放
考虑以下场景,delete都不会执行,资源都会泄漏,
- 如果在do_something过程出现return;
- 或者do_something过程抛出异常;
- 或者delete语句在循环内,被continue;
class Investment {...}; // 投资行为的父类
Investment* creatInvestment(); // 创建对象的工厂
void f() {
Investment* pInv = creatInvestment();
// do_something
delete pInv;
}
解决思路,以对象管理资源:
- 获得资源后立即放进管理对象内(RAII,Resource Acquisition Is Initialization);
- 管理对象运用析构函数自动调用的机制确保资源被释放。
上述函数可以使用智能指针的方式修改
#include <memory>
class Investment {};
Investment* creatInvestment() {
return new Investment;
}
void f() {
// std::auto_ptr<Investment> pInv(creatInvestment()); // c++98标准
std::shared_ptr<Investment> pInv(creatInvestment()); // c++11标准
}
/*
* auto_ptr 为了防止同一个对象被删除多次,通过拷贝和或者拷贝赋值,该指针会变为null
* 与stl容器要求的正常复制行为不兼容
* c++11 中unique_ptr会静止拷贝操作
int main() {
std::auto_ptr<Investment> pInv1(creatInvestment());
std::auto_ptr<Investment> pInv2(pInv1);
if (pInv1.get() == nullptr) std::cout << "pInv1 is null\n";
if (pInv2.get() != nullptr) std::cout << "pInv2 is not null\n";
return 0;
}
*/
注意事项:不能用来管理动态分配的数组,因为底层调用的时delete,而不是delete []
int main() {
std::shared_ptr<std::string> aps(new std::string[10]);
return 0;
}
/*
* 报错内容
munmap_chunk(): invalid pointer
Aborted (core dumped)
*/
Item14 在资源管理类中小心copying行为
当一个RAII对象被复制,常用的处理方式:
- 禁止复制,如:std::unique_lock
- 对底层资源采用”引用计数法“:如:std::shared_ptr
- 还可以指定自定义的删除函数
class newWidget { };
void customDeleter(newWidget* ptr) {
std::cout << "Custom deleter is called" << std::endl;
delete ptr;
}
std::shared_ptr<newWidget> ptr(new newWidget, customDeleter);
Item15 在资源管理类中提供对原始资源的访问
针对有些API需要访问原始资源的需求:
- 访问原始资源三种方式:pInv.get()、operator->和operator*;
- 自定义RAII对象可以提供显示转换函数和隐式转换(仿函数的方式)。
class FontHandle {};
FontHandle getFont() {
FontHandle fh;
return fh;
}
class Font {
public:
explicit Font(FontHandle fh) : f(fh) {}
FontHandle get() const { std::cout << "显示转换\n"; return f; }
operator FontHandle() const { std::cout << "隐式转换\n"; return f; }
private:
FontHandle f;
};
void changeFontSize(FontHandle f) {}
int main() {
// int days = daysHeld(pInv); // 编译失败
int days = daysHeld(pInv.get());
bool isTaxFree = pInv->isTaxFree();
bool isTaxFree2 = (*pInv).isTaxFree();
Font f1(getFont());
changeFontSize(f1.get());
Font f2(getFont());
changeFontSize(f2);
return 0;
}
Item16 成对使用new和delete时采用相同的形式
使用new动态生成一个对象,分为两个步骤:
- 分配内存(调用operator new)
- 在内存上调用构造函数
使用delete删除对象,也分为两个步骤:
- 在内存上调用析构函数
- 释放内存(调用operator delete)
- 使用delete删除对象需要知道内存中有多少个对象,决定了有多少个析构函数调用;
- 内存模型中需要保存一个变量记录数组大小,delete加上[]让编译器知道调用多少个析构函数
注意:不要对数组形式做typedef动作,很容易出现不对称的行为。
Item17 以独立语句将new对象放入智能指针
考虑以下场景:
class Widget {};
int priority() {return 0;}
void processWidget(std::shared_ptr<Widget> pw, int priority) {}
int main() {
// processWidget(new Widget, priority()); // error: could not convert ‘(Widget*)operator new(1)’ from ‘Widget*’ to ‘std::shared_ptr<Widget>’
// processWidget(std::shared_ptr<Widget>(new Widget), priority());
// std::shared_ptr<Widget> pw(new Widget);
// processWidget(pw, priority());
processWidget(std::make_shared<Widget>(), priority());
return 0;
}
processWidget函数可能的执行顺序(取决于编译器,保证1在3前面,2的执行顺序不确定):
- 执行 new Widget;
- 调用 priority();
- 调用 std::shared_ptr 构造函数
当调用 priority() 发生异常,new Widget 返回的指针将丢失,资源不能被正确释放
解决方案:
- 以独立的语句将对象存储在智能指针内;
- C++11标准中可以使用 std::make_shared<Widget>() 合并两个过程。