Resource Management in Modern C++

date
Aug 21, 2024
slug
smart-ptr-cpp
status
Published
tags
C++
summary
type
Post

1 - 手动管理资源的限制和挑战

C++ 的设计哲学是给予程序员全面的控制权,这意味着我们需要自己注意很多事情。
比如,在编写程序完成任务时,我们常常需要使用各种形式的资源,对于这些资源我们需要管理他们的生命周期:在获取资源时正确的初始化,在使用结束后正确的释放。
然而,手动进行资源的管理是繁琐的:
  • 需要在每个执行路径释放资源。
  • 需要在每种异常退出情况释放资源。
  • ……
几种情况的组合之下使得手动管理资源的方式变得不可持续,使得我们重新思考:
  • 获得全面的控制是否意味着我们就需要手动完成所有 low-level 的事情?

2 - RAII & Ownership

事实上拥有完全的控制权并不意味着需要完全手动管理资源,我们可以借助语言提供的机制进行一些更容易的操作:
  • 使用对象管理资源,不直接操纵资源而是操作拥有他们的对象。
  • RAII: 在对象的构造函数中初始化资源,在析构函数中释放他们。
  • Ownership: 使用 copy / move 构造或赋值管理所有权的转移。
 

3 - More Ownership

当一个对象拥有一些资源的 ownership, 这意味着它需要承担这些资源的获取,释放和 ownership 交接相关的动作,而不是由用户来承担。
ownership 可以分为两种类型:
  • unique ownership: 一个对象独享一组资源,它单独承担管理它们的责任。
  • shared ownership: 多个对象共同管理一组资源,最后一个析构的对象负责资源的释放。
 

4 - C++ 智能指针

在 C++ 11 之前,我们使用裸指针,存在如下问题:
  • 对于一个 T* 指针,不好判断到底应该由谁来释放它管理的资源,甚至不直接知道它到底在堆还是栈上。
  • 对于一个 T* 指针,无法判断它是由 new T 得到的单一对象还是 new T[N] 得到的数组,进而就不知道调用 delete 还是 delete[]
  • 难以在所有路径和异常情况释放资源
C++ 11 开始加入了新的智能指针 std::unique_ptr<T> , std::shared_ptr<T> 和 std::weak_ptr<T>, 用来协助实现资源的自动管理。
  • std::unique_ptr<T> = unique ownership
  • std::shared_ptr<T> = shared ownership
  • std::weak_ptr<T> = shared ownership (not quite)

4.1 - std::unique_ptr<T>

unique_ptr 代表独占所有权,单独对指向资源的管理负责,在 unique_ptr 离开作用域或者被赋予新的对象时释放资源。

4.2 - std::shared_ptr<T>

shared_ptr 代表共享所有权,具体来说其实现是引用计数,每个 shared_ptr 会指向一个控制块,这个控制块包含引用计数,弱计数(和 weak_ptr 有关),被管理的对象(如果是从 std::make_shared 构建的)或者指向它的指针,以及一些其他信息。
在以下情况下 shared_ptr 会创建一个新的控制块:
  • 用裸指针传入构造函数构造的 shared_ptr:此时假定这个裸指针还没被管理,因此需要创建控制块。
  • 使用 std::make_shared:此时对象也是一起创建的,还没有控制块,因此需要创建。
  • 从 unique_ptr 转换而来时:此时当前 shared_ptr 是第一个拥有对象所有权的,因此需要创建。
总结来说就是如果认为是第一个拥有所有权的对象就会创建。这也带来了某些情况下的奇怪的 bug: 把同一个裸指针传入两个 shared_ptr,这可能会造成 dangling pointer.
 

4.3 - std::weak_ptr<T>

weak_ptr 用来在不增加引用计数的情况下模拟共享所有权,其并不真的拥有所有权,无法解引用,但是可以在 shared_ptr 所指向的资源还没释放的时候充当某种观察的角色。weak_ptr 可以申请转换为 shared_ptr 来使用指向的资源。
 

5 - Raw Pointers + Smart Pointers

有了智能指针之后,裸指针在很多时候仍然很方便,解决了 RAII 和所有权的问题之后,我们可以结合使用两种指针:
  • 用智能指针来表示所有权
  • 用裸指针 / 引用来表示引用而不管理。

© Lifan Sun 2023 - 2024