C++智能指针的发展

这篇具有很好参考价值的文章主要介绍了C++智能指针的发展。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

智能指针

GC–garbage collection垃圾回收,Java里的机制。在头文件<memory>

内存泄漏

堆内存泄漏(Heap leak)

堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。

系统资源泄漏

指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。

内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。

采用RAII思想或者智能指针来管理资源。

RAII资源获得即初始化

RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。
相当于把资源生命周期和对象生命周期绑定在一起了==>在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:1.不需要显式地释放资源;2.采用这种方式,对象所需的资源在其生命期内始终保持有效。

// 使用RAII思想设计的SmartPtr类
template<class T>
class SmartPtr {
public:
    //保存资源
    SmartPtr(T* ptr = nullptr)
        : _ptr(ptr)
        {}
    //释放资源
    ~SmartPtr()
    {
        if(_ptr) delete _ptr;
    }
private:
	T* _ptr;
};

上述的SmartPtr还不能将其称为智能指针,因为它还不具有指针的行为。

C98 auto_ptr

原理如下:

  1. RAII特性
  2. 重载operator*和opertaor->,具有像指针一样的行为。

AutoPtr模板类中还得需要将* 、->重载下,才可让其像指针一样去使用。

template<class T>
class auto_ptr {
public:
    //保存资源
    auto_ptr(T* ptr = nullptr)
        : _ptr(ptr)
        {}
    //释放资源
    ~auto_ptr()
    {
        if(_ptr) delete _ptr;
    }
    //重载operator*
    T& operator*()
    {
        return *_ptr;
    }
    //重载operator->
    T* operator->()
    {
        return _ptr;
    }
private:
	T* _ptr;
};

存在的问题-对象悬空

auto_ptr类中没有写拷贝构造,构造函数就是浅拷贝,auto_ptr<int> auto_p1(new int); auto_ptr<int> autop2(auto_p2);这样的代码,就会造成资源重复析构。C98中对于auto_ptr资源重复析构提出的解决方法是资源管理器转移,即只有一个对象管一份资源,auto_p1就不管了,让auto_p2管==>会导致对象悬空,即auto_p1悬空。

auto_ptr(SmartPtr<T>& sp) :_ptr(sp.ptr) {
    // 管理权转移
    sp._ptr = nullptr;
}
auto_ptr<T>& operator=(SmartPtr<T>& sp) {
	// 检测是否为自己给自己赋值
    if (this != &sp) {
        // 释放当前对象中资源
        if (_ptr) delete _ptr;
        // 转移sp中资源到当前对象中
        _ptr = sp._ptr;
        sp._ptr = NULL;
    }
    return *this;
}

注意:解决办法绝对不能写深拷贝!!因为要模拟的是原生指针,且这份资源归属是用户的,咱不能偷偷地copy一份。

C++11 unique_ptr

借鉴的是boost的scoped_ptr(防拷贝的智能指针),只是在auto_ptr的基础上加了两句。详细的模拟实现请参考我的gitee库

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

C++11 shared_ptr

shared_ptr本身是线程安全的,用锁保护了引用计数的++/–,但它管理的资源不是线程安全的,需要用户手动控制。共享指针的mutex保护的是自己的计数器的线程安全,不保护资源的线程安全。

借鉴的是boost的shared_ptr(可以拷贝的智能指针),原理是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。

  1. shared_ptr在其内部,给每个资源都维护了着一份计数器,用于记录该份资源被几个对象共享。
  2. 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减一。
  3. 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。

计数器的实现

可以采用 static map<T* ptr, static int count>; ,但是后续加锁是个大问题。

简单的方法就是申请新资源的时候再开一块计数器空间,让对象中的一个指针指向资源,另个指针指向计数器空间。

成员变量的计数器要写成int* count; ,可以实现对单份资源的管理,让一份资源拥有一个计数器

不能写int count; 这个变成了单个对象的计数;

也不能写static int count; 因为静态成员变量是属于整个类的,所有对象均可以访问,相当于公共计数器,不是独一份资源的计数器。

锁的实现

一份资源对应一把锁,故写成mutex* _mutex;

在用shared_ptr的时候,如何保证资源的线程安全?**重新用一把锁保护资源!**注意此处不能去用计数器的锁,因为两块资源不是一样的空间。

存在的问题-循环引用

在如下所示struct ListNode对象创建的双向链表处会出问题,因为其成员变量是std::shared_ptr<ListNode>类型的,node1本身和node2._prev会同时指向node1的引用计数,node2本身和node1._next会同时指向node2的引用计数。

