C++11【智能指针详解】

智能指针

*
🏞️1. 为什么引入智能指针?
🌁2. 智能指针的使用及原理

+ 📖2.1 RAII思想
+ 📖2.2 智能指针的原理
🌠3. 常见智能指针

+ 📖3.1 auto_ptr
+ 📖3.2 unique_ptr
+ 📖3.3 shared_ptr
+ 📖3.4 shared_ptr的循环引用问题
+ 📖3.5 weak_ptr
🌌4. 定制删除器
⛺5. C++11和boost智能指针的关系

🏞️1. 为什么引入智能指针?

我们来看这样一段代码:

#include
using namespace std;

int div()
{
    int a, b;
    cin >> a >> b;

    if (b == 0)
    {
        throw "除0错误";
    }

    return a / b;
}

void func()
{
    int* p1 = new int;
    int* p2 = new int;

    cout << div() << endl;

    delete p1;
    delete p2;
}

int main()
{
    try
    {
        func();
    }
    catch (exception& e)
    {
        cout << e.what() << endl;
    }

    return 0;
}

在这段代码中,如果在 div函数中发生了除 0错误,我们在main函数捕获异常,那么最终异常抛出后会跳转到 main函数中的 catch处,对于 p1p2申请的资源就没有得到释放,就造成了 内存泄露问题.

关于内存泄露介绍,在另一篇文章中有详细的介绍:

🌁2. 智能指针的使用及原理

📖2.1 RAII思想

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

获取到资源以后去初始化一个对象,将资源交给对象管理:资源获取即初始化

在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源. 借此,我们实际是把管理一份资源的责任托管给了一个对象,这种做法有 两大好处

  1. 不需要显式的释放资源
  2. 采用这种方式,对象所需的资源在其生命周期内始终有效

template<class T>
class smart_ptr
{
public:
    smart_ptr(T* ptr = nullptr)
        : _ptr(ptr)
    {}

    ~smart_ptr()
    {
        cout << "delete " << _ptr << endl;

        if (_ptr)
        {
            delete _ptr;
            _ptr = nullptr;
        }
    }
private:
    T* _ptr;
};

此时,我们使用智能指针来代替裸指针,并让它发生除0错误:

#include
#include "smart_ptr.h"
using namespace std;

int div()
{
    int a, b;
    cin >> a >> b;

    if (b == 0)
    {
        throw invalid_argument("除0错误");
    }

    return a / b;
}

void func()
{
    smart_ptr<int> p1(new int);
    smart_ptr<int> p2(new int);

    cout << div() << endl;
}

int main()
{
    try
    {
        func();
    }
    catch (exception& e)
    {
        cout << e.what() << endl;
    }

    return 0;
}

C++11【智能指针详解】

可以看到,刚才 由于抛异常未能释放的资源现在可以正常释放.

📖2.2 智能指针的原理

我们所写的这个简单的智能指针 smart_ptr还不能称其为智能指针,因为 它还不具有指针的行为指针可以解引用,可以通过->去访问所指向空间内的内容,所以为了让它向指针一样,我们还需重载 *->运算符.

template<class T>
class smart_ptr
{
public:
    smart_ptr(T* ptr = nullptr)
        : _ptr(ptr)
    {}

    ~smart_ptr()
    {
        cout << "delete " << _ptr << endl;

        if (_ptr)
        {
            delete _ptr;
            _ptr = nullptr;
        }
    }

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

    T* get() const
    {
        return _ptr;
    }

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

但是这样的智能指针是有问题的,试一下它的拷贝?

C++11【智能指针详解】

那么,怎么去解决这个问题呢?

所以,接下来,我们来介绍几种C++标准库里的智能指针,来探究如何解决此问题

🌠3. 常见智能指针

📖3.1 auto_ptr

C++98版本的库中就提供了 auto_ptr智能指针,它解决了拷贝赋值的问题,但也还有一些不足

auto_ptr 实现原理:管理权转移的思想,下面将简化的模拟实现 auto_ptr ,主要体现它的思想:

namespace myPtr
{
    template<class T>
    class auto_ptr
    {
    public:
        auto_ptr(T* ptr = nullptr)
            : _ptr(ptr)
        {}

        ~auto_ptr()
        {
            if (_ptr)
            {
                cout << "delete " << _ptr << endl;

                delete _ptr;
                _ptr = nullptr;
            }
        }

