C++->智能指针

        C++智能指针是一种用于管理动态分配的内存资源的指针类。它们提供了自动内存管理的功能,可以避免内存泄漏和悬空指针问题。


一、RAII思想

        RAII(Resource Acquisition Is Initialization)是一种C++编程范式,在C++中主要用来管理资源,智能指针是一种RAII的实现方式,在C++中用于管理动态分配的内存。智能指针会自动管理内存的生命周期,在指针的作用域结束时自动释放资源,从而避免内存泄漏和野指针的问题。

二、智能指针的优势

        智能指针的核心思想:就是在构造函数中给指针分配资源(比如动态分配的内存),而在析构函数中释放指针指向的资源。从而实现对资源的自动管理。在一些特殊情况智能真有绝对的优势,如下简单的小例子。

#include <iostream>
#include <memory>
using namespace std;
void divide(int a, int b) {
    //int *p=new int(1);
    unique_ptr<int> p = make_unique<int>(1);
    if (b == 0) {
        throw runtime_error("Division by zero error");
    }
    //delete p;
    cout << "Result: " << a / b << endl;
}

int main() {
    try 
    {
        divide(10, 0);
    } catch (std::runtime_error& e) {
        cout << "Exception caught: " << e.what() << endl;
    }
    return 0;
}

    在上面的代码中,如果使用普通指针int *p = new int(1),当抛出异常时,会导致程序直接跳转到main函数中,打破正常代码执行流,p指向的资源没有被释放(对应的delete p;语句没有被执行),从而导致内存泄漏。

       而如果使用智能指针unique_ptr<int> p = make_unique<int>(1),当抛出异常时,智能指针p离开其作用域,会自动调用析构函数从而资源的自动释放,从而避免了内存泄漏问题。

三、智能指针的理解

1.智能指针的说明和分类

  • auto_ptr(c++98)标准:auto_ptr在进行指针拷贝时会出现资源转移的问题(现在几乎没有使用,可以完全用下面的智能指针替代)。
  • unique_ptr: 独占所有权,确保只有一个指针指向对象。简单理解,直接禁用了拷贝构造和赋值操作符。从而实现这个指针独占此资源。
  • shared_ptr: 允许多个指针共享对象的所有权,引用计数管理生命周期。可能会出现循环引用的问题,可以用weak_ptr来解决此问题。
  • weak_ptr: 解决shared_ptr可能的循环引用问题。

2.auto_ptr的理解

template<class T>
	class auto_ptr
	{
	public:
		//RAII思想
		auto_ptr(T* ptr)
			:_ptr(ptr)
		{}
		~auto_ptr()
		{
			if (_ptr)//_ptr不等于空再去释放资源
			{
				cout << "~auto_ptr()"<<"->" << _ptr << endl;
				delete _ptr;
				_ptr = nullptr;
			}
		}
		
		auto_ptr(auto_ptr<T>& ap)
			:_ptr(ap._ptr)
		{
			ap._ptr = nullptr;
		}
		//像指针一样访问
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
	private:
		T* _ptr;
	};

        如:ap2(ap1),auto_ptr 解决拷贝问题,通过管理权的转移,把拷贝出来的ap2指向那块资源把ap1置空,所以并没有实现两个指针指向同一块资源。两个指针并没有指向同一块空间,可能出现问题,所以几乎没有再使用了(在C++17标准中已经删除)。

3.unique_ptr

template<class T>
	class unique_ptr
	{
	public:
		unique_ptr(T* ptr)
			:_ptr(ptr)
		{}
		~unique_ptr()
		{
			if (_ptr)
			{
				cout << "~auto_ptr()" << "->" << _ptr << endl;
				delete _ptr;
				_ptr = nullptr;
			}
		}
		unique_ptr(const unique_ptr<T>&) = delete;
		unique_ptr<T>& operator=(const unique_ptr<T>&) = delete;
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
	private:
		T* _ptr;
	};

        unique_ptr主要是禁用了禁用了拷贝构造和赋值操作符。实现了资源的独享。

4.shared_ptr

