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

C++11 -- 智能指针

目录

前言

一、什么是智能指针?

二、C++11中的智能指针

2.1、unique_ptr

2.2、shared_ptr

2.2.1、循环引用问题

2.3、weak_ptr

2.4、其他问题

2.5、总结


前言

什么是内存泄漏?
        内存泄漏是指申请了空间却没能正确的释放,导致程序在运行过程中失去了对该内存的控制,造成的资源浪费。比如:new了,没delete;malloc了,没free。

而智能指针解决了手动内存管理的痛点。

一、什么是智能指针?

        智能指针的概念和用法很简单,就是把申请资源和释放资源的工作交给类去完成,从而达到在作用域自动调用构造申请资源,出作用域自动调用析构释放资源,也叫RAII机制。目的是为了防止内存泄漏。不需要手动的调delete

        RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内 存、文件句柄、网络连接、互斥量等等)的简单技术。

如下:

template<class T>
class SmartPtr
{
public:SmartPtr(T* ptr = nullptr):_ptr(ptr){}~SmartPtr(){if (_ptr){cout << "释放资源" << endl;delete _ptr;}}private:T* _ptr;
};int main()
{SmartPtr<int> ptr(new int); // 出作用域自动调用析构,释放资源return 0;
}

那么,类中只要再重载一些指针操作(*, ->),这个类实例的对象也就能像指针一样操作了。

template<class T>
class SmartPtr
{
public:// RAII// 资源交给对象管理,对象生命周期内,资源有效,对象生命周期到了,释放资源SmartPtr(T* ptr = nullptr):_ptr(ptr){}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}~SmartPtr(){if (_ptr){cout << "delete " << _ptr << endl;delete _ptr;}}private:T* _ptr;
};int main()
{SmartPtr<int> p1(new int(4));SmartPtr<int> p2(new int(3));cout << *p1 << endl; // 输出 4cout << *p2 << endl; // 输出 3return 0;
}

输出结果:

但是对于上面的类,还有问题:

  • 拷贝问题:对于编译器默认生成的赋值重载函数,完成的是浅拷贝,会有重复释放和内存泄漏问题

来看看C++11库中的智能指针是如何解决的


二、C++11中的智能指针

2.1、unique_ptr

C++11中的unique_ptr解决拷贝的方案是,直接禁掉了拷贝构造和赋值运算符重载,简单除暴

没有赋值和拷贝的场景还是可以用这个的

2.2、shared_ptr

shared_ptr的解决方案是,用引用计数来记录当前资源被多少对象访问。实际就是增加了个类似 int* count 的成员变量,每增加一个对象对资源的管理,则count++,一个对象失去对该资源的管理则count--。

  • 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;
  • 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。

比如:

int main()
{lsg::shared_ptr<int> p1(new int(4));lsg::shared_ptr<int> p2(p1);lsg::shared_ptr<int> p3(new int(6));return 0; 
}

注意引用计数的类型不能是static int和int,因为不同的shared_ptr需要管理不同对象的引用计数,每个对象独立计数 

底层类似下面,重点看拷贝构造和赋值运算符重载:

namespace lsg
{template<class T>class shared_ptr{public:// RAII// 资源交给对象管理,对象生命周期内,资源有效,对象生命周期到了,释放资源shared_ptr(T* ptr = nullptr):_ptr(ptr), _count(new int(1)){}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}shared_ptr(const shared_ptr<T>& sp):_ptr(sp._ptr), _count(sp._count){++(*_count);}// 注意自己给自己赋值的情况shared_ptr<T>& operator=(const shared_ptr<T>& sp){if (_ptr == sp._ptr)return *this;if (--(*count) == 0){delete _ptr;delete _count;}_ptr = sp._ptr;_count = sp._count;++(*_count);return *this;}~shared_ptr(){if (--(*_count) == 0){cout << "delete " << _ptr << endl;delete _ptr;delete _count;}}private:T* _ptr;int* _count;};
}

但上面代码并不是完全正确的,当多个线程同时对一个资源的对象赋值或拷贝构造,会有线程安全问题,因为(*count)++,(*_count)-- 并不是原子的。

2.2.1、循环引用问题

shared_ptr在绝大部分场景下是没问题的,但是下面这种情况会有问题

struct Node
{int val = 0;shared_ptr<Node> _next = nullptr; // 理解成Node* _nextshared_ptr<Node> _prev = nullptr; // 理解成Node* _prev
};int main()
{shared_ptr<Node> sp1(new Node);shared_ptr<Node> sp2(new Node);// 循环引用问题sp1->_next = sp2;sp2->_prev = sp1;return 0; 
}

