首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >【C++篇】智能指针

【C++篇】智能指针

作者头像
用户11719958
发布2025-12-30 11:42:15
发布2025-12-30 11:42:15
3270
举报

智能指针的概念及使用

智能指针是C++11引入的自动化内存管理工具,通过RAII(资源获取立即初始化)机制自动释放内存,避免内存泄漏和指针悬空的问题。

1,RAII和智能指针

  • RAII是Resource Acquisition Is Initialization的缩写,它是一种管理资源的类的设计思想。本质是一种利用对象生命周期来管理获取到的动态资源,避免资源泄漏。这里的资源可以是内存,文件,网络链接,互斥锁等。
  • RAII在获取资源时把资源委托给一个对象,接着控制对资源的访问, 资源在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源,这样保障了资源的正常释放,避免资源泄漏问题。
  • 智能指针除了满足RAII的设计思路,还要方便资源的访问。所以智能指针类还会像迭代器类一样重载一些operator* / operator-> /operator[ ]等运算符,方便访问资源。

template<class T> class SmartPtr { public: //RAII SmartPtr(T* ptr) ; _ptr(ptr) {} ~SmartPtr() { cout << "delete[]" << _ptr << endl; delete[] ptr; } //重载运算符,模拟指针行为,方便资源的访问 T& operator*() { return *_ptr; } T* operator->() { return _ptr; } T& operator[](size_t i) { return _ptr[i]; } private: T* _ptr; }; int main() { SmartPtr<int> sp1 = new int[10]; SmartPtr<pair<int, int>> sp2 = new pair<int, int>[10]; sp1[5] = 10; sp2->first = 1; sp2->second = 2; return 0; }

2,C++标准库中的智能指针

  • C++标准库中的智能指针都在<memory>这个头文件下面。
  • auto_ptr是C++98设计出来的智能指针,它的特点是拷贝时把 拷贝对象的资源的管理权转移,这会导致 被拷贝对象悬空,访问报错的问题。该智能指针已废弃,用 unqiue_ptr取代。
代码语言:javascript
复制
#include <memory>

class Date
{
public:
	Date() = default;
	Date(int year,int month,int day)
		:_year(year)
		,_month(month)
		,_day(day)
	{}
	~Date()
	{
		cout << "~Date()" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	//本质是想实现ap1和ap2共同管理一份Date资源
	auto_ptr<Date> ap1(new Date);
	//拷贝时,管理权转移,被拷贝对象ap1悬空  访问会出错
	auto_ptr<Date> ap2(ap1);

	return 0;
}
  • unique_ptr是C++11设计出来的智能指针,它的特点是不支持拷贝,只支持移动。

unique_ptr<Date> up1(new Date); //不支持拷贝 //unique_ptr<Date> up2(up1); //支持移动 unique_ptr<Date> up3(move(up1));

  • shared_ptr是C++11设计出来的智能指针,是一个共享指针。它的特点是支持拷贝,也支持移动。如果需要拷贝可以使用 shared_ptr。底层使用引用计数的方式实现的。

引用计数是一种内存管理技术,通过跟踪对象的引用次数来决定何时释放其占用的资源。当引用计数降为0时,对象会被自动销毁。

  • 计数器:每个对象关联一个整数计数器,记录当前有多少引用指向该对象。
  • 计数增减
    • 引用创建(如复制指针、赋值) → 计数+1。
    • 引用销毁(如指针离开作用域、被重置) → 计数-1。
  • 资源释放:当计数归零时,立即释放对象内存或资源。

shared_ptr<Date> sp1(new Date); //拷贝 shared_ptr<Date> sp2(sp1); //移动 shared_ptr<Date> sp3(move(sp1));

智能指针析构时,默认使用delete释放资源,这也就意味着如果不是new出来的资源,交给智能指针管理,析构时就会崩溃。示例:

int main() { shared_ptr<Date> sp(new Date[10]); //err return 0; }

上述代码运行会崩溃,对于这种情况,shared_ptr和unique_ptr都特化了一个[ ]版本

shared_ptr<Date[ ]> sp(new Date[ ]) ; unique_ptr<Date[ ]> up(new Date[ ])

这样就可以解决new[ ]的问题了。

但是这只是解决了new和new[ ]的问题,还有其他无法删除的情况。为了解决这个问题,智能指针支持在构造时给一个删除器,删除器本质是一个可调用 对象,比如仿函数,lambda表达式,函数指针等等。这个 可调用对象实现你想要释放资源的方式,当构造智能指针时,给了定制的删除器,在智能指针析构时,就会调用删除其释放资源。

库中shared_ptr的构造:

使用:

//仿函数做删除器 template<class T> class DeleteArray { public: void operator()(T* ptr) { delete[ ] ptr; ptr = nullptr; } }; class Fclose { public: void operator()(FILE* ptr) { fclose(ptr); } }; //函数指针做删除器 template<class T> void DeleteArrayFunc(T* ptr) { delete[ ] ptr; } int main() { //函数指针做删除器 shared_ptr<Date> sp1(new Date[10], DeleteArrayFunc<Date>); //lambda表达式做删除器 auto del = [ ](Date* ptr) {delete[] ptr; }; shared_ptr<Date> sp2(new Date[10], del); //仿函数做删除器 shared_ptr<FILE> sp3(fopen("test.cpp","r"), Fclose()); //lambda shared_ptr<FILE> sp4(fopen("test.cpp", "r"), [ ](FILE* ptr) {fclose(ptr); }); return 0; }

  • weak_ptr 是C++11设计出来的智能指针,与上面几个智能指针不同,它不支持RAII,也就意味着他不能直接用来管理资源。它的作用是为了解决shared_ptr的一个循环引用导致内存泄漏的问题。(循环引用在下面讲解)

3,智能指针的原理及模拟实现(shared_ptr)

shared_ptr要想支持拷贝,也就是几个对象共同管理一份资源,需要用到引用计数

引用计数是一种内存管理技术,通过跟踪对象的引用次数来决定何时释放其占用的资源。当引用计数降为0时,对象会被自动销毁。

  • 计数器:每个对象关联一个整数计数器,记录当前有多少引用指向该对象。
  • 计数增减
    • 引用创建(如复制指针、赋值) → 计数+1。
    • 引用销毁(如指针离开作用域、被重置) → 计数-1。
  • 资源释放:当计数归零时,立即释放对象内存或资源。

如果我们用count来记录一份资源被多少个对象管理,那么常见的是将count设为静态成员函数,新增一个管理对象count就++。如果这样设计,会造成下面的问题:

sp1和sp2共同管理资源1,引用计数为2,当定义一个新的对象sp3管理资源2时,引用计数会错误的为3。 所以这种方式是不行的。要使用堆上动态开辟的方式,构造智能指针对象时,就new一个引用计数。多个shared_ptr指向同一个资源时,++引用计数,析构时--引用计数,引用计数减为0,代表当前析构的shared_ptr是最后一个管理资源的对象,则析构资源。

以管理Date类为例:

class Date { public: Date() = default; Date(int year, int month, int day) :_year(year) , _month(month) , _day(day) {} ~Date() { cout << "~Date()" << endl; } private: int _year; int _month; int _day; }; template<class T> class shared_ptr { public: //构造函数 shared_ptr(T* ptr=nullptr) :_ptr(ptr) ,_pcount(new int(1)) {} //...... //...... private: T* ptr; int* pcount;//引用计数器 };

shared_ptr拷贝构造的实现:

//拷贝构造 shared_ptr(const shared_ptr& sp) :_ptr(sp._ptr) , _pcount(sp._pcount) { (*_pcount)++; //引用计数++ }

shared_ptr赋值运算符的重载:

//赋值 sp1=sp2 shared_ptr<T>& operator=(shared_ptr<T>& sp) { //1,防止自己给自己赋值//2,如果sp1和sp2管理同一份资源,也没必要赋值 if (_ptr != sp._ptr) { //sp1之前管理的资源引用计数--,如果等于0,就释放之前的资源 if (--(*_pcount) == 0) { delete _ptr; delete _Pcount; } _pcount = sp.pcount; _ptr = sp._ptr; ++(*_pcount); } return *this; }

定制删除器的实现:

这里和标准库库里的实现保持一值,在构造时传一个删除器。在类中定义删除器的时候,因为我们无法判断它的类型,所以无法定义,我们可以用一个包装器function来包装这个删除器。

function<(void)T*> _del = [](T* ptr) {delete ptr; }; //不传删除器时,默认为delete //含删除器的构造 shared_ptr(T* ptr,D del) :_ptr(ptr) , _pcount(new int(1)) ,_del(del) {}

shared_ptr模拟实现代码:

代码语言:javascript
复制
template<class T>
class shared_ptr
{
public:
	//构造函数
	shared_ptr(T* ptr=nullptr)
		:_ptr(ptr)
		,_pcount(new int(1))
	{}
	//含删除器的构造
	shared_ptr(T* ptr,D del)
		:_ptr(ptr)
		, _pcount(new int(1))
		,_del(del)
	{}
	

	//拷贝构造
	shared_ptr(const shared_ptr& sp)
		:_ptr(sp._ptr)
		, _pcount(sp._pcount)
		,_del(sp._del)
	{
		(*_pcount)++;
	}

	void release()
	{
		if (--(*_pcount) == 0)
		{
			//delete _ptr;
			_del _ptr;
			delete _pcount;
			_ptr = nullptr;
			_pcount = nullptr;
		}
	}

	//赋值 sp1=sp2
	shared_ptr<T>& operator=(shared_ptr<T>& sp)
	{
		//防止自己给自己赋值
		if (_ptr != sp._ptr)
		{
			release();

			_pcount = sp.pcount;
			_ptr = sp._ptr;
			++(*_pcount);
		}
		return *this;
	}
	
	//析构函数
	~shared_ptr()
	{
		release();
	}

	int use_count()
	{
		return  *_pcount;
	}

	T& operator*()
    {
	    return *_ptr;
    }
    T* operator->()
    {
	    return _ptr;
    }
private:
	T* _ptr;
	int* _pcount;//引用计数器
	function<void(T*)> _del = [](T* ptr) {delete ptr; }; //删除器
};

4,shared_ptr循环引用问题

shared_ptr大多数情况管理资源非常合适,支持RAII,也支持拷贝。但在循环引用场景下会导致内存泄漏。如下情节:

我们想实现一个链表结构,由shared_ptr管理。

struct ListNode { int _data; ListNode* next; ListNode* prev; ~ListNode() { cout << "~ListNode()" << endl; } }; int main() { shared_ptr<ListNode> n1(new ListNode); shared_ptr<ListNode> n2(new ListNode); //n1->next = n2; //err return 0; }

上面的写法无法形成链表,因为n2是shared_ptr类型的,而n1->next是ListNode* 类型的,不能赋值。需要改成shared_ptr<ListNode> prev; shared_ptr<ListNode> next;

struct ListNode { int _data; shared_ptr<ListNode> next; shared_ptr<ListNode> prev; ~ListNode() { cout << "~ListNode()" << endl; } }; int main() { shared_ptr<ListNode> n1(new ListNode); shared_ptr<ListNode> n2(new ListNode); n1->next = n2; n2->prev = n1; return 0; }

程序结束,析构时:

运行结果:

没有调用析构,节点资源没有释放。导致内存泄漏的问题。

解决办法,使用weak_ptr:

weak_ptr支持shared_ptr参数的构造,而且weak_ptr是不支持资源管理的,不支持RAII。所以用weak_ptr指向节点时,是不会增加 引用计数的

struct ListNode { int _data; weak_ptr<ListNode> next; weak_ptr<ListNode> prev; ~ListNode() { cout << "~ListNode()" << endl; } }; int main() { shared_ptr<ListNode> n1(new ListNode); shared_ptr<ListNode> n2(new ListNode); n1->next = n2; n2->prev = n1; return 0; }

5,weak_ptr

weak_ptr不支持资源管理,如果它绑定的shared_ptr已经释放资源了,那么它去访问资源是很危险的。

weak_ptr支持expired检查指向的资源是否过期,use_count也可以获取shared_ptr的引用计数。 weaak_ptr想访问资源时,可以调用lock函数返回一个管理资源的,如果资源已经释放,返回的shared_ptr是一个空对象,如果没有释放,则通过shared_ptr访问资源时安全的。

示例:

int main() { shared_ptr<string> sp1(new string("1111111")); shared_ptr<string> sp2(sp1); weak_ptr<string> wp(sp1); cout << wp.expired() << endl; //false表示资源没有过期 cout << wp.use_count() << endl; return 0; }

6,shared_ptr

std::make_shared 用于创建一个 std::shared_ptr,它比直接使用 std::shared_ptr 的构造函数更高效,因为它会同时分配对象和控制块(用于引用计数等管理信息)的内存,而不是分别分配。

#include <iostream> #include <memory> class MyClass { public: int value; MyClass(int v) : value(v) { std::cout << "Object created with value: " << value << std::endl; } ~MyClass() { std::cout << "Object destroyed" << std::endl; } }; int main() { // 使用 make_shared 创建 shared_ptr auto ptr = std::make_shared<MyClass>(42); // 使用 shared_ptr std::cout << "Value: " << ptr->value << std::endl; return 0; }

shared_ptr还重载了operator bool,可以判断一个shared_ptr对象是否管理着资源。没有管理资源返回false,否则返回true。

#include <iostream> #include <memory> int main() { std::shared_ptr<int> sp1; std::shared_ptr<int> sp2(new int(34)); if (sp1) std::cout << "sp1 points to " << endl; else std::cout << "sp1 is null\n"; if (sp2) std::cout << "sp2 points to " << endl; else std::cout << "sp2 is null\n"; return 0; }

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-02-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 智能指针的概念及使用
    • 1,RAII和智能指针
    • 2,C++标准库中的智能指针
    • 3,智能指针的原理及模拟实现(shared_ptr)
    • 4,shared_ptr循环引用问题
    • 5,weak_ptr
    • 6,shared_ptr
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档