C++ SQLite开发:无异常时如何消除重复的错误返回检查
无异常约束下消除重复返回码检查的实现
整套方案完全贴合现有代码的处理逻辑,不引入任何异常机制,既把重复的判断代码全部收敛,还能顺手补上原代码里藏着的资源泄漏隐患。
具体实现步骤
- 先封装通用流程宏,把固定的错误判断逻辑全部藏起来
用do-while包裹宏逻辑,保证宏可以在任意代码块里当合法语句用,不会出现语法歧义:// 通用检查宏:执行操作后校验返回码,非SQLITE_OK直接提前返回 #define SQLITE_CHECK_RC(expr) \ do { \ int current_rc_ = (expr); \ if (current_rc_ != SQLITE_OK) { \ return current_rc_; \ } \ } while(0) - 补个极简的语句句柄守卫,解决原代码的泄漏问题
原代码有个很容易忽略的坑:只要sqlite3_prepare_v2成功拿到有效stmt,后续任意一步绑定操作出错直接return的时候,都不会调用sqlite3_finalize释放句柄,跑久了必然内存泄漏。写个几十行的RAII守卫,进作用域的时候把stmt绑上,出作用域的时候自动释放就行:// 极简句柄守卫,禁止拷贝避免重复释放 class StmtGuard { public: explicit StmtGuard(sqlite3_stmt* stmt = nullptr) : stmt_(stmt) {} ~StmtGuard() { if (stmt_) sqlite3_finalize(stmt_); } StmtGuard(const StmtGuard&) = delete; StmtGuard& operator=(const StmtGuard&) = delete; // 正常执行到最后一步时,主动放弃所有权,避免重复释放 void Release() { stmt_ = nullptr; } private: sqlite3_stmt* stmt_; }; - 重写业务函数,把所有重复的判断全换成宏调用
原来的函数可以简化到非常清爽的结构,所有错误判断的逻辑都由宏接管:int sqlite::update(const std::string& table_name, const std::vector<column_values>& fields, const std::vector<column_values>& where) { if (db_ == nullptr) { return SQLITE_ERROR; } const std::string sql = update_helper(table_name, fields, where); sqlite3_stmt* stmt = nullptr; // 所有带返回码的SQLite操作,全用宏做错误校验 SQLITE_CHECK_RC(sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt, NULL)); // 绑上守卫,不管是正常走完流程还是中途报错return,stmt都能被正确释放 StmtGuard guard(stmt); SQLITE_CHECK_RC(bind_fields(stmt, fields)); SQLITE_CHECK_RC(bind_where(stmt, where)); // 最后执行前把stmt所有权交出去,让step_and_finalise自己处理释放 guard.Release(); return step_and_finalise(stmt); }
实际效果
- 完全符合不使用异常的开发要求,整个流程的错误处理逻辑和原代码没有任何区别:拿到操作返回码、不是
SQLITE_OK就立刻把对应错误码向上返回,不会引入逻辑偏差 - 原来每处3行的重复检查,现在每步操作只需要一行宏调用,冗余代码全部消掉
- 比原实现可靠性更高:补上了之前错误路径下的stmt泄漏问题
- 宏内部的返回码变量是块级局部变量,不会和外层定义的同名变量冲突,没有命名污染
内容的提问来源于stack exchange,提问作者Angus Comber
相关产品推荐
相关产品推荐