        刚开始,sp1和sp2初始化时,引用计数都为1。但是,当sp1->_next 指向 sp2时,sp2的引用计数会加1,因为对同个资源多了个访问对象。同理,sp1的引用计数也会加1。那么sp1和sp2出作用域时,自动调用析构,析构让引用计数减减为一,此时不会释放sp1和sp2。导致内存泄漏。这就是循环引用问题。

注意:循环引用问题,编译器时不会报错的

如何解决呢???   ---> weak_ptr

2.3、weak_ptr

weak_ptr是专门用来解决shared_ptr循环引用问题的,本身并没有RAII机制。不是用来单独使用的

解决的原理是,提供了个以shared_ptr为参数的拷贝构造和赋值,不增加引用计数。可以访问资源,但不参与资源的管理

struct Node
{int val = 0;weak_ptr<Node> _next; // 理解成Node* _nextweak_ptr<Node> _prev; // 理解成Node* _prev
};int main()
{shared_ptr<Node> sp1(new Node);shared_ptr<Node> sp2(new Node);sp1->_next = sp2;sp2->_prev = sp1;// 打印引用计数cout << sp1.use_count() << endl; // 输出1cout << sp2.use_count() << endl; // 输出1return 0; 
}

底层类似:

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

2.4、其他问题

标准智能指针默认使用 delete/delete[] 释放内存,但许多资源并非通过new分配

  • C库分配的内存(malloc, calloc)
  • 操作系统资源(文件句柄,套接字)
  • 第三方库分配的资源

class A
{
public:A(int a = 0):_a(a){cout << "A(int a = 0)" << endl;}~A(){cout << this;cout << " ~A()" << endl;}int _a;
};template<class T>
struct Del
{void operator()(const T* ptr){delete[] ptr;}
};int main()
{// 会报错,因为delete释放lsg::shared_ptr<A> sp1(new A[5], Del<A>());lsg::shared_ptr<A> sp2((A*)malloc(sizeof(A)));lsg::shared_ptr<FILE> sp3(fopen("test.cpp", "r"));return 0; 
}

所以,官方提供了第二个模板参数,叫定制删除器,申请的自定义类型的资源时,由我们提供的方法来释放资源

智能指针的定制删除器,允许我们自定义当智能指针管理对象生命周期结束时的清理行为。就是指定自定义类型的释放方式。

实现大概如下:

namespace lsg
{template<class T>class shared_ptr{public:// RAII// 资源交给对象管理,对象生命周期内,资源有效,对象生命周期到了,释放资源shared_ptr(T* ptr = nullptr):_ptr(ptr), _count(new int(1)){}template<class D>shared_ptr(T* ptr, D del):_ptr(ptr), _count(new int(1)), _del(del){}shared_ptr(const shared_ptr<T>& sp):_ptr(sp._ptr), _count(sp._count),_del(sp._del){++(*_count);}shared_ptr<T>& operator=(const shared_ptr<T>& sp){if (_ptr == sp._ptr)return *this;if (--(*_count) == 0){delete _ptr;delete _count;}_ptr = sp._ptr;_count = sp._count;_del = sp._del;++(*_count);return *this;}~shared_ptr(){if (--(*_count) == 0){//delete _ptr;_del(_ptr); // 定制删除器delete _count;}}T& operator*() { return *_ptr;}T* operator->() { return _ptr;}int use_count() const { return *_count;}T* get() const { return _ptr;}private:T* _ptr;int* _count;function<void(T*)> _del = [](T* ptr) {delete ptr;};};
}

2.5、总结

unique_ptr:禁止拷贝,日常使用,不需要拷贝的场景

shared_ptr:用引用计数来支持拷贝,需要拷贝的场景使用,但要小心循环引用导致的内存泄漏

weak_ptr:专门用来解决shared_ptr循环引用的问题,但不支持RAII,可以访问资源,但不能单独                     管理资源

http://www.dtcms.com/a/312178.html

相关文章:

  • 【故障处理】redis会话连接满导致业务系统某个模块数据不显示
  • JJWT 核心工具类 Jwts 源码解析
  • 3 数字字符串格式化
  • 安灯系统(Andon System)
  • h3c路由器查看温度是否正常
  • 记录一次Spring Cloud Gateway配置的跨域处理:解决 ‘Access-Control-Allow-Origin‘ 头包含多个值的问题
  • 【Shell自动化脚本——for循环创建账户,测试主机连通性,for循环密码的修改】
  • 【Java面试题】一分钟了解反射机制
  • 切换python多版本
  • 中州养老项目:Mybatis自动填充拦截器
  • 机器学习项目从零到一:加州房价预测模型(PART 2)
  • 李宏毅深度学习教程 第6-7章 自注意力机制 + Transformer
  • NVIDIA GPU架构
  • 浅拷贝与深拷贝的区别
  • 断路器瞬时跳闸曲线数据获取方式
  • 关于Sort的补充
  • SpringBoot 02 AOP
  • 王者荣耀模拟器:一款基于Python的文本角色扮演游戏
  • 译| Netflix内容推荐模型的一些改进方向
  • 测试时扩散的深度研究助手
  • Redis实战(6)-- 慢查询运用与理解
  • 图像加密学习日志————论文学习DAY4
  • 档案馆可视化:历史宝库的数字新貌
  • 使用 MySQL Shell 进行 MySQL 单机到 InnoDB Cluster 的数据迁移实践
  • 【Django】-5- ORM的其他用法
  • RAWINPUT避坑指南(涉及GetRawInputData/GetRawInputBuffer)
  • 详解Python标准库之命令行界面库
  • .env 文件
  • WinMerge:文件对比工具,支持各种格式文件对比,永久免费使用!
  • 软件交付终极闸口:验收测试全解析