【C++】智能指针

一、简介

为什么会有智能指针这个东西,智能指针又是什么东西。

由于 C++ 语言没有自动内存回收机制,程序员每次 new 出来的内存都要手动 delete。程序员忘记 delete,流程太复杂,最终导致没有 delete,异常导致程序过早退出,没有执行 delete 的情况并不罕见。那么就有了智能指针这个东西。

智能指针就是基于RAII的要求使用模板类,自动化地管理动态资源的释放(不管理资源的创建),智能指针看上去是指针,实际上是赋予了定义的对象。智能指针(smart pointer)是存储指向动态分配(堆)对象指针的类,用于生存期控制,能够确保自动正确的销毁动态分配的对象,防止内存泄露。

RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。

二、智能指针

C++STL提供了4种智能指针,包括auto_ptr、unique_ptr(scoped_ptr)、shared_ptr和weak_ptr。

(1)auto_ptr

独占所有权,所有权转移

auto_ptr版本的智能指针采用的是管理权转移的方法,即由a构造出b对象之后,a对象置为NULL,即之后不能再访问a对象,只有b一个对象维护该空间。

模板auto_ptr是C++98提供的解决方案,C+11已将将其摒弃。,不推荐使用。

旧版本的auto_ptr,用bool值控制
旧库使用拥有者会导致野指针。

template<class T>
class AutoPtr
{
public:
    AutoPtr(T *p = NULL)
        :_Ptr(p)
        , _owner(false)
    {
        if (_Ptr != NULL)
        {
            _owner = true;
        }
    }
    ~AutoPtr()
    {
        if (_owner)
        {
            delete _Ptr;
            _Ptr = NULL;
        }
    }
    AutoPtr(AutoPtr<T>& ap)
        :_Ptr(ap._Ptr)
        , _owner(ap._owner)
    {
        ap._owner = false;
    }
    AutoPtr& operator=(AutoPtr<T>& ap)
    {
        if (_Ptr != ap._Ptr) // 这里不要用this和ap 判断是否为自我赋值,
        {
            if (_owner)
                delete _Ptr;

            _Ptr = ap._Ptr;
            _owner = ap._owner; //_owner = ture;
            ap._owner = false;
        }

        return *this;
    }
    T& operator*()
    {
        return *_Ptr;
    }
    T* operator->()
    {
        if (_owner)
        {
            return _Ptr;
        }
        return NULL;
    }
    T* Get()
    {
        if (_owner)
        {
            return _Ptr;
        }
        return NULL;
    }
private:
    T* _Ptr;
    bool _owner;
};

新版本的auto_ptr

template<typename T>
class AutoPtrNew
{
public:
    AutoPtrNew(T *p = NULL)
        :_Ptr(p)
    {
    }
    ~AutoPtrNew()
    {
        if (NULL != _Ptr)
        {
            delete _Ptr;
            _Ptr = NULL;
        }
    }
    AutoPtrNew(AutoPtrNew<T>& ap)
        :_Ptr(ap._Ptr)
    {
        ap._Ptr = NULL;
    }
    AutoPtrNew& operator=(AutoPtrNew<T>& ap)
        // 思路 :判断是不是自我赋值;然后判断左值是不是空,不空的话释左值;
        //        右值赋值给左值,右值置空
    {
        if (_Ptr != ap._Ptr) // 这里不要用this和ap 判断是否为自我赋值,
        {
            if (NULL != _Ptr)
            {
                delete _Ptr;
            }
            _Ptr = ap._Ptr;
            ap._Ptr = NULL;
        }
        return *this;
    }
    T& operator*()
    {
        return *_Ptr;
    }
    T* operator->()
    {
        return _Ptr;
    }
    T* Get()
    {
        return _Ptr;
    }
private:
    T* _Ptr;
};

(2) scoped_ptr(unique_ptr)
独占所有权,防拷贝

scoped_ptr的实现原理是防止对象间的拷贝和赋值,即将拷贝构造和赋值运算符重载放入保护或私有的访问限定符内,只声明不定义防止他人在类外拷贝。简单粗暴地解决了auto_ptr的缺点,提高了代码的安全性,但是导致功能不完整。

unique_ptr 是 C++11 标识, ScopedPtr 是 boost库提供 还有ScopedAarray。

template<class T>
class ScopedPtr
{
public:
    ScopedPtr(T * p = NULL)
        :_Ptr(p)
    {
    }
    ~ScopedPtr()
    {
        if (NULL != _Ptr)
        {
            delete _Ptr;
            _Ptr = NULL;
        }
    }
    T& operator*()
    {
        return *_Ptr;
    }
    T* operator->()
    {
        return _Ptr;
    }
private:
    ScopedPtr(ScopedPtr<T>& sp);//只声明不实现,并且声明为私有,防止其他人实现并调用  
    ScopedPtr<T>& operator=(ScopedPtr<T>& sp);
private:
    T* _Ptr;
};

(3)shared_ptr

shared_ptr的实现原理是通过引用计数(int *count)来实现,拷贝或赋值时将引用计数加1,析构时只有当引用计数减到0才释放空间,否则只需将引用计数减1即可。

