zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    [leetcode]Remove Nth Node From End of List @ Python
    [leetcode]Swap Nodes in Pairs @ Python
    [leetcode]Linked List Cycle II @ Python
    [leetcode]Linked List Cycle @ Python
    [leetcode]LRU Cache @ Python
    [leetcode]Reorder List @ Python
    [leetcode]Insertion Sort List @ Python
    [leetcode]Sort List @ Python
    [leetcode]3Sum Closest @ Python
    [elk]elasticsearch实现冷热数据分离
  • 原文地址:https://www.cnblogs.com/cnhk19/p/15028812.html
Copyright © 2011-2022 走看看