C++:详细的说明智能指针的使用以及底层实现,以及删除器和包装器的使用

1.智能指针

1.1概念:

智能指针并非指针,他是一个类对象。这个对象托管了堆区的资源。

 1.2机制:

当栈上的智能指针对象出栈时,所托管的堆区对象自动进行析构。

用到的知识点就是栈上对象出栈自动销毁的特性,因为出栈即自动调用析构。

RAII 是 resource acquisition is initialization 的缩写,意为“资源获取即初始化”。它是 C++ 之父 Bjarne Stroustrup 提出的设计理念,其核心是把资源和对象的生命周期绑定,对象创建获取资源,对象销毁释放资源。在 RAII 的指导下,C++ 把底层的资源管理问题提升到了对象生命周期管理的更高层次。

那么到底什么是 RALL 机制?

使用 C++ 时,最让人头疼的便是内存管理,但却又正是对内存高度的可操作性给了 C++ 程序猿极大的自由与装逼资本。

当我们 new 出一块内存空间,在使用完之后,如果不使用 delete 来释放这块资源则将导致内存泄露,这在中大型项目中是极具破坏性的。但是人无完人,我们并不能保证每次都记得释放无法再次获取到且不再使用的内存。

2.auto_ptr资源所有权转移智能指针

2.1auto_ptr的使用:

#include <iostream>
#include<memory>

using namespace std;

class Stu
{
    int age;
    string name;
public:
    Stu(string name,int age)
    {
        this->age=age;
        this->name=name;

        cout<<"STU的构造"<<endl;
    }

    ~Stu()
    {
        cout<<"STU的析构"<<endl;
    }

    void show_info()
    {
        cout<<this->age<<this->name<<endl;
    }

};

int main()
{
    auto_ptr<Stu> stu(new Stu("lisi",12));
    stu->show_info();
    return 0;
}

结果图:

 分析:像这样我们即使不手动释放堆区的资源,他也会自动释放。

2.2auto_ptr的底层实现:

#include <iostream>

using namespace std;
class Stu
{
    string name;
    int age;
public:
    Stu(string name,int age)
    {
        this->name=name;
        this->age=age;
        cout<<"stu的构造"<<endl;
    }

    ~Stu()
    {
        cout<<"stu的析构"<<endl;
    }

    void show_info()
    {
        cout<<this->name<<","<<this->age<<endl;
    }
};

template <class T>
class Auto_ptr
{
    T* ptr;
public:
    Auto_ptr(T* _ptr)
    {
        this->ptr=_ptr;
    }

    ~Auto_ptr()
    {
        delete ptr;
        ptr=NULL;
    }

    Auto_ptr(const Auto_ptr& other)
    {
        this->ptr=other.ptr;
        const_cast<Auto_ptr*>(other)->ptr=nullptr;
    }

    Auto_ptr& operator=(const Auto_ptr& other)
    {
        if(this->ptr!=nullptr){
            delete this->ptr;
            this->ptr=nullptr;
        }

        this->ptr=other.ptr;
    }

    T* get()
    {
        return this->ptr;
    }

    T* operator->()
    {
        return this->ptr;
    }
};

int main()
{

    Auto_ptr<Stu> stu(new Stu("lisi",20));
    stu.operator->()->show_info();
    stu->show_info();

    return 0;
}

结果图:

 分析:这就是智能指针auto_ptr的实现。

2.3auto_ptr的缺点:

2.3.1通过代码来说明问题:

#include <iostream>
#include <memory>

using namespace std;
class Stu
{
    string name;
    int age;
public:
    Stu(string name,int age)
    {
        this->name=name;
        this->age=age;
        cout<<"stu的构造"<<endl;
    }

    ~Stu()
    {
        cout<<"stu的析构"<<endl;
    }

    void show_info()
    {
        cout<<this->name<<","<<this->age<<endl;
    }
};