node1和node2都是局部对象,出了作用域就会销毁,但是他们的_prev_next没法对引用计数器–。类对象的成员什么时候销毁?对象销毁的时候,其成员变量才会销毁,但是node2要等node1._next销毁了才会销毁,node1要等node2._prev销毁了才会销毁,这就造成了死循环,谁都没有销毁。

拓展来说,假设有两个类,类A中有个成员管理着类B的另一个成员,类B中有个成员管理着类A的另一个成员,这也会造成循环引用。

struct ListNode
{
    std::shared_ptr<ListNode> _prev;
    std::shared_ptr<ListNode> _next;
    ~ListNode() {std::cout << "~ListNode()" << std::endl; }//仅为观察实验现象,无其他作用
};
int main()
{
    std::shared_ptr<ListNode> node1(new ListNode);
    std::shared_ptr<ListNode> node2(new ListNode);
    node1->_next = node2;//赋值构造 会对node1的引用计数做++
    node2->_prev = node1;//赋值构造 会对node2的引用计数做++
    return 0;
}

只要屏蔽node1->_next = node2;或者node2->_prev = node1;里的任何一句,都不会有循环引用的问题。

C++11 weak_ptr

没有使用RAII思想。其功能是可以指向资源/访问资源,但是不参与资源的管理,不增加引用计数与shared_ptr搭配使用。

struct ListNode
{
    std::weak_ptr<ListNode> _prev;//使用weak_ptr来解决
    std::weak_ptr<ListNode> _next;//使用weak_ptr来解决
    ~ListNode() {std::cout << "~ListNode()" << std::endl; }//仅为观察实验现象,无其他作用
};
int main()
{
    std::shared_ptr<ListNode> node1(new ListNode);
    std::shared_ptr<ListNode> node2(new ListNode);
    node1->_next = node2;//赋值构造 会对node1的引用计数做++
    node2->_prev = node1;//赋值构造 会对node2的引用计数做++
    return 0;
}

C++11 default_delete

以上4种指针只能管理单个new空间,因为delete在释放资源的时候,需要考虑到是数组(要用delete[])还是单个数(delete即可),new[]delete[]要匹配,否则可能会导致问题。

于是C++11引入定制删除器,默认的定制删除器用的是delete。constexpr shared_ptr() noexcept;

用到定制删除器可以看这个构造函数template <class U, class D> shared_ptr (U* p, D del);,其中del可以用函数指针、仿函数、lambda表达式。文章来源地址https://www.toymoban.com/news/detail-418359.html

template<class T>
struct delete_array
{
	operator()(const T* ptr)
	{
		delete[] ptr;
		cout << "delete[] :" << ptr << endl;
	}
};

int main()
{
	std::shared_ptr<int> sp1(new int[10], delete_array<int>());
    //仿函数
	std::shared_ptr<string> sp2(new string[10], delete_array<string>());
    //lambda表达式
	std::shared_ptr<string> sp3(new string[10], [](string* ptr) {delete[] ptr; });
	std::shared_ptr<FILE> sp4(fopen("text.txt", "r"), [](FILE* ptr) {fclose(ptr); });
	
	return 0;
}

结合定制删除器改造shared_ptr

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

template<class T, class D = defaule_delete<T>>
class shared_ptr
{
public:
    shared_ptr(T* ptr = nullptr) :_ptr(ptr), _pRefCount(new int(1)), _mutex(new mutex)
    {}

    void Release()
    {
        int flag = 0;//标记是否需要释放锁,这是局部变量(独立栈空间)
        _mutex->lock();
        if (--(*_pRefCount) == 0)
        {
            flag = 1;
            //delete _ptr;
            _del(_ptr);
            delete _pRefCount;
        }
        _mutex->unlock();

        if (flag) delete _mutex;
    }

    ~shared_ptr()
    {
        Release();
    }
    shared_ptr(const shared_ptr<T>& sp) :_ptr(sp._ptr), _pRefCount(sp._pRefCount), _mutex(sp._mutex)
    {
        _mutex->lock();
        ++(*_pRefCount);
        _mutex->unlock();
    }
    shared_ptr<T>& operator=(const shared_ptr<T>& sp)
    {
        if (sp._ptr != _ptr)
        {
            Release();
            _ptr = sp._ptr;
            _pRefCount = sp._pRefCount;
            _mutex = sp._mutex;

            _mutex->lock();
            ++(*_pRefCount);
            _mutex->unlock();
        }

        return *this;
    }

    int use_count() { return *_pRefCount; }
    T* get()
    {
        return _ptr;
    }
    // 像指针一样使用
    T& operator*() { return *_ptr; }
    T* operator->() { return _ptr; }
    T* get() const { return _ptr; }
private:
    T* _ptr;
    int* _pRefCount;
    mutex* _mutex;
    D _del;
};

