关闭

智能指针和异常

59人阅读 评论(0) 收藏 举报

今天让我们来分析一下C++中的智能指针和异常,首先呢先普及一下概念!

(1)智能指针:智能或者自动化的管理指针所会向的动态资源的释放。

(2)异常:当一个函数发现自己无法处理的错误时,让函数的调用者直接或间接的处理这个问题。

(3)RAII:资源分配即初始化。构造函数完成对象的初始化,析构函数完成对象的清理,而不是删除。

在实际写代码过程中,我们很容易写出存在异常的代码,不信来看看下面几个例子 :

void Test()
{
	int *p = new int(1);
	if (1)
	{
		return;
	}
	delete p;
}

很容易可以看出在if语句中已经返回了,那后面的代码自然是执行不了了,所以就出现了内存泄露的危险,这可是非常可怕的呢 ,它可能会耗尽内存,不仅当前程序会崩溃,严重的整个系统都会崩溃,这是看你怎么办,哈哈。这时肯定会有人想到了C++里面不是有异常捕获吗?是的,为了增加代码的兼容性,C++采用了下面的代码来捕获异常:

throw 抛出异常;
try
{
	//可能发生异常的代码
}
catch (异常类型)
{
	//发生异常后的处理方法
}

上面的代码进行这样处理不就没事了吗?

void Test()
{
	int *p = new int(1);
	try
	{
		if (1)
		{
			throw 1;
		}
	}
	catch (int e)
	{
		delete p;
		throw;
	}
	delete p;
}

但是这里在catch中却二次抛出异常,这样管理起来非常混乱。所以就引入了智能指针,用它来解决异常更方便。上面提到的RAII就是编写异常安全代码的关键思想。

下来介绍一下Boost库里的智能指针吧。

wKioL1cE0dSgEKTGAAAwXv2phSs344.png

下面给出这些智能指针的模拟实现接口,具体的实现自己完成喽

(1)AutoPtr有两种实现方法

//现代写法
template <typename T>
class AutoPtr
{
public:
	AutoPtr(T* ptr);
	AutoPtr(AutoPtr<T>& ap);
	AutoPtr<T>& operator=(AutoPtr<T>& ap);
	T& operator*();
	T* operator->();
	~AutoPtr();
protected:
	T* _ptr;
};
//旧方法
template<typename T>
class AutoPtr
{
public:
	AutoPtr(T* ptr);
	AutoPtr(AutoPtr<T>& ap);
	AutoPtr<T>& operator=(AutoPtr<T>& ap);
	T& operator*();
	T* operator->();
	~AutoPtr();
protected:
	T* _ptr;
	bool _owner;
};

很容易可以看出旧写法里多了一个布尔类型的成员变量,大家都知道AutoPtr采用的是管理权转移的方式来管理的。所以谁是资源的管理者,谁的_owner值就为true,其他的对象的_owner自然就是false。在现代写法中去除了这种方式,直接采用对象赋空的方式,看起来还是旧的写法似乎更为合理一点,但是新事物的出现肯定有它存在的理由,下来我们看个例子:

AutoPtr<int> ap1(new int(1));
if (某个条件)
{
	AutoPtr<int> ap2(ap1);
}

大家想到了吗?对于旧写法,如果进入if语句,创建ap2后进行拷贝ap1._owner=false,ap2._owner=true;但是出了if代码块,就出了ap2的作用域,ap2被释放,但ap1里还保存着原来的地址,所以可能发生二次释放或访问问题,但这个时候ap1就是野指针啦,后果很严重呢!

但对于现代写法,拷贝后将ap1赋空,想要再用ap1时进行检测为空,那也就避免使用它啦。

(2)ScopedPtr

template<typename T>
class ScopedPtr
{
public:
	ScopedPtr(T* ptr);
	~ScopedPtr();
	T& operator*();
	T* operator->();
protected:
	ScopedPtr(ScopedPtr<T>& sp);
	ScopedPtr<T>& operator=(ScopedPtr<T>& sp);
protected:
	T* _ptr;
};

容易看出它里面直接把拷贝构造函数和重载赋值函数声明为了保护或者私有的,在类外是不能被调用的,也就起到了防拷贝的作用。

(3)SharedPtr

template<typename T>
class SharedPtr
{
public:
	SharedPtr(T* ptr);
	SharedPtr(const SharedPtr<T>& sp);
	~SharedPtr();
	SharedPtr<T>& operator=(const SharedPtr<T>& sp);
	T& operator*();
	T* operator->();
public:
	void UseCount();
	void GetPtr();
protected:
	void _Release();
protected:
	T* _ptr;
	long* _pCount;
};

(4)ScopedArray/SharedArray

template<typename T>
class ScopedArray
{
public:
	ScopedArray(T* ptr);
	~ScopedArray();
	T& operator[](size_t index);
protected:
	ScopedArray(const ScopedArray<T>& sp);
	ScopedArray<T>& operator=(const ScopedArray<T>& sp);
protected:
	T* _ptr;
};

