[C++] 智能指针 进阶

标题:[C++] 智能指针 进阶
水墨不写bug


在很久之前我们探讨了智能指针的浅显认识,接下来会更加深入,从源码角度认识智能指针,从而了解智能指针的设计原理,并应用到以后的工作项目中。

本文将会按照C++智能指针的发展历史,回顾智能指针的各个版本的设计。后半部分将会模拟实现最常考的shared_ptr的功能。


一、C++智能指针的发展历程

1. C++98时代:auto_ptr的尝试与缺陷

背景 :C++早期依赖手动new/delete,稍微有管理不当比如忘记释放,或者有异常抛出退出调用的函数栈,但是堆区的指针却找不到了 ),就可能导致内存泄漏。C++98引入auto_ptr,首次尝试自动化资源管理。
机制 :基于RAII(资源获取即初始化),在构造时获取内存,在析构时自动释放内存
问题

  • 隐式所有权转移 :复制auto_ptr时,原指针变为nullptr,导致难以追踪的悬空指针。
  • 不兼容容器:因拷贝语义异常,无法安全用于STL容器。

示例

cpp 复制代码
auto_ptr<int> p1(new int(32));
auto_ptr<int> p2 = p1; // p1变为nullptr,后续使用p1导致未定义行为

结局 :最终C++11弃用,C++17移除。因此为了代码的逻辑性和健壮性,以及可移植性,非常不建议使用auto_ptr


2. Boost库的智能指针

背景 :C++社区通过Boost库探索更健壮的解决方案,后来这些方案被C++委员会收录到了C++11
关键类型

  • scoped_ptr:禁止拷贝,严格独占所有权,轻量且安全。
  • shared_ptr:引用计数实现共享所有权,解决多所有者场景。
  • weak_ptr :打破shared_ptr循环引用,防止内存泄漏。

影响:直接为C++11智能指针奠定基础。


3. C++11:C++划时代的进步

核心类型

  • unique_ptr :取代auto_ptr,独占所有权,支持移动语义,禁止拷贝,可管理数组(unique_ptr<T[]>)。

    cpp 复制代码
    unique_ptr<int> p1(new int(10));
    unique_ptr<int> p2 = std::move(p1); // 显式所有权转移
  • shared_ptr :引用计数共享资源,线程安全但性能有开销。

    cpp 复制代码
    shared_ptr<A> a = make_shared<A>();
    shared_ptr<A> b = a; // 引用计数增至2
  • weak_ptr :可通过其获取对应的shared_ptr资源,不增加计数,需通过lock()获取临时shared_ptr

    cpp 复制代码
    weak_ptr<A> w = a;
    if (shared_ptr<A> tmp = w.lock()) { /* 使用tmp */ }

改进

  • 弃用auto_ptr,推荐unique_ptr
  • 引入make_shared:合并控制块与对象内存分配,提升性能与异常安全。

4. C++14:完善与便利性增强

  • make_unique :填补make_shared的对称性,安全构造unique_ptr

    cpp 复制代码
    auto p = make_unique<int>(20); // 避免显式new
  • shared_ptr增强:支持自定义删除器与分配器,更灵活的资源管理。


后期的C++17用法不多常见,在此就不再赘述了。

二、智能指针的模拟实现

1、unique_ptr

cpp 复制代码
template<class T>
class UniquePtr
{
    UniquePtr<T>& operator=(const UniquePtr<T>&) = delete;//删除拷贝构造和赋值重载
    UniquePtr(const UniquePtr<T>&) = delete;//这是unique_ptr的标志性的特点
public:
    UniquePtr(T* ptr)
        :_ptr(ptr)//浅拷贝
        //这就要求我们需要在外部new出堆区的空间,然后传递给智能指针来管理
    {}
    ~UniquePtr()
    {
        if(_ptr)//析构
            delete _ptr;
    }
    T& operator*()//重载*和->让智能指针(类)像普通指针(自定义类型)一样使用
    {
        return *_ptr;
    }
    T* operator->()
    {
        return ptr;
    }
private:
    T* _ptr;
};

2、shared_ptr

cpp 复制代码
template<class T>
class SharedPtr
{
public:
    SharedPtr(T* ptr)
        :_ptr(ptr)//要求从外部传一个指向堆区的指针
        ,_refcount(new int{1})//开辟在堆上,便于所有的管理同一资源的智能指针对象维护
        ,_pmtx(new std::mutex)//同样的原因
    {}

