在介绍 RAII 之前,先来看一段代码 [1]
bool fn(std::mutex &someMutex, SomeDataSource &src) {
someMutex.lock();
BufferClass buffer;
if (not src.readIntoBuffer(buffer)) {
someMutex.unlock();
return false;
}
buffer.display();
someMutex.unlock();
return true;
}这段代码有哪些问题?
首先,代码中有繁杂的加锁和解锁操作。在函数退出前,someMutex 必须被解锁,否则会造成死锁。这意味着每次因操作错误提前退出时,都要重复 someMutex.unlock(); 和 return false; 这两条语句,不仅写起来麻烦,也降低了可读性。如果哪一次忘了解锁,还会造成死锁。
其次,这段代码不是异常安全的。如果在第 5 行 src.readIntoBuffer(buffer) 中抛出了异常,而函数 fn 又没有捕获异常,那么 fn 就会直接退出,第 6 行的解锁代码就不会执行,这又导致了死锁。
为了使 fn 能正确处理异常,需要使用 try 块包裹 4 ~ 9 行,并在 catch 里处理解锁。
bool fn(std::mutex &someMutex, SomeDataSource &src) {
someMutex.lock();
try {
BufferClass buffer;
if (not src.readIntoBuffer(buffer)) {
someMutex.unlock();
return false;
}
buffer.display();
}
catch (std::exception &e) {
someMutex.unlock();
throw e;
}
someMutex.unlock();
return true;
}当抛出异常时,fn 首先在 catch 里解锁 someMutex,再向上层抛出异常。
显然,try-catch 使代码更加复杂,可读性进一步降低。
一个解决方法是使用 goto 语句并禁用异常。在操作出错时,使用 goto 直接跳转到错误处理的代码,在那里解锁并退出。使用这种方法就不需要每次退出都重复解锁代码。禁用异常保证了控制流的简单。
bool fn(std::mutex &someMutex, SomeDataSource &src) {
someMutex.lock();
BufferClass buffer;
if (not src.readIntoBuffer(buffer))
goto unlock_and_exit;
buffer.display();
someMutex.unlock();
return true;
unlock_and_exit:
someMutex.unlock();
return false;
}虽然 goto 语句通常被认为是有害的,但这个例子属于少数可以考虑使用 goto 的情况。
不过,这看起来并不像 C++ 的风格,在 C++ 里,你可以更好地处理这种情形......
RAII,即资源获取即初始化(Resource Acquisition Is Initialization),是一种自动管理资源的设计思想。RAII 的核心在于:将资源的获取和释放与 C++ 对象的生命周期绑定。当对象初始化时,资源也被获取并被初始化,当对象销毁时,资源也被释放。这里的资源可以是一段内存、一个文件、一个网络连接,或是一切需要获取和释放的东西。
C++11 引入的智能指针就是 RAII 的典型例子。
#include <memory>
int main() {
{
std::unique_ptr<int> ptr = std::make_unique<int>(10);
}
}当变量 ptr 初始化时,一段内存已经被分配好了,里面的内容被初始化为 10,这是资源的获取,这里的资源指一段内存。
当程序退出 ptr 所在的语句块时,ptr 被销毁,原先分配的内存被释放,这是资源的释放。
可以看到,内存资源和 ptr 的生命周期紧密绑定,当 ptr 被初始化时,内存也被分配,当 ptr 被销毁时,内存也被释放。
C++11 还引入了 lock_guard 类。lock_guard 持有一个互斥量,它会在构造函数里对互斥量加锁,在析构函数里对互斥量解锁。有了 lock_guard,就可以不用处处手动加锁解锁互斥量了。
用 lock_guard 重写本文开头的例子:
bool fn(std::mutex &someMutex, SomeDataSource &src) {
std::lock_guard<std::mutex> lock(someMutex);
BufferClass buffer;
if (not src.readIntoBuffer(buffer))
return false;
buffer.display();
return true;
}当 lock 被构造时,构造函数会自动给 someMutex 加锁,因此不需要手动加锁。当 src.readIntoBuffer(buffer) 返回 false 时,函数会提前退出,lock 在退出前会自动销毁,在析构函数里解锁 someMutex,因此也不需要手动解锁。当 src.readIntoBuffer(buffer) 抛出异常时,函数提前退出,lock 同样也会自动销毁并解锁 someMutex,无需特殊处理。当函数正常退出时,同样会自动解锁。
可以说,lock_guard 利用其生命周期自动管理了互斥量的加锁解锁,大大简化了对互斥量的处理,不仅代码更简洁,bug 也更少。
在上面的例子中,资源是互斥量。接下来,看看如何使用 RAII 管理文件资源。
struct file_deleter {
void operator()(std::FILE *file) {
std::fclose(file);
}
};
bool fn(const char *filename) {
std::unique_ptr<std::FILE, file_deleter> file(std::fopen("file.txt", "r"));
if (file == nullptr)
return false;
if (!validate(file.get()))
return false;
return true;
}这里使用了 unique_ptr 的自定义删除器。当 file 变量销毁时,会自动将其管理的指针传给删除器 file_deleter ,关闭文件。这段代码同样保证了文件一定会被关闭,无论是函数提前退出,还是发生异常。
这个例子使用 fopen 而不是 C++ 的 fstream,是为了展示使用 RAII 机制管理 C 语言资源。如果是自己写的 C++ 项目,推荐使用 fstream 或其他现代 C++ 的设施。
善用 RAII 设计可以减少很多资源泄露。在现代 C++ 中,应避免使用 new 和 delete 手动管理内存,而应该使用智能指针自动管理内存。对于具有唯一所有权的资源,使用 unique_ptr,对于具有多个所有权或需要在多线程使用的资源,使用 shared_ptr,对于无所有权的资源观察者,应使用裸指针(不要 delete 裸指针)。
对于其他非内存资源,最好也使用 RAII 类封装,以便自动管理资源的释放。
参考
[1] CppCon2022 Back to Basics: RAII in C++ - Andre Kostur
14 条评论
华纳公司官方开户渠道?(183-8890-9465)-薇-STS5099【6011643】
如何通过官方渠道申请华纳公司账户?(183-8890-9465)-薇-STS5099【6011643】
华纳总公司官方开户指南?(183-8890-9465)-薇-STS5099【6011643】
华纳公司官方开户所需材料?(183-8890-9465)-薇-STS5099【6011643】
华纳官方开户流程?(183-8890-9465)-薇-STS5099【6011643】
华纳公司官方开户申请步骤?(183-8890-9465)-薇-STS5099【6011643】
华纳官方开户指南?(183-8890-9465)-薇-STS5099【6011643】
华纳总公司官方开户?(183-8890-9465)-薇-STS5099【6011643】
华纳公司官方开户所需材料?(183-8890-9465)-薇-STS5099【6011643】
华纳官方开户申请流程?(183-8890-9465)-薇-STS5099【6011643】
华纳公司官方开户渠道?(183-8890-9465)-薇-STS5099【6011643】
如何通过官方渠道申请华纳公司账户?(183-8890-9465)-薇-STS5099【6011643】
华纳总公司官方开户指南?(183-8890-9465)-薇-STS5099【6011643】
华纳公司官方开户所需材料?(183-8890-9465)-薇-STS5099【6011643】
华纳官方开户流程?(183-8890-9465)-薇-STS5099【6011643】
华纳公司官方开户申请步骤?(183-8890-9465)-薇-STS5099【6011643】
华纳官方开户指南?(183-8890-9465)-薇-STS5099【6011643】
华纳总公司官方开户?(183-8890-9465)-薇-STS5099【6011643】
华纳公司官方开户所需材料?(183-8890-9465)-薇-STS5099【6011643】
华纳官方开户申请流程?(183-8890-9465)-薇-STS5099【6011643】
华纳圣淘沙公司开户新手教程
零基础学会(183-8890-9465薇-STS5099)
华纳圣淘沙公司开户
华纳圣淘沙公司开户保姆级教程(183-8890-9465薇-STS5099)
一步步教你开通华纳圣淘沙公司账户(183-8890-9465薇-STS5099)
华纳圣淘沙公司开户分步图解
首次开户必看:(183-8890-9465薇-STS5099)
华纳圣淘沙全攻略
华纳圣淘沙公司开户实操手册(183-8890-9465薇-STS5099)
华纳圣淘沙开户流程视频教程
手把手教学:(183-8890-9465薇-STS5099)
华纳圣淘沙公司开户
华纳圣淘沙公司开户完全指南(183-8890-9465薇-STS5099)
华纳圣淘沙开户步骤详解(183-8890-9465—?薇-STS5099【6011643】
华纳圣淘沙公司开户流程全解析(183-8890-9465—?薇-STS5099【6011643】
华纳圣淘沙公司账户注册指南(183-8890-9465—?薇-STS5099【6011643】
新手如何开通华纳圣淘沙公司账户(183-8890-9465—?薇-STS5099【6011643】
华纳圣淘沙企业开户标准流程(183-8890-9465—?薇-STS5099【6011643】
华纳圣淘沙公司开户:从零到一(183-8890-9465—?薇-STS5099【6011643】
官方指南:华纳圣淘沙公司开户流程(183-8890-9465—?薇-STS5099【6011643】
华纳圣淘沙公司开户流程说明书(183-8890-9465—?薇-STS5099【6011643】
东方明珠客服开户联系方式【182-8836-2750—】?μ- cxs20250806
东方明珠客服电话联系方式【182-8836-2750—】?- cxs20250806】
东方明珠开户流程【182-8836-2750—】?薇- cxs20250806】
东方明珠客服怎么联系【182-8836-2750—】?薇- cxs20250806】
果博东方客服开户联系方式【182-8836-2750—】?薇- cxs20250806】
果博东方公司客服电话联系方式【182-8836-2750—】?薇- cxs20250806】
果博东方开户流程【182-8836-2750—】?薇- cxs20250806】
果博东方客服怎么联系【182-8836-2750—】?薇- cxs20250806】
果博东方客服开户联系方式【182-8836-2750—】?薇- cxs20250806】
果博东方公司客服电话联系方式【182-8836-2750—】?薇- cxs20250806】
果博东方开户流程【182-8836-2750—】?薇- cxs20250806】
果博东方客服怎么联系【182-8836-2750—】?薇- cxs20250806】
新盛客服电话是多少?(?183-8890-9465—《?薇-STS5099】【
新盛开户专线联系方式?(?183-8890--9465—《?薇-STS5099】【?扣6011643??】
新盛客服开户电话全攻略,让娱乐更顺畅!(?183-8890--9465—《?薇-STS5099】客服开户流程,华纳新盛客服开户流程图(?183-8890--9465—《?薇-STS5099】
华纳东方明珠客服电话是多少?(??155--8729--1507?《?薇-STS5099】【?扣6011643?】
华纳东方明珠开户专线联系方式?(??155--8729--1507?《?薇-STS5099】【?扣6011643?】
华纳东方明珠客服电话是多少?(▲18288362750?《?微信STS5099? 】
如何联系华纳东方明珠客服?(▲18288362750?《?微信STS5099? 】
华纳东方明珠官方客服联系方式?(▲18288362750?《?微信STS5099?
华纳东方明珠客服热线?(▲18288362750?《?微信STS5099?
华纳东方明珠24小时客服电话?(▲18288362750?《?微信STS5099? 】
华纳东方明珠官方客服在线咨询?(▲18288362750?《?微信STS5099?
华纳东方明珠客服电话是多少?(▲18288362750?《?微信STS5099? 】【╃q 2704132802╃】
华纳东方明珠开户专线联系方式?(▲18288362750?《?微信STS5099? 】【╃q 2704132802╃】
如何联系华纳东方明珠客服?(▲18288362750?《?微信STS5099? 】【╃q 2704132802╃】
华纳东方明珠官方客服联系方式?(▲18288362750?《?微信STS5099? 】【╃q 2704132802╃】
华纳东方明珠客服热线?(▲18288362750?《?微信STS5099? 】【╃q 2704132802╃】
华纳东方明珠开户客服电话?(▲182(▲18288362750?《?微信STS5099? 】【╃q 2704132802╃】
华纳东方明珠24小时客服电话?(▲18288362750?《?微信STS5099? 】【╃q 2704132802╃】
华纳东方明珠客服邮箱?(▲18288362750?《?微信STS5099? 】【╃q 2704132802╃】
华纳东方明珠官方客服在线咨询?(▲18288362750?《?微信STS5099? 】【╃q 2704132802╃】
华纳东方明珠客服微信?(▲18288362750?《?微信STS5099? 】【╃q 2704132802╃】
华纳总公司开户流程详解?(▲18288362750?《?微信STS5099? 】【╃q 2704132802╃】
华纳公司开户注册步骤?(▲18288362750?《?微信STS5099? 】【╃q 2704132802╃】
华纳公司合作开户所需材料?电话号码15587291507 微信STS5099
华纳公司合作开户所需材料?电话号码15587291507 微信STS5099
华纳公司合作开户所需材料?电话号码15587291507 微信STS5099
华纳公司合作开户所需材料?电话号码15587291507 微信STS5099
华纳公司合作开户所需材料?电话号码15587291507 微信STS5099
华纳公司合作开户所需材料?电话号码15587291507 微信STS5099
华纳公司合作开户所需材料?电话号码15587291507 微信STS5099
华纳公司合作开户所需材料?电话号码15587291507 微信STS5099