张天昀的个人博客

C++ RAII/RRID特性及其使用

2020年04月15日

之前在高程中学了一点智能指针,学下来感觉智能指针除了计数没啥用了,今天看到了一些智能指针的用例。

RAII和RRID

  • RAII = Resource Acquisition Is Initialization
  • RRID = Resource Release Is Destruction

RAII与对象的生命周期有关,要求对象使用的资源(内存空间、文件描述符、套接字、互斥锁、数据库连接等)必须在使用前就已经获得。而RRID几乎相同,要求销毁对象是释放所使用的资源。

RAII保证任何时候使用对象都能访问到需要使用的资源:

  • 内存分配、打开文件描述符和套接字、线程通信(指尝试进入临界区)可能失败,但RAII对象一旦创建就表示内存分配、打开文件和套接字、上锁成功了,使用者不需要进行错误检测。
  • 由于C++ SBRM(Scope-Bound Resource Management)特性的影响,在程序运行到RAII对象的作用域结束时,RAII对象被自动销毁,此时自动释放所占有的资源。程序猿也不需要手动归还内存空间、手动关闭文件和套接字、手动解锁。
  • 使用RAII特性可以避免忘记判断错误、忘记归还内存、忘记关闭文件、忘记离开临界区等一系列难以调试的错误,极大的解放了生产力,隔壁Java都哭了(因为Java对象没有作用域)。

在C++中,RAII/RRID是通过类来实现的:

  • 在类的构造函数中获取资源;
  • 在类的析构函数中归还资源。

如此一来,只需要创建一个局部对象就可以保证资源可用,而又不需要手动的归还它了。

互斥锁

C++建议使用std::lock_guard来获取锁。

  • 进程中有一个std::mutex类型的互斥锁对象。
  • 进入临界区时,创建std::lock_guard(std::mutex &)对象;
  • lock_guard的生命周期结束被销毁,程序自动离开临界区。
std::mutex mtx;
void critical() {
  std::lock_guard<std::mutex> lock(mtx);
  // do something
}

程序猿无须在线程函数结束时手动释放锁离开临界区。

智能指针

当需要返回字符串、打开文件时,可以使用智能指针,这样只要所有使用该内存、文件的对象都被销毁了,智能指针计数变为0,也会被自动销毁。

void func() {
  Object foo, *bar;
  bar = new Object();
  {
    shared_ptr<File> file = new File("..."); // counter = 1, file opened
    foo.setFile(file);                       // counter = 2
    bar.setFile(file);                       // counter = 3
  }                                          // counter = 2
  delete bar;                                // counter = 1
}                                            // counter = 0, file closed

file的生命周期将会持续到foobar都被销毁之后,使用者无需关心文件有没有被关闭。

Reference