c++智能指针

本文介绍了C++中的智能指针(如unique_ptr、shared_ptr和weak_ptr)以及RAII(ResourceAcquisitionIsInitialization)技术,讨论了它们如何利用对象生命周期管理和资源自动释放,以及在C++中处理内存泄漏和异常安全问题的方法。
摘要由CSDN通过智能技术生成

一、智能指针

​ 总结:1.RAII,资源交给对象去管理,利用对象的生命周期;2.原来new之后返回一个指针,需要支持指针的一系列操作,即运算符重载;3.拷贝问题;

​ Java没有智能指针,而是使用的垃圾回收器garbage collection,即对于new之后的空间不需要手动的去delete,而是后台有一个垃圾回收器gc记录下来,最后会回收,但是垃圾回收器会占用空间,在游戏开发和嵌入式开发中就不会考虑Java;

​ c++出现了异常之后,new delete就不安全了,抛异常之后会影响执行流,可能就会使得程序不执行delete,导致内存泄漏,就算是使用异常捕获再抛出也处理不了,可能产生多种异常,具体处理哪一种异常的问题;

​ 为了解决上述问题,c++出现了智能指针,又为了适应各种异常,将智能指针设计成了类模板,成员是一个指针,构造函数将堆空间的地址赋值给成员指针,析构函数delete成员指针,实例化出来对象之后保存new之后的指针,这样new出来的对象就不需要另外写delete,而是实例化一个智能指针,交给智能指针去释放;

​ 主要是利用了对象生命周期结束会自动调用析构函数;异常处理之后,函数栈帧空间会正常销毁,即智能指针对象的生命周期结束会随着栈帧空间的销毁而自动调用析构函数;

​ 这种利用类的构造函数和析构函数的自动调用特性的机制叫做RAII;

template<class T>
class SmartPtr
{
public:
	SmartPtr(T* ptr) :ptr_(ptr) {}
    T& operator*()
    {
		return *ptr_;
    }
	T* operator->()
    {
		return ptr_;
    }
    ~SmartPtr()
    {
		cout << "delete" << endl;
		delete ptr_;
    }
private:
    T* ptr_;
};

二、RAII

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

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

1.不需要显式地释放资源;

2.采用这种方式,对象所需的资源在其生命期内始终保持有效;

三、正确使用智能指针

​ 对于智能指针,成员变量是内置类型浅拷贝就可以,但是其成员是堆上开辟空间的地址,所以使用赋值和拷贝的时候会指向同一块空间,使得堆空间被释放了两次;

解决:

​ 1.c++98 就使用了一个智能指针auto_ptr (使用了管理权转移,但是这种方式存在空指针问题),c++11使用了两种智能指针unique_ptr(唯一指针,不允许拷贝和赋值),shared_ptr(共享指针,使用了引用计数,但是存在一些问题,如循环引用),所以又创建了一个指针用来处理循环引用weak_ptr;

3.1auto_ptr

​ 一般智能指针在memory库文件里面,注意g++编译器支持使用此语法,已经弃用了;

​ 在拷贝的过程中进行了管理权转移,但是会产生悬空问题,会出现访问空指针问题,即被拷贝对象被置空了,使用时需要注意不能再使用被拷贝对象;

template <class T>
class auto_ptr
{
    public:
    auto_ptr(T* ptr) : ptr_(ptr) {}
    T& operator*()
    {
        return *ptr_;
    }
    T* operator->()
    {
        return ptr_;
    }
    ~auto_ptr()
    {
        delete ptr_;
    }
    // 管理权转移
    auto_ptr(auto_ptr<T>& ptr) : ptr_(ptr.ptr_)
    {
        ptr.ptr_ = nullptr;
    }
    auto_ptr<T>& operator=(auto_ptr<T>& ptr)
    {
        delete ptr_;
        ptr_ = ptr.ptr_;
        ptr.ptr_ = nullptr;
        return *this;
    }

    private:
    T* ptr_;
};

