<C++>智能指针

文章介绍了C++中的智能指针,如auto_ptr、unique_ptr、shared_ptr和weak_ptr,以及它们如何解决内存管理和异常安全问题。此外,还讨论了定制删除器在unique_ptr中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 智能指针

#define _CRT_SECURE_NO_WARNINGS 
#include<iostream>
#include<memory>
using namespace std;

int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");

	return a / b;
}

void func()
{
	int* p1 = new int[10]; // 这里亦可能会抛异常

	int* p2 = new int[10]; // 这里亦可能会抛异常
	int* p3 = new int[10]; // 这里亦可能会抛异常
	int* p4 = new int[10]; // 这里亦可能会抛异常

	try
	{
		div();
	}
	catch (...)
	{
		delete[] p1;
		delete[] p2;
		delete[] p3;
		delete[] p4;

		throw;
	}

	delete[] p1;
	delete[] p2;
	delete[] p3;
	delete[] p4;
}

int main()
{
	try
	{
		func();
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
		// ...
	}

	return 0;
}

上面这个解决方法很繁琐

我们引入智能指针

RAII是个指导思想:获取资源之后去初始化一个对象,将资源交给对象管理

能用于智能指针、lock(),unlock()

//SmartPtr.h
namespace tyyg
{
	template<class T>
	class SmartPtr
	{
	public:
		SmartPtr(T* ptr)
			: _ptr(ptr)
		{}

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

// test.c
#include "SmartPtr.h"
double div()
{
	double a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");

	return a / b;
}

void func()
{
	tyyg::SmartPtr<int> sp1(new int);
	tyyg::SmartPtr<int> sp2(new int);
	tyyg::SmartPtr<int> sp3(new int);// sp3的new抛异常,跳到catch的地方,sp1,sp2出作用域调用析构
	tyyg::SmartPtr<int> sp4(new int);
	tyyg::SmartPtr<pair<string, int>> sp5(new pair<string, int>("sort", 1));

	// div()抛异常,sp1,sp2,sp3,sp4出作用域调用析构,不会造成内存泄漏
	cout << div() << endl;
}

int main()
{
	try
	{
		func();
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
		// ...
	}
	return 0;
}

问题:拷贝

浅拷贝同一块资源会被析构两次,

回忆一下list的迭代器,我们用的是浅拷贝但它没问题,是因为我们不用迭代器负责节点的释放

但我们还是需要浅拷贝来拷贝智能指针,不然就不是管着这块资源

但我们分不清这是别人交给我们管理的,还是别人跟我们一起管理的,这就导致我们不知道该由谁来负责释放资源

如何解决:

1.1 auto_ptr

C++98 auto_ptr 管理权转移,被拷贝对象悬空(交给你管,我不管了)

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

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

    // sp2(sp1)
    auto_ptr(auto_ptr<T>& sp)
    	: _ptr(sp._ptr)
    {
    	sp._ptr = nullptr;
    }

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

private:
	T* _ptr;
};
int main()
{
	tyyg::auto_ptr<int> sp1(new int);
	tyyg::auto_ptr<int> sp2 = sp1;// sp1把资源给sp2管,sp1没用了
    // sp1悬空
    //*sp1 = 20;// 不能这样写

	return 0;
}

boost

C++11

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

1.2 unique_ptr

核心原理:不让拷贝 --> 拷贝编译就报错

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

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

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

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

		T* get()
		{
			return _ptr;
		}


	//private:
	//	// sp2(sp1)
	//	// C++98
	//	// 1、只声明,不实现
	//	// 2、声明成私有
	//	unique_ptr(const unique_ptr<T>& sp);

		unique_ptr(const unique_ptr<T>& sp) = delete;
		unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;

	private:
		T* _ptr;
	};
}

//C++11 unique_ptr 
// 核心原理:不让拷贝  --> 拷贝编译就报错
int main()
{
	tyyg::unique_ptr<int> up1(new int);
	tyyg::unique_ptr<int> up2(up1);// 报错
	return 0;
}

1.3 shared_ptr

原理:引用计数(string博客里写过)

count要一个资源一个计数

静态的static int _count不行,因为构造多个独立的对象时只会析构一次
如果是静态的计数,sp3构造时将count改成1,之后sp3析构,count变成0,再往后sp2和sp1指向的资源不释放

只能用int* _count

但也有缺陷,就是多线程的时候_count有可能会加错

namespace tyyg
{
	template<class T>
	class shared_ptr
	{
	public:
		void Release()
		{
			if (--(*_pCount) == 0 && _ptr)
			{
				cout << "delete" << _ptr << endl;
				delete _ptr;
				_ptr = nullptr;

				delete _pCount;
				_pCount = nullptr;
			}
		}

		// RAII思想
		shared_ptr(T* ptr)
			:_ptr(ptr)
			, _pCount(new int(1))
		{}

		~shared_ptr()
		{
			Release();
		}

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

		// sp1 = sp3
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			//if (this != &sp)
			if (_ptr != sp._ptr)
			{
				Release();

				_ptr = sp._ptr;
				_pCount = sp._pCount;
				++(*_pCount);
			}
			return *this;
		}


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

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

		T* get() const
		{
			return _ptr;
		}

		int use_count()
		{
			return *_pCount;
		}

