• STL源码剖析-智能指针shared_ptr源码


    目录

    一、 引言

    二、 代码实现

    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

  • 相关阅读:
    C/C++多文件之间的变量定义
    PKU POJ 2186 Popular Cows 强连通分量
    重载函数
    ZOJ 2763 Prison Break
    201357 训练赛总结
    hdu 4467 Graph 构造
    201356 训练赛总结
    201353 NEERC 2012, Eastern subregional contest
    2013512 CF 183 总结
    一道动态规划
  • 原文地址:https://www.cnblogs.com/cnhk19/p/15028812.html
Copyright © 2020-2023  润新知