//
template<typename T>
class SharedArray
{
public:
	SharedArray(T* ptr);
	~SharedArray();
	SharedArray(const SharedArray<T>& sp);
	SharedArray<T>& operator=(const SharedArray<T>& sp);
	T& operator[](size_t index);
protected:
	void _Release();
protected:
	T* _ptr;
	long* _pCount;
};

这些当中SharedPtr是使用最广泛的,但在上面SharedPtr的实现方法中还存在几个重要的问题:

1,引用计数器更新存在着线程安全

对于这个问题呢,我还没有能力去解决,待我学成归来,一定收拾它。

2,循环引用

我们用一个简单的例子来说明一下什么是循环引用

#include<memory>
struct Node//定义一个双向链表
{
	shared_ptr<Node> _next;
	shared_ptr<Node> _prev;
	~Node()
	{
		cout << "delete" << this << endl;
	}
};
void TestSharedPtr()
{
	shared_ptr<Node> cur(new Node());
	shared_ptr<Node> next(new Node())
	cur->_next = next;
	next->_prev = cur;

}

运行这段代码你会发现,咦,为什么没有输出呢?也就是说cur和next根本没用被释放。大家看下面的图

wKioL1cE4erC4i-2AAALXjvIYjQ751.png

在用SharedPtr实现双向链表时,会发生循环引用,这是一个很特殊的场景。

因为cur->_next和next->_prev相互依赖,所以在出了作用域以后都等着对方释放,处于一种僵持状态。因而没用调用析构函数。

解决方法是采用weak_ptr弱指针,如下:

#include<memory>
struct Node//定义一个双向链表
{
	weak_ptr<Node> _next;
	weak_ptr<Node> _prev;
	~Node()
	{
		cout << "delete" << this << endl;
	}
};

weak_ptr在创建对象时引用计数为1,而在cur->_next=next和next->_prev=cur中对cur和next的引用计数不加1,这就很好的解决了循环引用的问题。

3,定置删除器

先介绍一个概念仿函数,顾名思义它类似于函数但并不是函数,而是一个类。只不过在类的对象调用成员函数的时候特别像是函数的调用,也就是在 这个类中重载了operator()。看两个小例子:

(1)

void TestSharedPtr()
{
	int *p1 = (int *)malloc(sizeof(int)* 10);
	shared_ptr<int> sp1(p1);
}

程序看上去没有问题,可能也没有崩溃,可是malloc和free要成对使用,这里存在内存泄露的问题。

(2)

void TestSharedPtr()
{
	FILE* p2 = fopen("test.cpp", "r");
	shared_ptr<FILE> sp2(p2,Fclose());//出作用域释放文件,发生崩溃
}

当shared_ptr对象是文件时,出作用域释放,系统自然会崩溃。

怎么解决上面的问题的?

struct Free
{
	void operator()(void* ptr)
	{
		cout << "Free:" << ptr << endl;
		free(ptr);
	}
};
struct Fclose
{
	void operator()(void* ptr)
	{
		cout << "Fclose" << ptr << endl;
		fclose((FILE*)ptr);
	}
};
void TestSharedPtr()
{
	int *p1 = (int *)malloc(sizeof(int)* 10);
	shared_ptr<int> sp1(p1,Free());
	Fclose f;
	FILE* p2 = fopen("test.cpp", "r");
	shared_ptr<FILE> sp2(p2,Fclose());
}

这就是一个仿函数的使用实例。

那接下来就用它来模拟实现shared_ptr的定置删除器吧,同样还是给出接口。

template<typename T>
struct DefaultDel
{
	void operator()(T* ptr)
	{
		delete ptr;
	}
};
template<typename T>
struct Free
{
	void operator()(T* ptr)
	{
		free(ptr);
	}
};
template<typename T,typename D=DefaultDel<T>>
class SharedPtr
{
public:
	SharedPtr(T* ptr);
	SharedPtr(T* ptr,D del);
	SharedPtr(const SharedPtr<T,D>& sp);
	~SharedPtr();
	SharedPtr<T,D>& operator=(const SharedPtr<T,D>& sp);
	T& operator*();
	T* operator->();
public:
	void UseCount();
	void GetPtr();
protected:
	void _Release();
protected:
	T* _ptr;
	long* _pCount;
	D _del;
};
void TestDeleter()
{
	SharedPtr<int, DefaultDel<int>> sp1(new int(1));
	SharedPtr<int, Free<int>> sp2((int *)malloc(sizeof(int)));
	SharedPtr<int> sp3(new int(1));
	SharedPtr<int, Free<int>> sp4((int *)malloc(sizeof(int)));
}

好了,智能指针就说这么多啦。小伙伴明白了吗?

本文出自 “Stand out or Get out” 博客,请务必保留此出处http://jiazhenzhen.blog.51cto.com/10781724/1760193

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:9194次
    • 积分:152
    • 等级:
    • 排名:千里之外
    • 原创:40篇
    • 转载:0篇
    • 译文:0篇
    • 评论:0条
    文章分类
    文章存档