c++智能指针的不断演化

RAII

RAII资源分配即初始化,定义一个类来封装资源的分配和释放,在构造 函数完成资源的分配和初始化,在析构函数完成资源的清理,可以保证资源的正确初始化和释放。

智能指针的引入:

由于return ,throw等关键字的存在,导致顺序执行流的错乱,不断的进行跳转,使开辟的空间

看似被释放,而实际上导致内存的泄露。

例如以下两个例子:

void Test1()
{
	int *p1 = new int(1);
	if (1)
	{
		return;
	}
	delete p1;
}

void DoSomeThing() 
{     //...     
	throw 2 ;
	//... 
} 
void Test2 () 
{     
	int* p1 = new int(2);   
	//...    
	try 
	{          
		DoSomeThing();  
	}     
	catch(...)    
	{          
		//delete p1 ;         
		throw;  
	}     
	//...     
	delete p1 ;
}

以上两个例子,看似new与delete结合使用,但是实际上已经导致内存的泄露,为了解决以上问题,就需要在写此类代码时需要多加小心,但是对于大量的代码开发,想要注意就很难了。

于是乎就引入了智能指针:

所谓智能指针就是智能/自动化的管理指针所指向的动态资源的释放。

c++库函数中的智能指针为(auto_ptr,unique_ptr,shared_ptr);

Boost库函数中的智能指针为(auto_ptr,scoped_ptr,shared_ptr)。

接下来就一起来了解一下智能指针的发展历史。

1、在vc6.0之前的版本中auto_ptr的模拟实现方式

template<class T>
class OldAutoPtr
{
public:
	OldAutoPtr(T *ptr)
		:_ptr(ptr)
		, _owner(true)
	{}

	//拷贝构造
	OldAutoPtr(OldAutoPtr& ap)
		:_ptr(ap._ptr)
		,_owner(ap._owner)
	{
		ap._owner = false;//权限的转让
	}

	//运算符重载
	OldAutoPtr& operator=(OldAutoPtr& ap)
	{
		if (ap._ptr != _ptr)//自赋值和指向同一份空间
		{
			if (_ptr)
			{
				delete _ptr;
			}
			_ptr = ap._ptr;
			_owner = ap._owner;//权限的转让
			ap._owner = false;
		}
		return *this;
	}

	//析构函数
	~OldAutoPtr()
	{
		if (_owner)//只删除拥有权限的指针
		{
			delete _ptr;
		}
	}

public:

	T& operator *()
	{
		return *_ptr;
	}

	T* operator ->()
	{
		return _ptr;
	}
private:
	T* _ptr;
	bool _owner;//权限拥有者
};

评价:

看似最初版本的auto_ptr已经很接近了原声指针的使用,多个指针都可以指向一份空间,而且释放的同时不会导致同一份空间的多次释放。但是在下面的情境中却发生了致命的危害:

void TestOldAutoPtr()
{
	OldAutoPtr<int> ap1(new int(1));
	if (true)
	{
		OldAutoPtr<int> ap2(ap1);
		//OldAutoPtr<int> ap3(new int(2));
		//ap3 = ap2;
	}
	//ap1将析构的权限给予了ap2,ap1,ap2指向同一份空间,ap2在出了if作用域之后ap2对象释放,进而导致ap1也被释放。
	//但是在if作用域之外,又对ap1(ap2)指向的空间进行简引用,导致程序崩溃,如果不使用的话则会造成指针的悬挂(野指针)。
	*ap1 = 10;
}

针对以上的状况,在之后的版本中对auto_ptr进行了完全的权限转移,转移之后该指针不能使用。

2、现在的版本auto_ptr的实现

template<typename T>
class AutoPtr
{
public:
	AutoPtr(T* ptr)
		:_ptr(ptr)
	{}
	AutoPtr(AutoPtr<T>& ap)
		:_ptr(ap._ptr)
	{
		ap._ptr = NULL;//权限转移
	}

	AutoPtr<T>& operator=(AutoPtr<T> & ap)
	{
		if (this != &ap)//自赋值
		{
			if (_ptr)
			{
				delete _ptr;
			}
			_ptr = ap._ptr;
			ap._ptr = NULL;//权限转移
		}
		return *this;
	}

