C++ 智能指针

RAII

RAII机制就是一种自动管理资源的机制,其可以帮助程序员自动释放资源,来避免内存泄漏,C++中,智能指针就是基于RAII产生的。

内存泄漏的危害:

①如果程序申请的空间不需要使用之后,没有及时释放,就会引起内存泄漏,这一部分被申请的空间就会一直被占用,这部分资源就被浪费掉了

②如果程序运行的时间足够长,到最后就会无内存可用,最终程序就会挂掉,就算没有挂掉,程序也会越跑越慢,严重影响程序的稳定和性能

内存泄漏是 C++ 程序中一个非常常见的问题,如果不能及时发现并修复,会对程序的性能、稳定性和安全性造成严重影响。 

简单应用:

#include<iostream>
#include<vector>
using namespace std;

template<class T>
class smart_ptr
{
public:
	smart_ptr(T* p)
		:_ptr(p)
	{}

	~smart_ptr()
	{
		delete  _ptr;
		cout << "delete finish" << endl;
	}
private:
	T* _ptr;
};

int main()
{
	smart_ptr<int>p1 = new int(10);
	smart_ptr<double>p2 = new double(3.14);
	smart_ptr<vector<int>>p3 = new vector<int>(20);
	return 0;
}

 运行结果:

可以看到,申请了三分空间,我们并没有显示调用delete去清楚new出来的空间,但是smart_ptr自动调用析构函数,完成了空间释放,这样就可以避免内存空间的泄漏 

对smart_ptr进行一下重载operator * 和operator->

T operator*()
{
	return *_ptr;
}

T* operator->()
{
	return _ptr;
}

智能指针

首先看上面smart_ptr存在的问题:

#include<iostream>
#include<vector>
using namespace std;

template<class T>
class smart_ptr
{
public:
	smart_ptr(T* p)
		:_ptr(p)
	{}

	~smart_ptr()
	{
		delete  _ptr;
		cout << "delete finish" << endl;
	}
private:
	T* _ptr;
};

int main()
{
	smart_ptr<int>p1 = new int(10);
	smart_ptr<int>p2 = p1;
	return 0;
}

我们只需要对p1进行一次拷贝,就可以将程序挂掉,是因为,p1指针delete掉之后,p1成了野指针,我们再对p2进行delete必然会发生访问野指针

同一块空间被释放两次会将程序搞崩

接下来详细介绍 在C++库中为我们提供的智能指针的接口,就在<memory>头文件中:

 ​​​​​​#include<memory> 

里边包含了各种智能指针,接下来介绍几种最常用的几种 auto_ptrunique_ptrshared_ptrweak_ptr

auto_ptr

 auto_ptr 是C++中出现最早的智能指针,C++98中就存在了

构造函数 :

自带一个通过指针来进行初始化的构造函数,但是其被explicit修饰,这说明不允许进行类型转换:

可以看到这里不允许进行类型转换,但是可以进行直接构造,其实四种智能指针都不支持类型转换

拷贝构造:

#include<iostream>
#include<memory>

using namespace std;

int main()
{
	auto_ptr<int>p1(new int(10));

	auto_ptr<int>p2(p1);
	auto_ptr<int>p3 = p2;//支持拷贝构造
	return 0;
}

但是,如果进行拷贝,不就会发生上边提到的野指针现象吗 

这里auto_ptr拷贝的时候,会将p1直接转移给p2,,而原来的p1成了空指针,p3拷贝p2也是同理

cout << p1.get() << endl;
cout << p2.get() << endl;
cout << p3.get() << endl;

 运行结果:

0000000000000000
0000000000000000
0000029604836EA0

这种方法过于简单粗暴,显然不足以满足开发需求,所以C++11又出现的三个指针 

unique_ptr

unique_ptr 更加简单粗暴,直接不支持对对象进行拷贝

如果不需要进行拷贝,可以使用unique_ptr

unique_ptr对于auto_ptr来说,增加了访问[ ]的功能 

