前情提要

我们知道除了静态内存和栈内存外,每个程序还有一个内存池,这部分内存被称为自由空间或者堆。程序用堆来存储动态分配的对象即那些在程序运行时分配的对象,当动态对象不再使用时,我们的代码必须显式的销毁它们。

在C++中,动态内存的管理是用一对运算符完成的:new和delete,new:在动态内存中为对象分配一块空间并返回一个指向该对象的指针,delete指向一个动态独享的指针,销毁对象,并释放与之关联的内存。

动态内存管理经常会出现两种问题:一种是忘记释放内存,会造成内存泄漏;一种是尚有指针引用内存的情况下就释放了它,就会产生引用非法内存的指针。

为了更加容易(更加安全)的使用动态内存,引入了智能指针的概念。智能指针的行为类似常规指针,重要的区别是它负责自动释放所指向的对象

智能指针的原理

RAII:利用对象生命周期来控制程序资源。主要是通过对象的构造函数来获得资源的管理权,然后再通过析构函数来释放所管理的资源。其原理就是把管理一份资源的责任托管给了一个对象

//RAII
template<class T>
class SmartPtr
{public://构造函数获取资源管理权SmartPtr(T* ptr):_ptr(ptr){}//通过析构函数释放资源~SmartPtr(){if (_ptr)delete _ptr;}
private:T* _ptr;
};
class A
{private:int _a = 10;
};void test()
{//错误写法int* ptr = new int[10];SmartPtr<int> sp(ptr);//立即初始化--申请资源时就立即绑定资源SmartPtr<int> sp2(new int);SmartPtr<A> sp3(new A);
}

这并不是一个智能指针对象,智能指针应该要满足以下条件

  1. 实现RAII思想
  2. 使用方式和指针一样,例如需要支持*解引用和->操作

应在类中添加以下操作的重载

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

智能指针和普通的指针的区别之一是智能指针不需要手动释放空间

void test()
{//智能指针--编译器调用析构自动释放资源--不存在内存泄漏SmartPtr<A> sp(new A);(*sp)._a = 10;sp->_a = 100;//普通指针--手动释放内存int* p = new int;A* pa = new A;*p = 1;pa->_a = 10;//return  //提前结束普通指针就会导致内存泄漏delete p;delete pa;
}

C++标准库中的智能指针的使用

库中的智能指针分为 auto_ptrunique_ptrshare_ptr
他们都需要引入头文件#include <memory>才能使用

auto_ptr

