C++智能指针

时间:2021-11-17 01:26:57

一,智能指针的概念

  1. 为什么要引入智能指针

C++语言需要自己手动释放申请的堆区资源,而程序中往往需要大量的使用new或malloc,如果一个忘记释放就会造成内存泄漏,而查找内存泄漏需要大量的精力。因此C++引入了智能指针来处理之一问题。

  1. RAII技术

利用对象的生存期来控制资源 (内存,文件句柄等)的技术
在对象 构造时获取资源。
最后在对象 析构的时候释放资源。
借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:
(1)不需要手动的去释放资源
(2)采用这种方式,对象所需的资源在其生命期内始终保持有效。

3. 智能指针的概念

所谓的智能指针本质就是一个类模板,它可以创建任意的类型的指针对象,当智能指针对象使用完后,对象就会自动调用析构函数去释放该指针所指向的空间

智能指针的基本框架:

智能指针类中必须有的内容:指针对象构造函数析构函数operator*operator->

template<class T>
class smart_ptr
{
private:
    T* smartptr;  //指针对象
public:
    //构造
    smart_ptr(T* ptr = nullptr):smartptr(ptr) {}

    //operator*
    T& operator*() const { return *smartptr; }

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

    //析构
    ~smart_ptr(){ delete smartptr; }  
}

二,C++库中的智能指针

先写一个整形的包装类,下面仿写智能指针时会用到。

class Int
{
private:
    int val;
public:
    Int(int x = 0) :val(x) { cout << "构造Int" << endl; }

    int& getvalue() { return val; }

    void printvalue() { cout << val << endl; }

    Int(const Int& a) :val(a.val)
    {
        cout << "拷贝构造Int" << endl;
    }

    Int& operator=(const Int& a)
    {
        if (this != &a)
        {
            val = a.val;
            cout << "拷贝赋值Int" << endl;
        }
        return *this;
    }

    ~Int() { cout << "析构Int" << endl; }
};

  1. auto_ptr

  • 介绍

auto_ptr是c98版本提出的智能指针(当时并没有右值引用与移动语义),它的采取的是管理权转移的思想,也就是一个auto_ptr对象拷贝给另一个auto_ptr对象时,原对象被置为nullptr,新对象获取资源

auto_ptr不能管理一组对象,只能管理单一对象。

  • auto_ptr的内置函数

template<class T>

//重新设置管理对象
void reset(T* other = nullptr)

//清除并返回管理对象
T* release()

  • auto_ptr的使用

C++智能指针
C++智能指针

  • auto_ptr被舍弃的原因

当使用一个auto_ptr对象拷贝给另一个auto_ptr对象时,原对象被置为nullptr,新对象获取资源,此时对源对象进行操作将会导致系统崩溃。

C++智能指针

  • 仿写auto_ptr

namespace ztw
{
    //auto_ptr
    template<class T>
    class my_auto_ptr
    {
    private:
        T* ptr;
    public:
        my_auto_ptr(T* p = nullptr) :ptr(p) {}  //构造

        T& operator*() const  //重载*
        {
            return *ptr;
        }
        
        T* operator->() const  //重载->
        {
            return ptr;
        }

        my_auto_ptr(my_auto_ptr& p)  //拷贝构造
        {
            if (ptr != nullptr) { delete ptr; }
            ptr = p.ptr;
            p.ptr = nullptr;
        }

        my_auto_ptr& operator=(my_auto_ptr& p)  //拷贝赋值
        {
            if (this == &p) { return *this; }
            if (ptr != nullptr) { delete ptr; }
            ptr = p.ptr;
            p.ptr = nullptr;
            return *this;
        }

        void reset(T* otherptr = nullptr)  //重置管理对象
        {
            delete ptr;
            ptr = otherptr;
        }

        T* release()  //清处并返回管理对象
        {
            T* tmp = ptr;
            ptr = nullptr;
            return tmp;
        }

        void swap(my_auto_ptr& other)  //交换
        {
            std::swap(ptr, other.ptr);
        }

        ~my_auto_ptr() { delete ptr; }
    };
}

  1. 唯一性智能指针:unique_ptr

  • 介绍

unique_ptr是c++11版本库中提供的智能指针,它直接将拷贝构造函数和赋值重载函数给禁用掉,因此,不让其进行拷贝和赋值。但可以进行移动构造和移动赋值

unique_ptr可以管理一组对象或一个对象。

  • unique_ptr的内置函数

//default_delete是默认删除器,删除单个元素
template<class T,class D = std::default_delete>

//获取unique_ptr指向的指针对象
T* get()

//获取unique_ptr指向的删除器
D& get_deleter()

//重置指针对象
void reset(T* other)

//清除并返回指针对象
T* release()

//交换指针对象
void swap(unique_ptr<T>& other)

  • unique_ptr的使用

