Effective C++ ——资源管理
条款13:以对象来管理资源
在C++中我们经常会涉及到资源的申请与申请,一般都是由关键字new 和 delete来操作的,两者都是成对存在的,缺一不可,否则会出现意想不到的问题,例如:
class Investment{.....};
Investment* pinv = createInvestment();
我们在使用完后要动态的释放掉pinv所指向的资源,例如在下面的函数中做了调用:
void f(){
Investment* pinv = createInvestment();
...
delete pinv;
}
正常情况下这将运行良好,但是如果在...中函数提前的返回了或者在跟特别的出现了异常程序异常的停止了,这是delete函数将
得不到执行,此时就会出现传说中的内存泄露问题。
为了对这种情况进行处理,我们可以采用一个专门的类来对这里的资源进行处理,在这个类的析构函数中处理对资源的释放工作,这样当对象离开作用空间的时候会自动的调用其析构函数,资源也会自动的得到释放,在STL中,有一个专门的类来处理这中情况auto_ptr,也就是传说中的”智能指针“;我们可以这么用:
std::auto_ptr<Investment> pivn = createInvestment();
这样当函数f执行结束的时候,auto_ptr离开它的作用域,将会自动的调用auto_ptr的析构函数,此时pinv所指向的资源也就得到了释放!
需要注意的是:auto_ptr智能指针是独占的,也就是不能有同样的auto_ptr指向同样的资源。由于auto_ptr被销毁时会自动删除它所指之物,所以一定要注意别让多个auto_ptr同时指向同一对象,例如:
std::auto_ptr<Investment> pivn1(createInvestment());//pivn1指向createInvestment返回值
std::auto_ptr<Investment> pivn2(pivn1); //pivn2指向对象,pivn1被设置为NULL
pivn1 = pivn2; ////pivn1指向对象,pivn2被设置为NULL
auto_ptr有个不寻常的性质:若通过拷贝构造或赋值操作符复制它们,它们会变成null,而复制所得的指针将取得资源的唯一拥有权。
相对应的tr1::shared_ptr就能解决这种问题,他是通过引用计数来对资源进行释放的,当多个shared_ptr指向同样的资源的时候这个资源的引用计数也会随之增加,当资源的引用计数变为0的时候,资源会被析构,这样在一些通过资源的复制而实现的结构中可以采用shared_ptr,例如vector等。
不过shared_ptr对下面这两种情况无能为力:
1.对于动态分配的array上是不能使用的,因为shared_ptr内部采用的是delete而不delete[];
2. 对于环状引用的情况也是不能处理的,此时应该采用weaked_ptr;
总之如果你的程序中有delete相关操作的出现,那就说明你的程序有随时出现意想不到情况出现的可能!
请记住:
- 为防止内存泄露,请使用RAII,他在构造函数中获得资源,在析构函数中释放资源。
- 两种常用的RAII是指的:auto_ptr和shared_ptr,后者是最佳的选择,因为它能很好的处理copy操作,前者是独占的!
条款14:在资源管理类中小心copying行为
在条款中主要的介绍了智能指针的用法,那里解决为问题是指向一个heap空间的指针的申请与释放问题,然而并非所有的资源都是在heap中申请的,这时候智能指针就不适合了,例如对于类型为Mutex的互斥器对象,只有lock和unlock的操作,在这里lock与unlock是成对存在的,为了防止调用lock后忘记unlock我们可以自己管理资源,例如:
class Lock{
public:
explicit Lock(Mutex* pm):mutexPtr(pm){
lock(mutexPtr);
}
~Lock(){
unlock(mutexPtr);
}
private:
Mutex* mutexPtr;
};
Mutex m;
.....
{
......
Lock m1(&m)
......
}
这样在m1离开作用域的时候,会自动的调用Lock的析构函数也就是Mutex的unlock函数解锁!
但是Lock对象被复制,会发生什么事?
Lock m11(&m); //锁定m
Lock m12(m11);//将m11复制到m12身上。这会发生什么事?
此时会怎样来处理?这个主要有以下几种方法:
1.禁止复制,有时候有些对象是不适合被复制的,对于一个想Lock这样的对象就是这样的情况,我们可以采用前面介绍的方法,
class Lock:private Uncopyable{ //禁止复制
public:
....
};
2.对底层资源采用"引用计数法",tr1::shared_ptr就是这种情况,当资源的被复制的时候,资源的引用计数就会+1,对应的如果引用计数为0,就释放该资源,shared_ptr缺省的情况下会在指针计数为0 的时候释放资源,在特殊情况下我们可以制定指针为0的时候采用的动作,例如:
class Lock{
public:
explicit Lock(Mutex* pm):mutexPtr(pm,unlock){
lock(mutexPtr.get());
}
private:
tr1::shared_ptr<Mutex> mutexPtr;
};
在本例中不用再写析构函数,因为默认析构函数调用的时候,会自动的调用mutexPtr的析构函数,即为前面制定的unlock函数。
3.复制底部资源。只要你喜欢你可以对一个申请的资源做任何多份的copy,此时copy的时候不仅要copy资源管理类,还要对其包裹的任何的资源进行复制,这就是所谓的深度copy。
4.移交底部资源的所有权。例如auto_ptr,每份资源只有一个资源管理对象,当copy的时候,被copy的资源将变为空。
请记住:
- 复制RAII对象必须一并复制它所管理的资源,所以资源的copying行为决定RAII的行为
- 常见的RAII类的copy行为主要有:禁止copying,采用引用计数法,还有上面介绍的两种也是常用的方法!
条款15:在资源管理类中提供对原始资源的访问
前面几个条款主要的将了资源管理类对资源的管理,但是如果需要资源管理类的原始资源的时候该怎么做呢?例如:
class Investment{
public:
bool isTexFree() const;
...
};
std::tr1::shared_ptr<Investment> pinv = createInvestment();
此时有个函数调用:
void dayHeld(Investment* ph);
此时如果直接用dayHeld(pinv)是错误的,因为要求是指针!
在shared_ptr和auto_ptr等系统的智能指针资源管理类中,存在一个get()的函数,可以获得对应的原始资源例如:
void dayHeld(pinv.get());
此外在几乎所用的智能指针中几乎都对*和->操作符做了重载,例如:
bool isTrue = pinv->isTextFree();
bool isFalse = (*pinv).isTextFree();
为了兼容性,我们一般在自己的资源管理类中也会定义get()函数来获得对原始资源的调用,例如:
class Font{
public:
explicit Font(FontHandle fn):f(fn){}
....
FontHandle get() const{
return f;
}
...
~Font(){realseFont(f);}
private:
FontHandle f;
};
上面定义的Font类可以看做是FontHandle类的资源管理类,对于需要FontHandle类型的函数调用,我们可以通过Font类的get()函数获得,与智能指针的用法几乎相同。此外还可以用隐式类型转化来替换get()函数调用,这种用法容易出现问题,建议不要使用
请记住:
- APIs往往需要取得RAII的原始资源,因此对于RAII都要提供一种对原始资源的访问,就像get()函数
- 对原始资源的访问有隐式和显式两种,我们这里只介绍了显式类型转化,对于隐式类型转化应用比较少
条款16:当成对的使用new 和 delete时,要确保new 和 delete的格式是相同的
这个条款比较简单,但是却很容易出错,首先我们看下当我们使用new和delete的时候编译器为我们做了什么,当我们调用new操作符的时候,编译器会首先在内存中帮我们申请一块空间,然后调用对应对象的构造函数,想对应的当我们调用delete的时候,编译器会首先在该空间调用对应的析构函数然后再对该空间资源进行释放,例如:
std::string* p = new string[10];
对于上面的资源申请,我们释放的时候要对应的采用:
delete [] p;
如果我们采用了delete p的形式,将会出现不确定的结果,我们是通过[]符号来告诉编译器要析构的是一个对象还是一个对象的列表,对应的会调用一次析构函数或者多次析构函数,如果在单一对象上调用多次析构函数或者在多个对象上调用单次析构函数,后果可想而知!
在使用中我们需要注意的一种情况是typedef,例如:
typedef std::string AddressLine[10];
std::string* p = new AddressLine;
此时我们在调用delete的时候一定要注意使用delelet[], 在C++中存在强大的容器类,如果应用恰当完全可以将C中引入的array数组替代掉,例如上面我们完全可以采用vector<string>的形式,这样就不用担心资源的释放问题了!
请记住:
- 对于资源的申请和释放调用new和delelte一定要采用相同的形式,new 对应delete , new[] 对应delete[]!
条款17:以独立的语句将new的资源放入到智能指针中
考虑以下情况:
int prirority();
void processWidget(std::shared_ptr<Widget> pw, int priority);
我们在对processWidget函数进行调用的时候,可以采用如下形式:
processWidget(std::tr1::shared_ptr<widget> pw(new widget), priority());
注意我们不能直接将new widget作为实参传入std:shared_ptr<widget> pw形参中,在上面的函数调用中,看起来没有问题但是可能回出现内存泄露的情况,因为在函数中,参数的调用顺序会因为编译器的不同而不同的,例如上面的循序可能是:
- new widget
- priority调用
- std::tr1::shared_ptr<widget> 初始化
这样可能出现的问题就是当new widget成功后,如果priority()函数调用失败,由于new widget未能放入到智能指针中,但是退出的时候资源得不到释放,解决办法就是讲实参独立出来,例如:
std::tr1::shared_ptr<widget> pw(new widget);
int pri = priority();
processWidget(pw,pri);
请记住:
- 以独立语句将newed对象存储于(置入)智能指针内。如果不这样做,一旦异常被抛出,有可能导致难以察觉的资源泄露。
Effective C++ ——资源管理的更多相关文章
- Effective C++ —— 资源管理(三)
条款13 : 以对象管理资源 假设有如下代码: Investment* createInvestment(); //返回指针,指向Investment继承体系内的动态分配对象,调用者有责任删除它 vo ...
- C++笔记--thread pool【转】
版权声明:转载著名出处 https://blog.csdn.net/gcola007/article/details/78750220 背景 刚粗略看完一遍c++ primer第五版,一直在找一些c+ ...
- c++内存管理学习纲要
本系列文章,主要是学习c++内存管理这一块的学习笔记. 时间:6.7-21 之下以技术内幕的开头语,带入到学习C++内存管理的技术中吧: 内存管理是C++最令人切齿痛恨的问题,也是C++最有争议的问题 ...
- Effective C++(15) 在资源管理类中提供对原始资源的访问
问题聚焦: 资源管理类是为了对抗资源泄露. 如果一些函数需要访问原始资源,资源管理类应该怎么做呢? 关于资源管理的概念总是显得那么的高大上,其实只是抽象一点. 下面用 ...
- Effective C++(14) 在资源管理类中小心copying行为
问题聚焦: 上一条款所告诉我们的智能指针,只适合与在堆中的资源,而并非所有资源都是在堆中的. 这时候,我们可能需要建立自己的资源管理类,那么建立自己的资源管理类时,需要注意什么呢?. ...
- 《Effective C++》第3章 资源管理(2)-读书笔记
章节回顾: <Effective C++>第1章 让自己习惯C++-读书笔记 <Effective C++>第2章 构造/析构/赋值运算(1)-读书笔记 <Effecti ...
- 《Effective C++》第3章 资源管理(1)-读书笔记
章节回顾: <Effective C++>第1章 让自己习惯C++-读书笔记 <Effective C++>第2章 构造/析构/赋值运算(1)-读书笔记 <Effecti ...
- Effective C++笔记:资源管理
资源:动态分配的内存.文件描述器.互斥锁.图形界面中的字型与笔刷.数据库连接以及网络sockets等,无论哪一种资源,重要的是,当你不再使用它时,必须将它还给系统. 条款13:以对象管理资源 当我们向 ...
- [Effective C++ --015]在资源管理类中提供对原始资源的访问
引言 资源管理类是防止资源泄漏的有力武器,但是许多APIs直接指涉资源,除非你发誓永不使用这样的APIs,否则只得绕过资源管理对象(resource-managing objects)直接访问原始资源 ...
随机推荐
- 计蒜客NOIP模拟赛(2) D2T1 劫富济贫
[问题描述] 吕弗·普自小从英国长大,受到骑士精神的影响,吕弗·普的梦想便是成为一位劫富济贫的骑士. 吕弗·普拿到了一份全国富豪的名单(不在名单上的都是穷人),上面写着所有富豪的名字以及他们的总资产, ...
- 洛谷P3980:[NOI2008]志愿者招募
线性规划: #include<cstdio> #include<cstdlib> #include<algorithm> #include<cstring&g ...
- hdu 5877 线段树(2016 ACM/ICPC Asia Regional Dalian Online)
Weak Pair Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 262144/262144 K (Java/Others)Total ...
- Codeforces278E Tourists
来自FallDream的博客,未经允许,请勿转载,谢谢. 给定一张无向图,有点权,要支持单点修改点权和询问从一个点到另一个点不重复经过节点的路径上点权最小值的最小值. n,m<=10^5 考虑求 ...
- [Noi2016]国王饮水记
来自FallDream的博客,未经允许,请勿转载,谢谢. 跳蚤国有 n 个城市,伟大的跳蚤国王居住在跳蚤国首都中,即 1 号城市中.跳蚤国最大的问题就是饮水问题,由于首都中居住的跳蚤实在太多,跳蚤国王 ...
- Redis wind7 安装
下载地址:https://github.com/MSOpenTech/redis/releases. Redis 支持 32 位和 64 位.这个需要根据你系统平台的实际情况选择,这里我们下载 Red ...
- Linux学习之CentOS(八)----详解文件的搜寻、查找(转)
which (寻找『运行档』) [root@www ~]# which [-a] command 选项或参数: -a :将所有由 PATH 目录中可以找到的命令均列出,而不止第一个被找到的命令名称 分 ...
- java.lang.UnsatisfiedLinkError: D:\Tomcat\apache-tomcat-7.0.67\bin\tcnative-1.dll:
Can't load IA 32-bit .dll on a AMD 64-bit platform 错误原因 由错误提示可知,tcnative-1.dll是一个32位文件,但是运行在64位系统上 解 ...
- Java instanceof 关键字是如何实现的?
作者:RednaxelaFX链接:https://www.zhihu.com/question/21574535/answer/18998914来源:知乎著作权归作者所有.商业转载请联系作者获得授权, ...
- 四种方式实现子goroutine与主线程的同步
如何实现子goroutine与主线程的同步 第一种方式: 这种方式很太死板,就不演示了. 第二种方式:使用 channel机制,每个 goroutine传一个 channel进去然后往里写数据,在再主 ...