第三章 资源管理
2014-08-07 22:47
183 查看
所谓资源就是,一旦用了它,将来必须还给系统。如果不这样,糟糕的事情就会发生。C++程序中最常见使用的资源就是动态分配内存,导致的内存泄漏,但内存只是你必须管理的众多资源之一。其他常见的资源还包括文件描述器(file descriptors)、互斥锁(mutex locks)、图形界面中的字型和笔刷、数据库连接、以及网络sockets.不论哪一种资源,重要的是,当你不再使用它时,必须还给系统。
C++ Code
根据上面的例子,我们对“以对象管理资源”的两个关键想法。
获得资源后立刻放进管理对象内。(RAII:资源获取实际便是初始化时机)
管理对象运行析构函数确保资源被释放。
上面给出了auto_ptr的智能指针有个问题就是,一定不能让多个auto_ptr同时指向同一个对象,原因在于auto_ptrs有一个不寻常的性质:若通过copy构造函数或copy assignment操作符复制它们,它们会变成null,而复制所得的指针将获得资源的唯一拥有权,见如下代码:
C++ Code
解决方案二:
auto_ptr的替代方案是“引用计数智慧指针(RCSP)”,所谓的RCSPs也是智能指针,持续跟踪共有多少个对象指向某笔资源,并在无人指向它时自动删除该资源。RCSPs提供的行为类似垃圾回收,不同的是RCSPs无法打破环状引用,具体用法如下所示
C++ Code
不适用情况:
auto_ptr和tr1::shared_ptr两者都在其析构函数内做delete而不是delete[]动作,因此动态分配而得到的array身上适用这两个智能指针是个坏主意。
C++ Code
请记住:
为了防止资源泄漏,请使用RAII对象,它们在构造函数中获得资源并在析构函数中释放资源。
两个常用的RAII分别是tr1::shared_ptr和auto_ptr。前者通常是较佳的选择,因为其copy行为比较直观。若选择auto_ptr,复制它动作是它指向null。
C++ Code
这种复制行为会为程序带来不确定性。因为我们在进行copying行为时,应该要有以下四重考虑:
1、许多时候允许RAII对象被复制并不合理。对一个像Lock这样的类这是可能的,因为很少能够合理拥有“同步化基础物”的副本。如果复制对RAII并不合理,你应该禁止复制。条款6告诉我们怎么做:将copying操作声明为private,对Lock而言看起来是这样的:
C++ Code
2、有时候我们希望保持资源,直到它最后一个使用者被销毁,这种情况下复制RAII对象时,应该将资源的“被引用数”递增,trl::shared_ptr便是如此。
通常只要内含一个tr1::shared_ptr成员变量,RAII类便可实现出引用计数行为。如果前述Lock打算使用使用技术,它可以改变mutexPtr类型,将它从Mutex*改为tr1::shared_ptr<Mutex>。然而不幸tr1::shared_ptr的缺省行为是“当引用次数为0时删除其所指物”,那不是我们所要的行为。幸运的是tr1::shared_ptr允许指定所谓的“删除器”,那是一个函数或函数对象,当引用次数为0时便调用。删除器对tr1::shared_ptr构造函数而言是可有可无的第二参数,所以代码看起来像这样:
C++ Code
本例的Lock不再声明析构函数,因为没有必要。条款5说过,类的析构函数会自动调用其non_static成员变量的析构函数。而mutexPtr的析构函数会在互斥器的引用次数为0的时候自动调用tr1::shared_ptr的删除器。
3.复制底层资源
有时候只要你喜欢,可以针对一份资源拥有其任意数量的副件,而你需要“资源管理类”的唯一理由是,当你不再需要某个副本的时候确保它被释放,在此情况下复制资源管理对象,应该同时也复制其所包覆的资源,也就是说,复制资源管理了对象时,进行的是“深度拷贝”。
4.转移底部资源的拥有权
某些罕见场合下你可能希望确保永远只有一个RAII对象指向一个未加工资源,即使RAII对象被复制依然如此。此时资源的拥有权会从被复制物转移到目标物。如条款13所诉。这是atuo_ptr奉行的复制意义。
copying函数有可能被编译器自动创建出来,因而除非编译器所生成版本做了你想要做的事情,否则你的自己编写它们。某些情况下你或许也想支持这些函数版本,这样的版本描述于条款45
请记住:
复制RAII对象必须一并复制它所管理的资源,所以资源的copying行为决定RAII对象的copying行为。
普通而常见的RAII类copying行为是:抑制copying,施行引用计数法,不过其他行为都可能被实现。
在上两条款我们知道如何使用智能指针管理我们的申请的资源,但是读者是否发现,我们如何去访问我们原始资源的方法呢?在这一条款将得到解答以及类型转换的相关问题。
首先,我们用代码来说话吧,及输出的结构看到
C++ Code
从这里我们可以看到,程序输出了我们想要的结果。如书中所述:
tr1::shared_ptr 和 auto_ptr 都提供了一个 get 成员函数来进行显式转换,也就是说,返回一个智能指针对象中的裸指针(的副本):
myClass* myC=apMy.get();//////
似乎所有的智能指针类,包括 tr1::shared_ptr 和 auto_ptr 等等,都会重载指针解析运算符( operator-> 和 operator* ),这便使得对原始裸指针进行隐式转换成为现实,在这里我就不实际举例子了。下面使用书中的片段代码来说明一下问题吧:
C++ Code
出色的资源管理类型可以避免资源泄露并有效的管理资源,但世界并非是如你所愿的。当某个API需要使用资源管理类型把持的原始资源时,这样的麻烦又会随之而来。
也许您注意到了
C++ Code
就出现了问题。结果大家可以试一下。
2.隐式转换
因为有时候要取得RAII对象内原始资源, RAII设计者使用了一种隐式转换函数,
如果让资源管理类型提供隐式转换函数,可以让行为变的更自然,但这样的作法没有好下场,只会增加客户端发生错误的机率。比如下面的代码(简单的编写了一个自定义的AutoPtr,它重载了隐式转换operator):
C++ Code
pTP也获得了apI提供的Point指针,当apI作用域结束后或显示调用了Release方法,pTP也就成了迷途指针了。通常来说提供Get方法是比较大众切容易接受的正确方法,它将隐式转换所带来的恶意最小化。
也许在诸如auto_ptr、shared_ptr只提供原始指针的访问违背了面向对象的封装性,可能是令人产生误解和迷惑的地方。因为RAII classes并不是为了封装某物而存在的,更多的它只是一种模版编程的概念。
C++ Code
pTP也获得了apI提供的Point指针,当apI作用域结束后或显示调用了Release方法,pTP也就成了迷途指针了。通常来说提供Get方法是比较大众切容易接受的正确方法,它将隐式转换所带来的恶意最小化。
也许在诸如auto_ptr、shared_ptr只提供原始指针的访问违背了面向对象的封装性,可能是令人产生误解和迷惑的地方。因为RAII classes并不是为了封装某物而存在的,更多的它只是一种模版编程的概念。
牢记在心
l API 通常需要访问原始资源,因此每个 RAII 类都应该提供一个途径来获取它所管理的资源。
l 访问可以通过显式转换或隐式转换来实现。一般情况下,显式转换更安全,但是隐式转换对于客户端程序员来说使用更方便。
要有就是尽量少使用数组。因为C++标准库含有string、vector等template,可将数组的需求降至几乎为0.
C++ Code
Priority函数返回一个执行的优先级, processWidger函数则根据优先级来处理某个类型的对象(许多程序员有时候愿意将某个函数直接做为参数传递进另个函数内)。
1、调用X的构造函数。
2、调用auto_ptr<Widget>的构造函数。
3、调用Priority函数。
看上去井然有序的条件,C++编译器未必会选择这么做。也许编译器选择将调用Priority函数放在第二的位置会生成更高效的代码也说不定。那么顺序就会改为:
1、调用X的构造函数。
2、调用Priority函数。
3、调用auto_ptr<Widget>的构造函数。
那么如果调用Priority函数产生异常怎么办?auto_ptr并没有获得它需要保管的资源,而那段资源也不会遭到释放,有一种资源泄漏的方式。
解决方式就是在外面完成智能指针的存储,编译器对于跨越语句的各项操作不会选择重新排列。这样智能指针依然获得了对所指向资源的保护。
C++ Code
由于编译器对"跨越语句块的各项操作"失去了执行重新排列的自由,所以编译器不能在它们之间任意选择执行顺序.
请记住:
■ 以独立语句将newed对象存储于(置入)智能指针内.如果不这样做,一旦异常被抛出,有可能导致难以察觉的资源
泄露.
条款13 以对象管理资源
许多资源被动态分配与heap内而后被用于单一区块或函数内,他们应该在控制流离开那个区块或者函数时被释放。标准库通的auto_ptr正是针对这种形势特制的。auto_ptr是个“类指针对象”,也就是所谓的智能指针,其析构函数自动对其所指对象调用delete。用法如下:C++ Code
1 2 3 4 5 | void f() { std::auto_ptr<Investment> pInv(createInvestment()); //调用工厂函数,一如既往的使用pInv,经由auto_ptr的析构函数自动删除pInv } |
获得资源后立刻放进管理对象内。(RAII:资源获取实际便是初始化时机)
管理对象运行析构函数确保资源被释放。
上面给出了auto_ptr的智能指针有个问题就是,一定不能让多个auto_ptr同时指向同一个对象,原因在于auto_ptrs有一个不寻常的性质:若通过copy构造函数或copy assignment操作符复制它们,它们会变成null,而复制所得的指针将获得资源的唯一拥有权,见如下代码:
C++ Code
1 2 3 4 5 6 | std::auto_ptr<Investment> pInv1(createInvestment()); //pInv1指向createInvestment返回对象 std::auto_ptr<Investment> pInv2(pInv1); //pInv2指向createInvestment返回对象 ,pInv1为NULL pInv1=pInv2; //pInv1指向createInvestment返回对象,pInv2为NULL |
auto_ptr的替代方案是“引用计数智慧指针(RCSP)”,所谓的RCSPs也是智能指针,持续跟踪共有多少个对象指向某笔资源,并在无人指向它时自动删除该资源。RCSPs提供的行为类似垃圾回收,不同的是RCSPs无法打破环状引用,具体用法如下所示
C++ Code
1 2 3 4 5 6 7 8 9 10 11 12 | void f() { std::tr1::shared_ptr<Investment> pInv1(createInvestment()); //pInv1指向createInvestment返回对象 std::tr1::shared_ptr<Investment> pInv2(pInv1); //pInv2和pInv1同一个对象 pInv1 = pInv2; //同上 ... //pInv2和pInv1b被销毁 //他们所指的对象同样被销毁 } |
auto_ptr和tr1::shared_ptr两者都在其析构函数内做delete而不是delete[]动作,因此动态分配而得到的array身上适用这两个智能指针是个坏主意。
C++ Code
1 2 | std::auto_ptr<std::string> aps(new std::string[10]); //坏主意,能编译 std::tr1::shared_ptr<int> spi(new int[1024]); //相同问题 |
为了防止资源泄漏,请使用RAII对象,它们在构造函数中获得资源并在析构函数中释放资源。
两个常用的RAII分别是tr1::shared_ptr和auto_ptr。前者通常是较佳的选择,因为其copy行为比较直观。若选择auto_ptr,复制它动作是它指向null。
条款14 在资源管理类中小心copying行为
有时候,我们需要自己建立资源管理类,在这个时候,我们需要特别小心copying行为。如下例子:C++ Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | class Lock { public: explicit Lock(Mutex *pm):mutexPtr(pm) { lock(mutexPtr);//获得资源 } ~Lock() { unlock(mutexPtr); //释放资源 } private: Mutex *mutexPtr; } //定义互斥器 Mutex m; ... //建立一个区块用来定义critical section Lock m1(&m); //锁定互斥器 ... //执行critical section 内的操作、在区块最末尾,自动解除互斥器锁定 ///////如果Lock对象被复制,会发生什么呢? Lock m11(&m); //锁定m Lock m12(m11); //复制 |
1、许多时候允许RAII对象被复制并不合理。对一个像Lock这样的类这是可能的,因为很少能够合理拥有“同步化基础物”的副本。如果复制对RAII并不合理,你应该禁止复制。条款6告诉我们怎么做:将copying操作声明为private,对Lock而言看起来是这样的:
C++ Code
1 2 3 4 5 | class Lock : private Uncopyable//禁止复制 { public: ... }; |
通常只要内含一个tr1::shared_ptr成员变量,RAII类便可实现出引用计数行为。如果前述Lock打算使用使用技术,它可以改变mutexPtr类型,将它从Mutex*改为tr1::shared_ptr<Mutex>。然而不幸tr1::shared_ptr的缺省行为是“当引用次数为0时删除其所指物”,那不是我们所要的行为。幸运的是tr1::shared_ptr允许指定所谓的“删除器”,那是一个函数或函数对象,当引用次数为0时便调用。删除器对tr1::shared_ptr构造函数而言是可有可无的第二参数,所以代码看起来像这样:
C++ Code
1 2 3 4 5 6 7 8 9 10 11 | class Lock { public: explicit Lock(Mutex *pm)//以某个Mutex初始化shard_ptr并以unlock函数 : mutexPtr(pm, unlock) //为删除器 { lock(mutexPtr.get());//条款15谈到 get } private: std::tr1::shared_ptr<Mutex> mutexPtr; //使用shared_ptr }; |
3.复制底层资源
有时候只要你喜欢,可以针对一份资源拥有其任意数量的副件,而你需要“资源管理类”的唯一理由是,当你不再需要某个副本的时候确保它被释放,在此情况下复制资源管理对象,应该同时也复制其所包覆的资源,也就是说,复制资源管理了对象时,进行的是“深度拷贝”。
4.转移底部资源的拥有权
某些罕见场合下你可能希望确保永远只有一个RAII对象指向一个未加工资源,即使RAII对象被复制依然如此。此时资源的拥有权会从被复制物转移到目标物。如条款13所诉。这是atuo_ptr奉行的复制意义。
copying函数有可能被编译器自动创建出来,因而除非编译器所生成版本做了你想要做的事情,否则你的自己编写它们。某些情况下你或许也想支持这些函数版本,这样的版本描述于条款45
请记住:
复制RAII对象必须一并复制它所管理的资源,所以资源的copying行为决定RAII对象的copying行为。
普通而常见的RAII类copying行为是:抑制copying,施行引用计数法,不过其他行为都可能被实现。
条款15 在资源管理类中提供对原始资源的访问
1.如何访问原始资源在上两条款我们知道如何使用智能指针管理我们的申请的资源,但是读者是否发现,我们如何去访问我们原始资源的方法呢?在这一条款将得到解答以及类型转换的相关问题。
首先,我们用代码来说话吧,及输出的结构看到
C++ Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | // pro_acce.cpp : 定义控制台应用程序的入口点。 //2011/9/21 by wallwind on sunrise; #include "stdafx.h" #include <iostream> #include <memory> using namespace std; class myClass { public : myClass() { cout<<"myClass()"<<endl; } ~myClass() { cout<<"~myClass()"<<endl; } void printFunc() { cout<<"printFunc()"<<endl; } int getType()const { return type; } private: int type; }; myClass* creatMyClass() { myClass *my=new myClass(); return my; } void issue(myClass *my) { delete my; } int _tmain(int argc, _TCHAR* argv[]) { auto_ptr<myClass> apMy(creatMyClass()); myClass* myC=apMy.get();//////auto_ptr 给我们提供的函数,用来访问原始资源 myC->printFunc();//////调用了myClass的方法 return 0; } |
从这里我们可以看到,程序输出了我们想要的结果。如书中所述:
tr1::shared_ptr 和 auto_ptr 都提供了一个 get 成员函数来进行显式转换,也就是说,返回一个智能指针对象中的裸指针(的副本):
myClass* myC=apMy.get();//////
似乎所有的智能指针类,包括 tr1::shared_ptr 和 auto_ptr 等等,都会重载指针解析运算符( operator-> 和 operator* ),这便使得对原始裸指针进行隐式转换成为现实,在这里我就不实际举例子了。下面使用书中的片段代码来说明一下问题吧:
C++ Code
1 2 3 4 5 6 7 8 9 10 11 | std::tr1::shared_ptr<Investment> pi1(createInvestment()); // 使用 tr1::shared_ptr // 管理资源 bool taxable1 = !(pi1->isTaxFree()); // 通过 operator-> 访问资源 ... std::auto_ptr<Investment> pi2(createInvestment()); // 使用 auto_ptr 管理资源 bool taxable2 = !((*pi2).isTaxFree()); // 通过 operator* 访问资源 |
也许您注意到了
C++ Code
1 2 3 4 5 6 7 8 9 10 11 12 | void issue(myClass *my) { delete my; } //这个函数没有使用到,那么当你使用这个时候 int _tmain(int argc, _TCHAR* argv[]) { auto_ptr<myClass> apMy(creatMyClass()); issue(apMy.get()); return 0; } |
2.隐式转换
因为有时候要取得RAII对象内原始资源, RAII设计者使用了一种隐式转换函数,
如果让资源管理类型提供隐式转换函数,可以让行为变的更自然,但这样的作法没有好下场,只会增加客户端发生错误的机率。比如下面的代码(简单的编写了一个自定义的AutoPtr,它重载了隐式转换operator):
C++ Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | #include "stdafx.h" #include <stdlib.h> #include <memory> #include <string> using namespace std; template<typename T> class AutoPtr { public: AutoPtr(T *tP) : _tP(tP), _released(false) { } ~AutoPtr() { Release(); } T *operator->() { return _tP; } operator T *() const { return _tP; } void Release(void) { if (!_released) { delete _tP; _released = true; } } private: T *_tP; bool _released; }; typedef struct Point { double X; double Y; }; void PrintPoint(Point *pTP) { } int _tmain(int argc, _TCHAR *argv[]) { AutoPtr<Point> apI(new Point()); PrintPoint(apI); Point *pTP = apI; apI.Release(); system("pause"); return 0; } |
也许在诸如auto_ptr、shared_ptr只提供原始指针的访问违背了面向对象的封装性,可能是令人产生误解和迷惑的地方。因为RAII classes并不是为了封装某物而存在的,更多的它只是一种模版编程的概念。
C++ Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 | #include "stdafx.h" #include <stdlib.h> #include <memory> #include <string> using namespace std; template<typename T> class AutoPtr { public: AutoPtr(T *tP) : _tP(tP), _released(false) { } ~AutoPtr() { Release(); } T *operator->() { return _tP; } operator T *() const { return _tP; } void Release(void) { if (!_released) { delete _tP; _released = true; } } private: T *_tP; bool _released; }; typedef struct Point { double X; double Y; }; void PrintPoint(Point *pTP) { } int _tmain(int argc, _TCHAR *argv[]) { AutoPtr<Point> apI(new Point()); PrintPoint(apI); Point *pTP = apI; apI.Release(); system("pause"); return 0; } |
也许在诸如auto_ptr、shared_ptr只提供原始指针的访问违背了面向对象的封装性,可能是令人产生误解和迷惑的地方。因为RAII classes并不是为了封装某物而存在的,更多的它只是一种模版编程的概念。
牢记在心
l API 通常需要访问原始资源,因此每个 RAII 类都应该提供一个途径来获取它所管理的资源。
l 访问可以通过显式转换或隐式转换来实现。一般情况下,显式转换更安全,但是隐式转换对于客户端程序员来说使用更方便。
条款16 成对使用new和delete时要采用相同形式
原理还是:使用new,配对使用delete,使用new[],配对使用delete[] 。但是使用new[]时,采用delete呢?会导致析构函数少调用情况。要有就是尽量少使用数组。因为C++标准库含有string、vector等template,可将数组的需求降至几乎为0.
条款17 以独立语句将newed对象置入智能指针
c++编译器会优化你的代码,会根据优先权来选择优先执行哪些代码。C++编译器以什么样的此讯完成这些事情呢?弹性很大。这和其他诸如java,c#不同。她们总是以特定顺序来执行。C++ Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | // store_new.cpp : 定义控制台应用程序的入口点。 #include "stdafx.h" #include <iostream> #include <memory> using namespace std; class Widget { public: Widget() { cout << "Widget()" << endl; } ~Widget() { cout << "Widget()" << endl; } }; int priority() { throw new runtime_error("Exception"); //return 0; } void processWidger(auto_ptr<Widget> pw, int priority) {} int _tmain(int argc, _TCHAR *argv[]) { processWidger(auto_ptr<Widget>(new Widget()), priority()); system("pause"); return 0; } |
1、调用X的构造函数。
2、调用auto_ptr<Widget>的构造函数。
3、调用Priority函数。
看上去井然有序的条件,C++编译器未必会选择这么做。也许编译器选择将调用Priority函数放在第二的位置会生成更高效的代码也说不定。那么顺序就会改为:
1、调用X的构造函数。
2、调用Priority函数。
3、调用auto_ptr<Widget>的构造函数。
那么如果调用Priority函数产生异常怎么办?auto_ptr并没有获得它需要保管的资源,而那段资源也不会遭到释放,有一种资源泄漏的方式。
解决方式就是在外面完成智能指针的存储,编译器对于跨越语句的各项操作不会选择重新排列。这样智能指针依然获得了对所指向资源的保护。
C++ Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | // store_new.cpp : 定义控制台应用程序的入口点。 #include "stdafx.h" #include <iostream> #include <memory> using namespace std; class Widget { public: Widget() { cout << "Widget()" << endl; } ~Widget() { cout << "Widget()" << endl; } }; int priority() { throw new runtime_error("Exception"); //return 0; } void processWidger(auto_ptr<Widget> pw, int priority) {} int _tmain(int argc, _TCHAR *argv[]) { auto_ptr<Widget> pw(new Widget()); processWidger(pw, priority()); system("pause"); return 0; } |
请记住:
■ 以独立语句将newed对象存储于(置入)智能指针内.如果不这样做,一旦异常被抛出,有可能导致难以察觉的资源
泄露.
相关文章推荐
- 第三章信息系统资源管理
- Effective c++ 第三章 (资源管理)
- (Effective C++)第三章 资源管理(Resource Management)
- 第三章 资源管理
- 信息资源管理第三章——初识系统
- 信息资源管理第三章——初识系统
- 信息资源管理第三章——初识系统
- 第三章信息系统资源管理
- Effective C++第三章-资源管理-2
- 信息资源管理第三章——初识系统
- Effective C++第三章-资源管理-1
- 信息资源管理第三章——初识系统
- 信息资源管理第三章——初识系统
- 《Effective C++》第三章:资源管理
- 信息资源管理第三章——初识系统
- linux bible 第三章 存储管理
- 编程中的资源管理(二)
- ITSM网上资源导航 From:act.it.sohu.com 《IT服务管理:概念、理解与实施》
- 关于数据访问模式(六)—— 资源管理模式的重要性
- 网格建模资源管理(第一次翻译老外的东西,嘿嘿!)