C++智能指针

什么是智能指针?

智能指针是一种用于管理动态分配内存的数据结构,通常用于C++等编程语言中。与传统的原始指针相比,智能指针具有自动化内存管理的功能,可以帮助程序员避免内存泄漏和悬空指针等问题。

智能指针通过在其生命周期结束时自动释放其所指向的内存来提供这种自动化管理。这样可以确保在不再需要内存时,内存会被正确地释放,从而减少了内存泄漏的风险。

常见的智能指针类型包括:

  1. shared_ptr:允许多个指针共享同一块内存。内部包含一个引用计数,当最后一个指向内存的shared_ptr被销毁时,内存会被释放。

  2. unique_ptr:独占所指向的内存资源,确保在任何时候只有一个指针可以指向该内存块。当unique_ptr超出其作用域或被销毁时,它所指向的内存会被自动释放。

  3. weak_ptr:用于解决shared_ptr的循环引用问题,它不会增加引用计数。通常与shared_ptr配合使用,用于观察资源是否已被释放。

使用智能指针可以提高代码的安全性和可靠性,减少了手动管理内存的复杂性,但仍需注意避免循环引用等问题。

内存泄露

什么是内存泄露,内存泄露的危害

什么是内存泄露:内存泄露指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄露并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。

内存泄露的危害:长期运行的程序出现内存泄露,影响很大,如操作系统、后台服务等等,出现内存泄露会导致响应越来越慢,最终卡死。

内存泄露分类

  • 堆内存泄露(Heap leak)

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

  • 系统资源泄露

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

如何避免内存泄露

  1. 有良好的设计规范和良好的编码规范,申请的内存空间记得匹配的去释放。(手动控制很难做到完美)
  2. 采用RAII思想或智能指针来管理资源。(下文会说明)

智能指针的使用及原理

RAII

RAII(Resource Acquisition Is Initialization)是一种C++编程中的重要技术,它利用对象的生命周期来管理资源的获取和释放,从而确保资源在不再需要时被正确释放,避免资源泄漏。

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

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

智能指针原理

智能指针,浅拷贝,只是代替管理资源。

首先使用RAII思想设计的SmartPtr类

想要有指针的行为,这个模板类中还需要重载 * 、->,才能让其像指针一样使用。

template<class T>
class SmartPtr {
public:

	SmartPtr(T* ptr = nullptr)
		: _ptr(ptr)
	{}

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

		}
	}

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

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

struct Date
{
	int _year;
	int _month;
	int _day;
};

int main()
{
	SmartPtr<int> sp1(new int);
	*sp1 = 10;
	cout << *sp1 << endl;
	SmartPtr<Date> sparray(new Date);
	// 需要注意的是这里应该是sparray.operator->()->_year = 2018;
	// 本来应该是sparray->->_year这里语法上为了可读性,省略了一个->
	sparray->_year = 2018;
	sparray->_month = 1;
	sparray->_day = 1;

}

在这里插入图片描述

总结智能指针的原理:

  1. RAII特性
  2. 重载operator*和opereator->,具有像指针一样的行为。

std::auto_ptr

C++98版本的库中就提供了auto_ptr的智能指针。下面演示auto_ptr的使用及问题。

auto_ptr的实现原理:管理权转移的思想,下面简化模拟实现一份auto_ptr来了解一下它的原理。

// C++98 管理权转移 auto_ptr
namespace Nir
{
	template<class T>
	class auto_ptr
	{
	public:
		auto_ptr(T* ptr)
			:_ptr(ptr)
		{}

		auto_ptr(auto_ptr<T>& sp)
			:_ptr(sp._ptr)
		{
			// 管理权转移
			sp._ptr = nullptr;
		}

		auto_ptr<T>& operator=(auto_ptr<T>& ap)
		{
			// 检测是否为自己给自己赋值
			if (this != &ap)
			{
				// 释放当前对象中资源
				if (_ptr)
					delete _ptr;
				// 转移ap中资源到当前对象中
				_ptr = ap._ptr;
				ap._ptr = NULL;
			}
			return *this;
		}

		~auto_ptr()
		{
			if (_ptr)
			{
				cout << "delete:" << _ptr << endl;
				delete _ptr;
			}
		}

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

	private:
		T* _ptr;
	};
}

// 结论:auto_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;
}

在这里插入图片描述

在这里插入图片描述

std::unique_ptr

C++11中开始提供更靠谱的unique_ptr

unique_ptr的实现原理:简单粗暴的防拷贝,下面简化模拟实现了一份UniquePtr来了解它的原理。

// C++11库才更新智能指针实现
// C++11出来之前,boost搞出了更好用的scoped_ptr/shared_ptr/weak_ptr
// C++11将boost库中智能指针精华部分吸收了过来
// C++11->unique_ptr/shared_ptr/weak_ptr