    SharedPtr(const SharedPtr<T>& obj)
        :_ptr(obj._ptr)//只需要浅拷贝,然后引用计数++
        ,_refcount(obj._refcount)
        ,_pmtx(obj._pmtx)
    {
        AddRef();
    }

    SharedPtr<T>& operator=(const SharedPtr<T>& obj)
    {
        if(obj._ptr != _ptr)//不能给自身赋值
        {
            Release();//释放掉被赋值的智能指针对象的原有资源
            //浅拷贝
            _ptr = obj._ptr;//两个指针指向同一个T*对象
            _refcount = obj._refcount;//两个指针指向同一个int*
            _pmtx = obj._pmtx;//两个指针指向同一个mutex*的锁
            AddRef();
        }
    }
    void AddRef()
    {//增加引用计数需要加锁
        _pmtx->lock();
        (*_refcount)++;
        _pmtx->unlock();
    }

    void Release()
    {
        _pmtx->lock();
        bool f = false;
        if(--(*_refcount) == 0 && _ptr)
        {
            f = true;
            delete _ptr;
            delete _refcount;
        }
        _pmtx->unlock();
        //判断是否资源已经被释放,资源已经被释放,则释放锁
        if(f == true)
        {
            delete _pmtx;
        }
    }
    ~SharedPtr()
    {
        Release();
    }
    int UseCount()
    {
        return *_refcount;
    }
    T& operator*()//重载运算符
    {
        return *_ptr;
    }
    T* operator->()
    {
        return _ptr;
    }
    T* GetPtr()
    {
        return _ptr;
    }
private:
    T* _ptr;//指向堆区资源的裸指针
    int* _refcount;//开辟在堆区的引用计数
    std::mutex* _pmtx;//堆区的锁,考虑线程安全
    //如果设置在栈上,那么每一个对象都有一个独立的引用计数
    //每一次自增自减都需要对同一引用计数的所有对象维护,非常难以维护
};

3、weak_ptr

库中的wear_ptr的实现需要结合起来shared_ptr,shared_ptr内部含有两个引用计数,一个是记录管理资源的shared_ptr(占用引用计数)的个数,一个记录指向资源的weak_ptr(不占用引用计数)的个数。

在这里,为了简便而言,我们实现一个简化版本的weak_ptr,对上面的知识理解即可。

cpp 复制代码
// 简化版本的weak_ptr实现
template<class T>
class WeakPtr
{
public:
    WeakPtr()
        :_ptr(nullptr)
    {}

    WeakPtr(const SharedPtr<T>& sp)
        :_ptr(sp.get())
    {}

    WeakPtr<T>& operator=(const SharedPtr<T>& sp)
    {
        _ptr = sp.get();
        return *this;
    }
    T& operator*()
    {
        return *_ptr;
    }
    T* operator->()
    {
        return _ptr;
    }
private:
    T* _ptr;
};

~完
转载请注明出处

相关推荐
星途码客11 分钟前
C++位运算精要:高效解题的利器
java·c++·算法
秋风&萧瑟15 分钟前
【QT】练习1
开发语言·qt·命令模式
东雁西飞17 分钟前
MATLAB 控制系统设计与仿真 - 33
开发语言·算法·matlab·机器人·自动控制
闪电麦坤9534 分钟前
C#:尝试解析方法TryParse
开发语言·c#
我不是程序猿儿36 分钟前
【C#】构造协议帧通过串口下发
开发语言·c#
周Echo周1 小时前
5、vim编辑和shell编程【超详细】
java·linux·c++·后端·编辑器·vim
千野竹之卫1 小时前
2025最新云渲染网渲100渲染农场使用方法,渲染100邀请码1a12
开发语言·前端·javascript·数码相机·3d·3dsmax
榆榆欸1 小时前
6.实现 Reactor 模式的 EventLoop 和 Server 类
linux·服务器·网络·c++·tcp/ip
滴答滴答嗒嗒滴1 小时前
用 Python 实现机器学习小项目:从入门到实战
开发语言·python·机器学习
byxdaz1 小时前
Qt中绘制不规则控件
开发语言·qt