目录

一、 引言

二、 代码实现

2.1 模拟实现shared_ptr

2.2 测试用例

三、 潜在问题分析

你可能还需要了解模拟实现C++标准库中的auto_ptr
一、 引言

与auto_ptr大同小异,shared_ptr也是一个类。可以实现多个指针指向同一个对象(引用计数)。发生拷贝的话都指向相同的内存。

        每使用一次,内部引用计数加1;
        每析构一次,内部引用计数减1,;
        引用计数减为0时,自动释放原生指针所指向的内存。

二、 代码实现
2.1 模拟实现shared_ptr

命名说明:为了和boost库提供的智能指针shared_ptr区分开,我将模拟实现的指针命名为mshared_ptr(m是my的简写)。

难点一、我们知道,boost库中提供的shared_ptr的核心就是引用计数,实现的方法不尽相同,只要能达到目的就可以了。在这里,我采用静态map表的方式来实现。

    static map<T*, int> _map;        //静态数据成员需要在类外进行初始化

如何理解这种操作?map表建立了原生指针T* 和次数一个映射。 如图1所示,如果有四个mshared_ptr(自主实现)类型的变量同时指向一块堆内存,map表中就会建立原生指针_ptr和4之间的一个映射。如果有更多的变量指向该块堆内存或者A、B、C、D其中有任何一个变量析构了,都会引起引用计数的变化。
图1 map表简要说明

难点二、 为什么成员运算符(俗称箭头)的重载返回类型是原生指针的类型?这一点在模拟实现C++标准库中的auto_ptr已经讨论过了。在这里再次讨论也无妨!mshared_ptr名为指针,实际上是类。对一个类采用成员运算符重载,返回值很自然的就是类中的成员了。

    template<typename T>
    T* mshared_ptr<T>::operator->()        //成员运算符重载
    {
        return _ptr;
    }

难点三、 引用计数是如何实现按需变化的?如下代码所示:if语句一定会进入,是否执行还得两说!if语句一经进入,引用计数就自减1了,在决定释放内存之前,万万牢记:不要对NULL指针进行操作,这就是if语句后半部分存在的意义。这小段代码在析构函数和赋值运算符重载中都出现了。值得注意一下。

        if (--_map[_ptr] <= 0 && NULL != _ptr)
        {
            delete _ptr;
            _ptr = NULL;
            _map.erase(_ptr);
        }

完整代码段:

    #include<iostream>
    using namespace std;
    #include<map>
     
    template<typename T>
    class mshared_ptr
    {
    public:
        mshared_ptr(T *ptr = NULL);        //构造方法
        ~mshared_ptr();        //析构方法
        mshared_ptr(mshared_ptr<T> &src);        //拷贝构造
        mshared_ptr& operator = (mshared_ptr<T> &src);        //赋值运算符重载
        T& operator*();        //解引用运算符重载
        T* operator->();    //成员运算符重载
    private:
        T *_ptr;
        static map<T*, int> _map;        //静态数据成员需要在类外进行初始化
    };
     
    template<typename T>
    map<T*, int> mshared_ptr<T>::_map;
     
    template<typename T>
    mshared_ptr<T>::mshared_ptr(T *ptr)        //构造方法
    {
        cout << "mshared_ptr的构造方法正被调用!" << endl;
        _ptr = ptr;
        _map.insert(make_pair(_ptr, 1));
    }
     
    template<typename T>
    mshared_ptr<T>::~mshared_ptr()        //析构方法
    {
        cout << "mshared_ptr的析构方法正被调用!" << endl;
        if (--_map[_ptr] <= 0 && NULL != _ptr)
        {
            delete _ptr;
            _ptr = NULL;
            _map.erase(_ptr);
        }
    }
     
    template<typename T>
    mshared_ptr<T>::mshared_ptr(mshared_ptr<T> &src)    //拷贝构造
    {
        _ptr = src._ptr;
        _map[_ptr]++;
    }
     
    template<typename T>
    mshared_ptr<T>& mshared_ptr<T>::operator=(mshared_ptr<T> &src)        //赋值运算符重载
    {
        if (_ptr == src._ptr)
        {
            return *this;
        }
     
        if (--_map[_ptr] <= 0 && NULL != _ptr)
        {
            delete _ptr;
            _ptr = NULL;
            _map.erase(_ptr);
        }
     
        _ptr = src._ptr;
        _map[_ptr]++;
        return *this;
    }
     
    template<typename T>
    T& mshared_ptr<T>::operator*()        //解引用运算符重载
    {
        return *_ptr;
    }
     
    template<typename T>
    T* mshared_ptr<T>::operator->()        //成员运算符重载
    {
        return _ptr;
    }

