条款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++ ——资源管理的更多相关文章

  1. Effective C++ —— 资源管理(三)

    条款13 : 以对象管理资源 假设有如下代码: Investment* createInvestment(); //返回指针,指向Investment继承体系内的动态分配对象,调用者有责任删除它 vo ...

  2. C++笔记--thread pool【转】

    版权声明:转载著名出处 https://blog.csdn.net/gcola007/article/details/78750220 背景 刚粗略看完一遍c++ primer第五版,一直在找一些c+ ...

  3. c++内存管理学习纲要

    本系列文章,主要是学习c++内存管理这一块的学习笔记. 时间:6.7-21 之下以技术内幕的开头语,带入到学习C++内存管理的技术中吧: 内存管理是C++最令人切齿痛恨的问题,也是C++最有争议的问题 ...

  4. Effective C++(15) 在资源管理类中提供对原始资源的访问

      问题聚焦:     资源管理类是为了对抗资源泄露.     如果一些函数需要访问原始资源,资源管理类应该怎么做呢?        关于资源管理的概念总是显得那么的高大上,其实只是抽象一点. 下面用 ...

  5. Effective C++(14) 在资源管理类中小心copying行为

    问题聚焦:     上一条款所告诉我们的智能指针,只适合与在堆中的资源,而并非所有资源都是在堆中的.     这时候,我们可能需要建立自己的资源管理类,那么建立自己的资源管理类时,需要注意什么呢?. ...

  6. 《Effective C++》第3章 资源管理(2)-读书笔记

    章节回顾: <Effective C++>第1章 让自己习惯C++-读书笔记 <Effective C++>第2章 构造/析构/赋值运算(1)-读书笔记 <Effecti ...

  7. 《Effective C++》第3章 资源管理(1)-读书笔记

    章节回顾: <Effective C++>第1章 让自己习惯C++-读书笔记 <Effective C++>第2章 构造/析构/赋值运算(1)-读书笔记 <Effecti ...

  8. Effective C++笔记:资源管理

    资源:动态分配的内存.文件描述器.互斥锁.图形界面中的字型与笔刷.数据库连接以及网络sockets等,无论哪一种资源,重要的是,当你不再使用它时,必须将它还给系统. 条款13:以对象管理资源 当我们向 ...

  9. [Effective C++ --015]在资源管理类中提供对原始资源的访问

    引言 资源管理类是防止资源泄漏的有力武器,但是许多APIs直接指涉资源,除非你发誓永不使用这样的APIs,否则只得绕过资源管理对象(resource-managing objects)直接访问原始资源 ...

随机推荐

  1. Linux提示字符设置

    当我们登陆linux后,显示的提示字符究竟是什么意思呢?又可不可以设置呢. 首先来看看默认的显示: 普通用户: [fuwh@localhost ~]$ root用户: [root@localhost ...

  2. ●UOJ58 [WC2013]糖果公园

    题链: http://uoj.ac/problem/58题解: 树上带修莫队. 每个块的大小为$n^{\frac{2}{3}}$,在dfs时,把点集分为若干块. 然后类似序列带修莫队,三个关键字:be ...

  3. 【BZOJ3631】【JLOI2014】松鼠的新家

    原题传送门 题意:给你一棵树,然后有一个遍历顺序,你需要补全这个遍历顺序,然后输出这个遍历顺序中每个点的出现次数. 解题思路:本来想找树剖的题,结果发现了一题可以直接写lca的.... 做法1:非常简 ...

  4. IDEA 整合 SSM 框架学习

    认识 Spring 框架 更多详情请点击这里:这里 Spring 框架是 Java 应用最广的框架,它的成功来源于理念,而不是技术本身,它的理念包括 IoC (Inversion of Control ...

  5. Python SMTP邮件发送

    SMTP是发送邮件的协议,Python内置对SMTP的支持,可以发送纯文本邮件.HTML邮件以及带附件的邮件. Python对SMTP支持有smtplib和email两个模块: email负责构造邮件 ...

  6. Delphi7 ADO面板上的控件简介

    ? ADO Connection的主要方法:1) Begin Trans    开始启动一个新的事务,必须保证数据连接处于激活状态.2) Cancel    关闭于数据库的连接.3) Commit T ...

  7. c语言程序第2次作业

    (一)改错题 1.输出带框文字:在屏幕上输出以下3行信息. 错误信息1:{{uploading-image-560144.png(uploading...)} 错误原因:stdio误写为stido 错 ...

  8. 简介JSP与FreeMarker及Volicity区别

    FreeMarker FreeMarker是一款模板引擎: 即一种基于模板和要改变的数据, 并用来生成输出文本(HTML网页.电子邮件.配置文件.源代码等)的通用工具. 它不是面向最终用户的,而是一个 ...

  9. mysql 索引列为Null的走不走索引及null在统计时的问题

    要尽可能地把字段定义为 NOT NULL,即使应用程序无须保存 NULL(没有值),也有许多表包含了可空列(Nullable Column)这仅仅是因为它为默认选项.除非真的要保存 NULL,否则就把 ...

  10. 渗透测试环境DVWA搭建

    一.DVWA介绍 DVWA(Damn Vulnerable Web Application)是一个用来进行安全脆弱性鉴定的PHP/MySQL Web应用,旨在为安全专业人员测试自己的专业技能和工具提供 ...