Effective c++ 第三章 (资源管理)
来源:互联网 发布:unix高级编程第四版pdf 编辑:程序博客网 时间:2024/06/05 05:16
条款13:以对象管理资源
假设一个类层次,基类是Investment,然后通过一个工厂函数产生某个特定的invertment对象(可能是某个继承类对象),如下:
//基类class Investment{...}//程序库通过一个工厂函数产生某个特定的inverstment对象,返回指针Investment* createInvestment(); //这导致调用者需要对这个返回的指针进行删除void f(){Investment* pInv = createInvestment();//这里中间可能过早返回,或抛出异常,则都不会执行delete,也就会发生资源泄漏...delete pInv;}
为了解决上述问题,可以使用对象来管理这个对象,当退出f时,自动调用对象的析构函数。
C++有两个类可以提供:str::auto_ptr, TR1的tr1::shared_ptr。这两个都可以当ptr被销毁时,对象被销毁。为了防止资源泄漏,应使用RAII对象,它们在构造函数汇总获得资源并在析够函数中释放资源。
注意不能让多个auto_ptr指向同一对象,否则一个对象会被删除多次,严重错误。所以auto为了防止这个问题,有以下不同寻常的性质:若通过copy构造函数或拷贝赋值操作符复制它们,它们会变成null, 而复制所得的指针将取得资源的唯一所有权。由于它的异常拷贝行为,所以它并不是管理动态分配资源的利器。而且不能在STL容器中使用auto_ptr,因为STL容器要求其元素发挥“正常的复制行为”。auto_ptr的行为:
//解决方法1:使用auto_ptr管理对象资源void f(){//这里是RAII,获得资源后立刻放进管理对象 std::auto_ptr<Investment> pInv(createInvestment);//auto_ptr的复制行为std::auto_ptr<Investment> pInv2(pInv); //pInv2指向对象,pInv=nullpInv = pInv2; //pInv指向对象,pInv2=null}//离开函数的时候,调用auto_ptr的析构函数确保资源被释放auto_ptr的替代方案是“引用计数型智能指针”,它维护一个变量表示共有多少对象指向某笔资源,并在变量变为0时删除对象。类似垃圾回首,但无法打破环状引用。它的拷贝行为正常:
//解决方法2:使用tr1::shared_ptr管理对象资源void f(){//这里是RAII,获得资源后立刻放进管理对象 std::tr1::shared_ptr<Investment> pInv(createInvestment);//shared_ptr的复制行为std::tr1::shared_ptr<Investment> pInv2(pInv); //pInv2, pInv指向同一对象pInv = pInv2; //pInv,pInv2指向同一对象}//离开函数的时候,调用shared_ptr的析构函数确保pInv,pInv2被释放令auto_ptr与shared_ptr都在其析构函数内做delete而不是delete[],这意味着在动态分配而得的array上使用它们是不应该的,如:
//以下行为虽然可以通过编译器,但是会有资源泄漏,不应该使用std::auto_ptr<std::string> aps(new std::string[10]);std::tr1::shared_ptr<std::string> api(new int[1024]);
条款14:在资源管理类中小心copying行为
对于某些资源,并不适合使用auto_ptr和shared_ptr管理资源,有可能需要自己写管理资源的类,并遵守RAII,例如以下,Mutex表示互斥对象,lock, unlock对资源管理,可以建一个类来管理这个资源,并且对于这个类的拷贝行为,可以有以下四种行为,如下:
//资源管理类的copying行为class Lock{public:explicit Lock(Mutex* pm) : mutexPtr(pm){lock(mutexPtr); //构造函数锁住资源}~Lock(){unlock(mutexPtr); //析构函数释放资源}};//客户端的用法Mutex m;Lock ml(&m); //锁定,执行关键区域内的操作...//如果进行拷贝,可以发生以下情况:Lock ml1(&m);Lock ml2(ml1);//情况1:禁止复制,参考条款6的做法,//1声明为私有,不定义,如果复制发生链接错误//2建一个不能拷贝的基类,并私有继承于它,这将错误移到编译期class Lock : private Uncopyable{};//情况2:引用计数法,例如shared_ptr的行为,但当资源变为0时,它的默认行为是//删除所指物,而如果我们只是释放锁定,这种情况下可以利用shared_ptr中可以指定“删除器”//的一个函数或函数对象,当引用计数为0时,调用此函数,这个参数对它的构造函数是可有可无的第二个参数class Lock{public:explicit Lock(Mutex* pm) : mutexPtr(pm, unlock) //unlock即为指定的删除器{lock(mutexPtr.get());}private:std::tr1::shared_ptr<Mutex> mutexPtr; //用shared_ptr管理这个对象,并且指定删除器,自定义行为};//而且以上不再需要析够函数,因为析构函数会在引用计数为0时自动调用shared_ptr的删除器//情况3:复制底部资源,进行深度拷贝,例如string类,//情况4:转移底部所有权,如auto_ptr的行为,资源所有权从被复制物转移到目标物总结:1 赋值RAII对象必须一并复制它所管理的资源,所以资源的copying行为决定RAII对象的拷贝行为 2 普遍而常见的RAII类拷贝行为是:抑制拷贝,施行引用 计数法。不过其他行为也都可能实现。
条款15:在资源管理类中提供对原始资源的访问
APIs往往要求访问原始资源,所以每个RAII类应该提供一个“取得其所管理之资源”的方法。
对原始资源的访问可能经由显示转换或隐式转换。一般而言显示转换比较安全,但隐式转换对客户比较方便。例如shared_ptr与auto_ptr都提供了一个get函数,来返回指向的内部资源。再如下面的例子:
//资源管理类中提供对原始资源的访问class Font{public:explicit Font(FontHandle fh) : f(fh){}//可以提供两种访问原始资源的方式://1 显示转换函数,优点是安全,缺点是每次调用都需要访问该函数FontHandle get() const{return f;}//2 隐式转换,重载转换操作符,优点不需显示调用,会隐式执行,缺点:容易出错operator FontHandle() const{return f;}~Font(){releaseFont(f);}private:FontHandle f; //管理的原始字体资源};对于隐式的转换会增加错误发生的机会,如下:
Font f1(getFont());
FontHandle f2 = f1; //本来是想拷贝Font对象,却隐式转换f1为FontHandle,然后进行了复制
以上一个潜在的问题是,f1拥有的资源被f2取得,这不会有好下场,例如f1被销毁,字体释放,f2成为虚吊的。
所以获得RAII底部资源,是使用显示调用还是隐式转换,取决于RAII类被设计的特定工作,以及它被使用的情况。条款18有,让接口容易被正确使用,不易被勿用,所以显示的get通常比较受欢迎。
条款16:成对的使用new和delete时采取相同的形式
如果在new表达式中使用[ ], 必须在相应的delete表示式中也使用 [ ], 如果new不使用,delete也不要使用。
这其实就是数组与单个对象的形式。因为单一对象的内存布局一般而言不同于数组内存布局。数组内存通常还包括“数组大小”,以便知道delete需要调用多少次析构函数。单一对象没有这笔记录。
当使用new时,有两件事发生:1 内存会被分配出来(通过operator new) 2针对此内存调用一个(或多个)构造函数;
当使用delete时,有两件事发生:1 析构函数调用 2 内存被释放(通过operator delete)
当你写的一个类中含有一个指针指向动态分配的内存,并提供多个构造函数时,这时要使所有构造函数中使用相同的形式的new将指针成员初始化,因为析构函数中只能有一种delete形式。
最好不要对数组形式做typedefs动作,不然容易发生错误:
typedef std::string AddressLines[4]; //定义了一个AddressLines类型,执行string [4]std::string* pal = new AddressLines; //这里分配的是数组,相当于new string[4]delete pal; //错误,但是可能会发生delete [] pal; //正确的形式,但与以上new不对称
条款17:以独立语句将newed对象置如智能对象。
如以下例子会有隐式的资源泄漏:
//以独立语句将newed对象置入智能指针//有以下函数int priority();void processWidget(std::tr1::shared_ptr<Widget> pw, int priority);//以下调用形式,不会通过编译,因为tr1::shared_ptr的构造函数是explicit函数//虽然它接受一个widget的指针,但是不能进行隐式转换,processWidget(new Widget, priority());//所以如果使用以下强制转化,可以通过,但这可能有隐式的资源泄漏processWidget(std::tr1::shared_ptr<Widget>(new Widget), priority());
对于std::tr1::shared_ptr<Widget>(new Widget)的调用有两部分组成:
1 执行“new Widget”表达式
2 调用tr1::shared_ptr构造函数
于是在调用processWidget之前,必须做以下三件事:
* 调用priority
* 执行“new Widget”表达式
* 调用tr1::shared_ptr构造函数
但编译器完成以上事情的次序并不一定,但是new Widget一定在shared_ptr构造函数之前,它有可能是以下序列
1 执行“new Widget”表达式
2调用priority
3调用tr1::shared_ptr构造函数
这样可能会有资源泄漏,即如果priority调用过程中发生异常,则new出的指针会丢失!!太隐蔽了。。。为了避免以上事情的发生:使用分离语句 1 创建Widge,构造一个智能指针,2 传给processWidget
//解决方案:std::tr1::shared_ptr<Widget> pw(new Widget);processWidget(pw, priority());
总结:以独立语句将newed对象存储与智能指针内,如果不这么做,一旦异常被抛出,可能导致难以察觉的资源泄漏。
- 《Effective C++》第三章:资源管理
- (Effective C++)第三章 资源管理(Resource Management)
- Effective c++ 第三章 (资源管理)
- Effective C++(三)资源管理
- Effective C++第三章-资源管理-1
- Effective C++第三章-资源管理-2
- Effective C++ 第三章 资源管理 理解&总结
- 《Effective C++》读书笔记(四) 资源管理
- <Effective C++: 资源管理> 笔记
- Effective C++(三)资源管理
- effective C++: 3资源管理
- effective c++-资源管理
- 《Effective C++》资源管理章节
- effective c++:资源管理相关
- 《Effective C++》读书笔记之中资源管理
- Effective C++——资源管理
- <<Effective C++>>读书笔记3: 资源管理
- Effective C++读书笔记 第三部分 资源管理
- 关键帧动画CAKeyframeAnimation
- Triangle
- Cocos2d-JS打包, cocos2dx-3.0
- ubuntu13.04和windows双系统启动顺序的修改
- java笔记-银行业务调度系统
- Effective c++ 第三章 (资源管理)
- java如何调用webservice接口
- 新的开始
- 美食管家餐饮管理系统
- LeetCode题目索引
- php下载文件头信息
- 奇异值分解讲解
- 简单,就是最好的网络推广的方法
- Android自定义DataTimePicker(日期选择器)