【C++】智能指针如何实现引用计数?用static计数会产生什么问题?

1. 为什么需要智能指针?

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

动态内存的使用很容易出问题,比如new了一个变量,忘记delete,就会产生内存泄漏问题;有2个指针指向同一块内存,第一个指针成功delete与之关联的内存,那剩下那么指针再次delelte同一块内存,就会产生错误。

为了更容易地使用动态内存,C++11标准库提供了两种智能指针类型来管理动态对象。shared_ptr允许多个指针指向同一个对象,也称共享指针;unique_ptr则独占所指向的指针,也称独占指针。标准库还定义了一个名为wek_ptr的伴随类,它是一种弱定义,指向shared_ptr所管理的对象,它是为了解决共享指针循环引用的问题,该问题不是此文的重点。

2. shared_ptr指针的使用

智能指针也是模板,当创建智能指针时,必须提供指向的类型。

语法功能
shared_ptr p可以指向T类型的智能指针
p将将p用作一个条件判断,若p指向一个对象,则为true
*p解引用p,获得它指向的对象
p->mem等价于(*p).mem

3.shared_ptr指针的引用计数

当进行拷贝和赋值操作时,每个shared_ptr都会记录有多少个其他shared_ptr指向相同的对象。在每个shared_ptr都有一个关联的计数器,通常称其为引用计数。无论何时我们拷贝一个shared_ptr,计数器都会递增。例如,当用一个shared_ptr初始化另一个shared_ptr,或将它作为参数传递给一个函数以及作为函数的返回值吗,它所关联的计数器就会递增。当我们给shared_ptr赋予一个新值或是shared_ptr被销毁,计数器就会递减。当shared_ptr的计数器变为0,它就会自动释放自己管理的对象。

到底是用一个计数器还是其他数据结构来记录有多少指针共享对象,完全由标准库的具体实现来决定。关键是智能指针类能记录有多少个shared_ptr指向相同的对象,并能在恰当的时候自动释放对象。

4. 为什么不能用static计数?

static int count;

有些同学可能想要在智能指针类中添加一个static成员变量,每新增一个智能指针类,count就加1 。在只有一个对象的时候,该方法是合适的。但是static变量是属于类的,如下图所示,当出现第二个对象,此时新增一个shared_ptr指针,此时内部的count就变成了4,既不满足指向对象1的指针数量,也不满足指向对象2的指针数量。因此我们需要指向对象1的shared_ptr共享一个内部计数器,指向对象2的shared_ptr共享另外一个内部计数器,实现途径见下一节。
在这里插入图片描述

5. 实现简易版内部技术器

5.1 自定义类声明

定义一个myclass类,里面包含一个num成员变量和一个getNum函数。

class myclass {
private:
    int num;

public:
    myclass(int n) :num(n) { cout << "成功构成一个myclass类" << endl; }

    int getNum() const{
        return num;
    }
    ~myclass(){ cout << "成功析构一个myclass类" << endl; }
};

5.2 辅助类

定义一个辅助类,负责实现计数功能,里面的变量和函数均为私有类型,不能被其他类所访问。为了让智能指针类可以访问该类,要声明SmartPtr类为友元类。这个类含有一个计数器count和一个指向myclass的指针。

类可以允许其他类或函数访问它的非公有成员,方法是其他类或者函数成为它的友元,前面加上关键字friend。如果一个类指定了友元类,则友元类的成员函数可以访问此类包含非公有成员在内的所有成员。

class countClass {
private:
    friend class SmartPtr;

    countClass(myclass* ptr) :p(ptr), count(1) { cout << "成功构成一个countClass类,此时count等于1" << endl; }
    ~countClass() {
        cout << "进入countClass析构函数" << endl;
        delete p;
        cout << "成功释放p" << endl;
    }

    int count;
    myclass* p;
};

5.3 智能指针类

智能指针类SmartPtr只在初始化构造时new一个countClass类负责计数,此时count等于1 。当调用拷贝构造函数时,相当于又多了一个智能指针指向该对象,因此计数器加1 。 使用拷贝运算符时,等于将右边指向的对象拷贝给左边,因此左边的计数器得减1,因为它不指向原先的对象了,右边对应的技术器得加1,因为新增了一个智能指针指向该对象。