// unique_ptr/scoped_ptr
// 原理:简单粗暴 -- 防拷贝
namespace Nir
{
	template<class T>
	class unique_ptr
	{
	public:
		unique_ptr(T* ptr)
			:_ptr(ptr)
		{}

		~unique_ptr()
		{
			if (_ptr)
			{
				cout << "delete:" << _ptr << endl;
				delete _ptr;
			}
		}

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

		//防止拷贝
		unique_ptr(const unique_ptr<T>&sp) = delete;
		unique_ptr<T>& operator=(const unique_ptr<T>&sp) = delete;

	private:
		T* _ptr;
	};
}

int main()
{
	 Nir::unique_ptr<int> sp1(new int);
	 Nir::unique_ptr<int> sp2(sp1);

	 std::unique_ptr<int> sp3(new int);
	 std::unique_ptr<int> sp4(sp3);

 return 0;
}

在这里插入图片描述

在这里插入图片描述

std::shared_ptr

C++11中开始提供更靠谱的并且支持拷贝的shared_ptr

shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。例如:早八上课最后一个离开宿舍的人关门。

  1. shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享
  2. 对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减一
  3. 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源
  4. 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。
namespace Nir
{
	template<class T>
	class shared_ptr
	{
	public:
		// RAII
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			, _pcount(new int(1))
		{}

		// sp2(sp1)
		shared_ptr(const shared_ptr<T>& sp)
		{
			_ptr = sp._ptr;
			_pcount = sp._pcount;

			// 拷贝时++计数
			++(*_pcount);
		}

		// sp1 = sp4
		// sp4 = sp4;
		// sp1 = sp2;
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			//if (this != &sp)
			if (_ptr != sp._ptr)	//当指向资源不同时再进行赋值拷贝,防止指向资源相同时再次拷贝使得计数+1
			{
				release();	//先释放当前指向的资源(或者是当前指向的资源计数减1)

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

				// 拷贝时++计数
				++(*_pcount);
			}

			return *this;
		}

		void release()
		{
			// 说明最后一个管理对象析构了,可以释放资源了
			if (--(*_pcount) == 0)
			{
				cout << "delete:" << _ptr << endl;
				delete _ptr;
				delete _pcount;
			}
		}

		~shared_ptr()
		{
			// 析构时,--计数,计数减到0,
			release();
		}

		int use_count()
		{
			return *_pcount;
		}

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

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

		T* get() const
		{
			return _ptr;
		}
	private:
		T* _ptr;
		int* _pcount;
	};
    
    
    
	// 简化版本的weak_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;
	};
}



int main()
{
	 Nir::shared_ptr<int> sp1(new int);
	 Nir::shared_ptr<int> sp2(sp1);
	 Nir::shared_ptr<int> sp3(sp1);
	 cout << sp1.use_count() << endl;
	 Nir::shared_ptr<int> sp4(new int);
	 Nir::shared_ptr<int> sp5(sp4);
	 cout << sp4.use_count() << endl;

	 sp1 = sp1;
	 sp1 = sp2;
	 cout << sp1.use_count() << endl;

	 sp1 = sp4;
	 sp2 = sp4;
	 sp3 = sp4;
	 cout << sp1.use_count() << endl;

	 *sp1 = 2;
	 *sp2 = 3;

	 return 0;
}

在这里插入图片描述

std::shared_ptr的循环引用

 //shared_ptr的缺陷
struct ListNode
{
	int _val;

	Nir::shared_ptr<ListNode> _next;
	Nir::shared_ptr<ListNode> _prev;

	//Nir::weak_ptr<ListNode> _next;
	//Nir::weak_ptr<ListNode> _prev;

	ListNode(int val = 0)
		:_val(val)
	{}

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

int main()
{
	Nir::shared_ptr<ListNode> n1(new ListNode(10));
	Nir::shared_ptr<ListNode> n2(new ListNode(20));

	cout << n1.use_count() << endl;
	cout << n2.use_count() << endl;

	n1->_next = n2;
	n2->_prev = n1;

	cout << n1.use_count() << endl;
	cout << n2.use_count() << endl;



	return 0;
}

循环引用分析:

