全文约1800字,阅读时间约5分钟,阅读本文你可预期的收获:知晓智能指针的来龙去脉,通过比较不同的资源管理方式和他们的权衡来说明智能指针的作用。

什么是资源管理?

究竟我们为什么需要智能指针?我们不得不先明确什么叫做资源管理——我们编写代码的时候最重要的资源就是内存空间(当然,资源并不仅仅指的是内存,也许可以是句柄、网络套接字之类的,但是这里我们只谈内存),大部分C++er的第一份涉及内存管理的代码大概长这样:

ListNode* dummyHead = new ListNode(0);
//若干行代码逻辑
delete(dummyHead);

没错,这是经典的力扣链表题的虚拟头节点创建和销毁。
我们在这里使用了new和delete对虚拟头节点所占用的内存空间进行管理:

我非得用new与delete会怎么样?

使用new时,程序干了三件事:

  • 分配内存
  • 调构造函数
  • 返回指针

每一个new都必须对应一个delete,delete干了两件事:

  • 调析构函数
  • 释放内存

然而这种方式已经有点过时了!

为什么?因为就是很容易引入错误!拿我自己说,一道简简单单的链表力扣题,我也经常忘记写delete。

也许你会想,虚拟头节点并不是复杂的东西——不需要拷贝,不需要移动,不需要赋值,它只是一个创建后就不会动的东西罢了,也许是我太笨了,也许我只需要每次都记得delete就好了?

然而,new和delete在实际工程中做不到异常安全,因为new和delete要靠人写代码本身就不靠谱,人写的代码是顺序性的

new A;      // 第1步
do_B();     // 第2步
do_C();     // 第3步
delete A;   // 第4步

理想是到了第四步delete,我们回收资源,皆大欢喜,但是异常并不会乖乖按照人为定义好的顺序出现,但凡第一到三步抛出异常,delete都不会执行——导致资源泄露。

扪心自问:你敢在上线项目中写这种代码吗?

于是智能指针的作用来了

以unique_ptr为例,它是一个对象,对象就有析构函数:

~unique_ptr() {
    delete ptr;   // 清理代码在这里
}

我们的代码将会变成这样

auto A = std::make_unique<Widget>();  // 相当于 new A
do_B();
do_C();
// 不需要 delete A,自动完成

不论异常怎么抛出,我们将delete的逻辑写在析构里面的话,就能保证delete一定会执行,因为函数不论是如何退出的,最后都会调用到析构函数,这才真的皆大欢喜——我们轻松愉快地写出了安全的代码。

智能指针不是银弹,管中窥malloc/free的世界

其实说到底,new/delete的底层其实是malloc和free

// new 的底层实现伪码
void* operator new(size_t size) {
    void* ptr = malloc(size);      // 调用 malloc 拿内存
    if (!ptr) {
        // 省略:抛异常
    }
    return ptr;
}

// delete 的底层实现伪码
void operator delete(void* ptr) {
    free(ptr);                     // 直接调用 free
}

典中典的项目:内存分配器,是无法用智能指针的,你可以想下,智能指针本来就依赖于先存在一个new/malloc, delete/free,又怎么能用智能指针来创建内存分配器呢?哈哈。

如果你想不明白这个鸡生蛋,蛋生鸡的问题,不妨从系统层级的角度考虑:

以malloc为例,malloc封装了两个系统调用——对小的请求,使用brk,对于大块请求使用mmap。malloc所处理的问题层级是硬件资源级别的,而智能指针处理的问题级别是C++对象

尾声

智能指针是C++11引入的新特性,在此之前old school的C++程序员们必须刀耕火种地使用new/delete,面对着复杂的异常处理和安全性问题。之后,智能指针成为了现代C++程序的事实上的标准的资源管理编程方式——符合RAII(资源获取即初始化)思想。然而,智能指针也绝非银弹,在更接近硬件资源操作的层面——比如内存分配器领域,程序员们仍然使用着刀耕火种的编程方式,比如jemalloc这种项目,我们大量使用着它,这类软件是我们计算机世界的地基,我们使用智能指针的时候应该对这类项目的维护者们保持敬意。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