C++:智能指针及其实现原理

本文深入探讨了C++中的智能指针概念,包括RAII思想的应用、智能指针的特点及其如何有效管理资源,避免内存泄漏。文中详细介绍了四种智能指针的实现:auto_ptr、scoped_ptr、shared_ptr和weak_ptr,特别是如何通过weak_ptr解决shared_ptr的循环引用问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

更多C++知识点:C++目录索引


1. RAII思想
  1. 定义一个类来封装资源的分配与释放,

  2. 构造函数中完成资源的分配及初始化;

  3. 析构函数中完成资源的清理,可以保证资源的正确初始化和释放

  4. 如果对象是用声明的方式在栈上创建局部对象,那么RAII机制就会正常工作,当离开作用域对象会自动销毁而调用析构函数释放资源。

2. 智能指针的特点
  1. 具有 RAII的思想

  2. 向指针一样,可以使其指向对象成员,并且重载了 * 和 -> 运算符

  3. 是对资源的一种管理,不是拥有

注:对于管理的理解

  1. 情况1:当我们使用普通指针进行管理动态内存时,往往会忘记释放内存,这时就会造成内存泄露,此时使用智能指针暂时保管这块内存,使用完了会自动调用析构,完成对资源的回收
  2. 情况2:如下示例,当抛出异常时,就会打断执行流,不会在执行下面的delete工作,这样的情况也是一种内存泄露,当使用智能指针就能很好的避免这样的情况
 void test()
{
    int*_ptr=new int(1);
    if(_ptr)
    {
        throw 1;
    }
    delete _ptr;
}
3. 模拟实现几种智能指针

3.1 AutorPtr
  本质上是一种管理权的转移,当我把我的空间交给你管理后,我自己就置为空,不在管理这块空间

几个缺点

  1. 不要使用auto_ptr保存一个非动态开辟空间的指针,因为在作用域结束的时候,会执行智能指针的析构函数,释放这块空间,但非动态的空间又无法释放;
  2. 不要使用两个auto_ptr指针指向同一个指针
  3. 不要使用auto_ptr指向一个指针数组,因为auto_ptr的析构函数所用的是delete而不是delete[],不匹配;
  4. 不要将auto_ptr储存在容器中,因为赋值和拷贝构造后原指针无法使用。
    注:什么情况下也别使用auto_ptr智能指针。

代码实现:

template<class T>
class AutorPtr
{
public:
    AutorPtr(T* ap)
      :_Ptr(ap)
    {}

    AutorPtr(AutorPtr<T>& ap)//管理权的转移
    {
        _Ptr = ap._Ptr;
        ap._Ptr = NULL;
    }

    AutorPtr& operator= ( AutorPtr<T>& ap)
    {
        if (this != &ap)
        {
            _Ptr = ap._Ptr;//管理权进行转移,自己的指针置为空
            ap._Ptr = NULL;
        }
        return *this;
    }

    ~AutorPtr()
    {
        printf("%p\n", _Ptr);
        delete _Ptr;
    }

    T& operator*()
    {
        return *_Ptr;
    }

    T* operator->()
    {
        return _Ptr;
    }
protected:

    T* _Ptr;
};

void TestAutorptr()
{
    AutorPtr<int> ap1(new int(10));
    AutorPtr<int> ap2 = ap1;
    //*ap1 = 100;//管理权已经转移,ap1管理的空间已经被ap2管理,所以导致空指针解引用
    *ap2 = 200;

    AutorPtr<int> ap3(new int(11));
    ap2 = ap3;
}

3.2 ScopedPtr

  1. 于autoptr最大的不同就是scoped_ptr没有给出拷贝构造和赋值运算符的重载运算符的定义,只给了private下的声明,即表明scoped_ptr智能指针无法使用一个对象创建另一个对象,
  2. 也无法采用赋值的形式。这无疑提升了智能指针的安全性,但是又存在无法“++”、“- -”这些操作,
  3. 与此同时多了“*”、“->”这两种操作。

代码实现:

template<typename T>
class ScopedPtr
{
public:
    explicit ScopedPtr(T *p = 0) 
        :mp(p)
    {}


    ~ScopedPtr()
    {
        delete mp;
    }

    void reset(T *p = 0)//重置
    {
        if (mp != p)
        {
            delete mp;
            mp = p;
        }
    }

    T &operator*() const
    {
        if (mp != 0)
            return *mp;
        else
            throw std::runtime_error("the pointer is null");
    }

    T * operator->() const
    {
        if (mp != 0)
            return mp;
        else
            throw std::runtime_error("the pointer is null");
    }

