Resourse Mangement-13,14,15

C++程序中最常使用的资源就是动态分配内存,但内存只是你必须管理的众多资源之一。其他常见资源还包括文件描述符(file descriptors)、互斥锁(mutex locks)、数据库连接以及网络sockets等。
不论是哪一种资源,重要的是,当你不再使用它时,必须将它还给系统。

13.Use objects to mange resources.(以对象管理资源)

(1)有如下Investment类:

其中,createInvestment的调用端使用了函数返回的对象后,使用完该资源后,有责任删除之。进一步假设有如下函数f履行了这个责任:

(2)以为这就完了吗?NO!有若干情况下f可能无法删除它得自createInvestment的投资对象:

1)第一种情况,代码”…”中一个过早的return语句被执行起来,控制流将不会执行到delete语句处。
2)第二种情况,代码”…”中的语句抛出异常,那么控制流也不会执行到delete语句处。

因此单纯依赖f来执行其delete语句是行不通的。

(3)对此,为确保createInvestment返回的资源总是被释放,我们需要将资源放进对象内,当控制流离开f,该对象的析构函数会自动释放那些资源。实际上这正是隐身于本条款背后的半边想法:把资源放进对象内,我们便可依赖C++的“析构函数自动调用机制”确保资源被释放。(稍后讨论另半边想法)

(4)许多资源被动态分配于heap内而后被用于单一区块或函数内。它们应该在控制流离开那个区块或函数时被释放。auto_ptr是个“类指针对象”,也就是所谓“智能指针”,其析构函数自动对其所指对象调用delete:

这个例子示范“以对象管理资源”的两个关键想法:
1)获得资源后立刻放进管理对象(managing object)内。实际上“以对象管理资源”的观念通常被称为“资源取得时机便是初始化时机”(Resourse Acquisition Is Initialization, RAII)。
2)管理对象(managing object)运行析构函数确保资源被释放

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

(5)auto_ptr的替代方案是“引用计数型智能指针”(reference-counting smart pointer,RCSP)。RCSP提供的行为类似垃圾回收,不同的是RCSP无法打破环状引用,例如两个其实已经没被使用的对象彼此互指,因为好像还处在“被使用”状态。

1)TR1的tr1:shared_ptr就是个RCSP,所以可以这么写f:

2)auto_ptr和tr1::shared_ptr两者都在其析构函数内做delete而不是delete[]动作。那意味在动态分配而得的array身上使用这两个指针是个坏主意。

注:C++11新标准已经引入了shared_ptr、unique_ptr以及weak_ptr智能指针了。

请注意:
(1)为防止资源泄露,请使用RAII(资源获得时初始化)对象,它们在构造函数中获得资源并在析构函数中释放资源。
(2)两个常被使用的RAII classes分别是tr1::shared_ptr和auto_ptr。前者通常是较佳选择,因为其copy行为比较直观。若选择auto_ptr,复制动作会使他指向null。

14.Think carefully about copying behavior in resource-manging classes.(在资源管理类中小心copying行为)


请记住:
(1)赋值RAII对象必须一并复制它所管理的资源,所以资源的copying行为决定RAII对象的copying行为。
(2)普遍而常见的RAII class copying行为是:抑制copying、施行引用计数法(reference counting)。

15.Provide access to raw resources in resource-managing classes.(在资源管理类中提供对原始资源的访问)

(1)有的时候,我们需要绕开资源管理对象直接访问原始资源,比如条款13中,我们导入了一个观念:使用智能指针保存工厂函数createInvestment的调用结果:
std::tr1::shared_ptr<Invetsment> pInv(createInvestment());

假设我们希望以某个函数处理Investment对象,像这样:
int daysHeld(const, Investment* pi);
//如果我们这么调用它,编译不通过
int days = daysHeld(pInv);

因为daysHeld需要的是Investment*指针,我们传给它的却是个类型为tr1::shared_ptr<Investment>的对象。这个时候需要一个函数可将RAII class对象转换为其所内含之原始资源。

(2)有两个做法可以达到目标:显式转换和隐式转换

1)tr1::shared_ptr和auto_ptr都提供一个get成员函数,用来执行显式转换,也就是它会返回智能指针内部的原始指针(的复件):
int days = daysHeld(pInv.get());    //将pInv内的原始指针传给daysHeld

2)tr1::shared_ptr和auto_ptr也重载了指针取值操作符(operator->和operator*),它们允许转换至底部原始指针:

请记住:
(1)APIs往往要求访问原始资源raw resources,所以每一个RAII class应该提供一个“取得其所管理的资源”的办法。
(2)对原始资源的访问可能经由显示转换或隐式转换,一般而言显示转换比较安全,但隐式转换对客户比较方便。

发表评论

电子邮件地址不会被公开。 必填项已用*标注