3.2boost库

​ 由于c++标准库又很多的不足,官方有人就提出写一个第三方库弥补不足,所以boost库就出来了,为c++的发展做贡献的,c++就借鉴了boost库;

​ boost库在智能指针方面弄出了三个指针,scoped_ptr,shared_ptr,weak_ptr;

3.3c++11智能指针

​ c++11借鉴了boost库设计出了unique_ptr,shared_ptr,weak_ptr

3.3.1unique_ptr

​ 实现很简单就是不允许拷贝和赋值;

​ 实现方式:1.只声明不实现,并私有;2.直接使用=delete,表示为删除函数,不允许生成;

​ 补充:对于不允许生成的函数只是声明然后=delete修饰,=default指定生成默认成员函数,也是只声明;

template <class T>
class unique_ptr
{
    public:
    unique_ptr(T* ptr) : ptr_(ptr) {}
    T& operator*()
    {
        return *ptr_;
    }
    T* operator->()
    {
        return ptr_;
    }
    ~unique_ptr()
    {
        std::cout << "delete" << std::endl;
        delete ptr_;
    }
    unique_ptr(unique_ptr<T>& ptr) = delete;
    unique_ptr<T>& operator=(unique_ptr<T>& ptr) = delete;

    // private:
    //     // 不允许拷贝
    //     unique_ptr(unique_ptr<T> &ptr);

    private:
    T* ptr_;
};

3.3.2shared_ptr

​ 总有一些场景是需要拷贝的,需要使用引用计数的技术来实现

注意:

​ 1.期望的是一个资源伴随着一个引用计数,即要动态管理,堆空间开辟计数器,所有对象都可以用指针访问,不能是多个对象管理,如果使用static int来管理,则对其他资源还需要创建其他的static int来管理;

​ 2.构造函数要初始化指针成员,计数器资源你也要初始化且为1;

​ 3.析构函数要注意判断是否引用计数减为0,如果是0才能释放资源,此资源包括两个一个指针一个计数器,注意优先级和结合性;

​ 4.拷贝构造函数除了指针成员的浅拷贝之外,还需要注意的是计数器资源也要浅拷贝,即获取同一块地址,然后引用计数++;

​ 5.赋值运算符重载要注意,赋值前要先将引用计数–,然后检查自己是否引用计数为0,是就释放两个资源。对于指向同一块资源的对象赋值,需要使用资源的地址来判断,使用对象的地址可能存在着,象的地址不同,但是指向的资源是相同的,如果地址相同直接返回,否则将引用计数–,如果此资源的引用计数为1,赋值将导致引用计数为0然后释放资源;如果上述问题都处理了就可以两个资源浅拷贝,然后将引用计数++;

//template <class T>
class shared_ptr
{
public:
    shared_ptr(T *ptr = nullptr) : ptr_(ptr), count_(new int(1)), del_([](T *ptr)
                                                                       { delete ptr; }) {}
    template <class D>
        shared_ptr(T *ptr, D del) : ptr_(ptr), count_(new int(1)), del_(del) {}
    T &operator*()
    {
        return *ptr_;
    }
    T *operator->()
    {
        return ptr_;
    }
    ~shared_ptr()
    {
        if (!(--(*count_)))
        {
            delete count_;
            // delete ptr_;
            del_(ptr_);
        }
    }
    T *get() const
    {
        return ptr_;
    }
    size_t use_count()
    {
        return *count_;
    }
    shared_ptr(const shared_ptr<T> &ptr) : ptr_(ptr.ptr_), count_(ptr.count_)
    {
        ++(*count_);
    }
    shared_ptr<T> &operator=(const shared_ptr<T> &ptr)
    {
        if (ptr.ptr_ == ptr_)
        {
            // 如果不处理自己给自己赋值,会造成引用计数减为0,释放自己;
            return *this;
        }
        if (!(--(*count_)))
        {
            std::cout << "delete"
                << " " << ptr_ << std::endl;
            delete count_;
            delete ptr_;
        }
        ptr_ = ptr.ptr_;
        count_ = ptr.count_;
        ++(*count_);
        return *this;
    }

private:
    T *ptr_;     // 指针指向资源
    int *count_; // 指针指向计数
    std::function<void(T *)> del_;
};