int main()
{
	auto_ptr<int> ptr1(new int[10] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10});
	cout << ptr1[5] << endl;//错误

	return 0;
}
int main()
{

	unique_ptr<int[]> ptr1(new int[10] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10});
	cout << ptr1[5] << endl;//错误

	return 0;
}

shared_ptr

shared_ptr 是对拷贝处理的最好的指针,但是机制也是最复杂的,它是通过计数引用来实现的

对于同一块资源来说,有多少个shared_ptr指向这块空间,count 就为几,如果delete 一个shared_ptr count就会对应减一 ,知道count为零时,这块空间才会真正的被释放,这就避免了,同一块空间被释放多次的情况

对于不同的空间来说,count时独立的,一块空间会存在一个独立的count

shared_ptr也支持下标访问

底层实现:

template<class T>
class my_shared
{
public:
	my_shared(T* p=nullptr)
		:_ptr(nullptr)
		,_count(new int(1))
	{}

	my_shared(const my_shared<T>& p)
	{
		_ptr = p._ptr;
		_count = p._count;

		++(*_count);
	}

	my_shared<T>& operator=(const my_shared<T>& p)
	{
		if (_ptr != p._ptr)
		{
			release();//本身指针指向发生变化,count--

			_ptr = p._ptr;
			_count = p._count;

			++(*_count);
		}
		return *this;
	}

	int use_count()
	{
		return *_count;
	}

	void release()
	{
		if (--(*_count) == 0)//释放
		{
			delete _ptr;
			delete _count;
		}
	}

	T* operator->()
	{
		return _ptr;
	}

	T operator* ()
	{
		return *_ptr;
	}

	~my_shared()
	{
		release();
	}
private:
	T* _ptr;
	int* _count;
};

这里重载operator= 和拷贝构造,

测试:

int main()
{
	my_shared<int>p1 = new int(10);
	cout << p1.use_count() << endl;//1
	my_shared<int>p2(p1);
	cout << p2.use_count() << endl;//2
	my_shared<int>p3(p2);
	cout << p3.use_count() << endl;//3

	return 0;
}

shared_ptr其实有个大坑

struct ListNode
{
	my_shared<ListNode> prev;
	my_shared<ListNode> next;
	int x;
	ListNode(const int& val)
		:prev(nullptr)
		,next(nullptr)
		,x(val)
	{}

	~ListNode()
	{
		cout << "Deletd finish" << endl;
	}
};

int main()
{
	my_shared<ListNode> p1 = new ListNode(10);
	my_shared<ListNode> p2 = new ListNode(20);
	p1->next = p2;
	p2->prev = p1;
	p1->prev = p2;
	p2->next = p1;

	return 0;
}

 如果出现这种场景就会发生循环引用,程序就会迟迟不会正常释放p1 和p2

这是由于p1和p2的指针循环引用,各自的count 互相制约,都想要调用delete 但是count 始终不会变为1 ,所以这种情况可能发生时,prev 和 next 就可以不用shared_ptr 用普通的指针就可以

weak_ptr

为了优化shared_ptr 就有了weak_ptr

weak_ptr 有三种构造函数

①无参构造函数,此时weak_ptrnullptr

②拷贝构造函数,用来拷贝其他weak_ptr

③由shared_ptr构造,此时shared_ptr weak_ptr指向同一块空间

第③种情况下,weak_ptr不会进行引用计数,weak_ptr离开作用域的时候,不会释放自己指向的资源,其只负责访问资源

  模拟实现:

template<class T>
class my_weak
{
public:
	my_weak()
		:_ptr(nullptr)
	{}

	my_weak(const my_weak<T> p)
	{
		_ptr=p._ptr
	}

	my_weak<T> operator=(const shared_ptr<T>& p)
	{
		_ptr = p.get();
		return *this;
	}

	T& operator()
	{
		return *_ptr;
	}

	T* operator->()
	{
		return _ptr;
	}

private:
	T* _ptr;
};
  • 29
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值