《Effective C++ 》学习笔记——条款13

***************************************转载请注明出处:http://blog.csdn.net/lttree********************************************




三、Resource Management


到了第三张了,这一章主要讲述的就是资源的管理。

①.OK,首先,什么是资源——一旦使用,就必须还给系统的东西。C++程序员最长使用的资源就是动态分配内存(因为如果你分配内存却不曾归还,会导致内存泄露),但显然内存只是你必须管理的众多资源之一。

②来看一看还有哪些其他常见资源:文件描述器、互斥锁、图形界面中的字型和笔刷、数据库连接 以及 网络sockets。

无论什么样的资源,要记住—— 当你不再使用它,必须将它还给系统。




Rule 13:Use object to manage resource

规则13:以对象管理资源



1.以例开题


>现象 和平常一样还是用一个例子来开始,关于一个用来 塑模投资行为的程序库的 根类 Investment

class Investment  {  ...  };     // 继承体系中的根类

// 通过 factorty function(工厂函数)供应特定的Investment对象
Investment* createInvestment();    // 为简化,略掉参数,这是动态分配的内存,调用者有责任删除它

// 用一个函数来 履行上面要求的责任(删除)
void f(  )
{
    Investment* pInv = createInvestment();    // 调用 factory 函数
    ...
    delete pInv;    // 释放pInv所指对象
}


上面代码很正常,很多人也都是这么写的。BUT! 如果f函数中 ... 部分  ,有一个 return 语句,pInv所指对象就不会被删除。


>other? 除了上面这种情况,还有 在一个 for 循环内,但在这循环中,没到 delete 时,通过 continue 或者 goto提前跳出? 还有如果在 delete 语句之前,抛出异常。  这些都会导致 delete 被忽略过去,而且无论是怎样的忽略,我们泄露的不只是内含投资对象的那块内存,还有投资对象所保存的任何资源。


>避免它! 或许,你说这些东西是马虎造成,认真仔细一点,完全可以避免它。 NO! 或许有这种情况发生,但是做的软件不可能不去维护它,万一维护的时候,在delete之前 添加个return,或者维护完之后,出现从未发生过的异常抛出。 这些显然很难去人为的控制。


>解决 我们需要做的就是将资源放进对象内,当控制流离开f函数,该对象的析构函数自动释放这些资源,通过依赖C++的“析构函数调用机制”来解决这个问题,引出下面概念 RAII(resource acquisition is initialization)对象。




2.Resource Acquisition Is Initialization


来说一下关于“以对象管理资源”的两个关键想法

获得资源后立刻放进管理对象( managing object ) 内。 实际上 “以对象管理资源” 的观念通常被称为 “资源取得时机便是初始化实际”(RAII) 每一笔资源都在获得的同时立刻被放进管理对象中。

管理对象 运用析构函数确保资源被释放。 不论控制流如何离开区域块,一旦对象被销毁,其析构函数自然会被调用,如果抛出异常,事情会有些不好办,但我们在 条款8 已经搞定了。


>auto_ptr 先来看一下标准程序提供的 auto_ptr,这是个类指针(pointer-like)对象,也就是所谓的——智能指针,其析构函数自动对其所指对象调用 delete,来看一下对于上述例子,f函数的操作吧:

void f()
{
    std::auto_ptr<Investment> pInv(creatInvestment());    // 调用factory函数

    ...    // 该怎么用怎么用pInv
}    // auto_ptr的析构函数自动调用 pInv

但是要注意咯,auto_ptr被销毁时,会自动删除它所指的物,所以一定注意不要让多个 auto_ptr 共同指向同一个对象。 当然为了预防这个问题 auto_ptr 有个不寻常的性质:若通过 copying 函数(copy构造函数 或 copy assignment 操作符 )复制它们,它们会变成null,而复制所得的指针将取得 资源的唯一拥有权

不是很懂?Ok,看下面:

std::auto_ptr<Investment> pInv1(createInvestment() );    // pInv1 指向 函数返回的对象

std::auto_ptr<Investment> pInv2(pInv1);    // 现在pInv2指向那个对象,pInv1 为 null

pInv1 = pInv2;    // 现在 pInv1指向那个对象,pInv2为 null

懂了吧,所以 auto_ptr并非管理动态分配资源的神兵利器,就像 STL 容器 就容不得 auto_ptr。


>RSCP 替代方案,就是它——RSCP(reference-counting smart pointer,引用计数型智慧指针),这也是个智能指针, 它可以持续追踪共有多少对象指向某笔资源,并在无人指向它时自动删除该资源,这种行为类似于 垃圾回收( garbage collection),不同的是 RSCP 无法打破 环状引用 ,例如两个RSCP互相指向对方。

TR1的 tr1::shared_ptr (条款54会说)就是个 RSCP,用它来写上面的函数:

void f()
{
    ...
    std::tr1::shared_ptr<Investment> pInv1( createInvestment() );
    ...
}


auto_prt 和 tr1::shared_ptr 两者都在 析构函数内做 delete 而非 delete[] 动作(条款16会解释这两者不同),所以动态分配而得到的 array 上使用 auto_ptr 或 tr1::shared_ptr 是个错误的选择,遗憾的是这样也可以通过编译

但是,可惜的是,并没有针对C++动态分配数组 而设计的类似上面两个智能指针的东西,即使在TR1中也不存在。因为 vector 和 string 几乎总是可以取代 动态分配 而得到的数组。如果还是想要这样的东西,就在Boost中寻找吧,那里的 boost::scoped_array 或者 boost::shared_array 类 ,会很适合你。



3.建议

如果你打算手工释放资源,容易发生某些错误,罐装式的资源管理类(例如 auto_ptr , tr1::shared_ptr)往往能比较轻松的遵循本条款忠告,但有时候所使用的资源是目前这些预制式的类无法妥善管理的。这样就需要你自己来制作自己的资源管理类。那并非非常困难,但是要看接下来的 条款14 和 条款15。



4.请记住

☆ 为防止资源泄漏,请使用RAII 对象,它们在构造函数中 获得资源并在析构函数中释放资源。

☆ 两个常被使用的RAII类 分别是 auto_ptr 和 tr1::shared_ptr。后者通常是较佳的选择,因为它 copy 行为比较直观。若选择 auto_ptr,复制动作会使它(被复制物)指向null。




***************************************转载请注明出处:http://blog.csdn.net/lttree********************************************

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页