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
的生命周期将会持续到foo
和bar
都被销毁之后,使用者无需关心文件有没有被关闭。