	~AutoPtr()
	{
		if (_ptr != NULL)
		{
			delete _ptr;
			_ptr = NULL;
		}
	}
public:
	T& operator * ()//没有参数
	{
		return *_ptr;
	}

	T* operator->()//没有参数
	{
		return _ptr;
	}
private:
	T* _ptr;
};

评价:

这种实现方式很好的解决了old的版本特殊场景的野指针问题,但是它与原生指针的差别更大,由于实现了完全的权限转移,所以导致在拷贝构造和赋值之后只有一个指针可以使用,而其他指针都置为NULL,使用很不方便,而且还很容易导致对于NULL指针的截引用,导致程序崩溃,其危害也是比较大的。

3、针对以上的问题,在Boost库中引入了简单粗暴的解决方法scoped_ptr,直接不允许其拷贝构造和赋值(ps:新的C++11标准中叫做unique_ptr)

解决办法:将赋值和拷贝构造函数只声明,不实现,切记,将其声明为protected或者private以免在类的外部对其进行破环性处理,同时也是提高了代码的安全性。

template<typename T>
class ScopedPtr
{

public:
	ScopedPtr(T* ptr)
		:_ptr(ptr)
	{}
	~ScopedPtr()
	{
		if (_ptr != NULL)
		{
			delete _ptr;
		}
	}
protected://将其声明为protected或者private不能声明为public
	ScopedPtr(ScopedPtr<T>& sp);
	ScopedPtr<T>operator=(ScopedPtr<T>&sp);
public:
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
private:
	T *_ptr;
};

评价:

在一般的情况下,如果不需要对于指针的内容进行拷贝,赋值操作,而只是为了防止内存泄漏的发生,该只能指针完全可以满足需求。

4、允许拷贝构造和赋值的shared_ptr模拟实现

解决办法:

通过为每个空间多开辟4个字节做为引用计数器,在拷贝构造、赋值、析构时用计数器来解决。

template<typename T>
class SharedPtr
{
public:
	SharedPtr(T* ptr)
		:_ptr(ptr)
		, _pCount(new int(1))
	{}

	SharedPtr(const SharedPtr<T>& sp)
	{
		_ptr = sp._ptr;
		_pCount = sp._pCount;
		(*_pCount)++;
	}

	SharedPtr<T>operator=(SharedPtr<T> sp)
	{
		swap(_ptr, sp._ptr);
		swap(_pCount, sp._pCount);
		return *this;
	}

	~SharedPtr()
	{
		_Realse();
	}
public:
	void _Realse()
	{
		if (--(*_pCount) == 0)
		{
			delete _ptr;
			delete _pCount;
		}
	}

public:
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
private:
	T* _ptr;
	int* _pCount;
};

评价:

简化版智能指针SharedPtr看起来不错,但是实际上存在以下问题:

1)循环引用

2)定置删除器

循环引用

template<class T>
struct Node
{
public:
	~Node()
	{
		cout << "delete:" << this << endl;
	}
public:
	shared_ptr<Node> _prev;
	shared_ptr<Node> _next;
	/*weak_ptr<Node> _prev;
	weak_ptr<Node> _next;*/
};

void test_round()//循环引用
{
	shared_ptr<Node> cur(new Node());
	shared_ptr<Node> next(new Node());

	cout << "连接前:" << endl;
	cout << "cur:" << cur.use_count() << endl;
	cout << "next:" << next.use_count() << endl;

	cur->_next = next;
	next->_prev = cur;

	cout << "连接后:" << endl;
	cout << "cur:" << cur.use_count() << endl;
	cout << "next:" << next.use_count() << endl;

	/*shared_ptr<Node> cur(new Node());
	weak_ptr<Node> wp1(cur);*/
}

解决办法:

使用一个弱引用智能指针(weak_ptr)来打破循环引用(weak_ptr不增加引用计数)

