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;
}
相关推荐
tan180°4 小时前
MySQL表的操作(3)
linux·数据库·c++·vscode·后端·mysql
彭祥.6 小时前
Jetson边缘计算主板:Ubuntu 环境配置 CUDA 与 cudNN 推理环境 + OpenCV 与 C++ 进行目标分类
c++·opencv·分类
lzb_kkk6 小时前
【C++】C++四种类型转换操作符详解
开发语言·c++·windows·1024程序员节
胖大和尚8 小时前
clang 编译器怎么查看在编译过程中做了哪些优化
c++·clang
钱彬 (Qian Bin)9 小时前
一文掌握Qt Quick数字图像处理项目开发(基于Qt 6.9 C++和QML,代码开源)
c++·开源·qml·qt quick·qt6.9·数字图像处理项目·美观界面
双叶8369 小时前
(C++)学生管理系统(正式版)(map数组的应用)(string应用)(引用)(文件储存的应用)(C++教学)(C++项目)
c语言·开发语言·数据结构·c++
源代码•宸10 小时前
C++高频知识点(二)
开发语言·c++·经验分享
jyan_敬言11 小时前
【C++】string类(二)相关接口介绍及其使用
android·开发语言·c++·青少年编程·visual studio
liulilittle11 小时前
SNIProxy 轻量级匿名CDN代理架构与实现
开发语言·网络·c++·网关·架构·cdn·通信
tan77º12 小时前
【Linux网络编程】Socket - UDP
linux·服务器·网络·c++·udp