    T *get() const
    {
        return mp;
    }
    void swap(ScopedPtr &rhs)
    {
        T *temp = mp;
        mp = rhs.mp;
        rhs.mp = temp;
    }
private:
    ScopedPtr(const ScopedPtr& rhs);
    ScopedPtr &operator=(const ScopedPtr& rhs);//防拷贝,只声明不实现
    T *mp;
};


void TestScopedPtr()
{
    ScopedPtr<int> sp1(new int(3));
    //ScopedPtr<int> sp2(sp1);//防拷贝,不能拷贝
}

3.3 SharedPtr

  1. shared_ptr和以上二者的最大区别就是他维护了一个引用计数,用于检测当前对象所管理的指针是否还被其他智能指针使用(必须是shared_ptr管理的智能指针)
  2. 在析构函数时对其引用计数减一,判断是否为0,若为0,则释放这个指针和这个引用计数的空间。
  3. 其实,这个原理就和string类的浅拷贝是一样的。
  4. 这个类型的智能指针在一定程度上让我们的管理得到了很大的便捷和省心。
  5. 可以和其他共同管理这块空间的智能指针一起修改这块空间储存的值,达到“智能”的效果。

代码:

template<class T>
class SharedPtr
{
public:
    SharedPtr(SharedPtr<T>* sp,int* spcount)
        :_ptr(sp)
        , _pCount(spcount)
    {}

    SharedPtr(const SharedPtr<T>& sp)
    {
        _ptr = sp._ptr;
        _pCount = sp._pCount;
        ++(*pCount);
    }

    SharedPtr& opertaor=(const SharedPtr<T>& sp)
    {
        if (this != &sp)
        {
            if (--(*_pCount) == 0)
            {
                delete _ptr;
                delete _pCount;
            }
            _ptr = sp._ptr;
            _pCount = sp._pCount;
            ++(*pCount);
        }
        return *this;
    }

    ~SharedPtr()
    {
        if (--(*pCount) == 0)
        {
            delete _ptr;
            delete _pCount;
        }
    }
    T&operator*()
    {
        return *_ptr;
    }

    T*operator->()
    {
        return _ptr;
    }
private:
    T* _ptr;
    int* _pCount;
};

3.4 弱指针—weak_ptr

  先来说说弱指针的作用:用于专门解决SharedPtr循环引用的问题
  
两个问题:
 
  1. 什么是循环引用?

场景:

    struct ListNode
{
    SharedPtr<ListNode> _next;
    SharedPtr<ListNode> _prev;

    ListNode()
        :_prev(NULL)
        ,_next(NULL)
    {}

    ~ListNode()
    {
        cout << "delete ListNode" << endl;
    }
};

void TestSharePtrCycle()
{
    SharedPtr<ListNode> cur = new ListNode;
    SharedPtr<ListNode> next = new ListNode;
    cur->_next = next;
    next->_prev = cur;
}

分析场景:

这里写图片描述

  2. 什么是弱指针

弱指针是指当指针指向原来空间时,引用计数不在进行加1
注:不是管理和释放当前的指针,而是避免了一次引用计数

3.如何解决?

使用弱指针来负责cur->next 和next->prev的指向时,使其引用计数不在加1,这样两个节点的引用计数就是1,不会再增加,故此释放时,会将其节点进行释放

代码:

template<class T>
class WeakPtr;

template<class T>
class SharedPtr
{
    friend class WeakPtr<T>;
public:
    SharedPtr(T* ptr)
        :_ptr(ptr)
        , _pCount(new int(1))
    {}

    SharedPtr(const SharedPtr<T>& sp)
    {
        _ptr = sp._ptr;
        _pCount = sp._pCount;
        (*_pCount)++;
    }

    // sp1 = sp2;
    SharedPtr<T>& operator=(const SharedPtr<T>& sp)
    {
        if (this != &sp)
        {
            if (--(*_pCount) == 0)
            {
                delete _ptr;
                delete _pCount;
            }

            _pCount = sp._pCount;
            _ptr = sp._ptr;
            (*_pCount)++;
        }

        return *this;
    }

    ~SharedPtr()
    {
        //cout<<"~SharedPtr()"<<endl;
        if (--(*_pCount) == 0)
        {
            if (_ptr)
            {
                printf("ptr:%p\n", _ptr);
                delete _ptr;
            }
            delete _pCount;
        }
    }

    T& operator*()
    {
        return *_ptr;
    }

    T* operator->()
    {
        return _ptr;
    }

protected:
    T* _ptr;
    int* _pCount;
};