template<class T>
struct Node
{
public:
	~Node()
	{
		cout << "delete:" << this << endl;
	}
public:
	//shared_ptr<Node> _prev;
	//shared_ptr<Node> _next;
	weak_ptr<Node> _prev;
	weak_ptr<Node> _next;
};

void test_round()//循环引用
{
	shared_ptr<Node> cur(new Node());
	shared_ptr<Node> next(new Node());

	cout << "连接前:" << endl;
	cout << "cur:" << cur.use_count() << endl;
	cout << "next:" << next.use_count() << endl;

	cur->_next = next;
	next->_prev = cur;

	cout << "连接后:" << endl;
	cout << "cur:" << cur.use_count() << endl;
	cout << "next:" << next.use_count() << endl;

	/*shared_ptr<Node> cur(new Node());
	weak_ptr<Node> wp1(cur);*/
}

定置删除器

在shared_ptr中只能处理释放new开辟的空间,而对于malloc,以及fopen打开的文件指针不能处理,为了能够全面的处理各种各样的指针,所以提出了定制删除器,而其实现则是通过仿函数(通过对()运算符的重载)来实现。

1)模拟实现的解决

template<typename T,typename D>
class SharedPtr
{
public:
	SharedPtr(T* ptr)
		:_ptr(ptr)
		, _pCount(new int(1))
	{}

	SharedPtr(T* ptr, D del)
		:_ptr(ptr)
		, _pCount(new int(1))
		, _del(del)
	{}

	SharedPtr(const SharedPtr<T, D>& sp)
	{
		_ptr = sp._ptr;
		_pCount = sp._pCount;
		(*_pCount)++;
	}

	SharedPtr<T, D>operator=(SharedPtr<T, D> sp)
	{
		swap(_ptr, sp._ptr);
		swap(_pCount, sp._pCount);
		return *this;
	}
	~SharedPtr()
	{
		_Realse();
	}
public:
	void _Realse()
	{
		if (--(*_pCount) == 0)
		{
			_del(_ptr);
			delete _pCount;
		}
	}

public:
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
private:
	T* _ptr;
	int* _pCount;
	D _del;
};

template<typename T>
struct DeafaultDel
{
	void operator()(T* ptr)
	{
		cout << "DeafaultDel:" << ptr << endl;
	}
};

template<typename T>
struct Free
{
	void operator()(T*ptr)
	{
		cout << "Free:" << ptr << endl;
	}
};

template<typename T>
struct Fclose
{
	void operator()(T* ptr)
	{
		cout << "Fclose:" << ptr << endl;
		fclose(ptr);
	}
};
//测试代码
void TestDelete()
{
	int *p1 = (int*)malloc(sizeof(int));
	SharedPtr<int,Free<int>> sp1(p1);

	FILE* p2 = fopen("test.txt", "r");
	SharedPtr<FILE, Fclose<FILE>> sp2(p2);
}

测试结果

2)系统shared_ptr的解决

struct Free
{
	void operator()(void * ptr)
	{
		cout << "Free:" << ptr << endl;
		free(ptr);
	}
};

struct Fclose
{
	void operator ()(FILE* ptr)
	{
		cout << "Fclose" << ptr << endl;
		fclose(ptr);
	}
};
//测试代码
void test_shared_ptr_delete()
{
	int *p1 = (int*)malloc(sizeof(int));
	shared_ptr<int> sp1(p1,Free());

	FILE* p2 = fopen("test.txt", "r");
	shared_ptr<FILE> sp2(p2,Fclose());//崩溃

}

测试结果

总结:

1、如果需要使用智能指针的话,scoped_ptr完全可以胜任。在非常特殊的情况下,例如对STL容器对象,应该只使用shared_ptr,任何情况下都不要使用auto_ptr.

2、使用时尽量局部化,因为其是通过调用其析构函数来实现资源的回收。

时间: 2024-10-07 11:49:43

c++智能指针的不断演化的相关文章

智能指针的原理和简单实现

什么是智能指针? 智能指针实质上是一个类,定义一个类来封装资源的分配和释放.这个类的构造函数中传入一个指针,完成资源的分配和初始化.在析构函数中释放传入的该指针,完成资源的释放. 为什么要用智能指针? 智能指针就是智能,自动化的管理指针所指向的动态资源. 例如以下情况:代码中经常会忘记释放动态开辟的内存资源,导致内存泄露. // case1 void Test2() {  int* p1 = new int(2);  bool isEnd = true;  //...  if (isEnd)  