C++智能指针

  • 仿写unique_ptr

仿写删除器

在删除器中重载()运算符,当调用 删除器对象()时,自动选择合适的删除器进行删除。

泛化版本删除器(删除单个对象)
template<class T>
struct my_default_deleter
{
    void operator()(T* ptr) const
    {
        delete ptr;
    }
}
特化版本删除器(删除一组对象)
template<class T>
struct my_default_deleter<T[]>
{
    void operator()(T* ptr) const
    {
        delete []ptr;
    }
}

仿写unique_ptr

泛化版本my_unique_ptr(管理单个对象资源)可以使用*和->
namespace ztw
{
    class my_unique_ptr  //泛化版本
    {
    private:
        T* uniqueptr;
        D mydeleter;  //删除器对象
    public:
        my_unique_ptr(T* p = nullptr) :uniqueptr(p) {}  //构造
        
        T& operator*() const  //重载*
        {
            return *uniqueptr;
        }

        T* operator->() const  //重载->
        {
            return uniqueptr;
        }
        
        //删除拷贝构造和拷贝赋值
        my_unique_ptr(const my_unique_ptr&) = delete;
        my_unique_ptr& operator=(const my_unique_ptr&) = delete;

        my_unique_ptr(my_unique_ptr&& ptr)  //移动构造
        {
            if (uniqueptr != nullptr) { delete uniqueptr; }
            uniqueptr = ptr.uniqueptr;
            ptr.uniqueptr = nullptr;
        }

        my_unique_ptr& operator=(my_unique_ptr&& ptr)  //移动赋值
        {
            if (this != &ptr)
            {
                if (uniqueptr != nullptr) { delete uniqueptr; }
                uniqueptr = ptr.uniqueptr;
                ptr.uniqueptr = nullptr;
            }
            return *this;
        }

        T* get_uniqueptr() const  //获取指针对象
        {
            return uniqueptr;
        }

        D& get_deleter() const  //获取删除器对象
        {
            return mydeleter;
        }

        void reset(T* other = nullptr)
        {
            if (uniqueptr != nullptr) { mydeleter(uniqueptr); }
            uniqueptr = other;
        }

        T* release()
        {
            T* tmp = uniqueptr;
            uniqueptr = nullptr;
            return tmp;
        }

        void swap(my_unique_ptr& other)
        {
            std::swap(uniqueptr, other.uniqueptr);
            std::swap(mydeleter, other.mydeleter);
        }

        operator bool()  //重载bool()
        {
            return (uniqueptr != nullptr);
        }

        ~my_unique_ptr() { reset(); }
    };
}

特化版本my_unique_ptr(管理一组对象资源)不能使用*和->,但可以使用[]
namespace ztw  //和泛化版本放在同一命名空间中,这里写只是为了方便看
{ 
   template<class T, class D>
    class my_unique_ptr<T[], D>   //特化版本
    {
    private:
        T* uniqueptr;
        D mydeleter;  //删除器对象
    public:
        my_unique_ptr(T* p = nullptr) :uniqueptr(p) {}

        T& operator*() const = delete;
        T* operator->() const = delete;

        T* operator[](const std::size_t i)
        {
            //越界问题
            return uniqueptr[i];
        }

        my_unique_ptr(const my_unique_ptr&) = delete;
        my_unique_ptr& operator=(const my_unique_ptr&) = delete;

        my_unique_ptr(my_unique_ptr&& ptr)
        {
            if (uniqueptr != nullptr) { delete uniqueptr; }
            uniqueptr = ptr.uniqueptr;
            ptr.uniqueptr = nullptr;
        }

        my_unique_ptr& operator=(my_unique_ptr&& ptr)
        {
            if (this != &ptr)
            {
                if (uniqueptr != nullptr) { delete uniqueptr; }
                uniqueptr = ptr.uniqueptr;
                ptr.uniqueptr = nullptr;
            }
            return *this;
        }

        T* get_uniqueptr() const
        {
            return uniqueptr;
        }

        D& get_deleter() const
        {
            return mydeleter;
        }

        void reset(T* other = nullptr)
        {
            if (uniqueptr != nullptr) { mydeleter(uniqueptr); }
            uniqueptr = other;
        }

        T* release()
        {
            T* tmp = uniqueptr;
            uniqueptr = nullptr;
            return tmp;
        }

        void swap(my_unique_ptr& other)
        {
            std::swap(uniqueptr, other.uniqueptr);
            std::swap(mydeleter, other.mydeleter);
        }

        operator bool()
        {
            return (uniqueptr != nullptr);
        }

        ~my_unique_ptr() { reset(); }
    };
}

  • 删除器自动绑定原理

C++智能指针

  1. 共享智能指针:shared_ptr

  • 介绍