template <class T>
class Auto_ptr
{
    T* ptr;
public:
    Auto_ptr(T* _ptr)
    {
        this->ptr=_ptr;
        cout<<"构造"<<endl;
    }

    ~Auto_ptr()
    {
        delete ptr;
        ptr=NULL;
        cout<<"析构"<<endl;
    }

    Auto_ptr(const Auto_ptr& other)
    {
        this->ptr=other.ptr;
        const_cast<Auto_ptr*>(other)->ptr=nullptr;
    }

    Auto_ptr& operator=(const Auto_ptr& other)
    {
        if(this==&other){
            return *this;
         }

        if(this->ptr!=nullptr){
            delete this->ptr;
            this->ptr=nullptr;
        }

        this->ptr=other.ptr;
    }

    T* get()
    {
        return this->ptr;
    }

    T* operator->()
    {
        return this->ptr;
    }
};

int main()
{


    Auto_ptr<Stu> stu(new Stu("zhangsan",20));
    stu->show_info();
    Auto_ptr<Stu> stu1=stu;
    stu1->show_info();

    return 0;
}

报错图:

分析:当我们在main函数里面这么写的时候就会出现问题,就是我只要以把数值赋给另外一个,原来的那个就会消失,要是再大型项目中,这肯定会导致很多小问题,而且我们从这个报错也看到了,系统并不让我们删除,也说明了这个问题的严重性,所以这个智能指针被废弃了,当然也还能用哈。

