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;
}
相关推荐
码农新猿类1 小时前
服务器本地搭建
linux·网络·c++
GOTXX2 小时前
【Qt】Qt Creator开发基础:项目创建、界面解析与核心概念入门
开发语言·数据库·c++·qt·图形渲染·图形化界面·qt新手入门
徐行1102 小时前
C++核心机制-this 指针传递与内存布局分析
开发语言·c++
序属秋秋秋2 小时前
算法基础_数据结构【单链表 + 双链表 + 栈 + 队列 + 单调栈 + 单调队列】
c语言·数据结构·c++·算法
mldl_3 小时前
(个人题解)第十六届蓝桥杯大赛软件赛省赛C/C++ 研究生组
c语言·c++·蓝桥杯
一个小白14 小时前
C++ 用红黑树封装map/set
java·数据库·c++
Lenyiin4 小时前
《 C++ 点滴漫谈: 三十三 》当函数成为参数:解密 C++ 回调函数的全部姿势
c++·回调函数·lenyiin
埜玊4 小时前
C++之 多继承
c++
1024熙6 小时前
【C++】——lambda表达式
开发语言·数据结构·c++·算法·lambda表达式
mahuifa7 小时前
(2)VTK C++开发示例 --- 绘制多面锥体
c++·vtk·cmake·3d开发