template<class T>
	class shared_ptr //引用计数随着资源走 所以在构造中new出来引用计数
	{
	public:
		shared_ptr(T* ptr)
			:_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)
			{
				delete _ptr;
				delete _pcount;
			}
		}
		~shared_ptr()
		{
			release();
		}
		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			, _pcount(sp._pcount)
		{
			++(*_pcount);
		}
		//sp1=sp2
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			if (_ptr != sp._ptr)//避免sp1=sp1;或者两个指针指向同一份资源的情况
			{
				release();
				_ptr = sp._ptr;
				_pcount = sp._pcount;
				++(*_pcount);
			}
			return *this;
		}
		int use_count() const
		{
			return *_pcount;
		}
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
		T* get_ptr() const
		{
			return _ptr;
		}
	private:
		T* _ptr;
		int* _pcount;
	};

1.循环引用问题

        shared_ptr因为是采用引用计数来决定指针指向资源的释放,在一些特殊的场景下会出现循环引用的问题,如下的例子中会出现循环引用问题。

#include<iostream>
using namespace std;
struct ListNode
{
	int _val;
	shared_ptr<ListNode> _prev;
	shared_ptr<ListNode> _next;
    //weak_ptr<ListNode> _prev;
	//weak_ptr<ListNode> _next;
	~ListNode()
	{
		cout << "~ListNode()" << endl;
	}
};
void test_shared_ptr()
{
	shared_ptr<ListNode>n1 = make_shared<ListNode>();
	shared_ptr<ListNode>n2 = make_shared<ListNode>();
	//循环引用
	n1->_next = n2;
	n2->_prev = n1;
}

int main()
{
	test_shared_ptr();

	return 0;
}

代码运行结果:(可见并没有打印析构的语句,所以可知节点资源并没有被释放造成了内存泄漏)

        此例子中的循环引用,简单来说就是,在代码中创建两个ListNode对象 n1 和 n2,并且相互之间存在引用关系,这种循环引用会导致问题,导致智能指针中的引用计数无法降至零,导致内存泄漏。

        解决方法可以通过weak_ptr去接收shared_ptr的赋值,weak_ptr并不会增加由shared_ptr赋值的引用计数。(不会把图中的引用计数1改变成2) 所以就可以解决循环引用的问题了,资源可以正常释放了。

5.weak_ptr

    weak_ptr 是 C++11 标准引入的一种智能指针,用于解决 shared_ptr 的循环引用问题。weak_ptr 允许访问一个由 shared_ptr 管理的对象,但不会增加该对象的引用计数,常用于解决 shared_ptr循环引用的问题。weak_ptr本身并不拥有所指向对象的所有权,它仅仅是对shared_ptr指向的对象的一个观察者,并非资源的所有者。

	template<class T>
	class weak_ptr 
	{
	public:
		weak_ptr()
			:_ptr(nullptr)
		{}
		weak_ptr(const shared_ptr<T>& sp)
			:_ptr(sp.get_ptr())
		{}
		weak_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			_ptr = sp.get_ptr();
			return *this;
		}
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
	private:
		T* _ptr;
	};

四、定制删除器

        上述代码中,我们所有析构函数中我们都是用的delete去释放资源,那假如我们开辟的一块空间呢,本来应使用delete[ ]去释放空间,而我们缺使用了delete去释放资源那么的话就会发生错误。C++标准库中是通过定制删除器去实现对智能指针的指向资源的删除。同时也允许用户自定义

删除器来指定释放资源的方式。

#include<iostream>
#include<memory>

void Test_shared_ptr()
{
	std::shared_ptr<int> sp1(new int[10], [](int* ptr) {delete[] ptr; });
	std::shared_ptr<int> sp2(new int[10]); //没有指定删除器
}

int main()
{
	Test_shared_ptr();

	return 0;
}

  从C++17开始,std::shared_ptr 能够根据所管理的对象是否为数组来自动选择正确的删除器。对于非数组类型的动态分配,默认调用 delete;而对于数组类型(如 new int[10]),则默认调用 delete[]。所以当编译器支持C++17标准,如果是数组类型不指定删除器并不会出错。

        在C++17之前,std::shared_ptr无法自动识别动态分配的数组类型,因此会使用delete而不是delete[],这可能导致未定义的行为或内存泄漏。


总结

        本文分享了一些本人对智能指针知识的理解,希望对大家的学习有所帮助,如有错误请大佬指出,万分感谢。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值