读书笔记_Effective C++_资源管理
2015-05-05 09:57
218 查看
这个章节主要讲的是资源管理相关的知识,C++程序中最常使用的资源就是动态分配内存,但内存只是必须管理的众多资源之一,其他常见的资源还有文件描述器、互斥锁、图形界面的字型和笔刷、数据库连接、以及网络sockets。无论是哪一种资源,重要的是,当你不再使用它时,必须将它还给系统。
条款十三
通常调用这种factory函数,都需要在恰当的时候释放了pInv所指对象。
当很多意外的情况就会导致pInv无法释放,为了确保这一点,需要将资源放进对象中,当控制流离开f,该对象的析构函数会自动释放那些资源(把资源放进对象内,可以依赖C++的析构函数确保资源被释放)。
以对象管理资源的关键:
(1)获得资源后立刻放进管理对象内;
(2)管理对象运用析构函数确保资源被释放。
auto_ptr被销毁的时候会自动销毁所指的对象,所以应该要注意别让多个auto_ptr同时指向同一个对象。为了预防这个问题,auto_ptrs有一个不寻常的性质:若通过copy构造函数或copy assignment操作符复制它们,它们就会变成null,而复制所得的指针将获得资源的唯一拥有权。
受auto_ptrs管理的资源必须绝对没有一个以上的auto_ptr同时指向它,这样的话STL容器就不能存放auto_ptr(STL容器要求其元素发挥正常的复制行为)。
而TR1的tr1::shared_ptr就是个RCSP:
所以tr1::shared_ptr可被用于STL容器等语境上。
auto_ptr和tr1::shared_ptr两者都在其析构函数内做delete而不是delete[]动作,意味着动态分配而得的array是无法使用的。一般对于动态分配数组都是使用vector和string,当然使用了Boost程序库的话,可以用boost::scoped_array和boost::shared_array。
对于C++动态分配数组,没有类似auto_ptr或tr1::shared_ptr那样的东西,甚至TR1中也没有。因为vector和string几乎总是可以取代动态分配而得的数组,在Boost里面有boost::scoped_array和boost::shared_array_classes。
条款十四:在资源管理类中小心copying行为
auto_ptr和tr1::shared_ptr可以很好地管理heap-based的资源,但是对于非heap-based的资源就需要自己建立管理类来进行管理。
例如我们使用C API函数处理类型为Mutex的互斥器对象,共有lock和unlock两函数可用:
为了确保不会忘记把一个被锁住的Mutex解锁,我们需要建立一个类来管理锁。
目前的写法符合前一条的RAII方式,但如果Lock对象被复制要怎么处理?通常大家采用两种解决方案:
禁止复制:许多时候允许RAII对象被复制并不合理,所以应该像条款6一样直接禁止;
对底层资源使用引用计数:可以使用自己的引用计数器,也可以使用标准库中的tr1::shared_ptr,tr1::shared_ptr可以指定deleter,可以是一个函数或函数对象,当引用次数为0时便被调用(这个机制并不存在auto_ptr)。
这里就不要声明析构函数了,class析构函数会自动调用non-static成员变量的析构函数。
复制底部资源:某些标准字符串类型是由“指向heap内存”之指针构成(那内存被用来存放字符串的组成字符)。这种字符串对象内含一个指针指向一块heap内存。当这样一个字符串对象被复制,不论指针或其所指内存都会被***出一个复件。
Copying函数有可能被编译器自动创建出来,因此除非编译器所生版本做了你想要做的事,否则你得自己重写它们。
PS:
注意“Lock(Mutex* pm) : mutexPtr(pm)”的冒号声明,表示在调用Lock(Mutex* pm)之前,先调用mutexPtr(pm),这种写法强调了调用的时序。
条款十五:在资源管理类中提供对原始资源的访问
资源管理类是处理资源泄露的很好的设计模式,但是很多情况下类的APIs会直接指涉资源,这样的APIs会直接绕过资源管理对象直接访问原始资源。
例如:int daysHeld(const Investment* pi);这样一个函数,int days = daysHeld(pInv);会显示编译错误,因为传递的是一个tr1::shared_ptr对象。这个时候需要进行显示转换或隐式转换。
tr1::shared_ptr和auto_ptr都提供了一个get成员函数,用来执行显示转换,int days = daysHeld(pInv.get());
tr1::shared_ptr和auto_ptr也重载了指针取值操作符(operator->和operator*),它们允许隐式转换至底部原始指针。
我们在进行封装的时候也有类似的情况,可以提供一个显示的get转换函数,但是这也迫使了使用者每次调用API的时候都需要调用get。这是可以提供一个隐式的转换函数,使得调用更加轻松和自然。
对于许多设计良好的classes,它隐藏了使用者不需要看的部分,但备妥使用者需要的所有东西。
条款十六:成对使用new和delete时要采取相同形式
对于即将删除的那个指针,所指的是单一对象或对象数组?这是个必不可缺的问题,因为单一对象的内存布局一般而言不同于数组的内存布局。更明确地说,数组所用的内存通常还包括“数组大小”的记录,以便delete知道需要调用多少次析构函数。单一对象的内存则没有这笔记录。
当你对一个指针使用delete,唯一能够让delete知道内存中是否存在一个“数组大小记录”的办法就是:由你来告诉它,即使用delete时加上中括号,delete便认定指针指向一个数组,否则它便认定指针指向单一对象。
这个规则对于喜欢使用typedef的人也很重要,因为它意味typedef的作者必须说清楚,当使用者以new创建该种typedef类型对象时,该以哪一种delete形式删除之。
条款十七:以独立语句将newed对象置入智能指针
假设我们有个函数用来揭示处理程序的优先权,另一个函数用来在某动态分配所得的Widget上进行某些带有优先权的处理:
现在调用processWidget,”processWidget(new Widget, priority());”,这样的调用形式是无法通过编译的。tr1::shared_ptr构造函数需要一个原始指针,但该构造函数是个explict构造函数,无法进行隐式转换。可以写成这样”pricessWidget(std::tr1::shared_ptr(new Widget), priority());”,然后这种写法可能会泄露资源。
编译器在产出一个processWidget调用代码之前,必须首先核算即将被传递的各个实参。在调用processWidget之前,编译器必须创建代码,做一下三件事:
调用priority
执行“new Widget”
调用tr1::shared_ptr构造函数
C++编译器以什么样的次序完成这些事情呢?弹性很大,这和其他语言如Java和C#不同,那两种语言总是以特定次序完成函数参数的核算。可以确定的是“new Widget”一定执行于tr1::shared_ptr的构造函数被调用之前,因为这个表达式的结果还要被传递作为tr1::shared_ptr构造函数的一个实参,但对priority的调用则可以排在第一或第二或第三执行。如果编译器选择以第二顺位执行它,最终获得这样的操作序列。
执行“new Widget”
调用priority
调用tr1::shared_ptr构造函数
现在,如果对priority的调用导致异常,“new Widget”返回的指针将会遗失,因为它尚未被置入tr1::shared_ptr内,后者是我们用来防止资源泄露的武器。在“资源被创建”和“资源被转换为资源管理对象”两个时间点之间可能发生异常干扰。
避免这类问题的办法很简单:使用分离语句,分别写出(1)创建Widget(2)将它置入一个智能指针内,然后再把那个智能指针传给processWidget。
编译器对于“跨越语句的各项操作”没有重新排列的自由(只有在语句内才拥有那个自由度)。
条款十三
[code]Investment* createInvestment(); //返回指针,指向Investment继承体系内的动态分配对象,调用者有责任删除它
通常调用这种factory函数,都需要在恰当的时候释放了pInv所指对象。
[code]void f() { Investment* pInv = createInvestment(); ... delete pInv; }
当很多意外的情况就会导致pInv无法释放,为了确保这一点,需要将资源放进对象中,当控制流离开f,该对象的析构函数会自动释放那些资源(把资源放进对象内,可以依赖C++的析构函数确保资源被释放)。
[code]void f() { std::auto_ptr<Investment> pInv(createInvestment()); ... }
以对象管理资源的关键:
(1)获得资源后立刻放进管理对象内;
(2)管理对象运用析构函数确保资源被释放。
auto_ptr被销毁的时候会自动销毁所指的对象,所以应该要注意别让多个auto_ptr同时指向同一个对象。为了预防这个问题,auto_ptrs有一个不寻常的性质:若通过copy构造函数或copy assignment操作符复制它们,它们就会变成null,而复制所得的指针将获得资源的唯一拥有权。
[code]std::auto_ptr<Investment> pInv1(createInvestment()); std::auto_ptr<Investment> pInv2(pInv1); // 现在pInv2指向对象,pIn1被设为null pInv1 = pInv2; // 现在pInv1指向对象,pInv2被设为null
受auto_ptrs管理的资源必须绝对没有一个以上的auto_ptr同时指向它,这样的话STL容器就不能存放auto_ptr(STL容器要求其元素发挥正常的复制行为)。
而TR1的tr1::shared_ptr就是个RCSP:
[code]void f() { ... std::tr1::shared_ptr<Investment> pInv1(createInvestment()); std::tr1::shared_ptr<Investment> pInv2(pInv1); // pInv1和pInv2指向同一个对象 pInv1(pInv2); // 同理 pInv1 = pInv2; // 同理 ... }
所以tr1::shared_ptr可被用于STL容器等语境上。
auto_ptr和tr1::shared_ptr两者都在其析构函数内做delete而不是delete[]动作,意味着动态分配而得的array是无法使用的。一般对于动态分配数组都是使用vector和string,当然使用了Boost程序库的话,可以用boost::scoped_array和boost::shared_array。
对于C++动态分配数组,没有类似auto_ptr或tr1::shared_ptr那样的东西,甚至TR1中也没有。因为vector和string几乎总是可以取代动态分配而得的数组,在Boost里面有boost::scoped_array和boost::shared_array_classes。
条款十四:在资源管理类中小心copying行为
auto_ptr和tr1::shared_ptr可以很好地管理heap-based的资源,但是对于非heap-based的资源就需要自己建立管理类来进行管理。
例如我们使用C API函数处理类型为Mutex的互斥器对象,共有lock和unlock两函数可用:
[code]void lock(Mutex* pm); //锁定pm所指的互斥器 void unlock(Mutex* pm); //将互斥器解除锁定
为了确保不会忘记把一个被锁住的Mutex解锁,我们需要建立一个类来管理锁。
[code]class Lock { public: explicit Lock(Mutex* pm) : mutexPtr(pm) { lock(mutexPtr); //获得资源 } ~Lock() { unlock(mutexPtr); }; //释放资源 private: Mutex* mutexPtr; };
目前的写法符合前一条的RAII方式,但如果Lock对象被复制要怎么处理?通常大家采用两种解决方案:
禁止复制:许多时候允许RAII对象被复制并不合理,所以应该像条款6一样直接禁止;
对底层资源使用引用计数:可以使用自己的引用计数器,也可以使用标准库中的tr1::shared_ptr,tr1::shared_ptr可以指定deleter,可以是一个函数或函数对象,当引用次数为0时便被调用(这个机制并不存在auto_ptr)。
[code]class Lock { public: explicit Lock(Mutex* pm) : mutexPtr(pm, unlock) { lock(mutexPtr.get()); } private: std::tr1::shared_ptr<Mutex> mutexPtr; };
这里就不要声明析构函数了,class析构函数会自动调用non-static成员变量的析构函数。
复制底部资源:某些标准字符串类型是由“指向heap内存”之指针构成(那内存被用来存放字符串的组成字符)。这种字符串对象内含一个指针指向一块heap内存。当这样一个字符串对象被复制,不论指针或其所指内存都会被***出一个复件。
Copying函数有可能被编译器自动创建出来,因此除非编译器所生版本做了你想要做的事,否则你得自己重写它们。
PS:
注意“Lock(Mutex* pm) : mutexPtr(pm)”的冒号声明,表示在调用Lock(Mutex* pm)之前,先调用mutexPtr(pm),这种写法强调了调用的时序。
条款十五:在资源管理类中提供对原始资源的访问
资源管理类是处理资源泄露的很好的设计模式,但是很多情况下类的APIs会直接指涉资源,这样的APIs会直接绕过资源管理对象直接访问原始资源。
例如:int daysHeld(const Investment* pi);这样一个函数,int days = daysHeld(pInv);会显示编译错误,因为传递的是一个tr1::shared_ptr对象。这个时候需要进行显示转换或隐式转换。
tr1::shared_ptr和auto_ptr都提供了一个get成员函数,用来执行显示转换,int days = daysHeld(pInv.get());
tr1::shared_ptr和auto_ptr也重载了指针取值操作符(operator->和operator*),它们允许隐式转换至底部原始指针。
我们在进行封装的时候也有类似的情况,可以提供一个显示的get转换函数,但是这也迫使了使用者每次调用API的时候都需要调用get。这是可以提供一个隐式的转换函数,使得调用更加轻松和自然。
[code]class Font { public: ... operator FontHandle() const { return f; } ... };
对于许多设计良好的classes,它隐藏了使用者不需要看的部分,但备妥使用者需要的所有东西。
条款十六:成对使用new和delete时要采取相同形式
对于即将删除的那个指针,所指的是单一对象或对象数组?这是个必不可缺的问题,因为单一对象的内存布局一般而言不同于数组的内存布局。更明确地说,数组所用的内存通常还包括“数组大小”的记录,以便delete知道需要调用多少次析构函数。单一对象的内存则没有这笔记录。
当你对一个指针使用delete,唯一能够让delete知道内存中是否存在一个“数组大小记录”的办法就是:由你来告诉它,即使用delete时加上中括号,delete便认定指针指向一个数组,否则它便认定指针指向单一对象。
这个规则对于喜欢使用typedef的人也很重要,因为它意味typedef的作者必须说清楚,当使用者以new创建该种typedef类型对象时,该以哪一种delete形式删除之。
条款十七:以独立语句将newed对象置入智能指针
假设我们有个函数用来揭示处理程序的优先权,另一个函数用来在某动态分配所得的Widget上进行某些带有优先权的处理:
[code]int priority(); void processWidget(std::tr1::shared_ptr<Widget>pw, int priority);
现在调用processWidget,”processWidget(new Widget, priority());”,这样的调用形式是无法通过编译的。tr1::shared_ptr构造函数需要一个原始指针,但该构造函数是个explict构造函数,无法进行隐式转换。可以写成这样”pricessWidget(std::tr1::shared_ptr(new Widget), priority());”,然后这种写法可能会泄露资源。
编译器在产出一个processWidget调用代码之前,必须首先核算即将被传递的各个实参。在调用processWidget之前,编译器必须创建代码,做一下三件事:
调用priority
执行“new Widget”
调用tr1::shared_ptr构造函数
C++编译器以什么样的次序完成这些事情呢?弹性很大,这和其他语言如Java和C#不同,那两种语言总是以特定次序完成函数参数的核算。可以确定的是“new Widget”一定执行于tr1::shared_ptr的构造函数被调用之前,因为这个表达式的结果还要被传递作为tr1::shared_ptr构造函数的一个实参,但对priority的调用则可以排在第一或第二或第三执行。如果编译器选择以第二顺位执行它,最终获得这样的操作序列。
执行“new Widget”
调用priority
调用tr1::shared_ptr构造函数
现在,如果对priority的调用导致异常,“new Widget”返回的指针将会遗失,因为它尚未被置入tr1::shared_ptr内,后者是我们用来防止资源泄露的武器。在“资源被创建”和“资源被转换为资源管理对象”两个时间点之间可能发生异常干扰。
避免这类问题的办法很简单:使用分离语句,分别写出(1)创建Widget(2)将它置入一个智能指针内,然后再把那个智能指针传给processWidget。
[code]std::tr1::shared_ptr<Widget> pw(new Widget); processWidget(pw, priority());
编译器对于“跨越语句的各项操作”没有重新排列的自由(只有在语句内才拥有那个自由度)。
相关文章推荐
- 《Effective C++》第3章 资源管理(1)-读书笔记
- effective C++ 读书笔记 条款14 以对象管理资源
- effective C++ 13_以对象管理资源 读书笔记
- 《Effective C++》第3章 资源管理(2)-读书笔记
- 读书笔记《Effective C++》条款13:以对象管理资源
- 《Effective C++》 读书笔记之三 资源管理
- 《effective C++》读书笔记三——资源管理
- effective C++ 读书笔记 条款14 以对象管理资源
- 读书笔记《Effective c++》 条款13 以对象管理资源
- 读书笔记 Effective C++: 03 资源管理
- 《Pro Ogre 3D Programming》 读书笔记 之 第七章 资源管理(转)
- Effective C++ 精要(第三部分:资源管理)
- effective C++ 条款 13:以对象管理资源
- Effective C++ 对象管理资源
- Effective C++ 条款13学习笔记:以对象管理资源
- 读书笔记 effective c++ Item 13 用对象来管理资源
- 读书笔记《Effective C++》条款15:在资源管理类中提供对原始资源的访问
- Effective C++ 笔记 第三部分 资源管理
- 【读书笔记】Effective C++—3 资源管理(之1)
- C++中以对象管理资源<auto_ptr>(13)---《Effective C++》