刚学编程时,最常听到的一句话是不是“new 的内存用完要记得 delete,不然会造成内存泄漏”?然而事实上是:
跟踪引用计数,当最后一个 std::shared_ptr 对象离开作用域时,它会自动释放内存。
在远古时代,C++发明了指针这把双刃剑,既可以让程序员精确地控制堆上每一块内存,也让程序更容易发生crash,大大增加了使用指针的技术门槛。因此,从C++98开始便推出了auto_ptr,对裸指针进行封装,让程序员无需手动释放指针指向的内存区域,在auto_ptr生命周期结束时自动释放,然而,由于auto_ptr在转移指针所有权后会产生野指针,导致程序运行时crash,如下面示例代码所示:
转自: https://www.boost.org/doc/libs/1_74_0/libs/smart_ptr/doc/html/smart_ptr.html#techniques
C++的智能指针是一种特殊的指针类型,它能够自动管理内存资源,避免常见的内存泄漏和多次释放等问题。C++11引入了三种主要的智能指针:unique_ptr、shared_ptr和weak_ptr。
简单说,当我们独占资源的所有权的时候,可以使用 std::unique_ptr 对资源进行管理——离开 unique_ptr 对象的作用域时,会自动释放资源。这是很基本的 RAII 思想。
在C++编程中,使用智能指针是一种安全管理对象生命周期的方式。std::shared_ptr是一种允许多个指针共享对象所有权的智能指针。然而,当一个对象需要获取对自身的shared_ptr时,传统的方法可能导致未定义行为。为了解决这个问题,C++引入了std::enable_shared_from_this类,本文将深入探讨其基础知识、使用案例以及内部实现。
导语 | 智能指针在C++11标准中被引入真正标准库(C++98中引入的auto_ptr存在较多问题),但目前很多C++开发者仍习惯用原生指针,视智能指针为洪水猛兽。但很多实际场景下,智能指针却是解决问题的神器,尤其是一些涉及多线程的场景下。本文将介绍智能指针可以解决的问题、用法及最佳实践,并且根据源码分析智能指针的实现原理。 一、为什么需要使用智能指针 (一)内存泄漏 C++在堆上申请内存后,需要手动对内存进行释放。代码的初创者可能会注意内存的释放,但随着代码协作者加入,或者随着代码日趋复杂,很难保
具体来说,是在对象构造时获取资源,对资源的控制管理在整个对象的生命周期内都保持有效,并在对象析构时释放资源,也就是将资源的管理托管给一个对象,这有着一些好处:
多个shared_ptr管理同一个指针,仅当最后一个shared_ptr析构时,指针才被delete。这是怎么实现的呢?答案是:引用计数(reference counting)。引用计数指的是,所有管理同一个裸指针(raw pointer)的shared_ptr,都共享一个引用计数器,每当一个shared_ptr被赋值(或拷贝构造)给其它shared_ptr时,这个共享的引用计数器就加1,当一个shared_ptr析构或者被用于管理其它裸指针时,这个引用计数器就减1,如果此时发现引用计数器为0,那么说明它是管理这个指针的最后一个shared_ptr了,于是我们释放指针指向的资源。
导语: C++指针的内存管理相信是大部分C++入门程序员的梦魇,受到Boost的启发,C++11标准推出了智能指针,让我们从指针的内存管理中释放出来,几乎消灭所有new和delete。既然智能指针如此强大,今天我们来一窥智能指针的原理以及在多线程操作中需要注意的细节。 智能指针的由来 在远古时代,C++使用了指针这把双刃剑,既可以让程序员精确地控制堆上每一块内存,也让程序更容易发生crash,大大增加了使用指针的技术门槛。因此,从C++98开始便推出了auto_ptr,对裸指针进行封装,让程序员无需手
作者:lucasfan,腾讯 IEG Global Pub.Tech. 客户端工程师 智能指针在 C++11 标准中被引入真正标准库(C++98 中引入的 auto_ptr 存在较多问题),但目前很多 C++开发者仍习惯用原生指针,视智能指针为洪水猛兽。但很多实际场景下,智能指针却是解决问题的神器,尤其是一些涉及多线程的场景下。本文将介绍智能指针可以解决的问题,用法及最佳实践。并且根据源码分析智能指针的实现原理。 一、为什么需要使用智能指针 1.1 内存泄漏 C++在堆上申请内存后,需要手动对内存进行
一、shared_ptr类 头文件:#include<memory> 智能指针,是一个模板。创建智能指针时,必须提供指针所指的类型 如果当做前提条件判断,则是检测其是否为空 shared_ptr<string> p1; //指向stringshared_ptr<list<int>> p2;//指向int的listif(p1 && p1->empty())*p1="h1"; 二、make_shared函数 最安全的分配和使用动态内存的方法就是调用该函数 此函数在内存中动态分配对象并初始化,返回此对象的sh
我们知道shared_ptr是采用引用计数的智能指针,多个shared_ptr实例可以指向同一个动态对象,并维护了一个共享的引用计数器。
1. shared_ptr 介绍 使用过Boost的话对shared_ptr一定有很深的印象。多个shared_ptr指向同一个对象,每个shared_ptr会使对象的引用计数加+1,当引用计数为
shared_ptr的产生与unique_ptr类似,都是为了解决raw pointer的new和delete的成对使用,导致的野指针、内存泄漏、重复释放内存等。
C++11中推出了三种智能指针,unique_ptr、shared_ptr和weak_ptr,同时也将auto_ptr置为废弃(deprecated)。
学c++的人都知道,在c++里面有一个痛点,就是动态内存的管理,就我所经历的一些问题来看,很多莫名其妙的问题,最后都发现是内存管理不当引起的。
C++ 中使用 std::shared_ptr 智能指针不当有可能会造成循环引用,因为 std::shared_ptr 内部是基于引用计数来实现的, 当引用计数为 0 时,就会释放内部持有的裸指针。但是当 a 持有 b, b 也持有 a 时,相当于 a 和 b 的引用计数都至少为 1,因此得不到释放,RAII 此时也无能为力。这时就需要使用 weak_ptr 来打破循环引用。
在我们异常一节就已经讲过,当使用异常的时候,几个函数层层嵌套,其中如果抛异常就可能导致没有释放堆区开辟的空间。这样就很容易导致内存泄漏。关于内存泄漏,我也曾在C++内存管理一文中写过。
在上篇文章(内存泄漏-原因、避免以及定位)中,我们提到了用智能指针来避免内存泄漏,今天借助本文,从实践、避坑和实现原理三个角度分析下C++中的智能指针。
在上一讲《01 C++如何进行内存资源管理》中,提到了对于堆上的内存资源,需要我们手动分配和释放。管理这些资源是个技术活,一不小心,就会导致内存泄漏。
在谈到学习C++时,好多人都说它特别难,说它复杂。很可能有一部分原因就是C++的内存管理,在程序运行过程中很容易就会出现内存泄漏。然而从C++11引入的智能指针这一问题得到解决。
weak_ptr的产生,主要是为了配合shared_ptr的使用,对于shared_ptr来说,有一种循环引用会导致shared_ptr的引用计数一直不能变为0,如此以来导致内存一直不能释放的掉。例子可以参考下面例子:
C++11 中推出了三种智能指针,unique_ptr、shared_ptr 和 weak_ptr,同时也将 auto_ptr 置为废弃 (deprecated)。
Effective Modern C++(11&14)Chapter4: Smart Pointers 1. Introduction 原始指针 (raw pointer) p 的缺点 p 的声明不能暗示 p 指向的是单个对象还是一个数组 p 的声明不能暗示在使用完 p 后是否应该销毁 p 如果使用完 p 后决定销毁 p,无法知道是该使用 delete 还是其他析构机制来销毁 p 如果是使用 delete 来销毁 p,无法知道是该使用 delete 还是 delete[] 来销毁 p 即便知道了具体的销毁方
这个类可以完成智能指针的最基本的功能:对超出作用域的对象进行释放。但它缺了点东 西:
我们在上一节异常中提到了 C++ 没有垃圾回收机制,资源需要自己手动管理;同时,异常会导致执行流乱跳;所以 C++ 异常非常容易导致诸如内存泄露这样的安全问题。我们以下面的程序为例:
shared_ptr使用了引用计数,每一个shared_ptr的拷贝都指向相同的内存,每次拷贝都会触发引用计数+1,每次生命周期结束析构的时候引用计数-1,在最后一个shared_ptr析构的时候,内存才会释放。
Pool内存池: 只能开辟常规内存,数据类型为int,float,double,string等。
谈到C++,绕不开的一个特性是智能指针,智能指针见字如面:有两个概念:一个是指针,一个是“智能”。
《为何优先选用unique_ptr而不是裸指针?》中说到,如果有可能就使用unique_ptr,然后很多时候对象是需要共享的,因此shared_ptr也就会用得很多。shared_ptr允许多个指向同一个对象,当指向对象的最后一个shared_ptr销毁时,该对象也就会自动销毁。因此,善用shared_ptr,能够远离内存泄漏。
//智能指针式对裸指针进行包装,避免很对再使用裸指针时会遇到陷阱,为管理动态分配对象的生命周期设计
在基于C++的大型系统的设计实现中,由于缺乏语言级别的GC支持,资源生存周期往往是一个棘手的问题。系统地解决这个问题的方法无非两种:
Boost 库是一个由C/C++语言的开发者创建并更新维护的开源类库,其提供了许多功能强大的程序库和工具,用于开发高质量、可移植、高效的C应用程序。Boost库可以作为标准C库的后备,通常被称为准标准库,是C标准化进程的重要开发引擎之一。使用Boost库可以加速C应用程序的开发过程,提高代码质量和性能,并且可以适用于多种不同的系统平台和编译器。Boost库已被广泛应用于许多不同领域的C++应用程序开发中,如网络应用程序、图像处理、数值计算、多线程应用程序和文件系统处理等。
为了确保用 new 动态分配的内存空间在程序的各条执行路径都能被释放是一件麻烦的事情。C++ 11 模板库的 <memory> 头文件中定义的智能指针,即 shared _ptr 模板,就是用来部分解决这个问题的。 只要将 new 运算符返回的指针 p 交给一个 shared_ptr 对象“托管”,就不必担心在哪里写delete p语句——实际上根本不需要编写这条语句,托管 p 的 shared_ptr 对象在消亡时会自动执行delete p。而且,该 shared_ptr 对象能像指针 p —样使用,即假设托管 p 的 shared_ptr 对象叫作 ptr,那么 *ptr 就是 p 指向的对象。 通过 shared_ptr 的构造函数,可以让 shared_ptr 对象托管一个 new 运算符返回的指针,写法如下:
unique_ptr 类型智能指针在设计上最显著的特点是内部托管的指针一旦被创建就不能被任何形式的复制给另一个unique_ptr,只可以被移动给另一个unique_ptr。unique_ptr 没有拷贝构造函数,因此不能用于赋值。该指针最常用的情况是单例模式和编译防火墙的封装。
C++继承了C语言的指针,一直以来指针的一些问题困扰着开发人员,常见的指针问题主要有:内存泄露、野指针、访问越界等。值得庆幸的是C++标准委员会给我们提供了auto_ptr智能指针,后面又引入了share_ptr以及weak_ptr帮助我们正确和安全的使用指针,本文主要是介绍boost库提供的解决方案,期望通过本文能够给你提供一个新的天地。
一般情况下有三种内存,静态内存用于保存局部static对象、类static数据成员以及函数外被定义的变量,这种对象或者变量在使用之前被分配,程序结束时销毁;栈内存用于保存在函数内的非static对象,栈对象只在程序块运行时存在,程序流离开时销毁;以及自由空间,也叫堆,用来保存动态分配的对象,即运行时才被分配的对象,必须显示销毁。
new空间也有可能会抛出异常,对于p1如果抛出异常:没有问题,可以不管,直接到最外面去了。
boost 库中不单单这一种智能指针类型。但只有 shared_ptr 是最接近普通指针的一种智能指针,他具有一些非常良好的特性,比如计数器等等,使用 shared_ptr 可以让我们不用花费精力在内存回收上。其他的一些指针与 shared_ptr 的区别如下表:
智能指针除了像指针一样可以存储变量的地址,还提供了其他功能,比如可以管理动态内存分配,对引用进行计数等。
在学习异常的时候,我们知道了由于异常的反复横跳可能会导致内存泄露的问题,但是对于一些自定类类型来说他在栈帧销毁的时候会去调用对应的析构函数,但是以下这种必须手动释放的场景,一旦抛出异常就会造成内存泄露的结果。
大家好,今天是【重学C++】的第三讲,书接上回,第二讲《02 脱离指针陷阱:深入浅出 C++ 智能指针》介绍了C++智能指针的一些使用方法和基本原理。今天,我们自己动手,从0到1实现一下自己的unique_ptr和shared_ptr。
why:正确释放动态申请的内存 how:smart pointer(#include<memory>)
在讨论之前,我们先理清楚这样的一个简单但却容易混淆的逻辑。 std::shared_ptr 是个类模版,无法孤立存在的,因此实际使用中,我们都是使用他的具体模版类。这里使用 std::shared_ptr 来举例,我们讨论的时候,其实上是在讨论 std::shared_ptr 的线程安全性,并不是 SomeType 的线程安全性。
总结下个人对智能指针的理解,手写一个简易的c++实现,最后整理一下相关知识点,有不准确的部分欢迎大佬指正。
func函数中在堆中申请了资源,在func函数结束前也要释放资源,又因为异常的原因,所以抛异常的前面还需要再加一次资源释放,这非常的不方便,代码看起来也很差劲。 并且new本身也会抛异常,如果都抛异常了怎么办,也很容易导致内存泄漏。 所以这里就有了智能指针,这是大体思路:
智能指针主要用于管理在堆上分配的内存,它将普通的指针封装为一个栈对象。当栈对象的生存周期结束后,会在析构函数中释放掉申请的内存,从而防止内存泄漏。
领取专属 10元无门槛券
手把手带您无忧上云