在析构函数中,要先判断计数器是否为0,只有为0,才去释放内存。

class SmartPtr {
private:
    countClass* countPtr;

public:
    //初始化构造函数
    SmartPtr(myclass* ptr): countPtr(new countClass(ptr)){ cout << "成功构成一个SmartPtr类" << endl; };

    //拷贝构造函数
    SmartPtr(const SmartPtr& sp) :countPtr(sp.countPtr) { ++countPtr->count; cout << "调用拷贝构造函数,计数器加1" << endl;
    }

    //拷贝赋值运算符
    SmartPtr& operator=(const SmartPtr& rhs) {
        ++rhs.countPtr->count;
        cout << "调用拷贝运算符,右边ptr计数器加1,左边ptr计数器减1" << endl;
        if (--countPtr->count == 0) {
            cout << "左边ptr计数器为0,释放countPtr" << endl;
            delete countPtr;
        }
            
        countPtr = rhs.countPtr;
        return *this;
    }

    //析构函数
    ~SmartPtr() {
        if (--countPtr->count == 0) {
            delete countPtr;
            cout << "此时计数为0,成功释放countPtr" << endl;
        }  else
            cout << "计数器减1,还有" << countPtr->count << "个指针指向对象,不能释放" << endl;
    }
};

6. 测试

    myclass* mp = new myclass(10);
    {
        SmartPtr ptr1(mp);
        cout << mp->getNum() << endl;
    }
    cout << mp->getNum() << endl;

在这里插入图片描述
分析一下输出,在构造智能指针类之前必须先构造一个辅助类作为计数器,所以可以看到countClass类是比SmartPtr类更早构造出来的。因为ptr1处于一个局部函数体,所以当函数退出大括号时,该ptr1会去检查count是否等于0,是的话就去析构mp。等出了大括号,再去访问mp的变量,可以发现已经是随机值,代表mp已经被成功稀释了。

    myclass* mp = new myclass(10);
    {
        cout << "进入第一个括号" << endl;
        SmartPtr ptr1(mp);
        {
            cout << "进入第二个括号" << endl;
            SmartPtr ptr2(ptr1);
            cout << "退出第二个括号" << endl;
        }
        cout << "退出第一个括号" << endl;
    }

在这里插入图片描述
可以看到进入第二个括号后,调用了拷贝构造函数,计数器加1,此时就算退出第二个括号,因此还有一个指针指向对象,所以对象并不会释放。

《C++ Primer》
C++ 引用计数技术及智能指针的简单实现

  • 7
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
可以使用智能指针和原子变量来实现全局静态变量的安全访问和线程安全。 具体实现方法如下: 1. 定义一个全局静态变量,使用智能指针来管理其内存。 2. 在智能指针类中使用原子变量来实现引用计数的原子操作。 3. 在全局静态变量的访问函数中,使用原子变量来保证变量的安全访问。 示例代码如下: ```cpp #include <atomic> #include <memory> class GlobalStatic { public: static int getValue() { std::atomic_int& refCount = getRefCount(); refCount.fetch_add(1, std::memory_order_relaxed); static std::shared_ptr<int> data(new int(0), [](int* p) { delete p; getRefCount().fetch_sub(1, std::memory_order_relaxed); }); int value = *data; refCount.fetch_sub(1, std::memory_order_relaxed); return value; } private: static std::atomic_int& getRefCount() { static std::atomic_int refCount(0); return refCount; } }; ``` 在上面的示例代码中,我们定义了一个名为GlobalStatic的类,其中包含了一个名为getValue的静态函数,用于获取全局静态变量的值。 在getValue函数中,我们首先获取了一个原子变量refCount,用于记录当前有多少线程正在访问全局静态变量。 然后,我们使用一个static std::shared_ptr<int> data对象来管理全局静态变量的内存。在data对象的析构函数中,我们将refCount的值减去1,以保证引用计数的正确性。 最后,我们使用原子变量来保证对全局静态变量的安全访问,保证了线程安全。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值