share_ptr是c++11版本库中的智能指针,shared_ptr允许多个智能指针可以指向同一块资源,并且能够保证共享的资源只会被释放一次,程序不会崩溃掉。

  • shared_ptr底层原理

shared_ptr采用的是引用计数原理来实现多个shared_ptr对象之间共享资源:

shared_ptr有两个指针:_Ptr和_Rep

  1. _Ptr:指向资源

  1. _Rep:指向引用计数控制块

引用计数控制块有两个长整型属性:_Uses和_Weaks

  1. _Uses:指向此资源共享指针的数目

  1. _Weaks:指向此资源弱指针的数目

其中

  • 当创建一个shared_ptr对象时,该对象的_Uses和_Weaks都置为1。

  • 当一个shared_ptr对象被销毁时(调用析构函数),析构函数内就会将_Uses减1

  • 如果_Uses减为0后,则表示自己是最后一个使用该资源的shared_ptr对象,会先释放资源,再将_Weaks减1

  • 如果_Weaks减为0后,则表示该引用计数控制块没有人使用,会释放引用计数控制块

(使用make_shared创建智能指针则不是这样,make_shared创建的智能指针在_Uses减为0时,会将资源和引用计数控制块都释放,原理接下来会讲)

C++智能指针

  • shared_ptr的内置函数

template<class T>

//获取unique_ptr指向的指针对象
T* get()

//重置指针对象
void reset(T* other)

//交换指针对象
void swap(unique_ptr<T>& other)

  • shared_ptr的使用

C++智能指针

  • shared_ptr的循环引用问题

我们给出以下场景:

定义两个类,类中都有一个shared_ptr智能指针对象(指向另一个类)

class Child;
class Parent
{
public:
    shared_ptr<Child> child;
public:
    Parent() { cout << "构造Parent" << endl; }
    void say()
    {
        cout << "Parent:child" << endl;
    }
    ~Parent() { cout << "析构Parent" << endl; }
};

class Child
{
public:
    shared_ptr<Parent> parent;
public:
    Child(){ cout << "构造Child" << endl; }
    void say()
    {
        cout << "Child:parent" << endl;
    }
    ~Child() { cout << "析构Child" << endl; }
};

当我们分别用shared_ptr初始化这两个类对象,并用类中的shared_ptr指向另一个智能指针对象

int main()
{
    shared_ptr<Child> pc(new Child());
    shared_ptr<Parent> pp(new Parent());
    pc->parent = pp;
    pp->child = pc;
    pc->say();
    pp->say();
    return 0;
}

此时就会出现问题:

C++智能指针

产生这样问题的原因:

C++智能指针

为了解决shared_ptr的循环引用问题我们引入了弱指针weak_ptr

  1. 弱智能指针:weak_ptr

  • 介绍

c++库中存在weak_ptr类型的智能指针。weak_ptr类的对象它可以指向shared_ptr,并且不会改变shared_ptr的引用计数

weak_ptr的目的就是检测当前资源有多少shared_ptr使用

  • weak_ptr的底层原理

weak_ptr底层和shared_ptr一样,都是由两个指针_Ptr和_Rep构成

同时_Rep指向的引用计数控制器是所指向的shared_ptr的引用技术控制器。

只是weak_ptr不能直接操作资源。

  • 当用shared_ptr构建weak_ptr对象时,会将引用计数控制块的_Weaks+1

  • weak_ptr的内置函数

template<class T>

//重置指针对象
void reset(T* other)

//返回当前指向此资源的shared_ptr的个数,也就是_Uses的值
unsigned long use_count()

//创建并返回一个shared_ptr对象
shared_ptr<T> lock()

//交换
swap(weak_ptr<T>& other)

  • weak_ptr的使用

C++智能指针

  • 使用weak_ptr解决shared_ptr的循环引用问题

我们将之前两个类中的shared_ptr改为weak_ptr

class Child;
class Parent
{
public:
    weak_ptr<Child> child;
public:
    Parent() { cout << "构造Parent" << endl; }
    void say()
    {
        cout << "Parent:child" << endl;
    }
    ~Parent() { cout << "析构Parent" << endl; }
};

class Child
{
public:
    weak_ptr<Parent> parent;
public:
    Child(){ cout << "构造Child" << endl; }
    void say()
    {
        cout << "Child:parent" << endl;
    }
    ~Child() { cout << "析构Child" << endl; }
};

int main()
{
    shared_ptr<Child> pc(new Child());
    shared_ptr<Parent> pp(new Parent());
    pc->parent = pp;
    pp->child = pc;
    pc->say();
    pp->say();
    return 0;
}
C++智能指针

此时可以看到完美的进行了析构。

原理:

C++智能指针

其实只改一个类中的shared_ptr为weak_ptr也能达到防止内存泄漏的效果,原理和上图分析方法一致。