C++————智能指针


将到智能指针首先得讲下智能指针出现的原因,在C++中我们会动态开辟一些空间,而这些空间的释放就得程序员自己动手了,像java这些语言有自动回收机制,而C++就要求程序员自己释放空间,否则会导致内存泄漏(这个后果很严重!!!!)

下面举一些例子来讲下内存失败的场景:
1.在return之后释放空间
void test()
{
int *p = (int*)malloc(100);
//...
//...
return;
free(p);
像这种在程序返回return之后释放空间时会导致内存泄漏。
2.执行流跳转导致释放空间失败
void func1()
{
   //....
   throw;
  //....
}
void test()
{
    int* p1 = (int*)malloc(100);
    try
    {
        func();
    }
    catch(...)
    {
        delete p1;
        throw;
    }
    delete p1;
}   

为了解决这些问题,C++98引入了auto_ptr,不过这个智能指针的是带有缺陷的它还存在这不少问题;在到后来就出现了boost库里的智能指针(scope_ptr、share_ptr、weak_ptr);而c++11在参考了boost库之后出了一个版本的智能指针(unique_ptr、shared_ptr、weak_ptr);


首先来模拟实现一下C++98的auto_ptr
class AutoPtr//实现方法管理权转移
{
public:
    AutoPtr(T* ptr = T())
        :_ptr(ptr)
    { }
    AutoPtr(AutoPtr<T>& P)
    {
        _ptr = P._ptr;
        P._ptr = NULL;//管理权转移
    }
    AutoPtr& operator = (AutoPtr<T>& P)
    { 
        if (this != &P)
        {
            _ptr = P._ptr;
            P._ptr = NULL;
        }
        return *this;
    }
    ~AutoPtr()
    {
        delete _ptr;
    }
    T& operator *()
    {
        return *_ptr;
    }
    T* operator ->()
    {
        return _ptr;
    }
public:
    T *_ptr;
};
可以看到这里采用的是管理权转移的思路,那么何为管理权转移呢?即就是将管理这块空间权限给予最后一个指向这块空间的人,这样就会导致将p1 = p2时,p2为NULL,当对p2解引用时会出错!!!!


模拟实现scope_ptr(unique_ptr)//防拷贝
class ScopePtr//防拷贝
{
public:
    ScopePtr(T* ptr = T())
    :_ptr(ptr)
    { }
    ~ScopePtr()
    {
        if (_ptr != NULL)
            delete _ptr;
    }
    T& operator* ()
    {
        return *_ptr;
    }
    T* operator-> ()
    {
        return _ptr;
    }
private:
    ScopePtr(const ScopePtr& p)//将拷贝构造写为私有
    { }
    ScopePtr& operator=(const ScopePtr& p)//复制运算写成私有,将所有能赋值的操作都禁止用户访问
    { }
public:
    T* _ptr;
};
既然auto_ptr出现了将指针赋给指针时,可能会出现一些错误那么这就得来解决,所以出现了防拷贝(scope_ptr),这就做的比较绝了,既然赋值会出错,那我就不让你赋值(①将拷贝构造、复制运算重载写为私有,在类外面调不到;②只声明不实现);缺点:不能将指针赋值给指针(这样会导致失去指针的一些功能);


模拟实现share_ptr//引用计数
template<class T>
class SharePtr
{
public:
    SharePtr(T* ptr = new T())
        :_ptr(ptr)
        , _refcount(new int())
    {
        *_refcount = 1;
    }
    SharePtr(const SharePtr& p)
        :_ptr(NULL)
    {
        _refcount = p._refcount;
        _ptr = p._ptr;
        (*_refcount)++;
    }
    SharePtr& operator=(const SharePtr& p)
    {
        if (this != &p)
        {
            if (--*_refcount == 0)
            {
                delete _ptr;
            }
            _ptr = p._ptr;
            _refcount = p._refcount;
            (*_refcount)++;
        }
        return *this;
    }
    ~SharePtr()
    {
        if (--*_refcount == 0)
        {
            delete _ptr;
        }
        cout << "SharePtr析构函数" << endl;
    }
    T operator* ()
    {
        return *_ptr;
    }
    T* operator->()
    {
        return _ptr;
    }
public:
    T* _ptr;
    int* _refcount;
};
采用引用计数来控制空间的释放(防止多次释放同一块空间);这样就可以很好的来使用这块空间,解决了scope_ptr的缺陷,但问题又来了,这样的场景会导致内存泄露,如下场景:
struct Node
{
public:
    Node()
        :_next(NULL)
        , _prev(NULL)
    {}
public:
    SharePtr<Node> _next;
    SharePtr<Node> _prev;
};
void testSharePtr()
{
    SharePtr<Node> cur(new Node);
    SharePtr<Node> next(new Node);
    cur->_next = next;
    next->_prev = cur;
}
为甚末这里会导致内存泄漏呢?原因是:

这里写图片描述

cur指向的空间同时被next->_prev管理着,next指向的空间同时被cur->_next管理着,着就很坑了,当“释放”掉cur时cur的*_refcount– ,*_refcount = 1;同时“释放”掉next时next的*_refcount– ,*_refcount = 1;但是cur和next原本指向的空间并没有被释放而是被next->_prev和cur->_next管理着,因此导致内存泄露;故产生了weak_ptr来配合解决这一缺陷(循环引用);


模拟实现weak_ptr(share_ptr的辅助)
template<class T>
class WeakPtr
{
public:
    WeakPtr(const SharePtr<T>& p)
        :_ptr(p._ptr)
    { }
    WeakPtr<T>& operator = (const SharePtr<T>& p)
    {
        _ptr = p._ptr;
        return *this;
    }
    T* operator->()
    {
        return _ptr;
    }
    T operator*()
    {
        return *_ptr;
    }
    ~WeakPtr()
    {
        cout << "WeakPtr析构函数" << endl;
    }
public:
    T* _ptr;
};
实现方法:将他的构造函数和赋值运算符重载通过share_ptr来实现;而对share_ptr的引用计数不采取操作;
场景验证:
struct ListNode
{
public:
    ListNode()
        :_next(NULL)
        , _prev(NULL)
    {}
public:
    WeakPtr<ListNode> _next;
    WeakPtr<ListNode> _prev;
};
void testWeakPtr()
{
    SharePtr<ListNode> cur(new ListNode);
    SharePtr<ListNode>  next(new ListNode);
    cur->_next = next;
    next->_prev = cur;
}

这里写图片描述

因为_next 和 _prev并没有对引用计数进行任何操作所以,当释放掉cur和next时_refcount已经减为0,所以真正释放掉了cur和next;
桥接模式是一种结构型设计模式,它将抽象和实现分离,使它们可以独立地变化。桥接模式的核心思想是将一个大类或一组类分解成抽象和实现两个独立的维度,使它们可以独立地变化和扩展,同时通过桥接来将它们连接起来。 在C++中,桥接模式通常通过虚函数实现。抽象部分通过基类定义接口,而实现部分通过派生类实现具体的功能。通过将抽象部分的指针作为参数传递给实现部分的函数,就可以实现两个部分的连接。 下面是一个简单的桥接模式的C++示例: ```c++ class Implementor { public: virtual void operation() = 0; virtual ~Implementor() {} }; class ConcreteImplementorA : public Implementor { public: void operation() override { // 具体的实现A } }; class ConcreteImplementorB : public Implementor { public: void operation() override { // 具体的实现B } }; class Abstraction { public: Abstraction(Implementor* implementor) : m_implementor(implementor) {} virtual void operation() = 0; virtual ~Abstraction() {} protected: Implementor* m_implementor; }; class RefinedAbstraction : public Abstraction { public: RefinedAbstraction(Implementor* implementor) : Abstraction(implementor) {} void operation() override { m_implementor->operation(); // 其他操作 } }; int main() { Implementor* implementorA = new ConcreteImplementorA(); Implementor* implementorB = new ConcreteImplementorB(); Abstraction* abstractionA = new RefinedAbstraction(implementorA); Abstraction* abstractionB = new RefinedAbstraction(implementorB); abstractionA->operation(); abstractionB->operation(); delete abstractionA; delete abstractionB; delete implementorA; delete implementorB; return 0; } ``` 在上面的示例中,Implementor是实现部分的抽象基类,ConcreteImplementorA和ConcreteImplementorB是具体的实现类。Abstraction是抽象部分的基类,RefinedAbstraction是抽象部分的具体实现类。在main函数中,我们创建了不同的Implementor和Abstraction对象,并通过它们来完成不同的操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值