【Effective C++】读书笔记(三)---资源管理

所谓资源管理就是,一旦用了它,将来必须还给操作系统.如果不这样,糟糕的事情就会发生,C++程序中最常使用的资源就是动态分配内存,但内存只是你必须管理的众多资源之一. 其他常见的资源还包括文件描述器,互斥锁,图形界面中的字型和笔刷,数据库连接,以及网络sockets。不论是哪一种资源,重要的是,当你不再使用它你就要将它返回给系统.

  • 条款13:以对象管理资源

举个例子一个大型的项目里面的代码都是成千上万行,我们现在手边的new delete,大家都知道new之后,要在不使用该资源的时候delete,但是呢当你的项目太大,里面各种文件,里面有许许多多的资源.这个时候对程序员来说这真的就是一个折磨,因为真的很容易忘记delete就内存泄露. 幸存C++11的横空出世,并带来了强力助手智能指针,妈妈再也不用担心我没有delete了

获得资源后立刻放进管理对象:实际上"以对象管理资源“的观念常被称为”资源取得时机便是初始化时机",因为我们几乎总是在获得一笔资源后同一语句内以他初始化某个管理对象.反正就是记住一句话->每一笔资源都在获得的同时立刻被放进管理对象中.

管理对象运用析构函数确保资源被释放:无论控制流如何离开区块,一旦对象被销毁其析构函数自然会被自动调用,于是资源被释放.如果资源释放动作可能导致抛出异常,事情变得有点棘手,但是你只要不要让异常逃出析构函数即可,前面的条款8.关于我们智能指针的运用是非常重要的.

我们可以去看看这篇博客,一定要去看呦:智能指针的运用以及实现原理

当然如果你想手工释放资源也可以嘛,但是真的很容易出现错误.灌装式的资源管理类如使用的资源是目前这些预制式classes无法妥善管理的.既然如此就需要精巧制作你自己的资源管理类了.不过那样其实也不是很困难因为接下来就会介绍到.

总结:

1.为了防止资源泄露,请使用RALL对象,他们在构造函数当中获得资源并且在析构函数中释放资源.

2.两个常被使用的RAII classes分别是shared_ptr和auto_ptr.前者通常是较佳选择,若选择auto_ptr,复制动作会使他指向NULL.

  • 条款14:在资源管理类中小心copying行为

再上一个条款我们了解到auto_ptr,shared_ptr如何将这个观念表现在heap-based资源上面,但是并非所有的资源都是在heap上面,对于那种资源来说auto_ptr和shared_ptr这样的智能指针往往不适合作为资源的掌管者了,既然如此,有时候你就要自己建立自己的资源管理类.

例如,假设我们使用C API函数处理类型为Mutex的互斥器对象,共有lock和unclok两个函数可用.

void lock(Mutex* pm); //锁定

void unlock(Mutex* pm); //解锁

为了确保绝不会忘记讲一个锁住的Mutex解锁,你可能会希望建立一个class用来管理锁.这样的class的基本结构由RALL守则支配,也就是"资源在构造期间获得,在析构期间释放":

class Lock
{
public:
	explicit Lock(Mutex* pm)
		:mutexptr(pm)
	{
		lock(mutexptr);
	}
	~Lock()
	{
		unlock(mutexptr);
	}
private:
	Mutex* mutexptr;
};

接下来客户对Lock的用法符合RAII方式:

Metex m;  //定义你需要的互斥器
...
{
	Lock m1(&m); //锁定互斥器
	...         //执行操作
} //在文件末尾自动解除互斥器锁定
 
//接下的操作会发生什么?
Lock m11(&m);  //锁定m
Lock m12(m11); //将m11复制到m12身上,这会发生什么事情?

当一个RALL对象被复制,会发生什么事情? 大多数时候你会选择一下两种可能:

禁止复制

许多时候允许RAII对象被复制并不合理. 如果一定要使用也必须加上引用计数法.

对底层资源祭出"引用计数法"

有时候我们希望保有资源,知道它的最后一个使用者被销毁.这种情况下复制RAII对象时,应该将资源的"被引用数递增", shared_ptr便是如此.通常只有内含一个shared_ptr成员变量,RAII classes便可实现出reference-counting copying行为,如果前述的Lock打算使用reference-counting,他可以改变mutexptr的类型,将它从Mutex* 改为shared_ptr. 然而很不幸shared_ptr的缺省行为是当引用计数为0的时候删除其所指物,那不是我们想要的行为.当我们用上一个Metex,我们想要做的释放动作是解锁,并非删除.但是auto_ptr它的删除操作,永远都是清理到该片资源,没有办法利用.

