当前位置: 首页 > news >正文

c++11 | 细说智能指针

💓个人主页:mooridy
💓专栏地址:C++
关注我🌹,和我一起学习更多计算机的知识
🔝🔝🔝

什么是智能指针?

智能指针是 C++ 中一种用于管理动态内存的机制。它提供了一种更安全、更方便的方式来处理对象的生命周期,自动释放不再需要的内存,从而避免内存泄漏和悬空指针等问题。

为什么要有智能指针?

你可能会想,智能指针虽然方便,但也不是必须的呀?我只要自己记得释放就好了。但有的时候,我们会遇到如下面代码这样无解的问题。

我们在int* p1 = new int,int* p2 = new int,div()这三个地方都有可能发生抛异常导致无法执行后续代码,即无法释放掉申请的空间,造成内存泄露。

这时我们就需要引入智能指针。

int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
	throw invalid_argument("除0错误");
	return a / b;
}
void Func()
{
	// 1、如果p1这里new 抛异常会如何?
	// 2、如果p2这里new 抛异常会如何?
	// 3、如果div调用这里又会抛异常会如何?
	int* p1 = new int;
	int* p2 = new int;
	cout << div() << endl;
	delete p1;
	delete p2;
}
int main()
{
	try
	{
		Func();
	}
	catch (exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

RAII思想

RAII是`是⼀种管理资源的类的设计思想,本质是⼀种利⽤对象⽣命周期来管理获取到的动态资源,避免资源泄漏,这⾥的资源可以是内存、⽂件指针、⽹络连接、互斥锁等等。RAII在获取资源时把资源委托给⼀个对象,接着控制对资源的访问,资源在对象的⽣命周期内始终保持有效,最后在对象析构的时候释放资源,这样保障了资源的正常释放,避免资源泄漏问题。

智能指针的设计就是依据RAII思想。

智能指针的基本思想

我们知道当对象出了作用域会自动调用析构,那么我们就把申请资源放在构造函数里,把释放资源放在析构函数里。

// 使用RAII思想设计的SmartPtr类
template<class T>
class SmartPtr {
public:    
SmartPtr(T* ptr = nullptr)    
	: _ptr(ptr)    
{}
~SmartPtr()    
{
	if(_ptr!=nullptr)    
		delete _ptr;    
}
T& operator*() {return *_ptr;}    
T* operator->() {return _ptr;}    
private:    
    T* _ptr;    
};
    

C++标准库中的几种智能指针

• C++标准库中的智能指针都在这个头⽂件下面。

auto_ptr是C++98时设计出来的智能指针,他的特点是拷⻉时把被拷⻉对象的资源的管理权转移给

拷⻉对象,这是⼀个⾮常糟糕的设计,因为他会到被拷⻉对象悬空,访问报错的问题。强烈建议不要使⽤auto_ptr

#include <iostream>
#include <memory>

int main() { 
    // 创建一个 std::auto_ptr 并让它管理一个 int 对象

    std::auto_ptr<int> ptr1(new int(42)); 

    // 输出 ptr1 所指向的值

    std::cout << "ptr1 value before copy: " << *ptr1 << std::endl;


    // 拷贝 ptr1 到 ptr2,此时 ptr1 的资源管理权转移给 ptr2
    std::auto_ptr<int> ptr2(ptr1);

    // 尝试访问 ptr1 所指向的值,这会导致未定义行为
    // 因为 ptr1 已经失去了对资源的管理权,变成了悬空指针
    std::cout << "ptr1 value after copy: " << *ptr1 << std::endl;

    return 0;
}    

unique_ptr是C++11设计出来的智能指针,他的特点的不⽀持拷⻉,只⽀持移动。

shared_ptr是C++11设计出来的智能指针,他的特点是⽀持拷⻉,也⽀持移动。底层是⽤引⽤计数的⽅式实现的。

weak_ptr是C++11设计出来的智能指针,他完全不同于上⾯的智能指

针,他不⽀持RAII,也就意味着不能⽤它直接管理资源,weak_ptr的产⽣本质是要解决shared_ptr

的⼀个循环引⽤导致内存泄漏的问题。(这个问题文章后面会谈到)

shared_ptr的模拟实现

这段代码经常面试手撕,一定要会!
这⾥⼀份资源就需要⼀个引⽤计数,多个资源时引⽤计数⽤静态成员的⽅式是⽆法实现的,所以要使⽤堆上动态开辟的⽅式,构造智能指针对象时来⼀份资源,就要new⼀个引⽤计数出来。多个shared_ptr指向资源时就++引⽤计数,shared_ptr对象析构时就–引⽤计数,引⽤计数减到0时代表当前析构的shared_ptr是最后⼀个管理资源的对象,则析构资源。

#include <iostream>
#include <mutex>

template<class T>
class Smart_Ptr // 实现的 C++11 的 shared_ptr 版本
{
public:
   // 构造函数,默认参数为 nullptr
   // 接收一个指向类型 T 对象的指针 ptr,用于初始化智能指针所管理的资源
   // 同时创建一个新的计数器,初始值为 1,表示当前只有一个智能指针管理该资源
   Smart_Ptr(T* ptr = nullptr)
       : _ptr(ptr)
       , _pcount(new int(1))
   {}

   // 析构函数
   // 当智能指针对象生命周期结束时,调用 Release 函数来处理资源的释放
   ~Smart_Ptr()
   {
       Release();
   }

   // 拷贝构造函数
   // 接收另一个 Smart_Ptr 对象的引用 sp
   // 将当前智能指针的 _ptr 和 _pcount 指向 sp 的对应成员
   // 并调用 Addcount 函数将引用计数加 1
   Smart_Ptr(const Smart_Ptr<T>& sp)
       : _ptr(sp._ptr)
       , _pcount(sp._pcount)
   {
       Addcount();
   }

   // 赋值运算符重载
   // 接收另一个 Smart_Ptr 对象的引用 sp
   // 首先检查是否是自我赋值(即 this 和 sp 是否指向同一个对象)
   // 如果不是,则先调用 Release 函数释放当前智能指针管理的资源
   // 然后将 _ptr 和 _pcount 指向 sp 的对应成员
   // 最后调用 Addcount 函数将引用计数加 1
   Smart_Ptr<T>& operator=(const Smart_Ptr<T>& sp)
   {
       if (this != &sp) // 避免自我赋值
       {
           Release();
           _ptr = sp._ptr;
           _pcount = sp._pcount;
           Addcount();
       }
       return *this;
   }

   // 释放资源的函数
   // 首先对引用计数进行减 1 操作
   // 如果减 1 后引用计数变为 0,表示没有智能指针再管理该资源
   // 此时释放 _ptr 指向的资源、_pcount 指向的计数器
   void Release()
   {
       if (--(*_pcount) == 0) // 销毁最后一个变量时才释放资源
       {
           delete _ptr;
           delete _pcount;
           delete _pmtx;
       }
   }

   // 增加引用计数的函数
   // 对引用计数进行加 1 操作
   void Addcount()
   {
       (*_pcount)++;  
   }

 
   void Subcount()  
   {
       Release();   
   }

private:   
   T* _ptr; // 指向实际管理的资源的指针



   int* _pcount; // 指向引用计数的指针,用于记录有多少个智能指针管理该资源



 
};

shared_ptr的循环引用问题

下面这段代码演示的就是循环引用问题。

struct ListNode 
{
int _data; 
std::shared_ptr<ListNode> _next; 
std::shared_ptr<ListNode> _prev; 
// 这⾥改成weak_ptr,当n1->_next = n2;绑定shared_ptr时

// 不增加n2的引⽤计数,不参与资源释放的管理,就不会形成循环引⽤了

/*std::weak_ptr<ListNode> _next; 
std::weak_ptr<ListNode> _prev;*/
~ListNode()
{
cout << "~ListNode()" << endl;
}
};
int main()
{
// 循环引⽤ -- 内存泄露
std::shared_ptr<ListNode> n1(new ListNode);
std::shared_ptr<ListNode> n2(new ListNode);
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;
// weak_ptr不⽀持管理资源,不⽀持RAII
// weak_ptr是专⻔绑定shared_ptr,不增加他的引⽤计数,作为⼀些场景的辅助管理
//std::weak_ptr<ListNode> wp(new ListNode);
return 0;
}

这里我画了个图帮助理解。在这里插入图片描述

对于这种场景,最好的解决方案就是不要在这里使用智能指针!!!

weak_ptr不⽀持RAII,也不⽀持访问资源,weak_ptr构造时不⽀持绑定到资源,只⽀持绑定到shared_ptr,绑定到shared_ptr时,不增加shared_ptr的引⽤计数,也可以解决上述的循环引⽤问题。

weak_ptr也没有重载operator*operator->等,因为他不参与资源管理,那么如果他绑定的
shared_ptr已经释放了资源,那么他去访问资源就是很危险的。weak_ptr⽀持expired检查指向的资源是否过期,use_count也可获取shared_ptr的引⽤计数,weak_ptr想访问资源时,可以调⽤lock返回⼀个管理资源的shared_ptr,如果资源已经被释放,返回的shared_ptr是⼀个空对象,如果资源没有释放,则通过返回的shared_ptr访问资源是安全的。

相关文章:

  • Linux驱动开发--IIC子系统
  • windows与linux开发板之间设置nfs共享文件
  • 透析React Fiber架构
  • 【Cadence速成】半小时速成Cadence制图与PCB绘制(OrCAD+Allegro)
  • struts2框架漏洞攻略
  • Spring Boot3 配置文件
  • maven如何区分多环境配置
  • 尝试在软考62天前开始成为软件设计师-信息系统安全
  • 模糊数学 | 模型 / 集合 / 关系 / 矩阵
  • value-key 的作用
  • C语言为什么不考虑对齐规则?
  • Go常见问题与回答(上)
  • 100天精通Python(爬虫篇)——第122天:基于selenium接管已启动的浏览器(反反爬策略)
  • 机器学习——神经网络、感知机
  • 【AIGC】WIN10极速部署OpenManus(含WEB界面)
  • Linux实操篇-权限管理
  • 【机器学习】什么是支持向量机?
  • [操作系统] 进程间通信:进程池的实现
  • 解读InnoDB数据库索引页与数据行的紧密关联
  • 如何在 React 项目中进行服务器端渲染(SSR),它有什么优势
  • 外交部:正确认识和对待历史是检验日本能否恪守和平发展承诺的重要标准
  • 时隔4年多,这一次普京和泽连斯基能见面吗?
  • 中美发布日内瓦经贸会谈联合声明达成关税共识,外交部回应
  • 普京提议恢复直接谈判,泽连斯基:望俄明日停火,乌愿谈判
  • 姚洋将全职加盟上海财经大学,担任滴水湖高级金融学院院长
  • 第19届威尼斯建筑双年展开幕,中国案例呈现“容·智慧”