浅谈 C++ RAII 实现方案
RAII 规范
什么是RAII?
资源获取即初始化(资源析构即释放)
RAII(Resoure Acquisition is Initialization)范式(即:对象构造的时候所需的资源应该在构造函数中初始化,而对象析构的时候应该释放资源)。RAII 告诉我们应该应用类来封装和管理资源。
RAII 示例
哪些资源需要使用 RAII 机制保护?
| 分类 | 示例 | 适用范围 |
|---|---|---|
| STL | ||
| 智能指针 | ||
| 锁 |
不同场景推荐 RAII 方案
内存管理
// 手动管理 void func1(int len) { int* buf = new int[len]; input_buffer(buf); if (...) { run(buf); } print_buffer(buf); delete buf; } // vector 管理 void func2(int len) { std::vector<int> buf; buf.resize(len); input_buffer(buf.data()); if (...) { run(buf.data()); } print_buffer(buf.data()); } // unique_ptr 管理 void func3(int len) { std::unique_ptr<int[]> buf(new int[len]); input_buffer(buf.get()); if (...) { run(buf.get()); } print_buffer(buf.get()); } 复制代码推荐:
操作管理
RXScopeGuard
智能指针是对对象的内存进行管理,但对于操作层面并不方便处理(如文件关闭)。
class RXScopeGuard { public: explicit RXScopeGuard(std::function<void()> on_exit_scope) : on_exit_scope_(on_exit_scope) {} ~RXScopeGuard() { on_exit_scope_(); } private: std::function<void()> on_exit_scope_; }; // 避免变量命名问题,通过 function & line 自动命名变量名 #define _RX_SCOPEGUARD_NAME_(name, line) name##line #define RX_SCOPEGUARD_NAME_(line) _RX_SCOPEGUARD_NAME_(_on_exit_, line) #define RX_ON_SCOPE_EXIT(callback) \ RXScopeGuard RX_SCOPEGUARD_NAME_(__LINE__)(callback) 复制代码示例
void func() { std::fstream fs("filename", ios::binary); RX_ON_SCOPE_EXIT( [&] { fs.close(); } ); // fs.read( ... ); int* buf = new int[__LINE__]; RX_ON_SCOPE_EXIT( [&] { delete[] buf; } ); // scanf(buf); std::FILE* ff = std::fopen("filename2", "r"); RX_ON_SCOPE_EXIT( [&] { std::fclose(ff); } ); // std::fread(ff, ... ); } 复制代码优点:
缺点
RXScopeGuard1
改进方案:使用函数指针
template <typename T, typename Ref = typename std::add_lvalue_reference<T>::type, typename U = typename std::conditional<std::is_pointer<T>::value, T, Ref>::type> class RXScopeGuard1 { public: using OnExitScopeFunc = void (*)(U); explicit RXScopeGuard1(OnExitScopeFunc on_exit_scope, U arg) : on_exit_scope_(on_exit_scope), arg_(arg) {} ~RXScopeGuard1() { on_exit_scope_(arg_); } private: OnExitScopeFunc on_exit_scope_; U arg_; }; // 避免变量命名问题,通过 function & line 自动命名变量名 #define RX_SCOPEGUARD_NAME_(line) _on_exit_##line #define RX_ON_SCOPE_EXIT_1(callback, obj) \ RXScopeGuard1<decltype(obj)> RX_SCOPEGUARD_NAME_(__LINE__)(callback, obj) 复制代码void rxOnExitScope_DeleteArray(char* buf) { RX_LOG(kRXLSError) << "~ delete[] buf"; delete[] buf; } void rxOnExitScope_StdClose(std::FILE* file) { RX_LOG(kRXLSError) << "~ std::fclose(file)"; std::fclose(file); } void rxOnExitScope_StdIfstreamClose(std::ifstream& ifs) { RX_LOG(kRXLSError) << "~ ifs.close()"; ifs.close(); } void func() { char* buf = new char[128]; // RX_ON_SCOPE_EXIT_1(rxOnExitScope_DeleteArray, buf); RX_ON_SCOPE_EXIT_1( [](char* buf) { RX_LOG(kRXLSError) << "~ delete[] buf"; delete[] buf; }, buf); { std::ifstream ifs("./a.txt", std::ios::binary); // RX_ON_SCOPE_EXIT_1(rxOnExitScope_StdIfstreamClose, ifs); RX_ON_SCOPE_EXIT_1( [](std::ifstream& ifs) { RX_LOG(kRXLSError) << "~ ifs.close()"; ifs.close(); }, ifs); // ifs.read( ... ); std::FILE* ff = std::fopen("./a.txt", "r"); // RX_ON_SCOPE_EXIT_1(rxOnExitScope_StdClose, ff); RX_ON_SCOPE_EXIT_1( [](std::FILE* file) { RX_LOG(kRXLSError) << "~ std::fclose(file)"; std::fclose(file); }, ff); // std::fread( ... ); } } 复制代码运行结果
~ ifs.close() ~ std::fclose(file) ~ delete[] buf 复制代码
优点:
缺点
RXScopeGuardList
使用 void* 数组保存输入参数的指针。
template<uint8_t kSize> class RXScopeGuardList { public: using OnExitScopeFunc = void (*)(void**); OnExitScopeFunc on_exit_scope_; void* args_[kSize]; ~RXScopeGuardList() { on_exit_scope_(args_); } }; #define RX_ARG_T(t) t #define RX_ARG_N(a1, a2, a3, a4, a5, N, ...) N #define RX_ARG_N_HELPER(...) RX_ARG_T(RX_ARG_N(__VA_ARGS__)) // 计算可变参数的数量(必须在 [0, 5] 范围内) #define RX_COUNT_ARG(...) RX_ARG_N_HELPER(__VA_ARGS__, 5, 4, 3, 2, 1, 0) #define _RX_SCOPEGUARD_NAME_(name, line) name##line #define RX_SCOPEGUARD_NAME_(line) _RX_SCOPEGUARD_NAME_(_on_exit_, line) #define RX_ON_SCOPE_EXIT_LIST(callback, arg, ...) \ RXScopeGuardList<RX_COUNT_ARG(arg, ##__VA_ARGS__)> RX_SCOPEGUARD_NAME_(__LINE__) { \ callback, arg, ##__VA_ARGS__ \ } 复制代码void func() { char* buf = new char[128]; std::ifstream ifs("./a.txt", std::ios::binary); std::FILE* ff = std::fopen("./a.txt", "r"); // args[] 的参数顺序等于输入参数的顺序 RX_ON_SCOPE_EXIT_LIST([](void** args){ // 1. ifs RX_LOG(kRXLSError) << "~ ifs.close()"; static_cast<std::ifstream*>(args[0])->close(); // 2. ff RX_LOG(kRXLSError) << "~ std::fclose(file)"; std::fclose(static_cast<std::FILE*>(args[1])); // 3. buf RX_LOG(kRXLSError) << "~ delete[] buf"; delete[] static_cast<char*>(args[2]); }, &ifs, ff, buf); // fs.read( ... ); // std::fread( ... ); } 复制代码优点:
缺点:
正确的示例
void func() { char* buf = new char[128]; RX_ON_SCOPE_EXIT_LIST([](void** args){ RX_LOG(kRXLSError) << "~ delete[] buf"; delete[] static_cast<char*>(args[0]); }; std::ifstream ifs("./a.txt", std::ios::binary); RX_ON_SCOPE_EXIT_LIST([](void** args){ RX_LOG(kRXLSError) << "~ ifs.close()"; static_cast<std::ifstream*>(args[0])->close(); }; std::FILE* ff = std::fopen("./a.txt", "r"); RX_ON_SCOPE_EXIT_LIST([](void** args){ RX_LOG(kRXLSError) << "~ std::fclose(file)"; std::fclose(static_cast<std::FILE*>(args[0])); }; // fs.read( ... ); // std::fread( ... ); } 复制代码封装成 class
RXScopeGuard / RXScopeGuardList 作用域范围比较小,一般用于函数内部。
示例:消息订阅和取消订阅
class RXTest { public: void init() { message_center = RXGlobalContext->message_center; // 1. 订阅消息 message_center->subscribe_message(handler1, RXMessageFilter{...}); message_center->subscribe_message(handler2, RXMessageFilter{...}); message_center->subscribe_message(handler3, RXMessageFilter{...}); } RXTest() { init(); } ~RXTest() { // 2. 取消订阅消息 message_center->unsubscribe_all_messages(handler1); message_center->unsubscribe_all_messages(handler2); message_center->unsubscribe_all_messages(handler3); } private: RXMessageCenter* message_center; RXMessageHandler* handler1; RXMessageHandler* handler2; RXMessageHandler* handler3; }; 复制代码RXMessageSubscriber
class RXMessageSubscriber { public: RXMessageSubscriber(RXMessageCenter* center, RXMessageHandler* handler) : center_(center), handler_(handler) {} ~RXMessageSubscriber() { // 封装了 取消所有消息订阅 的操作 center_->unsubscribe_all_messages(handler_); } void subscribe_message(RXMessageFilter filter, RXMessageQueueInterface* queue = nullptr) { center_->subscribe_message(handler_, filter, queue); } private: RXMessageCenter* center_; RXMessageHandler* handler_; }; class RXTest { public: void init() { message_center = RXGlobalContext->message_center; // 1. 订阅消息: 相当于封装了一个 unsubscribe_all_messages 操作 subscribers.emplace_back(message_center, handler1); subscribers.back()->subscribe_message(RXMessageFilter{...}); subscribers.emplace_back(message_center, handler2); subscribers.back()->subscribe_message(RXMessageFilter{...}); subscribers.emplace_back(message_center, handler3); subscribers.back()->subscribe_message(RXMessageFilter{...}); } RXTest() { init(); } private: RXMessageCenter* message_center; std::vector<RXMessageSubscriber> subscribers; }; 复制代码优点:
缺点:
对资源回收有顺序要求的示例
class RXEngine { public: void init() { message_center = new RXMessageCenter{}; message_queue = new RXMessageQueue{}; // 1. 订阅消息 message_center->subscribe_message(handler1, RXMessageFilter{...}); message_center->subscribe_message(handler2, RXMessageFilter{...}); message_center->subscribe_message(handler3, RXMessageFilter{...}, message_queue); } void destory() { // 2.1 取消订阅消息 message_center->unsubscribe_all_messages(handler1); message_center->unsubscribe_all_messages(handler2); message_center->unsubscribe_all_messages(handler3); // 2.2 清空任务 delete message_queue; // 2.3 移除消息中心 delete message_center; } private: RXMessageCenter* message_center; RXMessageHandler* handler1; RXMessageHandler* handler2; RXMessageHandler* handler3; RXMessageQueueInterface* message_queue; std::unique_ptr<RXScopeGuardList<5>> scope_gurad_list_; }; 复制代码资源回收有顺序依赖
class RXEngine { public: void init() { message_center = new RXMessageCenter{}; message_queue = new RXMessageQueue{}; // 1. 订阅消息 message_center->subscribe_message(handler1, RXMessageFilter{...}); message_center->subscribe_message(handler2, RXMessageFilter{...}); message_center->subscribe_message(handler3, RXMessageFilter{...}, message_queue); scope_gurad_list_ = std::make_unique<RXScopeGuardList<5>>( [](void** args) { RXMessageCenter* center = static_cast<RXMessageCenter*>(args[4]); // 2.1 取消订阅消息 center->unsubscribe_all_messages(static_cast<RXMessageHandler*>(args[0])); center->unsubscribe_all_messages(static_cast<RXMessageHandler*>(args[1])); center->unsubscribe_all_messages(static_cast<RXMessageHandler*>(args[2])); // 2.2 清空任务 delete static_cast<RXMessageQueueInterface*>(args[3]); // 2.3 移除消息中心 delete center; }, handler1, handler2, handler3, message_queue, message_center); } void destory() { scope_gurad_list_.reset(); } private: RXMessageCenter* message_center; RXMessageHandler* handler1; RXMessageHandler* handler2; RXMessageHandler* handler3; RXMessageQueueInterface* message_queue; std::unique_ptr<RXScopeGuardList<5>> scope_gurad_list_; }; 复制代码总结
建议把资源的申请和释放放在一起实现。避免遗漏或逻辑不一致导致资源泄漏。
对于资源释放顺序有依赖的,需要注意回收方式(集中回收可能导致部分资源申请出错时,前面未保护的资源泄漏)
推荐使用 RXScopeGuardList / 自定义的class 封装资源的回收操作(注意ut测试是否有泄漏)。
推荐使用 std::unique_ptr > STL / RXScopeGuardList 进行内存管理
作者:爱撕code魔人
链接:https://juejin.cn/post/7166495467960811556