Effective C++ 读书笔记(三)
3、 资源管理
条款 13:以对象管理资源
-
在一个作用域内,在delete 之前就return了,会造成内存泄漏,所以delete管理内存远远不够
void fun() { Investment* pInv=CreateInvestment(); ……//这里提前 return delete pInv;//释放资源 }
-
用对象控制对象,离开了作用域自然会调用析构函数析构,比如使用智能指针auto_ptr(唯一资源使用权)这里
条款 14: 在资源管理类中小心coping行为
-
但是并不是所有资源都是开辟在堆上,有时候我们需要自己建立资源管理类
class Lock{ public: explicit Lock(Mutex* mu):mutexPtr(mu) { lock(mutexPtr); } ~Lock() { unlock(mutexPtr); } private: Mutex* mutexPtr; };
这样客户对Lock的使用方法符合RAII方式:
Mutex m;//定义互斥器 …… {//建立区块来定义critical section Lock(&m); ……//执行critical section 内的操作 }//在区块末尾,自动解除互斥器的锁
当一个RAII对象被复制,会发生什么?有以下做法
-
禁止复制,将coping函数设置为私有,条款6
-
对管理资源使用引用计数法,复制的时候就加1 。mutexPrt变为类型从Mutex*变为shared即可
class Lock:private Uncopyable{ public: explicit Lock(Mutex* mu):mutexPtr(mu,unlock)//以某个Mutex初始化,unlock作为删除其 { lock(mutexPtr); } private: shared_prt<Mutex> mutexPtr; };
注意的是在这个类中并没有自己编写析构函数。因为mutexPtr是类中的普通成员变量,编译器会自动生成析构函数类析构这样的变量。这个在条款5中有说明。
-
复制底部资源
使用资源管理类的目的是保证不需要这个资源时能正确释放。如果这种资源可以任意复制,我们只需编写好适当的copying函数即可。确保复制时是深度复制。关于深复制,参考这里。
C++中的string类,内部是指向heap的指针。当string复制时,底层的指针指向的内容都会多出一份拷贝 -
转移底层资源的拥有权。
有时候资源的拥有权只能给一个对象,这时候当资源复制时,就需要剥夺原RAII类对该资源的拥有权。像auto_ptr。在C++11新标准中的std::move便是这个功能。可以把一个左值转换为一个右值左值与右值。
copying函数如果你不编写,编译器会帮你合成,其合成版本行为可参考条款5。要记住的是不论是自己编写还是编译器合成,都要符合自己资源管理类的需要。
-
条款 15 :在资源管理类中提供对原始资源的访问
-
原始资源,没有经过封装的指针(可以这样理解)
//用智能指针来保存返回值 shared_prt<Investment> pInv=(createInvestment()); //有这样一个函数,显然是无法将只能指针对象的,这时就需要一个函数将管理的原始资源暴露出来 int dayHeld(const Investment* pi); //shared_ptr和auto_ptr都提供一个get函数,用于执行这样的显示转换 dayHeld(pInv.get());
-
为了使智能指针使用起来像普通指针一样,它们要重载指针取值(pointerdereferencing)操作符(operator->和operator*),它们允许转换至底部原始指针。
-
RAII class内的返回资源的函数和封装资源之间有矛盾。的确是这样,但这样不是什么灾难。RAII class不是为了封装资源,而是为确保资源释放。
条款 16 : 成对使用new和delete时要采取相同形式
- 如果使用new开辟内存,就使用delete释放。如果使用new[]开辟内存,就使用delete[]释放。
- 尽量不使用对数组做typedef动作。在C++的STL中有string、vector等templates(条款54),可以将数组需求降至几乎为零
条款 17 :以独立语句将newed对象置入智能指针
-
在使用智能指针时,应该用独立的语句把新创建的对象指针放入智能指针,否则可能会造成内存泄露
//对于这个的传参 int processWidget(shared_ptr<Widget> pw, int priority); 在调用processWidget之前有三件事: 1、执行priority()函数 2、执行new Widget 3、执行shared_ptr构造函数
C++编译器会以什么样的次序来完成这些事情呢?弹性很大。在Java和C#中,总是以特定的次序来完成这样函数参数的计算,但在C++中却不一定。唯一可以确定的是new Widget在shared_ptr之前调用。但是函数priority排在第几却不一定。假设排在第二,那么顺序就是1、执行new Widget。2、执行函数priority()。3执行shared_ptr构造函数。
如果对函数priority()调用出现异常,那么new Widget返回的指针还没来得及放入shared_ptr中。这样会造成内存泄露。
因此可以分开写
shared_prt<Widget> pw(new Widget); processWidget(pw,priority());