都说 C++ 没有 GC,RAII: 那么我算个啥?

c++,没有,gc,raii,那么 · 浏览次数 : 155

小编点评

**C++ 中的资源回收** C++ 中没有自动垃圾回收器,这意味着当内存分配结束后,无法自动释放它们。这意味着当在循环中频繁使用变量或对象时,可能导致内存泄漏。 **一般 C++ 程序是如何回收资源的?** 一般 C++ 程序在内存分配后,会将申请的内存释放给系统。这可以通过以下几种方式实现: * **局部变量:**局部变量在函数退出时被自动释放。 * **堆:**类对象在构造函数中申请内存,在析构函数中释放内存。 * **智能指针:**智能指针可以自动管理和释放资源。 **RAII (资源获取是初始化) 的概念** RAII 是指在对象创建之前自动执行资源释放的操作。这可以帮助避免内存泄漏并提高代码可读性。 **C++ 11 中的智能指针** C++ 11 引入智能指针,它们可以自动管理和释放资源。使用智能指针可以简化代码并避免内存泄漏。 **实例** 以下是一个使用智能指针进行资源管理的示例: ```cpp #include #include using namespace std; class Helper { private: int* data; public: Helper() { data = new int; // 在堆上申请内存 } ~Helper() { delete data; // 释放堆上申请的内存 } void do_something_with_data() {} }; int main() { // 实例化智能指针对象,输入需要被管理的内存首地址 unique_ptr data(new int); cout << "data=" << *data << endl; // 使用智能指针调用成员函数 data->do_something_with_data(); // 释放资源 data.release(); return 0; } ``` **注意** 在实例化智能指针对象时,必须传入内存地址。

正文

*以下内容为本人的学习笔记,如需要转载,请声明原文链接 微信公众号「ENG八戒」https://mp.weixin.qq.com/s/7A9-tGZxf4w_7eZl3OUQ4A

学过 Java、C# 或者其他托管语言(managed languages)的同学,回过头来看 C++ 的时候,第一反应就是 C++ 没有自动垃圾回收器(GC),而不能充分利用的资源被称为垃圾。

那么 C++ 真的不能自动回收垃圾吗?带着这个疑问我们来看看一般 C++ 程序都是怎样回收资源的。

内存在计算机系统中是有限的资源,通常申请内存和释放内存是这样子的,假设有个被调用的函数 function():

void function()
{
    int *p = new int; // 申请内存

    // 资源申请下来了,不玩有个 p 用?
    // do something

    delete p; // 释放内存
}

这段示例代码在 function() 函数开始的时候申请了一块内存,大小对应于 int 类型,然后在函数结束的时候释放它。通常来说,这看起来很OK,没毛病,但是,如果遇到了下面几种情况呢?

  • 程序如果中途有逻辑让它提前退出 function() 函数
  • 发生了异常而没有被捕获到

那么在函数尾部执行释放内存的动作有几率不会被执行,意味着发生也会内存泄漏。像上面这段代码,如果调用的次数不多也不碍事,不过,如果循环调用 function(),这时泄露的内存资源会不断累积,而且一直被浪费掉,期间系统无法再次使用这些被浪费的内存,直到进程被终止,严重的话,会导致系统资源被耗尽,跑着跑着系统都崩溃了。这种 bug 在 C 范式的编程语言中真的很常见。

RAII 是什么

众所周知 C++ 具有面向对象的特性,在初始化类对象的时候,系统会调用类构造函数。如果类对象是存放在栈空间的话,比如声明为局部变量,那么当类对象超出生命周期时,比如退出局部变量的作用域,系统会调用这个对象的类析构函数;如果类对象是存放在堆空间的话,比如通过 new 操作符创建的类对象,那么当类对象被销毁时,比如对对象执行 delete 操作,系统同样会调用类析构函数。

C++ 的这个特性可以用来解决上面提到的资源泄露问题,怎么利用呢?

modern C++ 实践建议优先把资源存放在栈上。如果只是个变量类型,完全可以用局部变量的形式定义声明,这样代码块在退出后系统自动回收栈上的资源。

对上面的函数 function() 修改

void function()
{
    // 声明定义为局部变量,资源存储在栈区
    int data = 0;

    // do something with data

    // 函数退出时,自动释放 data 占用的空间
}

当资源比较占空间时,需要在堆上分配资源,可以通过指针引用它,资源的申请放在类的构造函数里,然后在析构函数里释放。下面举个例子

class Helper
{
private:
    int* data;
public:
    Helper() {
        data = new int; // 在堆上申请内存
    }
    ~Helper() {
        delete data; // 释放堆上申请的内存
    }
    void do_something_with_data() {}
};

void function()
{
    // 声明定义为局部变量,对象存储在栈区
    // 调用 Helper 类构造函数在堆上申请资源
    Helper help;

    // 通过对象 help 调用成员 data
    // 如果 data 是 Helper 私有成员
    // 在类外面必须通过类成员方法调用 data
    help.do_something_with_data();

    // 函数退出时,自动释放 help 对象占用的栈空间
    // 就算发生了异常或者中途退出都会执行这一步
    // help 对象被销毁时,调用 Helper 类析构函数
    // Helper 类析构函数释放已申请的堆上资源
}

利用这种特性的行为被 C++ 发明人称呼为 RAII,英文全称是「resource acquisition is initialization」,中文翻译过来是「资源获取即是初始化」。而我喜欢把它叫做上下文管理,实现资源申请释放的类叫做上下文管理器(context manager)。

