C++中的智能指针

Posted TangguTae

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++中的智能指针相关的知识,希望对你有一定的参考价值。

目录

内存泄漏

auto_ptr

unique_ptr

shared_ptr


内存泄漏

在说智能指针之前先谈谈内存泄漏

内存泄漏是因为疏忽或者是错误造成程序未能释放已经不再使用的内存(说的不好听的就是占着茅坑不拉屎)。其实在编程过程中一不小心就会造成内存泄漏,如果长期运行的程序出现内存泄漏,慢慢的系统可用的内存就会变少,就会导致系统变慢、卡死等情况的出现。

常见的内存泄漏:

1、在堆上开辟空间,当不在使用时这块空间时忘记delete释放资源(堆内存泄漏

2、打开的文件描述符资源、套接字、父进程没有回收子进程资源等等系统级资源泄漏

避免内存泄漏的方法

最最最重要的就是良好的编程习惯。对于堆上的资源记得释放,对于系统的资源也要记得及时的释放掉。

不过,有些情况,即使你知道要释放资源,且也在代码中调用delete释放,在delete前比如出现异常情况,抛出异常会跳过释放的步骤,从而造成资源的浪费,这种小细节有些时候会被编程者给忽略掉(也是一种异常安全的问题)。看下面的例子,真的是防不胜防。

float mydiv(int n,int m)

	int* ptr = new int(1);
	if (m == 0)//一旦发生异常,就会造成资源泄露
		throw string("除0错误!!!");
	delete ptr;
	return m / (float)n;


int main()


	try 
		int n, m;
		cin >> n >> m;
		cout << mydiv(n, m) << endl;
	
	catch (string& error)
	
		cout << error << endl;
	
	return 0;

为了更好的避免这种意外造成的内存泄漏,C++引入了智能指针。

智能指针SmartPtr

template<class T>
class SmartPtr

public:
	SmartPtr(T* ptr = nullptr)
		:_ptr(ptr)
	
	~SmartPtr()
	
		if (_ptr)
		
			delete _ptr;
			_ptr = nullptr;
		
	
    //让智能指针看起来更像指针
	T* operator->()
	
		return _ptr;
	
	T& operator*()
	
		return *_ptr;
	
private:
	T* _ptr;
;
int main()

	SmartPtr<int> sp1(new int);
	SmartPtr<int> sp2;
	sp2 = sp1;
	return 0;

这种智能指针的其实是RAII思想,RAII是一种利用对象生命周期来控制程序资源的技术,在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源

这样的好处是:不需要显示的释放资源,并且对象所需的资源在其声明周期内始终保持有效

上面代码所存在的问题:

sp1拷贝给sp2,出作用域后,同一块地址会被析构两次,程序会崩溃。

解决方法:C++98中的auto_ptr

auto_ptr

思想:发生拷贝时,将管理权转移给拷贝对象。

template<class T>
class auto_ptr

public:
	auto_ptr(T* ptr = nullptr)
		:_ptr(ptr)
	
	~auto_ptr()
	
		if (_ptr)
		
			delete _ptr;
			_ptr = nullptr;
		
	
    //管理权转移
	auto_ptr(auto_ptr& sp)
		: _ptr(sp._ptr)
	
		sp._ptr = nullptr;
	
	auto_ptr& operator=(auto_ptr& sp)
	
		if (_ptr)//释放以前管理的资源
			delete _ptr;
		_ptr = sp._ptr;
		sp._ptr = nullptr;
		return *this;
	
	T* operator->()
	
		return _ptr;
	
	T& operator*()
	
		return *_ptr;
	
private:
	T* _ptr;
;

可以看到,这种方法存在一种缺陷

int main()

	auto_ptr<int> sp1(new int(1));
	auto_ptr<int> sp2;
	sp2 = sp1;
	cout << *sp1 << endl;//系统在此处崩溃
	return 0;

对于编程者很容易忘记是哪个智能指针在管理对象。所以又引出了unique_ptr

unique_ptr

思想:禁止智能指针的拷贝,防止拷贝的出现

1、采用传统方法,将拷贝构造函数设为私有

template<class T>
class unique_ptr 
public:
	unique_ptr(T* ptr = nullptr)
		:_ptr(ptr)
	
	~unique_ptr()
	
		if (_ptr)
		
			delete _ptr;
			_ptr = nullptr;
		
	
	T* operator->()
	
		return _ptr;
	
	T& operator*()
	
		return *_ptr;
	
private:
	T* _ptr;
	unique_ptr(unique_ptr& up)//无法拷贝和赋值
	T* operator=(unique_ptr& up)
;

2、采用C++11里面的delete函数禁用拷贝构造

template<class T>
class unique_ptr 
public:
	unique_ptr(T* ptr = nullptr)
		:_ptr(ptr)
	
	~unique_ptr()
	
		if (_ptr)
		
			delete _ptr;
			_ptr = nullptr;
		
	
	T* operator->()
	
		return _ptr;
	
	T& operator*()
	
		return *_ptr;
	
	unique_ptr(unique_ptr& up) = delete;
	T* operator=(unique_ptr& up) = delete;
private:
	T* _ptr;
;

但是这样也存在一些不方便,很显然,问题就是unique_ptr不能拷贝了。

shared_ptr

思想:采用计数器

问题是,我想要拷贝,但是不希望是auto_ptr那样的权限托管,想法是采用计数器来对当前管理的资源的智能指针进行计数。

template<class T>
class shared_ptr

public:
	shared_ptr(T* ptr = nullptr)
		:_ptr(ptr)
		, _pcount(new int(1))
	
	shared_ptr(shared_ptr<T>& sp)
		:_ptr(sp._ptr)
		, _pcount(sp._pcount)
	
		(*_pcount)++;//每次拷贝构造都要将管理者的个数增加
	
	~shared_ptr()
	
		if (--(*_pcount) == 0 && _ptr)//析构的时候每次判断是否是最后一个管理者
		
			delete _ptr;
			_ptr = nullptr;
			delete _pcount;
			_pcount = nullptr;
			cout << "delete:" << endl;
		
	
	shared_ptr<T>& operator=(shared_ptr<T>& sp)
	
		if (_ptr != sp._ptr)
		
			if (sp._ptr)
			
				if (--(*_pcount) == 0)//判断管理对象的管理者是否为最后一个
				
					delete _ptr;
					_ptr = nullptr;
					delete _pcount;
					_pcount = nullptr;
				
				_ptr = sp._ptr;
				_pcount = sp._pcount;
				(*_pcount)++;
			
		
		return *this;
	
    int use_count()
    
	    return *_pcount;
    

    T* operator->()
	
		return _ptr;
	
	T& operator*()
	
		return *_ptr;
	
private:
	T* _ptr;
	int* _pcount;//引入计数器
;

shared_ptr所存在的问题

1、线程不安全

线程不安全主要体现在两个地方

其一是引用计数_pcount是多个智能指针所共享的,在多线程的场景下,对p_count进行++或者--是线程不安全的;其二是_ptr也是共享的,对指针所指向的内容进行操作时,也是线程不安全的。

解决方法:加互斥锁。

2、存在循环引用的问题

template<class T>
class ListNode 

public:
	zy::shared_ptr<ListNode<T>> _pre;
	zy::shared_ptr<ListNode<T>> _next;
	T val;
	~ListNode()
	
		cout << "~ListNode()" << endl;
	
;

int main()

	zy::shared_ptr<ListNode<int>> sp1(new ListNode<int>);
	zy::shared_ptr<ListNode<int>> sp2(new ListNode<int>);
	cout << sp1.use_count() << endl;
	cout << sp2.use_count() << endl;
	sp1->_next = sp2;
	sp2->_pre = sp1;
	cout << sp1.use_count() << endl;
	cout << sp2.use_count() << endl;
	return 0;

可以看到并没有调用析构函数释放资源

因为:sp1的next也参与了对sp2里面的资源进行了管理,他的count会变成2,同理,sp2的pre参与了sp1里面的资源管理,当程序结束时,调用析构函数,但是count的值并不为0,二者管理的对象_ptr都不会被析构,所以会出现资源的泄漏。

假如断开一个管理

int main()

	zy::shared_ptr<ListNode<int>> sp1(new ListNode<int>);
	zy::shared_ptr<ListNode<int>> sp2(new ListNode<int>);
	cout << sp1.use_count() << endl;
	cout << sp2.use_count() << endl;
	sp1->_next = sp2;
	//sp2->_pre = sp1;
	cout << sp1.use_count() << endl;
	cout << sp2.use_count() << endl;
	return 0;

正确的现象应该是上面运行结果。

针对循环引用的问题,出现了weak_ptr这种智能指针。

如果是管理一个数组空间,正确的析构方法是用仿函数来定制删除的方式。

template<class T>
struct DeleteArray

    void operator()(T* DA)
    
        delete[] DA;
    
;

std::shared_ptr<ListNode<int>> sp(new ListNode<int>[10], DeleteArray<ListNode<int>>());

以上是关于C++中的智能指针的主要内容,如果未能解决你的问题,请参考以下文章

智能指针

c++中的智能指针

智能指针的原理和实现

智能指针的原理和实现

硬核 | C++ 基础大全

第61课 智能指针类模板