	private:
		T* _ptr;
		int* _pCount;
		
	};
}
//shared_ptr 引用计数,
// 析构的时候计数,最后一个析构对象释放资源
int main()
{
	tyyg::shared_ptr<int> sp1(new int);
	tyyg::shared_ptr<int> sp2(sp1);
	// 静态的count不行,因为构造多个独立的对象时只会析构一次
	tyyg::shared_ptr<int> sp3(new int);// 如果是静态的计数,sp3将count改成1,之后sp3析构,count变成0,再往后sp2和sp1指向的资源不释放
	sp1 = sp3;
	return 0;
}

新问题:循环引用

struct ListNode
{
	tyyg::shared_ptr<ListNode> _next = nullptr;
	tyyg::shared_ptr<ListNode> _prev = nullptr;

	int _val = 0;

	~ListNode()
	{
		cout << "~ListNode()" << endl;
	}
};
int main()
{
	// 循环引用
	std::shared_ptr<ListNode> p1(new ListNode);
	std::shared_ptr<ListNode> p2(new ListNode);
    
	cout << p1.use_count() << endl;
	cout << p2.use_count() << endl;

	//p1->_next = p2;// p2和_next一起管理它
	//p2->_prev = p1;// p1和_prev一起管理它
	// 这就导致p1,p2析构的时候这两块空间还未析构,直到_next和_prev析构的时候这两块空间才会析构
	//(但_prev析构取决于_next;_next析构取决于_prev,矛盾了)
	// 这就导致空间没法释放

	return 0;
}

解决方法:别让_prev, _next来管理空间,它们的指向空间时不增加计数,引入weak_ptr来辅助

1.4 weak_ptr

shared_ptr的小弟

专门解决shared_ptr循环引用

weak_ptr拷贝shared_ptr,但不增加计数,

weak_ptr不参与资源管理

namespace tyyg
{
	// 不参与指向资源的释放管理
	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)// 能接收shared_ptr,但不增加计数
		{
			if (_ptr != sp.get())
			{
				_ptr = sp.get();
			}
			return *this;
		}

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

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

struct ListNode
{
	tyyg::weak_ptr<ListNode> _next;// 解决循环引用的方法,把这里换成weak_ptr
	tyyg::weak_ptr<ListNode> _prev;
    
	int _val = 0;

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

int main()
{
	// 循环引用
	tyyg::shared_ptr<ListNode> p1(new ListNode);
	tyyg::shared_ptr<ListNode> p2(new ListNode);

	cout << p1.use_count() << endl;
	cout << p2.use_count() << endl;

	p1->_next = p2;// p2和_next一起管理它
	p2->_prev = p1;// p1和_prev一起管理它
	// 但p1->_next和p2->_prev都是weak_ptr类型的,能够调用weak_ptr<T>& operator=(const shared_ptr<T>& sp),实现增加指向但不计数

	cout << p1.use_count() << endl;
	cout << p2.use_count() << endl;
	return 0;
}

上面的模拟实现只考虑了最基本的情况,跟库里的weak_ptr根本比不了,我们模拟实现只是为了更好地理解它,而不是为了造出更好的轮子

shared_ptr要和weak_ptr一起才能解决循环引用问题!用的时候一定要小心

2. 定制删除器 – 仿函数

unique_ptr/shared_ptr 默认释放资源用的delete
如何匹配申请方式去对应释放呢?回忆一下排序时我们对各种类型排序的处理方法:仿函数

我们先用库里的unique_ptr试一下效果(这里写了一部分仿函数来封装是为了看清楚调用的是哪个函数)

class Date
{
public:
	~Date()
	{
		cout << "~Date()" << endl;
	}
private:
	int _year = 1;
	int _month = 1;
	int _day = 1;
};

// unique_ptr/shared_ptr  默认释放资源用的delete
// 如何匹配申请方式去对应释放呢?

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

		delete[] ptr;
	}
};

template<class T>
struct Free
{
	void operator()(T* ptr)
	{
		cout << "free" << ptr << endl;

		free(ptr);
	}
};

struct Fclose
{
	void operator()(FILE* ptr)
	{
		cout << "fclose" << ptr << endl;

		fclose(ptr);
	}
};

int main()
{
	std::unique_ptr<Date> up1(new Date);
	std::unique_ptr<Date, DeleteArray<Date>> up2(new Date[10]);
	std::unique_ptr<Date, Free<Date>> up3((Date*)malloc(sizeof(Date) * 10));
	std::unique_ptr<FILE, Fclose> up4((FILE*)fopen("Test.cpp", "r"));

	return 0;
}

然后就自己实现一下吧,其实也就在模板里加一个仿函数,再改一下析构函数

unique_ptr在类的构造参数支持定制删除器,我们的unique_ptr没法在在构造函数传参支持定制删除器,因为通过构造函数传入的仿函数在析构函数里也用不了

namespace tyyg
{
	template<class T, class D = default_delete<T>>
	class unique_ptr
	{
	public:
		// RAII思想
		unique_ptr(T* ptr)
			:_ptr(ptr)
		{}

		~unique_ptr()
		{
			if (_ptr)
			{
				/*cout << "delete" << _ptr << endl;
				delete _ptr;*/
				D del;// unique_ptr的定制删除器
				del(_ptr);
				_ptr = nullptr;
			}
		}

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

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

		T* get()
		{
			return _ptr;
		}

		unique_ptr(const unique_ptr<T>& sp) = delete;
		unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;

	private:
		T* _ptr;
	};
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

天影云光

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

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

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

打赏作者

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

抵扣说明:

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

余额充值