从条款13到条款17都在讲资源管理,那么什么是资源呢?我觉得,资源就是诸如new出来的东西(堆内存),互斥锁啊,数据库的链接等等,操作这些东西要特别小心,因为用了就没有了,比如堆内存,你new了不释放,迟早就是bad_alloc,比如锁,你占了不释放,别人一辈子也得不到。
C++的常见资源有动态分配内存、文件描述器、互斥锁、图形界面下的字符和笔刷、数据库连接以及网络的 sockets。不论哪一种资源,当不再使用时,应该将它还给系统。
本条款建议程序员使用对象管理资源(如申请的内存),核心思想就是:把资源放到类里面,将资源释放的活放在类的析构函数中。我们利用的思想就是类的析构函数在对象的生命周期结束,或者有异常的时候自动调用,因此,我们不用担心造成资源的泄露。并且,由条款08知道,析构函数绝不抛异常,因此我们可以放心使用
书上一句经典的话:获得资源后立即放进资源管理对象,我们总是在获得一笔资源后于同一语句内以它初始化某个管理对象。因此“资源取得时便是资源管理对象初始化的时候”
给出的经验是:
(1)为防止资源泄漏(常常是由于程序员忘记delete申请的内存,或是由于程序过早的return、continue、goto语句等等导致),请使用RAII(“资源取得时机便是初始化时机”,Resource Acquisition Is Initialization)对象,它们在构造函数中获得资源并在析构函数中释放资源。例如使用“类指针对象”auto_ptr:
void f(){
std::auto_ptr<investment> pInv(createInvesment());
.... //auto_ptr的析构函数会自动删除pInv
}
(2)两个常被使用的RAII classes 分别是:auto_ptr和trl::shared_ptr(“引用计数型智慧指针”,reference-counting smart pointer,RCSP)。为了防止多个auto_ptr指向同一对象时,对象可能会被删除一次以上而导致错误,auto_ptr复制动作会使它(被复制物)指向null。
std::auto_ptr<Investment>pInv1(createInvestment()); //pInv1指向createInvestment()返回物;
std::auto_ptr<Investment> pInv2(pInv1); //现在pInv2指向对象,而pInv1被设为NULL;
pInv1 = pInv2; //现在pInv1指向对象,而pIn2被设为NULL;
RCSP则持续追踪共有多少对象指向某笔资源,并在无人指向它时自动删除该资源。因此,trl::shared_ptr的copy行为更加直观一些。这两个智能指针都不能用于数组,因为,他们的析构函数调用的是delete而不是delete[](他们觉得,vector和string已经足够了,没有必要再用数组)。可以看看boost的智能指针,那儿有对数组需求的支持
本条款提醒程序员,使用资源管理类时需根据实际需要管理copying行为,常见的有:抑制copying(将copying操作声明为private);施行引用计数法(使用智能指针:trl::shared_ptr,当引用次数为0时,自动调用删除器);深度拷贝(复制指针和所指内存);转移底部资源的拥有权(将资源的拥有权从被复制物转移到目标物,例如auto_ptr)。
1、禁止复制(比如锁啊,数据库的链接啊,这种),实现的方式就是条款6讲的,将copying函数作为private或者继承Uncopyable类
2、底层采用 “引用计数”,及tr1::shared_ptr这种方法,就是说,资源记者又几个对象指着它,直到最后一个对象死了就释放资源
3、复制底层资源:深度赋值,不光复制指针,还复制指针指着的东西
4、转移资源的拥有权:就是一旦被copying,自己就失去了资源,例如auto_ptr这种。
(1) APIs往往要求访问原始资源(raw resources) ,所以每一个RAII class 应该提供一个“取得其所管理之资源”的办法。
(2)对原始资源的访问可能经由显式转换或隐式转换。一般而言显式转换(如调用get()成员函数)比较安全,但隐式转换对客户比较方便。
tr1::shared_ptr和auto_ptr都提供一个get成员函数,用来执行显示转换,也就是返回智能指针内部的原始指针(的复件)。就像所有智能指针一样, tr1::shared_ptr和auto_ptr也重载了指针取值操作符(operator->和operator*),它们允许隐式转换至底部原始指针。(即在对智能指针对象实施->和*操作时,实际被转换为被封装的资源的指针。)
本条款提醒程序员在申请和释放资源时应采用相同形式:如果在new 表达式中使用[],必须在相应的delete表达式中也使用[];如果你在new 表达式中不使用[],一定不要在相应的delete表达式中使用[]。
当new被调用时,首先分配内存,再针对此内存会有一个(或更多)构造函数;当delete被调用时,针对此内存会有一个(或更多)析构函数被调用,再释放内存。由于delete必须知道即将被删除的内存内究竟存在多少个对象,从而决定调用多少个析构函数,所以采用相同形式很重要。
最好尽量不要对数组形式作typedefs动作。因为这样容易引起delete操作的“疑惑
本条款涉及C++对语句的执行次序,如果次序不能确定,最好写成独立的语句。以智能指针为例,给出的建议是:以独立语句将newed 对象存储于(置入)智能指针内。如果不这样做,一旦异常被抛出,有可能导致难以察觉的资源泄漏。假设:
processWidget(std::trl::shared ptr<Widget> (new Widget) , priority());
在调用processWidget之前,编译器必须创建代码,做以下三件事:
(1)执行“new Widget”
(2)调用priority
(3)调用trl::shared ptr构造函数
不同的C++ 编译器执行这三条语句的顺序不一样,但对priority的调用可以排在第一或第二或第三执行。如果编译器选择以第二顺位执行且priority函数抛出了异常,则新创建的对象Widget将导致内存泄漏。解决方法如下:
std::trl::shared_ptr<Widget> pw(new Widget); //在独立语句内以智能指针存储Widget对象
processWidget(pw, priority()); //这个调用肯定不存在内存泄漏
这一章的核心思想:用类去管理资源,然后,我们只要按规矩(这个规矩已经系统化,成熟化)去写好我们的资源管理类(核心就是copying函数)。