        auto_ptr(auto_ptr<T>& sp)
            : _ptr(sp._ptr)
        {
            sp._ptr = nullptr;
        }

        auto_ptr<T>& operator=(auto_ptr<T>& ap)
        {
            if (this != &ap)
            {

                if (_ptr)
                    delete _ptr;

                _ptr = ap._ptr;
                ap._ptr = nullptr;
            }

            return *this;
        }

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

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

从它的拷贝构造和赋值重载可以看出,它以资源管理权转移的方式解决拷贝的问题.

但是,这样也带来另一个问题:

int main()
{
    myPtr::auto_ptr<int> p1(new int);
    myPtr::auto_ptr<int> p2 = p1;

    return 0;
}

由于 p1的资源管理权已经转移给了 p2,那它自己就失去对资源的管理及使用权,造成 p1悬空.

📖3.2 unique_ptr

unique_ptr是C++11才开始提供的一种智能指针,它相比于 auto_ptr更靠谱.

unique 的实现原理:简单粗暴的禁止拷贝

我们依然是模拟实现一个 unique_ptr来理解它的原理:

template<class T>
class unique_ptr
{
    public:
    unique_ptr(T* ptr = nullptr)
        : _ptr(ptr)
        {}

    ~unique_ptr()
    {
        if (_ptr)
        {
            cout << "delete " << _ptr << endl;

            delete _ptr;
            _ptr = nullptr;
        }
    }

    unique_ptr(const unique_ptr<T>&) = delete;
    unique_ptr<T>& operator=(const unique_ptr<T>&) = delete;

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

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

    private:
    T* _ptr;
};

unique_ptr 采用一种简单粗暴的方式来解决拷贝的问题:直接删除拷贝构造函数和赋值重载函数,禁止拷贝

📖3.3 shared_ptr

shared_ptr也是C++11开始提供的,它能够解决智能指针拷贝的问题,并且它不像 unique_ptr那样直接禁止拷贝,它是支持拷贝的.

shared_ptr 的原理:通过引用计数的方式来实现多个 shared_ptr 对象之间共享资源

  1. shared_ptr 内部,给它所管理的资源维护了一份引用计数,用来记录该资源被几个对象共同管理(共享)
  2. 在对象被销毁时(调用析构函数),就说明自己不使用该资源了,对用的引用计数减1
  3. 如果引用计数减到0,就说明当前自己已经是最后一个使用该资源的对象,所以此时必须释放该资源
  4. 如果不是0.那就说明还有其他对象管理这份资源,此时只需将引用计数减减即可,不需要释放资源
template<class T>
class shared_ptr
{
    public:
    shared_ptr(T* ptr = nullptr)
        : _ptr(ptr)
        {

        }

    ~shared_ptr()
    {
        if (_ptr)
        {
            cout << "delete " << _ptr << endl;

            delete _ptr;
            _ptr = nullptr;
        }
    }

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

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

};

那么,这个引用计数,我们应该怎样去维护呢?

  1. 使用普通变量 int _pCount 显然不行,由于私有成员变量是两个对象独有的,假如我们有两个智能指针 p1p2,对 p1的引用计数的–不会影响 p2
  2. 定义一个静态成员变量 static int _pCount 看似,好像可以,但是试一下如下场景:
template<class T>
class shared_ptr
{
    public:
        shared_ptr(T* ptr = nullptr)
            : _ptr(ptr)
        {

             _pCount = 1;
        }

        ~shared_ptr()
        {
            if (--_pCount == 0 && _ptr)
            {
                cout << "delete " << _ptr << endl;

                delete _ptr;
                _ptr = nullptr;
            }
        }

        shared_ptr(const shared_ptr<T>& sp)
        {
            _ptr = sp._ptr;

            ++_pCount;
        }

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

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

        static int _pCount;
};

template<class T>
int shared_ptr<T>::_pCount = 0;
int main()
{

    myPtr::shared_ptr<int> p1(new int);

    myPtr::shared_ptr<int> p2(p1);

    myPtr::shared_ptr<int> p3(new int);

    return 0;
}
  1. 定义一个指针成员变量 int* _pCount
template<class T>
class shared_ptr
{
    public:
    shared_ptr(T* ptr = nullptr)
        : _ptr(ptr)
        , _pCount(new int(1))
    {}