3.带有引用记数器的共享智能指针shared_ptr(重点

3.1shared_ptr的使用方法:

#include <iostream>
#include <memory>

using namespace std;
class Stu
{
    string name;
    int age;
public:
    Stu(string name,int age)
    {
        this->name=name;
        this->age=age;
        cout<<"stu的构造"<<endl;
    }

    ~Stu()
    {
        cout<<"stu的析构"<<endl;
    }

    void show_info()
    {
        cout<<this->name<<","<<this->age<<endl;
    }
};



int main()
{

    shared_ptr<Stu> stu(new Stu("lisi",23));
    stu->show_info();
    cout<<stu.use_count()<<endl;

    shared_ptr<Stu> stu1=stu;
    stu1->show_info();
    cout<<stu1.use_count()<<endl;

    return 0;
}

结果图:

 3.2shared_ptr的底层实现:

#include <iostream>

using namespace std;


class Stu
{
    string name;
    int age;
public:
    Stu(string name,int age)
    {
        this->name=name;
        this->age=age;
        cout<<"stu的构造"<<endl;
    }

    ~Stu()
    {
        cout<<"stu的析构"<<endl;
    }

    void show_info()
    {
        cout<<this->name<<","<<this->age<<endl;
    }
};

template<class T>
class Recount
{
    T* my_ptr;
    int count;
public:
    Recount(T* ptr=nullptr)
    {
        this->my_ptr = ptr;
        if (this->my_ptr != nullptr){
            this->count = 1;
        }
    }

    void add_count()
    {
        this->count++;
    }

    int sub_count()
    {
        return --(this->count);
    }

    int get_count()
    {
        return this->count;
    }
};

template<class T>
class Shared_ptr{
  T* my_ptr;

  Recount<T>* recount;
public:
    Shared_ptr(T* ptr)
    {
        this->my_ptr = ptr;

        if (this->my_ptr != nullptr){
            this->recount = new Recount<T>(this->my_ptr);
        }
    }

    Shared_ptr(const Shared_ptr& other)
    {
        this->my_ptr = other.my_ptr;
        this->recount = other.recount;

        this->recount->add_count();
    }

    Shared_ptr& operator=(const Shared_ptr& other)
    {
        if (this == &other){
            return *this;
        }

        if (this->my_ptr != nullptr){

            if (recount->sub_count() == 0){
                delete this->my_ptr;
                delete this->recount;
                this->my_ptr = nullptr;
                this->recount = nullptr;
            }
        }

        this->my_ptr = other.my_ptr;
        this->recount = other.recount;

        recount->sub_count();

        return *this;
    }

    ~Shared_ptr()
    {
        if (recount->sub_count() == 0){
            delete this->my_ptr;
            delete this->recount;
            this->my_ptr = nullptr;
            this->recount = nullptr;
        }
    }

    int get_count()
    {
        return recount->get_count();
    }

    T* operator->()
    {
        return this->my_ptr;
    }
};

int main()
{
    Shared_ptr<Stu> stu(new Stu("lisi", 123));

    Shared_ptr<Stu> stu1 = stu;
    stu1->show_info();
}

结果图:

4.weak_ptr

4.1概念:

和shared_ptr搭配着用,防止内存泄漏。

4.1代码说明:

4.2.1shared_ptr的缺点:

当用shared_ptr实现环状引用的时候,会使引用计数多加一次,所以当进行析构的时候,引用计数无法达到等于0的那个条件所以就会出现资源泄漏。

#include <iostream>
#include <memory>

using namespace std;

class B;
class A
{
public:
    shared_ptr<B> ptr_b;
    A()
    {
        cout<<"A的构造"<<endl;
    }

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

    void show_info()
    {
        cout<<"mrr ai chi shi"<<endl;
    }

};

class B
{
public:
    shared_ptr<A> ptr_a;
    B()
    {
        cout<<"B的构造"<<endl;
    }

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

    void single()
    {
        ptr_a->show_info();
    }

};

int main()
{
    shared_ptr<A> pa(new A);
    shared_ptr<B> pb(new B);

    pa->ptr_b=pb;
    pb->ptr_a=pa;

    pb->single();

    return 0;
}

结果图:

 由结果图片我们可以看出来,没有进行析构,这是由于当给pa给与堆区地址的时候,计数器的数字加1,当又进行拷贝赋值的时候,pa->ptr_b又在pa的基础上又加了1,所以pa->ptr->b的计数器的数字为2,又因为他们都共享一块内存pb的计数器数字也为2。

具体实现图:

 这个时候我们就可以引出来我们的weak_ptr和shared_ptr是天然的搭档。

4.2weak_ptr和shared_ptr的天然搭配代码:

#include <iostream>
#include <memory>

using namespace std;

class B;
class A
{
public:
    weak_ptr<B> ptr_b;
    A()
    {
        cout<<"A的构造"<<endl;
    }

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

    void show_info()
    {
        cout<<"mrr ai chi shi"<<endl;
    }

};

class B
{
public:
    weak_ptr<A> ptr_a;
    B()
    {
        cout<<"B的构造"<<endl;
    }

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

    void single()
    {
        if(ptr_a.lock()==nullptr){
            return;
        }

        ptr_a.lock()->show_info();
    }

};

int main()
{
    shared_ptr<A> pa(new A);
    shared_ptr<B> pb(new B);

    pa->ptr_b=pb;
    pb->ptr_a=pa;

    pb->single();

    return 0;
}

结果图:

 分析:因为使用weak_ptr后计数器不会加1, 这样就能防止内存泄漏了。

5.独占智能指针:unique_ptr

5.1概念:

独一无二的,唯一的智能指针,他是不共享的,他不想其它的共享对象也操作他的指向的堆区空间,他是唯一一个可操作这个堆区空间的对象。也就是说他的类对象是不可以发生拷贝构造与赋值运算符重载的。

5.2使用方式:

#include <iostream>
#include <memory>

using namespace std;
class A
{
public:
    A()
    {
        cout<<"A的构造"<<endl;
    }

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

    void show_info()
    {
        cout<<"mrr ai chi shi"<<endl;
    }

};



int main()
{
  unique_ptr<A> a(new A);
  a->show_info();
  
    return 0;
}

结果图:
 

 这个智能指针修饰的内存只能一个人享用,如图:

 5.3底层实现:

#include <iostream>
#include <memory>

using namespace std;

class A
{
public:
    A()
    {
        cout<<"A的构造"<<endl;
    }

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

    void show_info()
    {
        cout<<"mrr ai chi shi"<<endl;
    }

};
template <class T>
class Unique_ptr
{
    T* ptr;
public:
    Unique_ptr(T* _ptr)
    {
        this->ptr=_ptr;
    }

    Unique_ptr(const Unique_ptr& other)=delete ;
    Unique_ptr& operator=(const Unique_ptr& other)=delete;

    ~Unique_ptr()
    {
        delete this->ptr;
        this->ptr=nullptr;
    }

    T* operator->()
    {
        return this->ptr;
    }
};


int main()
{
   Unique_ptr<A> a(new A);
   a->show_info();
   
    return 0;
}

6.智能指针与自定义删除器及包装器的使用:

6.1为什么要定义删除器:

我们可以看一下上面几根智能指针的实现,都是delete ptr;并不是delete []ptr,如果我们不自定义删除的话,就会导致内存泄漏,如下:

#include <iostream>
#include <memory>

using namespace std;

class A
{
public:
   A()
   {
       cout<<"A的构造"<<endl;
   }

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

int main()
{
   shared_ptr<A> a(new A[5]);

    return 0;
}

结果图:


 

 分析:由图可知,构造了五次,析构了一次,造成了内存泄漏,这就是为什么我们要进行用删除器。

6.2自定义的删除器(函数对象):

#include <iostream>
#include <memory>

using namespace std;
template <class T>
class Deleter
{
public:
    void operator()(T* p)
    {
        delete [] p;
    }
};


class A
{
public:
   A()
   {
       cout<<"A的构造"<<endl;
   }

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

int main()
{
   shared_ptr<A> a(new A[5],Deleter<A>());

    return 0;
}

结果图:

6.3自定义的删除器(匿名6.4函数对象):

#include <iostream>
#include <memory>

using namespace std;
template <class T>
class Deleter
{
public:
    void operator()(T* p)
    {
        delete [] p;
    }
};


class A
{
public:
   A()
   {
       cout<<"A的构造"<<endl;
   }

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

int main()
{
    shared_ptr<A> a(new A[5],[](A* p){
        delete [] p;
    });

    return 0;
}

结果图:

 6.4智能指针的C++官方提供的释放连续空间的删除器

注意:每个智能指针删除所放的位置不一定相同,可以自己去查一下c++官方手册。

#include <iostream>
#include <memory>

using namespace std;
template <class T>
class Deleter
{
public:
    void operator()(T* p)
    {
        delete [] p;
    }
};


class A
{
public:
   A()
   {
       cout<<"A的构造"<<endl;
   }

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

int main()
{
    unique_ptr<A,default_delete<A[]>> a(new A[5]);
    //unique_ptr<A,Deleter<A>> a(new A[5]);//这个也是可以的
    return 0;
}

结果图:
 

 6.5包装一个匿名函数对象的类型:

#include <iostream>
#include <memory>
#include <functional>

using namespace std;
template <class T>
class Deleter
{
public:
    void operator()(T* p)
    {
        delete [] p;
    }
};


class A
{
public:
   A()
   {
       cout<<"A的构造"<<endl;
   }

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

int main()
{
    unique_ptr<A,function<void (A*)>> a(new A[5],Deleter<A>());

    return 0;
}

6.5.1包装器的使用:可以提升函数的效率,就想变成()函数一样,效率很高。

#include <iostream>
#include <memory>
#include <functional>

using namespace std;
template <class T>
class Deleter
{
public:
    void operator()(T* p)
    {
        delete [] p;
    }
};


class A
{
public:
   A()
   {
       cout<<"A的构造"<<endl;
   }

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

void show_info()
{
    cout<<"加油"<<endl;
}

int main()
{
    function<void ()> f=show_info;
    f();

    return 0;
}

  • 6
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值