template<class T>
struct delete_array
{
    void operator()(const T* ptr)
    {
        delete[] ptr;
        //cout << "delete[]" << endl;
    }
};

struct close_file
{
    void operator()(FILE* ptr)
    {
        fclose(ptr);
    }
};
int main()
{
    shared_ptr<int, delete_array<int>> sp1(new int[5]);
    shared_ptr<string, delete_array<string>> node1(new string[5]);
    shared_ptr<FILE, close_file> file1(fopen("test.txt", "r"));
    //shared_ptr<int> sp2(new int);
    return 0;
}

到了这里,关于C++智能指针的发展的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C++——智能指针

    内存泄漏 什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内 存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对 该段内存的控制,因而造成了内存的浪费。 内存泄漏的危害:长期运行的

    2024年02月09日
    浏览(72)
  • C++进阶 智能指针

    本篇博客简介:介绍C++中的智能指针 我们首先来看下面的这段代码 在上面这段代码中有着一个很明显的内存泄露风险 当我们的程序运行在Func函数内的div函数时 很可能因为除0错误而跳转到另外一个执行流从而导致Func函数内两个new出来的内存没法被回收 为了解决这个问题我

    2024年02月13日
    浏览(50)
  • 智能指针——C++

    智能指针相较于普通指针的区别,就是智能指针可以不用主动释放内存空间,系统会自动释放,避免了内存泄漏。 需包含的头文件: #include memory unique_ptr 三种定义方式 先定义一个类 使用std::make_unique的方式:推荐使用的方式 程序结束自动调用析构函数 使用new的方式声明智能

    2023年04月22日
    浏览(49)
  • 【C++学习】智能指针

    🐱作者:一只大喵咪1201 🐱专栏:《C++学习》 🔥格言: 你只管努力,剩下的交给时间! 如上图代码所示,在Func中开辟动态空间,在调用完Division函数后释放该空间。 如果Division没有抛异常,那么动态空间会被正常释放。 如果Division抛了异常,就会去匹配对应的catch,而Fu

    2024年02月06日
    浏览(57)
  • c++ 学习系列 -- 智能指针

    C++ 程序设计中使用堆内存是非常频繁的操作,堆内存的申请和释放都由程序员自己管理。但使用普通指针,容易造成内存泄露(忘记释放)、二次释放、程序发生异常时内存泄露等问题等。 另外,使用普通指针容易产生 野指针、悬空指针 等问题。 所以 C++11 就引入了智能指

    2024年02月13日
    浏览(59)
  • 面试:C++ 11 智能指针

    内存泄露在维基百科中的解释如下: 在计算机科学中,内存泄漏指由于疏忽或错误造成程序未能释放已经不再使用的内存。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了

    2024年02月07日
    浏览(43)
  • 【C++入门到精通】智能指针 [ C++入门 ]

    在C++编程中,内存管理一直是一个重要的话题。手动分配和释放内存可能会导致各种问题,例如内存泄漏和悬挂指针,这些问题往往会导致程序崩溃或产生不可预测的结果。为了解决这些问题, C++提供了一种称为智能指针的机制,它可以自动管理内存分配和释放,从而避免了

    2024年01月21日
    浏览(45)
  • C++知识点 -- 智能指针

    对于上面的场景,p1和p2在new申请空间后,div函数如果出现了除0错误,那么程序就会抛出异常,跳到接受异常的程序段继续执行,p1和p2申请的空间就没有被正常释放,造成了内存泄漏; 这种场景我们就可以使用智能指针来解决空间的释放问题。 RAII(Resource Acquisition Is Initia

    2024年02月03日
    浏览(61)
  • 【C++】智能指针(RAII)详解

      我们在上篇文章中(异常处理详解)提到了 RAII 。那么本篇文章会对此进行详解。重点是智能指针的详解。其中会讲解到 RAII 思想、auto_ptr、unique_ptr、shared_ptr、weak_ptr、循环引用问题 。希望本篇文章会对你有所帮助。 文章目录 一、为什么需要智能指针 二、智能指针的使用

    2024年02月10日
    浏览(37)
  • 面试—C++《智能指针》常考点

    目录 1.为什么需要智能指针 2. 内存泄漏 2.1 什么是内存泄漏,内存泄漏的危害 2.2 内存泄漏分类 2.3 如何检测内存泄漏 2.4如何避免内存泄漏 3.智能指针的使用及原理 3.3 std::auto_ptr 3.4 std::unique_ptr 3.5 std::shared_ptr  下面我们先分析一下下面这段程序有没有什么内存方面的问题?

    2023年04月11日
    浏览(33)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包