    void Release()
    {
        if (--(*_pCount) == 0 && _ptr)
        {
            cout << "delete " << _ptr << endl;

            delete _ptr;
            _ptr = nullptr;

            delete _pCount;
            _pCount = nullptr;
        }
    }

    ~shared_ptr()
    {
        Release();
    }

    shared_ptr(const shared_ptr<T>& sp)
    {
        _ptr = sp._ptr;

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

    shared_ptr<T>& operator=(const shared_ptr<T>& sp)
    {

        if (_ptr != sp._ptr)
        {
            Release();

            _ptr = sp._ptr;

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

        return *this;
    }

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

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

    int* _pCount;
};

这就是我们最终模拟实现出的 shared_ptr.

📖3.4 shared_ptr的循环引用问题

shared_ptr 的循环引用问题

struct ListNode
{
    ListNode(const int& val = int())
        : _next(nullptr)
        , _prev(nullptr)
        , _val(val)
    {}

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

    myPtr::shared_ptr<ListNode> _next;
    myPtr::shared_ptr<ListNode> _prev;

    int _val;
};

int main()
{
    myPtr::shared_ptr<ListNode> p1(new ListNode(1));
    myPtr::shared_ptr<ListNode> p2(new ListNode(2));

    p1->_next = p2;
    p2->_prev = p1;

    return 0;
}

在上面的代码中,我们应该是有两份 ListNode节点需要释放,运行程序:

C++11【智能指针详解】

没有任何节点被释放.

C++11【智能指针详解】

这便是 shared_ptr的循环引用问题.

解决方案:在引用计数的场景下,把节点中的 _prev_next改成 weak_ptr就可以

📖3.5 weak_ptr

weak_ptr原理:

node1->_next = node2;
node2->_prev = node1;

weak_ptr模拟实现

template<class T>
class weak_ptr
{
    public:
    weak_ptr()
    {}

    weak_ptr(const shared_ptr<T>& sp)
    {
        _ptr = sp._ptr;
    }

    weak_ptr<T>& operator=(const shared_ptr<T>& sp)
    {
        if (_ptr != sp.get())
        {
            _ptr = sp.get();
        }

        return *this;
    }

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

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

🌌4. 定制删除器

在我们写的智能指针的析构函数中,统一都使用delete来释放资源,但是,如果资源不是用new申请出来的呢?比如: new T[]&#xFF0C;malloc ,所以我们就需要定制删除器来规范释放资源的方式.

我们使用 shared_ptr来做演示:

template<class T>
struct default_delete
{
    void operator()(T* ptr)
    {
        delete ptr;
    }
};

template<class T, class Del = default_delete<T>>
class shared_ptr
{
    public:
    shared_ptr(T* ptr = nullptr)
        : _ptr(ptr)
            , _pCount(new int(1))
        {}

    void Release()
    {
        if (--(*_pCount) == 0 && _ptr)
        {
            cout << "delete " << _ptr << endl;

            Del del;
            del(_ptr);
            _ptr = nullptr;

            delete _pCount;
            _pCount = nullptr;
        }
    }

    ~shared_ptr()
    {
        Release();
    }

    shared_ptr(const shared_ptr<T>& sp)
    {
        _ptr = sp._ptr;

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

    shared_ptr<T>& operator=(const shared_ptr<T>& sp)
    {

        if (_ptr != sp._ptr)
        {
            Release();

            _ptr = sp._ptr;

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

        return *this;
    }

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

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

    T* get() const
    {
        return _ptr;
    }
    private:
    T* _ptr;

    int* _pCount;
};

template<class T>
struct DeleteArray
{
    void operator()(T* ptr)
    {
        delete[] ptr;
    }
};

int main()
{
    myPtr::shared_ptr<int, DeleteArray<int>> p(new int[10]);

    return 0;
}

⛺5. C++11和boost智能指针的关系

  1. C++98中产生了第一个智能指针 auto_ptr
  2. C++ boost库给出了更实用的scoped_ptr和shared_ptr以及weak_ptr
  3. C++ TR1,引入了shared_ptr等,不过需要注意的是TR1并不是标准版
  4. C++ 11,引入了unique_ptr和shared_ptr以及weak_ptr,需要注意的是unique_ptr对应的boost的scoped_ptr,并且这些智能指针的实现原理是参考了boost库中的.

Original: https://blog.csdn.net/smf12138/article/details/127823235
Author: 沉默.@
Title: C++11【智能指针详解】

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/658939/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球