幸运的是shared_ptr允许所谓的删除器,传入一个函数或者是函数对象,当引用计数为0的时候便调用. 删除器对shared_ptr是一个可有可无的第二参数. 所以实现代码是这样的:

class Lock
{
public:
	explicit Lock(Mutex* pm)
		:mutexPtr(pm, unlock) //函数unlock函数为删除器.
	{
		lock(mutexPtr.get());  //接下来的条款会提到get().
	}
 
private:
	shared_ptr<Mutex> mutexPtr;
};

请注意,本例当中Lock class不用再声明析构函数.因为没有必要,class析构函数会自动调用其non-static成员变量的析构函数.而mutexPtr的析构函数会在互斥器的引用计数为0的时候自动调用shared_ptr删除器,也就是你传进去的unlcok.你并没有忘记析构,你只是依赖了编译器生产的缺省行为.

总结:

1.赋值RAII对象必须一并复制它所管理的资源,所以资源的copying行为决定RAII对象的copying行为,

2.普遍而常见的RALL class copying行为是: 抑制copying,施行引用计数法.

  • 条款15:在资源管理类中提供对原始资源的访问

资源管理类很棒.他们是你对抗资源泄露的利器. 但是接下来你讲依赖这个利器来进行classes之间的资源处理与互动.这个时候别人想拿到你的资源处理,但是你还说杵着一个利器不让别人靠近一步这是不对的,所以你要提供一个供别人访问你原始数据的接口.这个很容易理解的,我先拿出来一个我以前自己实现智能指针的代码出来.

template<class T>
class AutoPtr
{
public:
 
	AutoPtr(T* ptr)
		:_ptr(ptr)
	{}
 
	T* operator->()
	{
		return _ptr;
	}
 
	T& operator*()
	{
		return *_ptr;
	}
 
	AutoPtr(AutoPtr<T>& ap)
	{
		this->_ptr = ap._ptr;
		ap._ptr = NULL;
	}
	AutoPtr<T>& operator=(AutoPtr<T>& ap)
	{
		if (this != &ap)
		{
			delete this->_ptr;
			this->_ptr = ap._ptr;
			ap._ptr = NULL;
		}
		return *this;
	}
	~AutoPtr()
	{
		cout << "智能指针爸爸已经释放过空间了" << endl;
		delete _ptr;
	}
protected:
	T* _ptr;
};

我们可以看到这个智能指针的底层利用的就是我们的_ptr.但是你要使用_ptr进行数据交流时候应该怎么办呢? 那么别人想拿到你的_ptr是不是就很困难了. 所以我们就得定义一个接口. get()

  T* get()
	{
		return _ptr;
	}

这个条例其实就是让你在你的资源管理类当中添加一个返回原始数据的接口,对就这么简单.

总结:

1.APIs往往要求访问原始资源,所以每一个RAII class应该提供一个"取得其所管理之资源"的方法.

2.对原始资源的访问可能经由显示转换或隐式转换. 一般而言显示转换较为安全,但隐式转换对客户比较方便.

  • 条款16:成对使用new和delete时要采用相同形式

以下动作有什么错?

std::string* stringArray = new std::string[100];
...
delete stringArray;

这件事情看起来都井然有序…发生使用了new,也搭配想应的delete. 但还是有某样东西完全错误: 你的程序行为不明确. stringArray所含的100个string的对象中的99个可能都没有被删除.因为它们的析构函数没有调用.
当你使用new,有两件事:第一,内存被分配出来 第二 针对此内存会有一个构造函数被调用. 当你使用delete,也有两件事 针对此内存的构造函数被调用 第二 然后内存被释放.

delete最大的问题就是即将被删除的内存之内究竟存有多少对象?

这个问题很简单,当你开辟出来一个对象数组的时候,我在vs下编译器是这样实现来记录需要调用多少次析构函数.
在这里插入图片描述

当你对着一个指针使用delete,唯一能够让delete知道内存中是否存在一个"数组大小记录"的办法就是:由你来告诉它

如果你使用delete的时候加上中括号,delete便认定指针指向一个数组,否则他便认定指针指向单一对象.

string* stringPtr1 = new string;

string* stringPtr2 = new[] string[100];

delete stringPtr1; //删除一个对象

delete[] stringPtr2; //删除一个由对象组成的数组.

所以这里想告诉你的东西很简单,很明白,那就是如果你调用new时使用[],你必须在对应调用delete时也使用[]。如果你调用new时没有用[],那么也不该在对应delete的时候加[].

  • 条款17:以独立语句将newd对象置入智能指针

以独立语句将newd对象存储于智能指针内.如果不这样做,一旦异常被抛出,有可能导致难以察觉的资源泄露.

©️2020 CSDN 皮肤主题: 技术黑板 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值