auto_ptr是一种存在缺陷的智能指针(禁用

#include <memory>
using namespace std;
void test()
{auto_ptr<int> ap(new int);auto_ptr<int> ap2(new int(2));*ap = 10;*ap2 = 20;
}

auto_ptr指针进行赋值操作时会将资源进行转移,目的是为了防止多个智能指针指向同一块内存资源。但是这种设计显然不符合我们的需求

我们来简单模拟实现auto_ptr,看他底层是如何进行资源权的转移的

//实现auto_ptr
template<class T>
class Auto_ptr
{public:Auto_ptr(T* ptr):_ptr(ptr){}~Auto_ptr(){if (_ptr)delete _ptr;}T* operator->(){return _ptr;}T& operator*(){return *_ptr;}Auto_ptr(Auto_ptr<T>& ap):_ptr(ap._ptr){//资源管理权转移 ap._ptr = nullptr;}Auto_ptr<T>& operator=(Auto_ptr<T>& ap){if (this != &ap){if (_ptr)delete _ptr;//资源管理权转移 _ptr = ap._ptr;ap._ptr = nullptr;}return *this;}
private:T* _ptr;
};

unique_ptr

unique_ptr智能指针是通过防拷贝来解决资源管理权限转移问题----将unique_ptr赋值运算符函数和拷贝构造函数设置为删除函数

void test()
{unique_ptr<int> up(new int(10));unique_ptr<int> up2(up);//errorunique_ptr<int> up3(new int(20));up = up3; //error
}

报错原因:拷贝构造和赋值重载函数都是已经删除的函数

底层实现:

template<class T>
class Unique_ptr
{public:Unique_ptr(T* ptr):_ptr(ptr){}Unique_ptr(const Unique_ptr<T>& up) = delete;Unique_ptr<T>& operator=(const Unique_ptr<T>& up) = delete;~Unique_ptr(){if (_ptr){delete _ptr;_ptr = nullptr;}}
private:T* _ptr;
};

shared_ptr

shared_ptr是C++11中新提供的一种智能指针,不仅解决了资源管理权限转移问题,还提供靠谱的拷贝功能

class A
{public:int _a = 10;~A(){cout << "~A()" << endl;}
};void test()
{shared_ptr<A> sp(new A);shared_ptr<A> sp2(new A);shared_ptr<A> sp3(sp2);//oksp3 = sp;//oksp->_a = 100;sp2->_a = 1000;sp3->_a = 10000;cout << sp->_a << endl;cout << sp2->_a << endl;cout << sp3->_a << endl;
}

运行结果:

我们发现申请多少资源就会释放多少资源,此时的sp和sp3共享一份资源,修改sp3也就相等于修改了sp。所以最终都会打印10000。那共享了一份资源,是如何实现资源只释放一次呢?----引用计数

我们可以通过shared_ptr提供的接口use_count()来查看,当前有多少个智能指针来管理同一份资源

void test()
{shared_ptr<A> sp(new A);cout << sp.use_count() << endl;//1shared_ptr<A> sp2(sp);cout << sp.use_count() << endl;//2cout << sp2.use_count() << endl;//2shared_ptr<A> sp3(new A);cout << sp.use_count() << endl;//2cout << sp2.use_count() << endl;//2cout << sp3.use_count() << endl;//1sp3 = sp;sp3 = sp2;cout << sp.use_count() << endl;//2cout << sp2.use_count() << endl;//2cout << sp3.use_count() << endl;//2
}

运行截图:之所以中间会有调析构函数,是因为当sp3指向sp时,sp3的引用计数为0,则会调用析构函数来释放资源。此时sp创建的资源就有3个指智能指针来管理

图解

在实现时,我们应该确保一个资源只对应一个计数器,而不是每个智能指针都有各自的计数器。所以我们可以将资源和计数器绑定在一起,此时指向同一份资源的智能指针,访问的也都是同一个计数器
成员变量:成员变量应该有两个变量,分别是资源指针的变量_ptr和计数器变量_countPtr,他们都是一个指针类型的变量
拷贝构造函数:在拷贝构造函数中,应该将当前对象的指针指向要拷贝的对象的资源,而且还要拷贝它的计数器,最后还需要将计数器进行++
赋值运算符重载:我们不能判断两个对象是否相等,而应该为只要两个对象的资源不同,那么才需要进行赋值。在赋值中,先让当前对象的计数器进行- -,如果为0则表示当前对象的资源是只被当前对象管理,则需要释放资源。然后再将当前对象指针修改为要拷贝的对象的资源,并且拷贝它的计数器。最后还要对计数器进行++操作
析构函数:判断当前对象的资源的计数器,先进行–操作,在判断计数器是否为0,如果为0才会将资源释放,不为0则什么都不做

template<class T>
class Shared_ptr
{public:Shared_ptr(T* ptr):_ptr(ptr), _countPtr(new size_t(1))//初始化为1{}Shared_ptr(const Shared_ptr<T>& sp):_ptr(sp._ptr), _countPtr(sp._countPtr){//计数器累加++(*_countPtr);}Shared_ptr<T> operator=(const Shared_ptr<T>& sp){if (_ptr != sp._ptr){//本身计数器自减//计数器为0,则当前对象需要释放资源if (--(*_countPtr) == 0){delete _ptr;delete _countPtr;}_ptr = sp._ptr;_countPtr = sp._countPtr;++(*_countPtr);}return *this;}~Shared_ptr(){//计数器自减if (--(*_countPtr) == 0){delete _ptr;delete _countPtr;_ptr = nullptr;_countPtr = nullptr;}}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}
private:T* _ptr;size_t* _countPtr;//计数器指针
};

我们实现的shared_ptr智能指针在多线程的场景下其实是不存在线程安全问题的----引用计数器指针是一个共享变量,多个线程进行修改时会导致计数器混乱。导致资源提前被释放或者会产生内存泄漏问题
我们来看看一下代码, 如果是安全的,那么最后析构函数应该只被调用一次

void fun(const Shared_ptr<A>& sp, int n)
{for (int i = 0; i < n; ++i)Shared_ptr<A> copy(sp);//创建copy智能指针
}void test()
{Shared_ptr<A> sp(new A);int n = 100000;thread t1(fun, ref(sp), n);thread t2(fun, ref(sp), n);t1.join();t2.join();
}

运行结果1:我们发现并没有调用对象的析构函数,说明此时产生了内存泄漏的问题

运行结果2:调用两次析构函数,也就说明一份资源被释放两次。

我们可以在类中提供获取计数器的值的接口

 size_t getCount(){return *_countPtr;}

然后再代码中运行并获取计数器的值,发现计数器的值并没有为0,所以就不会调用调用析构函数


所以我们可以在修改计数器的地方进行加锁保护。而这个锁不能为全局变量的锁,资源之间不能被有影响,否则当一个资源进行加锁修改时,另一个资源会被收到影响,此时会影响代码的执行效率。应当给每一个计数器提供单独的锁
这里++操作和–操作都进行封装

template<class T>
class Shared_ptr
{public:Shared_ptr(T* ptr):_ptr(ptr), _countPtr(new size_t(1))//初始化为1, _mtx(new mutex){}Shared_ptr(const Shared_ptr<T>& sp):_ptr(sp._ptr), _countPtr(sp._countPtr),_mtx(sp._mtx){//计数器累加//++(*_countPtr);addCount();}Shared_ptr<T> operator=(const Shared_ptr<T>& sp){if (_ptr != sp._ptr){//本身计数器自减//计数器为0,则当前对象需要释放资源//if (--(*_countPtr) == 0)if (subCount() == 0){delete _ptr;delete _countPtr;delete _mtx;}_ptr = sp._ptr;_countPtr = sp._countPtr;addCount();}return *this;}~Shared_ptr(){//计数器自减if (subCount() == 0){delete _ptr;delete _countPtr;delete _mtx;_ptr = nullptr;_countPtr = nullptr;_mtx = nullptr;}}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}size_t getCount(){return *_countPtr;}size_t addCount(){_mtx->lock();++(*_countPtr);_mtx->unlock();return *_countPtr;}size_t subCount(){_mtx->lock();--(*_countPtr);_mtx->unlock();return *_countPtr;}
private:T* _ptr;size_t* _countPtr;//计数器指针mutex* _mtx;
};

运行结果:我们发现多线程场景下,也是都可以正常释放的

循环引用问题

shared_ptr其实也存在一些小问题,也就是循环引用问题
我们先来看看以下代码

struct ListNode
{shared_ptr<ListNode> _next;shared_ptr<ListNode> _prev;int _data;~ListNode(){cout << "~ListNode()" << endl;}
};void test()
{shared_ptr<ListNode> n1(new ListNode);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;
}

运行结果:我们发现并没有释放资源,计数器也在自增

图解:

在C++11中,专门为了解决这个问题,又引入了一种新的智能指针waek_ptr,这种指针称为弱指针。在赋值或者拷贝的时候,计数器并不会进行++。析构时也并不会进行真正资源的释放。waek_ptr不能单独使用,其最大的作用就是解决shared_ptr循环引用的问题。

struct ListNode
{weak_ptr<ListNode> _next;weak_ptr<ListNode> _prev;int _data;~ListNode(){cout << "~ListNode()" << endl;}
};void test()
{shared_ptr<ListNode> n1(new ListNode);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;
}

运行结果:

在我们自己实现的shared_ptr中,我们在释放资源时都单单以delete来释放,而在我们申请空间方式中并非就只用new来申请空间们也有可能是用malloc来申请,则此时就应该要用free来释放。所以我们还要给智能指针添加一个删除器

void test()
{Shared_ptr<A> sp(new A[100]);//调用析构会报错
}

删除器主要可以通过仿函数来实现

template<class T>
struct DeleteDel
{void operator()(T* ptr){delete ptr;}
};template<class T>
struct FreeDel
{void operator()(T* ptr){free(ptr);}
};template<class T>
struct DeleteArrDel
{void operator()(T* ptr){delete[] ptr;}
};
template<class T, class Del = DeleteDel<T>>
class Shared_ptr
{public:Shared_ptr(T* ptr):_ptr(ptr), _countPtr(new size_t(1))//初始化为1, _mtx(new mutex){}Shared_ptr(const Shared_ptr<T>& sp):_ptr(sp._ptr), _countPtr(sp._countPtr),_mtx(sp._mtx){//计数器累加//++(*_countPtr);addCount();}Shared_ptr<T> operator=(const Shared_ptr<T>& sp){if (_ptr != sp._ptr){//本身计数器自减//计数器为0,则当前对象需要释放资源//if (--(*_countPtr) == 0)if (subCount() == 0){//delete _ptr;//通过删除器来释放空间_del(_ptr);delete _countPtr;delete _mtx;}_ptr = sp._ptr;_countPtr = sp._countPtr;addCount();}return *this;}~Shared_ptr(){//计数器自减if (subCount() == 0){//delete _ptr;//通过删除器来释放空间_del(_ptr);delete _countPtr;delete _mtx;_ptr = nullptr;_countPtr = nullptr;_mtx = nullptr;}}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}size_t getCount(){return *_countPtr;}size_t addCount(){_mtx->lock();++(*_countPtr);_mtx->unlock();return *_countPtr;}size_t subCount(){_mtx->lock();--(*_countPtr);_mtx->unlock();return *_countPtr;}
private:T* _ptr;size_t* _countPtr;//计数器指针mutex* _mtx;Del _del;
};

C++ 一篇文章让你知道智能指针的魅力相关推荐

  1. 【c++基础】第五章 RALL机制与智能指针

    第五章 RALL机制与智能指针 RAII机制 auto_ptr资源所有权转移智能指针 独占智能指针unique_ptr shared_ptr共享智能指针 weak_ptr破环指针 智能指针的问题与解决 ...

  2. 【C++ Primer 第5版 笔记】第12章 动态内存与智能指针

    转载:http://blog.csdn.net/wwh578867817/article/details/41866315 第 12 章 动态内存 与 智能指针 静态内存 用来保存:(1)局部stat ...

  3. 一篇文章让你了解智能合约以及和区块链的关系

    链客,专为开发者而生,有问必答! 此文章来自区块链技术社区,未经允许拒绝转载. 智能合约是区块链最重要的特性,也是区块链能够被称为颠覆性技术的主要原因,更是各国央行考虑使用区块链技术来发行数字货币的重 ...

  4. 【C/C++】一篇文章教你区分数组指针指针数组

    1数组指针: 指向数组的指针,(可以理解为指向数组首元素地址的地址)本质是指针. #include<iostream> using namespace std; //指针是基本数据变量类型 ...

  5. Effective Modern C++ 第四章,C++智能指针

    智能指针 Smart Pointer 一些说明: C++98中有智能指针std::auto_ptr,在C++11中,出现std::unique_ptr.std::unique_ptr包含了std::a ...

  6. 【Cpp】第十四章-智能指针

    智能指针 基础概念 为什么要有智能指针   首先先看一段程序,看看这段程序可能会出现什么问题. #include <iostream> using namespace std; void ...

  7. 【C 语言进阶】一篇文章将你的 C 语言指针升华

    文章目录 1. 字符指针 定义 用法 2. 指针数组 定义 用法 3. 数组指针 定义 补充 注意 用法 4. 数组传参和指针传参 一维数组 二维数组 一级指针 二级指针 小结 5. 函数指针 定义 ...

  8. bartender一行打印两个二次开发_C++ 智能指针和二叉树:图解层序遍历和逐层打印二叉树...

    作者:apocelipes  链接:https://www.cnblogs.com/apocelipes/p/10758692.html 二叉树是极为常见的数据结构,关于如何遍历其中元素的文章更是数不 ...

  9. 五点讲述C++智能指针的点点滴滴

    (在学习C/C++或者想要学习C/C++可以加我们的学习交流QQ群:712263501群内有相关学习资料) 0.摘要 本文先讲了智能指针存在之前C++面临的窘境,并顺理成章地引出利用RAII技术封装普 ...

最新文章

  1. Python变量作用域问题
  2. #sora#celery worker guide abstract
  3. 如何在windows系统上安装ubuntu双系统
  4. html js 做的小游戏,用js做一个小游戏平台 (一)
  5. 一句话进行浏览器版本识别
  6. 【读书笔记】沉默的大多数
  7. 原生APP与移动Web App的比较
  8. mysql数据类型的验证_MYSQL数据类型详解
  9. Aptana3 SVN Client安装
  10. (77)FPGA时钟激励(always)
  11. Mac下安装Flink的local模式(flink-1.2.0)
  12. 偶然遇到的Java泛型错误,百思不得其解。
  13. 个人生活助手app_“3·15可信赖应用白名单”发布 360旗下多款APP获评甲级认证
  14. clickhouse语句_Siem落地方案:初识clickhouse
  15. deepin linux字体设置,在deepin系统中如何安装系统字体? - Deepin深度系统用户手册...
  16. windows防火墙
  17. linux 多核cpu监控,Linux 下多核CPU知识
  18. graphics.h头文件图形绘画详解(史上最详细)
  19. mysql mmm 扩展_Mysql - 高可用方案之MMM(一)
  20. 浅谈医药工业洁净类厂房智能照明设计与选型

热门文章

  1. 基于matlab的升压斩波实验,升降压斩波电路matlab仿真
  2. 安卓开发mysql数据库_Android编程之数据库的创建方法详解
  3. python以列表的形式输出_简单介绍python输出列表元素的所有排列形式
  4. java的robot一直按住某个键_SHIFT键在CAD中使用技巧
  5. mysql通过存储过程批量造测试数据
  6. html设置缩放级别,自定义页面缩放「Custom Page Zoom」
  7. spring cloud Eureka 之配置信息
  8. 基于JAVA+SpringMVC+Mybatis+MYSQL的大学生毕业设计管理系统
  9. Android Bitmap(位图)详解
  10. Java开发知识之Java的异常处理