C++之智能指针

目录

智能指针的价值

内存泄漏

内存泄漏的分类

如何避免内存泄漏

 智能指针的使用及原理

RAII

auto_ptr

unique_ptr

shared_ptr

循环引用

shared_ptr的删除器

weak_ptr


在谈智能指针之前,先谈谈为什么需要智能指针?

智能指针的价值

1.自动内存管理:
智能指针可以自动管理它们所指向的内存。当智能指针离开其作用域或被重置时,它们会自动删除所指向的对象,从而避免了程序员显式调用delete 的需要。这有助于减少由于忘记释放内存而导致的内存泄漏。

2.防止悬空指针:
悬空指针是指那些指向已经被释放的内存的指针。智能指针通过确保在删除所指向的对象之前不会复制或复制指针,来防止悬空指针的产生。
3.简化内存管理:
智能指针使得动态内存管理更加简单和直观。程序员可以像使用普通变量一样使用智能指针,而不需要担心内存管理的细节。
4.提高代码的可读性和可维护性:
使用智能指针可以使代码更加清晰和易于理解,因为它们封装了内存管理的细节。这有助于提高代码的可读性和可维护性。
5.与标准库和其他库兼容:
智能指针与C++标准库和其他许多库兼容,这使得它们可以在各种应用程序和项目中轻松使用。

6.安全:
通过减少程序员直接管理内存的需要,智能指针有助于提高代码的安全性。它们减少了由于内存泄漏、悬空指针和其他与内存管理相关的错误而导致的程序崩溃和数据损坏的风险。

内存泄漏
什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。
内存泄漏的分类

C/C++程序中一般我们关心两种方面的内存泄漏:
堆内存泄漏(Heap leak)

堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一
块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分
内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。


系统资源泄漏

指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放
掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。 

如何避免内存泄漏
1. 工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。
2. 采用 RAII 思想或者智能指针来管理资源。
3. 有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。
4. 出问题了使用内存泄漏工具检测。
内存泄漏非常常见,解决方案分为两种: 1 、事前预防型。如智能指针等。 2 、事后查错型。如泄漏检测工具。
 智能指针的使用及原理
RAII

RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。

在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:

不需要显式地释放资源。
采用这种方式,对象所需的资源在其生命期内始终保持有效

template<class T>
class SmartPtr
{
public:
	// RAII
	SmartPtr(T* ptr)
		:_ptr(ptr)
	{}

	~SmartPtr()
	{
		cout << "~SmartPtr()->"<<_ptr << endl;

		delete _ptr;
	}

private:
	T* _ptr;
};

上述的SmartPtr还不能将其称为智能指针,因为它还不具有指针的行为。

指针可以解引用,也可以通过 -> 去访问所指空间中的内容, 因此: AutoPtr  模板类中还得需要将* 、->重载下,才可让其像指针一样去使用。

即:

template<class T>
class SmartPtr
{
public:
	// RAII
	SmartPtr(T* ptr)
		:_ptr(ptr)
	{}

	~SmartPtr()
	{
		cout << "~SmartPtr()->"<<_ptr << endl;

		delete _ptr;
	}

	// 像指针一样
	T& operator*()
	{
		return *_ptr;
	}

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

auto_ptr的实现原理:管理权转移的思想,即最后一个拷贝对象管理资源,被拷贝对象被置空。

	template<class T>
	class auto_ptr
	{
	public:
		// RAII
		auto_ptr(T* ptr)
			:_ptr(ptr)
		{}

		~auto_ptr()
		{
			if (_ptr)
			{
				cout << "delete->" << _ptr << endl;
				delete _ptr;
				_ptr = nullptr;
			}
		}

		// ap2(ap1)
		auto_ptr(auto_ptr<T>& ap)
			:_ptr(ap._ptr)
		{
			ap._ptr = nullptr;
		}

		// 像指针一样
		T& operator*()
		{
			return *_ptr;
		}

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

	private:
		T* _ptr;
	};

缺点:由于auto_ptr原理是管理权转移,被转移对象会悬空,因此对于不知道底层原理的人来说,可能会解引用进行访问,那么就会导致程序崩溃。

int main()
{
    std::auto_ptr<int> sp1(new int);
    std::auto_ptr<int> sp2(sp1); // 管理权转移

    // sp1悬空
    *sp2 = 10;
    cout << *sp2 << endl;
    cout << *sp1 << endl;
    return 0;
}
unique_ptr

unique_ptr的实现原理:简单粗暴的防拷贝.

std:unique_ptr:表示对对象的独占所有权。同一时间只能有一个unique_ptr指向一个对象。当unique_ptr 被销毁时,它所指向的对象也会被销毁。

	template<class T>
	class unique_ptr
	{
	public:
		// RAII
		unique_ptr(T* ptr)
			:_ptr(ptr)
		{}

		~unique_ptr()
		{
			cout << "delete->" << _ptr << endl;

			delete _ptr;
		}

		// 像指针一样
		T& operator*()
		{
			return *_ptr;
		}

		T* operator->()
		{
			return _ptr;
		}
		