template<class T>
class SharedPtr
{
public:
    SharedPtr(T* p = NULL)
        :_Ptr(p)
        , _pCount(NULL)
    {
        if (NULL != _Ptr)
        {
            _pCount = new int(1);
        }
    }
    ~SharedPtr()
        //要判断计数器指针是否为空
    {
        if (NULL != _pCount && --(*_pCount) == 0)
        {
            delete _Ptr;
            _Ptr = NULL;
            delete _pCount;
            _pCount = NULL;
        }
    }
    SharedPtr(SharedPtr<T>& sp)
        :_Ptr(sp._Ptr)
        , _pCount(sp._pCount)
    {
        if (NULL != _pCount)
            ++(*_pCount);
    }
    SharedPtr<T>& operator=(SharedPtr<T>& sp)
        //思路:判断自我赋值 左值_Ptr分三种情况 空 1个使用  多个使用
    {
        if (_Ptr != sp._Ptr)
        {
            if (NULL == _Ptr) //空
            {
                _Ptr = sp._Ptr;
                _pCount = sp._pCount;
            }
            else
            {
                if (--(*_pCount) == 0) //1
                {
                    delete _Ptr;
                    delete _pCount;
                }
                _Ptr = sp._Ptr;
                _pCount = sp._pCount;
            }
            if (NULL != _pCount)
                (*_pCount)++;
        }
        return *this;
    }
    int UserCount()
    {
        return *_pCount;
    }
    T* operator->()
    {
        return _Ptr;
    }
    T& operator*()
    {
        return *_Ptr;
    }
private:
    T* _Ptr;
    int* _pCount;
};

循环引用的问题。

就是指两个对象进行相互引用,造成相互制约的关系,导致智能指针不能释放的问题。

测试代码

//循环引用
#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>
using namespace boost;
struct ListNode
{
    shared_ptr<ListNode > _prev;
    shared_ptr<ListNode > _next;
    //weak_ptr<ListNode > _prev;
    //weak_ptr<ListNode > _next;
    ~ListNode()
    {
        cout << "~ListNode()" << endl;
    }
};

void Test()
{
    // 循环引用问题
    shared_ptr <ListNode > p1(new ListNode());
    shared_ptr <ListNode > p2(new ListNode());
    cout << "p1->Count:" << p1.use_count() << endl;
    cout << "p2->Count:" << p2.use_count() << endl;
    // p1节点的_next指向 p2节点
    p1->_next = p2;
    // p2节点的_prev指向 p1节点
    p2->_prev = p1;
    cout << "p1->Count:" << p1.use_count() << endl; //2
    cout << "p2->Count:" << p2.use_count() << endl; //2

}

这里写图片描述

通过上面的代码结合图可以知道,p1指针的count =2,(一个是自己本身对象,另一个是p2中的prev指向p1对象),同样的p2指针的count = 2;

这样当函数运行完结束的时候,p2先释放,count = 1,没有被释放,p1再释放,count = 1,没有被释放;而且,p1对象的释放是由p2的prev指向p1的,p1的释放,需要等待p2的释放,才能完成p1的释放,同理p2的释放也需要p1的释放,才能完成自己的p2的释放。

这样的话,它们互相等待,悲剧就发生了,两个都无法释放,

循环引用–使用一个弱引用智能指针(weak_ptr)来打破循环引用(weak_ptr不增加引用计数)

weak_ptr是一个弱引用,只引用,不计数。如果一块内存被shared_ptr和weak_ptr同时引用,当所有shared_ptr析构了之后,不管还有没有weak_ptr引用该内存,内存也会被释放。所以weak_ptr不保证它指向的内存一定是有效的,在使用之前需要检查weak_ptr是否为空指针。

weak_ptr是一种不控制所指向对象生存期的智能指针,它指向一个shared_ptr管理的对象。将一个weak_ptr绑定到shared_ptr不会改变shared_ptr的引用计数。一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放,即使有weak_ptr指向对象,对象还是会被释放。当我们创建一个weak_ptr时,要用一个shared_ptr来初始化它。
解决循环引用。

template<typename T>
struct ListNode{
    T _value;
    weak_ptr<ListNode> _prev;
    weak_ptr<ListNode> _next;

    ListNode(const T & value)
        :_value(value)
        ,_prev(NULL)
        ,_next(NULL){}

    ~ListNode(){
        std::cout<<"~ListNode()"<<std::endl;
    }
};
void TestWeekPtr(){
    std::shared_ptr<ListNode<int>> sp1(new ListNode<int>(10));
    std::shared_ptr<ListNode<int>> sp2(new ListNode<int>(20));
    sp1->_next = sp2;
    sp2->_prev = sp1;

    std::cout<<sp1.use_count()<<std::endl;
    std::cout<<sp2.use_count()<<std::endl;
}

定制删除器

由于每种指针释放资源的方式不同,比如数组用delete[]释放资源,文件用fclose关闭等。我们需要针对不同的资源选择合适的释放方式,定制删除器便是通过仿函数来完成该功能。

#include<memory>
#include<iostream>
using namespace std;


class Fclose1
{
public:
    void operator () (FILE *& fp)
    {

        if (NULL != fp)
        {
            fclose(fp);
            fp = NULL;
        }
    }
};
void Fclose(FILE* fp)
{
    if (NULL != fp)
    {
        fclose(fp);
        fp = NULL;
    }
}
void FunTest()
{
    FILE* fp = fopen("1.txt", "w");

    shared_ptr<FILE> sp1(fp, Fclose1());//仿函数
    shared_ptr<FILE> sp2(fp, Fclose);//函数指针
}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值