2.2 测试用例

    int main()
    {
        int *p = new int(10);
     
        mshared_ptr<int>mshared_p1(p);
        mshared_ptr<int>mshared_p2(new int(20));
        cout << *mshared_p1 << endl;
        cout << *mshared_p2 << endl;
        system("pause");
        return 0;
    }

图2 VS2017运行结果
三、 潜在问题分析

在多线程环境下,引用计数可能会出错是不可避免的。但是通过加锁就能解决这个问题。本篇博客的关注点不在于多线程的环境下运行,故而未曾加锁。有一个问题,即使是boost库中的shared_ptr不可避免,那就是——循环引用(交叉引用)导致内存泄漏。现说明如下:
图3 循环引用示意图

mshared_ptr 利用引用计数来决定是否释放堆区的内存。如果存在循环引用的话,引用计数到最后还是会降不下去。如图3所示,类A只有成员_ptr_B,类B只有成员_ptr_A,如果发生上述情况,在ptr_A析构的时候,仅仅会将引用计数减1而不真正释放其所指向的内存;在ptr_B析构的时候也一样,究其根源,是因为类内的指针也占用了引用计数。

    class B;    //同文件,从上至下编译,故而需要告诉类A——类B确实存在
    class A
    {
    public:
        mshared_ptr<B>_ptr_B;
    };
    class B
    {
    public:
        mshared_ptr<A>_ptr_A;
    };
     
    int main()
    {
        mshared_ptr<A>ptr_A(new A);
        mshared_ptr<B>ptr_B(new B);
        ptr_A->_ptr_B = ptr_B;
        ptr_B->_ptr_A = ptr_A;
        return 0;
    }

图4  VS2017下验证示意图

从运行结果我们可以看到,ptr_A和ptr_B都已被析构,但是类内的指针没有被析构,这就是导致内存泄漏的罪魁祸首。如何解决这个问题,我们需要使用mshared_ptr的好搭档——mweak_ptr。模拟实现boost库中的weak_ptr 。
————————————————
版权声明:本文为CSDN博主「楚楚可薇」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_41822235/article/details/82934681

STL源码剖析-智能指针shared_ptr源码的更多相关文章

  1. 【java集合框架源码剖析系列】java源码剖析之TreeSet

    本博客将从源码的角度带领大家学习TreeSet相关的知识. 一TreeSet类的定义: public class TreeSet<E> extends AbstractSet<E&g ...

  2. 【java集合框架源码剖析系列】java源码剖析之HashSet

    注:博主java集合框架源码剖析系列的源码全部基于JDK1.8.0版本.本博客将从源码角度带领大家学习关于HashSet的知识. 一HashSet的定义: public class HashSet&l ...

  3. c/c++ 智能指针 shared_ptr 和 new结合使用

    智能指针 shared_ptr 和 new结合使用 用make_shared函数初始化shared_ptr是最推荐的,但有的时候还是需要用new关键字来初始化shared_ptr. 一,先来个表格,唠 ...

  4. c/c++ 智能指针 shared_ptr 使用

    智能指针 shared_ptr 使用 上一篇智能指针是啥玩意,介绍了什么是智能指针. 这一篇简单说说如何使用智能指针. 一,智能指针分3类:今天只唠唠shared_ptr shared_ptr uni ...

  5. C++智能指针shared_ptr

    shared_ptr 这里有一个你在标准库中找不到的—引用数智能指针.大部分人都应当有过使用智能指针的经历,并且已经有很多关于引用数的文章.最重要的一个细节是引用数是如何被执行的—插入,意思是说你将引 ...

  6. 【java集合框架源码剖析系列】java源码剖析之LinkedList

    注:博主java集合框架源码剖析系列的源码全部基于JDK1.8.0版本. 在实际项目中LinkedList也是使用频率非常高的一种集合,本博客将从源码角度带领大家学习关于LinkedList的知识. ...

  7. 【java集合框架源码剖析系列】java源码剖析之TreeMap

    注:博主java集合框架源码剖析系列的源码全部基于JDK1.8.0版本.本博客将从源码角度带领大家学习关于TreeMap的知识. 一TreeMap的定义: public class TreeMap&l ...

  8. 【java集合框架源码剖析系列】java源码剖析之ArrayList

    注:博主java集合框架源码剖析系列的源码全部基于JDK1.8.0版本. 本博客将从源码角度带领大家学习关于ArrayList的知识. 一ArrayList类的定义: public class Arr ...

  9. 【java集合框架源码剖析系列】java源码剖析之HashMap

    前言:之所以打算写java集合框架源码剖析系列博客是因为自己反思了一下阿里内推一面的失败(估计没过,因为写此博客已距阿里巴巴一面一个星期),当时面试完之后感觉自己回答的挺好的,而且据面试官最后说的这几 ...