		// C++11
		unique_ptr(const unique_ptr<T>& up) = delete;
		unique_ptr<T>& operator=(const unique_ptr<T>& up) = delete;

	private:
		// C++98
		// 1、只声明不实现
		// 2、限定为私有
		//unique_ptr(const unique_ptr<T>& up);
		//unique_ptr<T>& operator=(const unique_ptr<T>& up);
	private:
		T* _ptr;
	};

对于C++98的方式,如果单纯只是只声明不实现,架不住会在类外实现,因此对于C++98的方式,最好进行private私有化。

shared_ptr

shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。

std:shared_ptr:表示对对象的共享所有权。多个shared_ptr可以指向同一个对象,并且当最后一个指向该对象的shared_ptr 被销毁时,对象才会被销毁。

1. shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。
2. 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减一。
3. 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;
4. 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。

那么问题来了,这个引用计数存在在哪里呢?

是对象指向的资源里?-----不对,因为你开辟内存的时候,并不能指定在某个位置进行指定存储.

是在栈上?----不对,因为栈上变量的生命周期只在当前函数或作用域,与堆上资源生命周期不同步 

是在堆上?----对,多个智能指针可以指向同一个引用计数.

 

	template<class T>
	class shared_ptr
	{
	public:
		// RAII
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			,_pcount(new int(1))
		{}

		void release()
		{
			if (--(*_pcount) == 0)
			{
				//cout << "delete->" << _ptr << endl;
				delete _ptr;
				delete _pcount;
			}
		}

		~shared_ptr()
		{
			release();
		}

		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			, _pcount(sp._pcount)
		{
			++(*_pcount);
		}

		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			if (_ptr != sp._ptr)
			{
				release();

				_ptr = sp._ptr;
				_pcount = sp._pcount;

				++(*_pcount);
			}

			return *this;
		}

		// 像指针一样
		T& operator*()
		{
			return *_ptr;
		}

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

		int use_count() const
		{
			return *_pcount;
		}

		T* get() const
		{
			return _ptr;
		}

	private:
		T* _ptr;
		int* _pcount;
	};

缺点:无法应对循环引用

循环引用

如果只有红色点这一行代码或者只有紫色这一行的代码,不会出问题;

但是当二者同时出现时,就出问题了。

原因为:

对前者而言,假设是只有红色这一行代码,那么n2先释放,但是n2的资源并不会释放,n2的资源的引用计数由2->1,然后n1释放,但是n1释放前,先释放n2的资源,然后n1才释放。

对后者而言,n2先释放,但是n2的资源不释放,n2资源的引用计数由2->1,然后释放n1,但是n1的资源不释放,n1的资源的引用计数由2->1,那么n1和n2的资源何时释放呢?

n2资源要释放-》n1的_next先释放-》n1的_next何时释放-》n1的资源释放-》n1的资源何时释放-》n2的_prev释放-》n2的_prev何时释放-》n2资源释放。

显然,我们发现,形成了一个闭环,结果表现为:资源得不到释放。

那么如何解决呢?------智能指针weak_ptr就派上用场了,weak_ptr为解决shared_ptr的循环引用问题而生。

将shared_ptr更换为weak_ptr,问题就解决了,它是怎么解决的?

_prev和_next不会增加n1和n2的引用计数。

shared_ptr的删除器

如果不是new出来的对象如何通过智能指针管理呢?

shared_ptr设计了一个删除器来解决这个问题。

	template<class T>
	class shared_ptr
	{
	public:
		// RAII
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			,_pcount(new int(1))
		{}

		template<class D>
		shared_ptr(T* ptr, D del)
			:_ptr(ptr)
			, _pcount(new int(1))
			, _del(del)
		{}

		// function<void(T*)> _del;

		void release()
		{
			if (--(*_pcount) == 0)
			{
				//cout << "delete->" << _ptr << endl;
				//delete _ptr;
				_del(_ptr);

				delete _pcount;
			}
		}

		~shared_ptr()
		{
			release();
		}

		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			, _pcount(sp._pcount)
		{
			++(*_pcount);
		}

		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			if (_ptr != sp._ptr)
			{
				release();

				_ptr = sp._ptr;
				_pcount = sp._pcount;

				++(*_pcount);
			}

			return *this;
		}

		// 像指针一样
		T& operator*()
		{
			return *_ptr;
		}

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

		int use_count() const
		{
			return *_pcount;
		}

		T* get() const
		{
			return _ptr;
		}

	private:
		T* _ptr;
		int* _pcount;

		function<void(T*)> _del = [](T* ptr) {delete ptr; };
	};

通过删除器,包装器,缺省参数,这样,就解决了。

以上四种都可以得到解决。 

weak_ptr

std:weak ptr:是对 shared_ptr 的一个补充,它不会增加引用计数,因此不会导致对象被销毁。它主要用于解决shared_ptr可能导致的循环引用问题。

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

		weak_ptr(const shared_ptr<T>& sp)
			:_ptr(sp.get())
		{}

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

		// 像指针一样
		T& operator*()
		{
			return *_ptr;
		}

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

评论 53
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

新绿MEHO

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值