阅读 41

浅谈 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


文章分类
代码人生
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