C++智能指针

原理

shared_ptr在底层实现上,维护一个引用计数,来管理内存对象的生命周期:当新构造一个对象时,引用计数初始化为1,拷贝对象时,引用计数加1,对象作用域结束析构时,引用计数减1,当最后一个对象被销毁时,引用计数会减为0,所持有的资源被释放!

线程安全性

shared_ptr保证多个线程能够安全地增加或减少其引用计数。但如果多个线程同时读写同一个shared_ptr或操作其管理的对象,那么需要额外进行同步机制,比如使用互斥锁mutex来保护这些操作。

手写一个shared_ptr

shared_ptr内部包含两部分指针,一个指向实际管理的资源,另一个指向控制块。控制块中包含引用计数,当shared_ptr被拷贝的时候,引用计数增加,销毁的时候引用计数减少,当引用计数变为0的时候,资源被释放!

cpp 复制代码
// 管理引用计数的类
class SharedCount
{
public:
    SharedCount() : count_{1} {};

    void add() { ++count_; }

    void minus() { --count_; }

    int get() const { return count_; }
private:
    std::atomic<int> count_;
};

// 智能指针类
template<typename T>
class SharedPtr
{
public:
    SharedPtr(T* ptr) : ptr_{ptr}, ref_count_{new SharedCount} {}

    SharedPtr() : ptr_{nullptr}, ref_count_{new SharedCount} {}

    // 拷贝构造函数
    SharedPtr(const SharedPtr& p)
    {
        this->ptr_ = p.ptr_;
        this->ref_count_ = p.ref_count_;
        ref_count_->add();
    }

    // 拷贝赋值函数
    SharedPtr& operator=(const SharedPtr& p)
    {
        if (&p == this) return *this;
        clean();
        this->ptr_ = p.ptr_;
        this->ref_count_ = p.ref_count_;
        ref_count_->add();
        return *this;
    }

    // 移动语义中,引用计数不变,同时清空原参数中的指针!
    // 移动构造函数
    SharedPtr(SharedPtr&& p)
    {
        this->ptr_ = p.ptr_;
        this->ref_count_ = p.ref_count_;
        p.ptr_ = nullptr;
        p.ref_count_ = nullptr;
    }

    // 移动赋值函数
    SharedPtr& operator=(SharedPtr && p)
    {
        if (&p == this) return *this;
        clean();
        this->ptr_ = p.ptr_;
        this->ref_count_ = p.ref_count_;
        p.ptr_ = nullptr;
        p.ref_count_ = nullptr;
        return *this;
    }

    ~SharedPtr { clean(); }
private:
    T* ptr_;
    SharedCount* ref_count_;

    // 引用计数减1,若最终引用计数减至0,则释放指针
    void clean()
    {
        if (ref_count_)
        {
            ref_count_->minus();
            if (ref_count_->get() == 0)
            {
                if (ptr_)
                    delete ptr_;
                delete ref_count_;
            }
        }
    }
};

shared_ptr的交叉引用问题

cpp 复制代码
#include <iostream>
#include <memory>

using namespace std;

class B; // 前置声明
class A
{
public:
    A() { cout << "A()" << endl; }
    ~A() { cout << "~A()" << endl; }
    shared_ptr<B> _ptrb;
};

class B
{
public:
    B() { cout << "B()" << endl; }
    ~B() { cout << "~B()" << endl; }
    shared_ptr<A> _ptra;
};

int main()
{
    shared_ptr<A> pa(new A());
    shared_ptr<B> pb(new B());

    pa->_ptrb = pb;
    pb->_ptra = pa;

    cout << pa.use_count() << endl;
    cout << pb.use_count() << endl;

    return 0;
}
  • 以上代码输出A() B() 2 2,显然强智能指针管理的资源未能释放,这是因为程序结束时,强智能指针管理的资源计数-1不为0,因此无法释放。

weak_ptr解决交叉引用问题

shared_ptr交叉引用会导致new的资源无法释放,从而造成资源泄露问题!→ 注意到weak_ptr并不改变资源的引用计数,相当于"观察者",故可以用weak_ptr解决交叉引用问题!

cpp 复制代码
#include <iostream>
#include <memory>

using namespace std;

class B;
class A
{
public:
    A() { cout << "A()" << endl; }
    ~A() { cout << "~A()" << endl; }
    weak_ptr<B> _ptrb; // 弱智能指针不会改变资源的引用计数
};

class B
{
public:
    B() { cout << "B()" << endl; }
    ~B() { cout << "~B()" << endl; }
    weak_ptr<A> _ptra; // 弱智能指针不会改变资源的引用计数
};

int main()
{
    shared_ptr<A> pa(new A());
    shared_ptr<B> pb(new B());
    
    pa->_ptrb = pb;
    pb->_ptra = pa;

    cout << pa.use_count() << endl;
    cout << pb.use_count() << endl;

    return 0;
}

weak_ptr调用其它类中的方法

实际场景中,利用弱智能指针的特性解决了资源泄露问题,但是类B想通过智能指针调用类A中的一个方法,并不能直接调用,因为弱智能指针weak_ptr只能观察资源,而无法使用资源。

cpp 复制代码
#include <iostream>
#include <memory>

using namespace std;

class B;
class A
{
public:
    A() { cout << "A()" << endl; }
    ~A() { cout << "~A()" << endl; }
    void testA() { cout << "testA()" << endl; }
    weak_ptr<B> _ptrb;
};

class B
{
public:
    B() { cout << "B()" << endl; }
    ~B() { cout << "~B()" << endl; }
    void func()
    {
        // _ptra->testA(); // 弱智能指针无法直接使用资源
        shared_ptr<A> ps = _ptra.lock(); // 提升方法
        if (ps != nullptr) // 提升成功!
        {
            ps->testA();
        }
    }
    weak_ptr<A> _ptra;
};

int main()
{
    shared_ptr<A> pa(new A());
    shared_ptr<B> pb(new B());

    pa->_ptrb = pb;
    pb->_ptra = pa;

    pb->func();

    cout << pa.use_count() << endl;
    cout << pb.use_count() << endl;

    return 0;
}
相关推荐
顾晨阳——15 分钟前
C/C++字符串
c语言·c++·字符串
深耕AI15 分钟前
【MFC实用技巧】对话框“边框”属性四大选项:None、Thin、Resizing、对话框外框,到底怎么选?
c++·mfc
ajassi200018 分钟前
开源 C++ QT QML 开发(二十一)多媒体--视频播放
c++·qt·开源
仰泳的熊猫41 分钟前
LeetCode:95. 不同的二叉搜索树 II
数据结构·c++·算法·leetcode
liu****2 小时前
笔试强训(六)
数据结构·c++·算法
「QT(C++)开发工程师」4 小时前
C++语言编程规范-并发
java·linux·c++
1白天的黑夜14 小时前
递归-21.合并两个有序链表-力扣(LeetCode)
c++·leetcode·链表·递归
adny-code4 小时前
[fastgrind] 一个轻量级C++内存监控及可视化开源库
c++·内存·性能分析·高性能计算
郝学胜-神的一滴5 小时前
Linux系统函数link、unlink与dentry的关系及使用注意事项
linux·运维·服务器·开发语言·前端·c++
赵杰伦cpp5 小时前
list的迭代器
开发语言·数据结构·c++·算法·链表·list