3.3.3weak_ptr

​ shared_ptr已经解决了很多的问题,但是还有循环引用这种问题没有解决;即ab两个资源分别被对方资源内部的智能指针管理,a资源的释放需要b中的智能指针释放,b中的智能指针释放需要b释放,b释放需要a中的智能指针释放,a中的智能指针的释放需要a释放,这样就形成了循环引用问题。

​ 针对循环引用的问题可以使用弱指针来解决,解决的原理就是不增加引用计数;

​ weak_ptr不增加引用计数,不参加资源释放的管理,可以访问资源;

要注意:weak_ptr不是RAII的智能指针,他的产生是专门为了解决循环引用问题;不允许使用原生指针进行初始化,只允许使用weak_ptr或者shared_ptr进行赋值;

解决方案:在ab资源内部使用weak_ptr而不是shared_ptr;

template <class T>
class weak_ptr
{
    public:
    weak_ptr(T* ptr = nullptr) : ptr_(ptr) {}
    weak_ptr(const weak_ptr<T>& ptr) : ptr_(ptr.ptr_) {}
    weak_ptr(const shared_ptr<T>& ptr) : ptr_(ptr.get()) {}
    weak_ptr<T>& operator=(const weak_ptr<T>& ptr)
    {
        ptr_ = ptr.ptr_;
        return *this;
    }
    weak_ptr<T>& operator=(shared_ptr<T>& ptr)
    {
        weak_ptr<T> tmp(ptr);
        std::swap(tmp.ptr_, ptr_);
        return *this;
    }
    T& operator*()
    {
        return *ptr_;
    }
    T* operator->()
    {
        return ptr_;
    }
    private:
    T* ptr_; // 指针指向资源
};

四、定制删除器

​ 外部控制删除的方式,是一个可调用对象;一般是shared_ptr和unique_ptr写了定制删除器,其他没写;

​ 可以注意到,很多智能指针初始化的时候使用的是new,也可能有使用malloc,new A[]等情况,但是内部的析构函数都是使用的delete,这样就会出现使用不匹配的情况;

​ 库里面使用了定制删除器,由于初始化时在外部进行的,所以可以在外部使用可调用对象进行控制析构函数的实现;

template <class T>
struct deletearray
{
    void operator()(T *ptr)
    {
        delete[] ptr;
    }
};
std::shared_ptr<node<int>> sp1(new node<int>[10], Smartptr::deletearray<node<int>>());
std::shared_ptr<node<int>> sp2(new node<int>[10], [](node<int> *ptr)
                                   { delete[] ptr; });

自己设计:

//对于shared_ptr需要注意的是库里面并没有将D写成类模板参数,而是写成了函数模板参数,这时就会遇到一个问题,这个可调用对象只有构造函数能看到,但是要让析构函数进行使用,解决方式,因为一定知道的是可调用对象的返回值是void,参数是T*,所以可以在shared_ptr类的内部写一个包装器成员,再写一个构造函数用来初始化包装器;
public:
shared_ptr(T* ptr = nullptr) : ptr_(ptr), count_(new int(1)), del_([](T* ptr)
            { delete ptr; }) {}
template <class D>
shared_ptr(T *ptr, D del/*此处是可调用对象*/) : ptr_(ptr), count_(new int(1)), del_(del) {}
~shared_ptr()
{
    if (!(--(*count_)))
    {
        delete count_;
        del_(ptr_);
    }
}
private:
    T *ptr_;     // 指针指向资源
    int *count_; // 指针指向计数
    std::function<void(T *)> del_;
//对于unique_ptr使用的方式是类模板参数传参,先实例化可调用对象,然后实现析构函数调用可调用对象;
  • 19
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值