实战c++中的智能指针unique_ptr系列-- 使用std::unique_ptr代替new operator(错误:‘unique_ptr’ is not a member of ‘std’)

写了很多篇关于vector的博客,其实vector很便捷,也很简单.但是很多易错的问题都是vector中的元素为智能指针所引起的.所以决定开始写一写关于智能指针的故事,尤其是unique_ptr指针的故事. 这是个开始,就让我们使用std::unique_ptr代替new operator吧! 还是用程序说话: #include<iostream> int main() { while (true) int *x = new int; } 看下任务管理器中的内存: 此时使用智能指针unique

C++智能指针简单剖析

导读 最近在补看<C++ Primer Plus>第六版,这的确是本好书,其中关于智能指针的章节解析的非常清晰,一解我以前的多处困惑.C++面试过程中,很多面试官都喜欢问智能指针相关的问题,比如你知道哪些智能指针?shared_ptr的设计原理是什么?如果让你自己设计一个智能指针,你如何完成?等等--.而且在看开源的C++项目时,也能随处看到智能指针的影子.这说明智能指针不仅是面试官爱问的题材,更是非常有实用价值. 下面是我在看智能指针时所做的笔记,希望能够解决你对智能指针的一些困扰. 目录

boost智能指针使用

#include <iostream> #include <tr1/memory> #include <boost/scoped_ptr.hpp> //scoped_ptr还不属于tr1 #include <boost/scoped_array.hpp> //scored_array也不属于tr1 #include <boost/shared_array.hpp> //shared_array也不属于tr1 class CTest { publi

webkit智能指针 - RefPtr, PassRefPtr

历史 2005年之前,Webkit中很多对象都采用引用计数的方式.它们通过继承RefCounted]类模板来实现这种模式.RefCounted主要是实现了ref()和deref()两个函数.在需要引用对象时要调用ref()增加引用计数,在不再需要对象时,要调用deref()函数减少引用计数.ref()和deref()需要成对出现.这和使用new/delete一样,多调用.少调用.没调用的问题总是时有发生.如果能由编译器自动完成ref, deref的调用,C/C++编程的bug至少也可以减少一半以

智能指针tr1::shared_ptr、boost::shared_ptr使用

对于tr1::shared_ptr在安装vs同时会自带安装,但是版本较低的不存在.而boost作为tr1的实现品,包含 "Algorithms Broken Compiler Workarounds Concurrent Programming Containers Correctness and Testing Data Structures Domain Specific Function Objects and Higher-order Programming Generic Progra

C++ Primer笔记8_动态内存_智能指针

1.动态内存 C++中,动态内存管理是通过一对运算符完成的:new和delete.C语言中通过malloc与free函数来实现先动态内存的分配与释放.C++中new与delete的实现其实会调用malloc与free. new分配: 分配变量空间: int *a = new int; // 不初始化 int *b = new int(10); //初始化为10 string *str = new string(10, ); 分配数组空间: int *arr = new int[10];//分配的

C++之智能指针20170920

/******************************************************************************************************************/ 一.C++智能指针_自己实现智能指针 1.使用局部变量结合new的方式,防止new导致的内存泄漏 class sp { private: Person *p; public: sp() : p(0) {}//表明sp的构造函数 继承person的无参构造函数 sp(

智能指针简介

智能指针用于解决常规指针所带来的内存泄露.重复释放.野指针等内存问题.智能指针基于这样的事实得以发挥作用:定义在栈中的智能指针,当超出其作用域时,会自动调用它的析构函数,从而可以释放其关联的内存资源. 之前C++标准库中定义的智能指针std::auto_ptr<T>,因其设计存在缺陷,所以已不再推荐使用.C++11引入了新的智能指针:unique_ptr.shared_ptr和weak_ptr. 一:unique_ptr unique_ptr类似于auto_ptr.两个unique_ptr实例