智能指针和异常

原创 2016年06月02日 10:45:22

今天让我们来分析一下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

JavaSE之异常

理解异常的概念 运用try块、catch块和finally块处理异常 Java中异常类的继承体系结构 运用多重catch块处理异常 运用嵌套try/catch块处理异常 运用关键字throw和throws处理异常 用户自定义异常
  • 2015年06月16日 13:28

异常处理与MiniDump详解(2) 智能指针与C++异常 .

异常处理与MiniDump详解(2)  智能指针与C++异常 write by 九天雁翎(JTianLing) -- blog.csdn.net/vagrxie 讨论新闻组及文件 一、  ...
  • mandytong
  • mandytong
  • 2014-10-29 15:54:05
  • 363

智能指针自动识别类型,正确调用析构函数的方法

智能指针自动识别类型,正确调用析构函数的方法 1,把任何指针封装进Destructor,并把Destructor转化为VoidClass保存。 2,把该指针封装进Destructor之后的Do函数(...
  • a63140180
  • a63140180
  • 2014-02-11 12:49:56
  • 1200

实战c++中的vector系列--emplace_back造成的引用失效

上篇将了对于struct或是class为何emplace_back要优越于push_back,但是还有一些细节没有提及。今天就谈一谈emplace_back造成的引用失效。直接撸代码了:#includ...
  • wangshubo1989
  • wangshubo1989
  • 2015-12-19 13:32:22
  • 2361

C++ Primer : 第十二章 : 动态内存之shared_ptr与new的结合使用、智能指针异常

shared_ptr与new的结合使用、智能指针异常
  • JY_95
  • JY_95
  • 2015-08-26 00:27:32
  • 2403

stl智能指针和boost智能指针对比

先说说stl中的智能指针auto_ptr,先说说auto_ptr的特点。std::auto_ptr用来管理单个堆内存对象,但是独享所有权,且不允许赋值和拷贝(没有重载operator=),所以如果在函...
  • zhangqi_gsts
  • zhangqi_gsts
  • 2015-12-14 20:15:01
  • 1403

智能指针最全资料-5篇经典讲义

  • 2011年09月05日 22:36
  • 300KB
  • 下载

智能指针的作用与原理

对于C/C++程序员来说,指针是天堂,同时指针也是地狱。指针有多少好处,又有多少让人头疼的问题我们这里就不多说了。但为了局部解决指针的问题,我们提出了智能指针这个概念。   实际上,我一直不明白,...
  • chen825919148
  • chen825919148
  • 2012-11-06 22:32:00
  • 3337

智能指针的简单实现及两种误用方式

最近在自己写智能指针的时候,遇到了一些小的疑惑,也纠正了之前的一些理解上的偏差。 首先是最简单的智能指针的一个实现: template class SmartPointer{ private: T*...
  • a3137732
  • a3137732
  • 2016-04-24 11:44:36
  • 768

C++智能指针与类继承多态

我在做编译器项目的时候, 我们采用c++语言,但要使用多态的性质,一是用引用,二是用指针。可是引用不够灵活,指针还具有内存管理问题。所以SmartPtr是一个必然的选择,可我发现通常的SmartPtr...
  • jiangzw625
  • jiangzw625
  • 2008-03-01 09:33:00
  • 2321
收藏助手
不良信息举报
您举报文章:智能指针和异常
举报原因:
原因补充:

(最多只允许输入30个字)