  1. node1和node2两个智能指针对象指向两个节点,引用计数变成1,我们不需要手动delete。
  2. node1的__next指向node2,node2的_prev指向node1,引用计数变成2。
  3. node1和node2析构,引用计数减到1,但是_next还指向下一个节点。但是_prev还指向上一个节点。
  4. 也就是说_next析构了,node2就释放了。
  5. 也就是说_prev析构了,node1就释放了。
  6. 但是__next属于node的成员,node1释放了,_next才会析构,而node1由_prev管理,_prev属于node2成员,所以这就叫循环引用,谁也不会释放。

在这里插入图片描述

循环引用解决(weak_ptr)

weak_ptr无引用计数,不支持RAII(即构造时直接将对象的生命周期与自己绑在一起),不参与资源管理。

// 解决方案:在引用计数的场景下,把节点中的_prev和_next改成weak_ptr就可以了
// 原理就是,node1->_next = node2;和node2->_prev = node1;时weak_ptr的_next和_prev不会增加node1和node2的引用计数。

struct ListNode
{
	int _val;

	Nir::weak_ptr<ListNode> _next;
	Nir::weak_ptr<ListNode> _prev;

	ListNode(int val = 0)
		:_val(val)
	{}

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

int main()
{
	Nir::shared_ptr<ListNode> n1(new ListNode(10));
	Nir::shared_ptr<ListNode> n2(new ListNode(20));

	cout << n1.use_count() << endl;
	cout << n2.use_count() << endl;

	n1->_next = n2;
	n2->_prev = n1;

	cout << n1.use_count() << endl;
	cout << n2.use_count() << endl;



	return 0;
}

删除器管理资源

使用删除器管理资源,可以自定义删除器,以确保自定义资源能够得到正确的释放。

利用仿函数或者lambda表达式来自定义删除器。

namespace Nir
{
	template<class T>
	class shared_ptr
	{
	public:

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


		// RAII
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			, _pcount(new int(1))
		{}

		// sp2(sp1)
		shared_ptr(const shared_ptr<T>& sp)
		{
			_ptr = sp._ptr;
			_pcount = sp._pcount;

			// 拷贝时++计数
			++(*_pcount);
		}

		// sp1 = sp4
		// sp4 = sp4;
		// sp1 = sp2;
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			//if (this != &sp)		//防止自己给自己拷贝  (释放自己)
			if (_ptr != sp._ptr)	//当指向资源不同时再进行赋值拷贝(优化)
			{
				release();	//先释放当前指向的资源(或者是当前指向的资源计数减1)

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

				// 拷贝时++计数
				++(*_pcount);
			}

			return *this;
		}

		void release()
		{
			// 说明最后一个管理对象析构了,可以释放资源了
			if (--(*_pcount) == 0)
			{
				cout << "delete:" << _ptr << endl;
				_del(_ptr);
				delete _pcount;
			}
		}

		~shared_ptr()
		{
			// 析构时,--计数,计数减到0,
			release();
		}

		int use_count()
		{
			return *_pcount;
		}

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

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

		T* get() const
		{
			return _ptr;
		}
	private:
		T* _ptr;
		int* _pcount;
		function<void(T*)> _del = [](T* ptr) {delete ptr; };
	};


	// 简化版本的weak_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;
	};
}



template<class T>
struct DeleteArray
{
	void operator()(T* ptr)
	{
		delete[] ptr;
	}
};

//int main()
//{
//	std::shared_ptr<ListNode> p1(new ListNode(10));
//	std::shared_ptr<ListNode[]> p2(new ListNode[10]);  //可以的
//
//	std::shared_ptr<ListNode> p3(new ListNode[10], DeleteArray<ListNode>());
//	std::shared_ptr<FILE> p4(fopen("Test.cpp", "r"), [](FILE* ptr) {fclose(ptr); });
//
//	return 0;
//}
int main()
{
	Nir::shared_ptr<ListNode> p1(new ListNode(10));
    
    //保证资源的正确释放
	Nir::shared_ptr<ListNode> p2(new ListNode[10], DeleteArray<ListNode>());
	
    Nir::shared_ptr<FILE> p3(fopen("Test.cpp", "r"), [](FILE* ptr) {fclose(ptr); });

	return 0;
}

char*类型指针

char* 类型打印地址要强转为 void*,因为编译器会默认认为要打印字符串,其他类型不需要。

int main()
{
	char* p1 = new char[1024 * 1024 * 1024];
	cout << p1 << endl;

	return 0;
}

在这里插入图片描述

int main()
{
	char* p1 = new char[1024 * 1024 * 1024];
	cout << (void*)p1 << endl;

	return 0;
}

在这里插入图片描述

C++11和boost中智能指针的关系

  1. C++ 98中产生了第一个智能指针auto_ptr
  2. C++ boost给出了更实用的scoped_ptrshared_ptrweak_ptr
  3. C++ TR1,引入了shared_ptr等,不过TR1并不是标准版
  4. C++ 11,引入了unique_ptrshared_ptrweak_ptrunique_ptr对应boost的scoped_ptr。并且这些智能指针的实现原理是参考boost中的实现的
  • 24
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值