// 专门解决SharedPtr的循环引用
// 弱指针是指当指针指向原来空间时,引用计数不在进行加1,释放时不在等待,故此解决了循环引用的问题
// 不是管理和释放当前的指针,而是避免了一次引用计数
template<class T>
class WeakPtr
{
public:
    WeakPtr(SharedPtr<T>& sp)//可以将sharedptr的对像传给他,匿名对象
        :_ptr(sp._ptr)       //这里将sharedptr的对象给weakptr(弱指针)进行初始化,此时弱指针指向的是sharedptr的对象
    {}

    WeakPtr()
        :_ptr(NULL)
    {}

    T& operator*()
    {
        return *_ptr;
    }

    T* operator->()
    {
        return _ptr;
    }
protected:
    T* _ptr;
};




struct ListNode
{
    WeakPtr<ListNode> _next;
    WeakPtr<ListNode> _prev;

    //ListNode()
    //  :_prev(NULL)
    //  ,_next(NULL)
    //{}

    ~ListNode()
    {
        cout << "delete ListNode" << endl;
    }
};

void TestSharePtrCycle()
{
    SharedPtr<ListNode> cur = new ListNode;//使用弱指针
    SharedPtr<ListNode> next = new ListNode;
    cur->_next = next;//weakptr接收的是sharedptr的对象
    next->_prev = cur;
}

注:关于解决引用计数的具体步骤,可参考链接:
(https://blog.youkuaiyun.com/zhourong0511/article/details/80315961)

在C语言中,虽然没有直接支持智能指针的语言特性,但可以通过结构体、函数指针和宏定义等方式模拟实现类似C++智能指针的功能,尤其是`std::shared_ptr`和`std::unique_ptr`的行为。核心思想是将资源(如内存)的生命周期与结构体实例绑定,并通过引用计数或所有权机制来管理资源的释放。 ### 使用结构体和函数指针实现 shared_ptr 类似机制 可以定义一个包含资源指针和引用计数的结构体,并为其提供初始化、增加引用、释放资源等函数。例如: ```c typedef struct { void *data; int *ref_count; } shared_ptr; void shared_ptr_init(shared_ptr *ptr, void *data) { ptr->data = data; ptr->ref_count = malloc(sizeof(int)); *ptr->ref_count = 1; } void shared_ptr_clone(shared_ptr *dst, const shared_ptr *src) { dst->data = src->data; dst->ref_count = src->ref_count; (*dst->ref_count)++; } void shared_ptr_free(shared_ptr *ptr) { (*ptr->ref_count)--; if (*ptr->ref_count == 0) { free(ptr->data); free(ptr->ref_count); } ptr->data = NULL; ptr->ref_count = NULL; } ``` 上述代码中,`shared_ptr_init`用于初始化智能指针,`shared_ptr_clone`用于复制并增加引用计数,`shared_ptr_free`用于释放资源,只有当引用计数为零时才真正释放内存[^2]。 ### 使用宏简化智能指针使用 为了提高代码的可读性和易用性,可以定义宏来隐藏底层实现细节。例如: ```c #define MAKE_SHARED(type, value) ({ \ type *data = malloc(sizeof(type)); \ *data = value; \ shared_ptr ptr; \ shared_ptr_init(&ptr, data); \ ptr; \ }) ``` 这样可以像C++中那样简化智能指针的创建过程。 ### 实现 unique_ptr 类似机制 对于独占所有权的智能指针,可以采用类似的结构体封装资源,并在释放时直接释放资源而无需引用计数: ```c typedef struct { void *data; } unique_ptr; void unique_ptr_init(unique_ptr *ptr, void *data) { ptr->data = data; } void unique_ptr_free(unique_ptr *ptr) { if (ptr->data) { free(ptr->data); ptr->data = NULL; } } ``` 通过封装`unique_ptr_free`函数,在结构体销毁时自动释放资源,实现类似`std::unique_ptr`的行为[^1]。 ### 使用函数指针模拟析构行为 为了支持自定义析构函数(如C++中`std::shared_ptr`的删除器),可以在结构体中引入函数指针成员,用于指定释放资源的方式: ```c typedef void (*deleter_t)(void *); typedef struct { void *data; int *ref_count; deleter_t deleter; } shared_ptr_with_deleter; void shared_ptr_with_deleter_init(shared_ptr_with_deleter *ptr, void *data, deleter_t deleter) { ptr->data = data; ptr->deleter = deleter; ptr->ref_count = malloc(sizeof(int)); *ptr->ref_count = 1; } void shared_ptr_with_deleter_free(shared_ptr_with_deleter *ptr) { (*ptr->ref_count)--; if (*ptr->ref_count == 0) { ptr->deleter(ptr->data); free(ptr->ref_count); } ptr->data = NULL; ptr->ref_count = NULL; } ``` 这种方式允许为不同类型的资源指定不同的释放逻辑,例如关闭文件描述符、释放网络连接等[^4]。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值