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;
}
相关推荐
史迪仔011243 分钟前
[QML] QML IMage图像处理
开发语言·前端·javascript·c++·qt
会编程的土豆2 小时前
【数据结构与算法】再次全面了解LCS底层
开发语言·数据结构·c++·算法
低频电磁之道2 小时前
解决 Windows C++ DLL 导出类不可见的编译错误
c++·windows
君义_noip4 小时前
信息学奥赛一本通 4150:【GESP2509七级】⾦币收集 | 洛谷 P14078 [GESP202509 七级] 金币收集
c++·算法·gesp·信息学奥赛·csp-s
Ricky_Theseus4 小时前
静态链接与动态链接
c++
澈2074 小时前
双指针,数组去重
c++·算法
小辉同志4 小时前
207. 课程表
c++·算法·力扣·图论
feng_you_ying_li5 小时前
C++11,{}的初始化情况与左右值及其引用
开发语言·数据结构·c++
小樱花的樱花5 小时前
打造高效记事本:UI设计到功能实现
开发语言·c++·qt·ui
零二年的冬6 小时前
epoll详解
java·linux·开发语言·c++·链表