随机推荐

  1. flutter第一课

    网上搜到有一个8小时体验flutter的教程,感觉可以尝试一个hello world出来:https://www.jianshu.com/p/9aaabc60d8af 官网下载很慢,可以使用镜像下载, ...

  2. 洛谷 P4002 - [清华集训2017]生成树计数(多项式)

    题面传送门 神题. 考虑将所有连通块缩成一个点,那么所有连好边的生成树在缩点之后一定是一个 \(n\) 个点的生成树.我们记 \(d_i\) 为第 \(i\) 个连通块缩完点之后的度数 \(-1\), ...

  3. AT4168 [ARC100C] Or Plus Max

    从\(whk\)回来了. 考虑我们需要维护一个子集的信息. 对于二进制的子集信息维护有一个很经典的操作: 高维前缀和. AT4168 [ARC100C] Or Plus Max // Problem: ...

  4. LOJ #2769 -「ROI 2017 Day 1」前往大都会(单调栈维护斜率优化)

    LOJ 题面传送门 orz 斜率优化-- 模拟赛时被这题送走了,所以来写篇题解( 首先这个最短路的求法是 trivial 的,直接一遍 dijkstra 即可( 重点在于怎样求第二问.注意到这个第二问 ...

  5. Mysql优化,ICP、BNL算法、BKA算法、MMR算法

    ICP(Index Condition Pushdown,索引条件下推)是MySQL5.6版本中的新特性,是一种在存储引擎层使用索引过滤数据的一种优化方式. 出现原因:ICP出现Mysql5.6以前, ...

  6. miRNA 基本知识

    miRNA MicroRNA (miRNA)  是一类内生的.长度约为20-24个核苷酸的小 RNA,其在细胞内具有多种重要的调节作用.每个 miRNA 可以有多个靶基因的表达,而几个 miRNA 也 ...

  7. ubuntu20.04安装EasyConnect兼容性问题解决

    目录 1. 命令行启动EasyConnect 2. 降级pango 3. 重新启动EasyConnect,即可成功启动 Ubuntu20.04安装EasyConnect后无法启动的解决方案 工作使用操 ...

  8. C#表格,表格信息、GridView使用。

    page: <%@ Control Language="C#" AutoEventWireup="true" CodeFile="test1.a ...

  9. C++中Try Catch中的继承

    1.C++中Try Catch简介:我们编译运行程序出错的时候,编译器就会抛出异常.抛出异常要比终止程序灵活许多. 而C++异常是指在程序运行时发生的反常行为,这些行为超出了函数正常功能的范围.当程序 ...

  10. linux "/tmp/crontab.14QJ49":1: bad minute errors in crontab file, can't install" 错误

    目录 报错及原因 crontab语句格式 报错及原因 这个错误的原因是crontab格式错误 "/tmp/crontab.sdXvj4":5: bad minute errors ...