经典实践--智能指针

上面的示例代码写起来略显啰嗦,为了推广这种设计核心思路和简化代码编写,在 C++ 11 之后标准库里添加了 unique_ptr。

unique_ptr 属于 Smart Points 中的一种,Smart Points 在国内通常翻译为「智能指针」。智能指针负责管理和释放资源。上面的 function() 函数可以改成这样子

#include <memory>
void function()
{
    // 实例化智能指针对象,输入需要被管理的内存首地址
    // 对象为局部变量,存储在栈区
    std::unique_ptr<int> data(new int);

    // 智能指针对象就像普通指针一样调用
    printf("data=%d\n", *data);

    // 函数退出时,自动释放 data 对象占用的栈空间
    // 就算发生了异常或者中途退出都会执行这一步
    // data 对象被销毁时,同步释放被管理的内存资源
}

可见,用了智能指针后,不需要像之前那样定义类 Helper (上下文管理器)了,代码清爽很多。

不过,上面的示例代码中有个地方需要注意,在实例化智能指针对象时必须传入内存地址,有没有其它更好的方式设置被管理的内存地址?

有的,C++ 14 之后标准库添加了 make_unique,演示一下怎么用

std::unique_ptr<int> data = std::make_unique<int>();

与都说 C++ 没有 GC,RAII: 那么我算个啥?相似的内容:

都说 C++ 没有 GC,RAII: 那么我算个啥?

学过 Java、C# 或者其他托管语言(managed languages)的同学,回过头来看 C++ 的时候,第一反应就是 C++ 没有自动垃圾回收器(GC),而不能充分利用的资源被称为垃圾。

记一次 .NET某报关系统 非托管泄露分析

## 一:背景 ### 1. 讲故事 前段时间有位朋友找到我,说他的程序内存会出现暴涨,让我看下是怎么事情?而且还告诉我是在 Linux 环境下,说实话在Linux上分析.NET程序难度会很大,难度大的原因在于Linux上的各种开源工具主要是针对 C/C++, 和 .NET 一毛钱关系都没有,说到底

谁说爬虫只能Python?看我用C#快速简单实现爬虫开发和演示!

前言:说到爬虫,基本上清一色的都知道用Python,但是对于一些没玩过或者不想玩Python的来说,却比较头大一点。所以以下我站在C# 的角度,来写一个简单的Demo,用来演示C# 实现的简单小爬虫。大家感兴趣可以自己拓展出更加丰富的爬虫功能。 前提:引用包HtmlAgilityPack 先来个爬取

谁说.net core不好动态访问webservice?看这篇文章,C#快速实现动态访问webservice,兼容.net framework和.net core+

前言:访问webservice,大多数人都是用服务引用的方式,但是这种方式比较麻烦,例如遇到服务更新了,你还需要手动更新你的服务引用,再重新发布,很麻烦。或者已有的一些例子,至少我看到的很多案例,动态访问也只能止步于使用.net framework环境,没看到有啥.net core上面动态访问的案例

PerfView专题 (第十五篇): 如何洞察 C# 中的慢速方法

## 一:背景 ### 1. 讲故事 在 dump 分析旅程中,经常会遇到很多朋友反馈一类问题,比如: * 方法平时都执行的特别快,但有时候会特别慢,怎么排查? * 我的方法第一次执行特别慢,能看到慢在哪里吗? 相信有朋友肯定说,加些日志不就好了,大方向肯定是没问题的,但加日志的颗粒度会比较粗而且侵

记一次 .NET 某自动化采集软件 崩溃分析

一:背景 1.讲故事 前段时间有位朋友找到我,说他的程序在客户的机器上跑着跑着会出现偶发卡死,然后就崩掉了,但在本地怎么也没复现,dump也抓到了,让我帮忙看下到底怎么回事,其实崩溃类的dump也有简单的,也有非常复杂的,因为大多情况下都是非托管层面出现的各种故障,非常考验对 C, C++, Win

.NET下免费开源的PDF类库(PDFSharp)

前言 目前.NET 体系下常见的PDF类库有Aspose、QuestPDF、Spire、iTextSharp等,有一说一都挺好用的,我个人特别喜欢QuestPDF它基于 C# Fluent API 提供全面的布局引擎;但是这些库要么属于商业库价格不菲(能理解收费),但是年费太贵了。要么是有条件限制开

C#中关于 object,dynamic 一点使用心得

首先说一下使用场景 WebAPI接口入参使用 object和 dynamic 后续解析和处理 1.object和dynamic 区别 在.NET中,object和dynamic也有一些区别: object:object是.NET中的顶级类,所有类都是object的子类。在C#中,您可以使用objec

golang技术降本增效的手段

最近一年各大中小厂都在搞"优化",说到优化,目的还是"降本增效",降低成本,增加效益(效率)。 技术层面,也有一些降本增效的常规操作。 比如池化、io缓冲区技术 | | golang | C# | eg. | | | | | | | 池化技术 | snnc.Pool | ObjectPool | 前

聊一聊对一个 C# 商业程序的反反调试

一:背景 1.讲故事 前段时间有位朋友在微信上找到我,说他对一个商业的 C# 程序用 WinDbg 附加不上去,每次附加之后那个 C# 程序就自动退出了,问一下到底是怎么回事?是不是哪里搞错了,有经验的朋友应该知道,其实这是 商业程序 的反调试机制捣鬼的,为了保护程序隐私,一般都不希望他人对自己做逆