C++ try{}catch{} 语句块中潜藏问题排查指南
报错内容
home/zry/src/modules/web/OpWebMD.cpp: In member function ‘virtual grpc::Status WeatherObservationServiceImpl::SaveEmergency(grpc::ServerContext*, const protoBufferWeb::SaveEmergencyRequest*, protoBufferWeb::CommonResponse*)’:
/home/zry/src/modules/web/OpWebMD.cpp:11680:47: error: ‘const class std::exception’ has no member named ‘evaporation’
11680 | TRY_CONVERT_DOUBLE(sData.EVAPB, e.evaporation().v(), "EVAPB");| ^~~~~~~~~~~
/home/zry/src/modules/web/../../include/tool/../zryHeard.hpp:111:24: note: in definition of macro ‘ZRY_LOG_ERROR’111 | msg, ##__VA_ARGS__)| ^~~~~~~~~~~
/home/zry/src/modules/web/OpWebMD.cpp:11680:13: note: in expansion of macro ‘TRY_CONVERT_DOUBLE’
11680 | TRY_CONVERT_DOUBLE(sData.EVAPB, e.evaporation().v(), "EVAPB");| ^~~~~~~~~~~~~~~~~~
/home/zry/src/modules/web/OpWebMD.cpp:11681:54: error: ‘const class std::exception’ has no member named ‘totalevap’
11681 | TRY_CONVERT_DOUBLE(sData.EVAPB_ddaccu, e.totalevap().v(), "EVAPB_ddaccu");| ^~~~~~~~~
/home/zry/src/modules/web/../../include/tool/../zryHeard.hpp:111:24: note: in definition of macro ‘ZRY_LOG_ERROR’111 | msg, ##__VA_ARGS__)| ^~~~~~~~~~~
/home/zry/src/modules/web/OpWebMD.cpp:11681:13: note: in expansion of macro ‘TRY_CONVERT_DOUBLE’
11681 | TRY_CONVERT_DOUBLE(sData.EVAPB_ddaccu, e.totalevap().v(), "EVAPB_ddaccu");
问题出现代码
// 安全转换宏定义
#define TRY_CONVERT_DOUBLE(target, source, field_name) \try { \if (!source.empty()) { \ZRY_LOG_DEBUG("Converting double for {}: source='{}'", field_name, source); \target = std::stod(source); \ZRY_LOG_DEBUG("Converted double for {}: success={}", field_name, target); \} else { \ZRY_LOG_DEBUG("Skipping double conversion for {}: source is empty", field_name); \} \} catch (const std::exception& e) { \ZRY_LOG_ERROR("{} double conversion failed! Source='{}' Error: {}", field_name, source, e.what()); \}#define TRY_CONVERT_INT(target, source, field_name) \try { \if (!source.empty()) { \ZRY_LOG_DEBUG("Converting int for {}: source='{}'", field_name, source); \target = std::stoi(source); \ZRY_LOG_DEBUG("Converted int for {}: success={}", field_name, target); \} else { \ZRY_LOG_DEBUG("Skipping int conversion for {}: source is empty", field_name); \} \} catch (const std::exception& e) { \ZRY_LOG_ERROR("{} int conversion failed! Source='{}' Error: {}", field_name, source, e.what()); \}try
{std::unique_ptr<CWebModuleMysqlTool> mysqlOnceUserTool; // 数据库操作工具类指针mysqlOnceUserTool = std::make_unique<CWebModuleMysqlTool>(strMySqlIp, strMySqlUser, strMySqlPassword, strMySqlDatabase);sta_obdata_hourly sData;const auto& data = request->data();// 1. 基础时间字段sData.sta_time = request->time(); // YYYYMMDDHH格式ZRY_LOG_DEBUG("Set sta_time: {}", sData.sta_time);// 6. 蒸发数据if (data.has_evaporation()) {ZRY_LOG_DEBUG("Processing evaporation data");const auto& e = data.evaporation();TRY_CONVERT_DOUBLE(sData.EVAPB, e.evaporation().v(), "EVAPB");TRY_CONVERT_DOUBLE(sData.EVAPB_ddaccu, e.totalevap().v(), "EVAPB_ddaccu");}// 二、更新传入时次天数的天气现象try {ZRY_LOG_DEBUG("Updating weather phenomena for the day");// 提取日期部分 (格式: YYYY-MM-DD)std::string dateOnly = sData.sta_time.substr(0, 10);// 构造当天的起始时间和次日零时时间点std::string startTime = dateOnly + " 00:00:00";std::string nextDayTime = dateOnly + " 23:59:59"; // 当日截止时间(含毫秒)// 修改SQL:更新指定日期的所有数据行std::string strSql = "UPDATE sta_obdata_hourly ""SET WEATA_wwWW = '" + sData.WEATA_wwWW + "', "" WEATA = '" + sData.WEATA + "' ""WHERE sta_time BETWEEN '" + startTime + "' AND '" + nextDayTime + "';";ZRY_LOG_DEBUG("Executing weather phenomena update SQL: {}", strSql);mysqlOnceUserTool->Myexecute(strSql);}catch (const std::runtime_error &e) {ZRY_LOG_ERROR("Runtime error occurred during weather phenomena update: {}", e.what());}catch (const std::exception &e) {ZRY_LOG_ERROR("Exception occurred during weather phenomena update: {}", e.what());}catch (...) {ZRY_LOG_ERROR("Unknown exception occurred during weather phenomena update");}// 三、最后更新日照数据到辐射快照表try {ZRY_LOG_DEBUG("Updating sun data to radiation snapshot table");// 提取日期部分 (格式: YYYY-MM-DD)std::string dateOnly = sData.sta_time.substr(0, 10);if (data.has_sunlist()) {const auto& sl = data.sunlist();for (const auto& sh : sl.hoursunlist()) {std::string strTimec = dateOnly + " " + sh.hour() + ":00:00";if (!sh.data().v().empty()) {// 修正SQL:移除末尾多余的逗号std::string strSql = "UPDATE sta_obysdr_hourly ""SET SUNDA_p0accu = '" + sh.data().v() + "' " // 注意这里移除了逗号"WHERE timec = '" + strTimec + "';";ZRY_LOG_DEBUG("Executing sun data update SQL for {}: {}", strTimec, strSql);mysqlOnceUserTool->Myexecute(strSql);} else {ZRY_LOG_WARN("Skipping sun data update for {}: data is empty", strTimec);}}}}catch (const std::runtime_error &e) {ZRY_LOG_ERROR("Runtime error occurred during sun data update: {}", e.what());}catch (const std::exception &e) {ZRY_LOG_ERROR("Exception occurred during sun data update: {}", e.what());}catch (...) {ZRY_LOG_ERROR("Unknown exception occurred during sun data update");}
}
catch (const std::exception& e) {ZRY_LOG_ERROR("Overall processing error: {}", e.what());
}
catch (...) {ZRY_LOG_ERROR("Unknown overall processing error");
}
错误分析
根据错误信息,编译器抱怨在e.evaporation()
和e.totalevap()
中,e
的类型是const class std::exception
,而std::exception
并没有evaporation
和totalevap
成员。这显然是不正确的,因为e
应该是蒸发数据类型的对象。
观察错误发生的上下文:
在代码中,蒸发数据部分如下:
if (data.has_evaporation()) {const auto& e = data.evaporation();// ...
}
错误信息显示编译器将e
视为std::exception
,这通常发生在宏展开时出现了问题。注意,在宏中我们使用了e
作为参数,而宏内部可能使用了e
作为变量名,从而与异常对象e
冲突。
让我们检查宏定义:
#define TRY_CONVERT_DOUBLE(target, source, field_name) \try { \if (!source.empty()) { \ZRY_LOG_DEBUG("Converting double for {}: source='{}'", field_name, source); \target = std::stod(source); \ZRY_LOG_DEBUG("Converted double for {}: success={}", field_name, target); \} else { \ZRY_LOG_DEBUG("Skipping double conversion for {}: source is empty", field_name); \} \} catch (const std::exception& e) { \ZRY_LOG_ERROR("{} double conversion failed! Source='{}' Error: {}", field_name, source, e.what()); \}
注意,在宏的catch
块中,我们捕获了异常并命名为e
。而在蒸发数据部分,我们使用了e
作为蒸发数据的常量引用。当宏展开时,在catch
块中使用的e
就会与外部作用域中的e
冲突。
例如,对于TRY_CONVERT_DOUBLE(sData.EVAPB, e.evaporation().v(), "EVAPB");
,宏展开后:
try {if (!e.evaporation().v().empty()) {ZRY_LOG_DEBUG(...);sData.EVAPB = std::stod(e.evaporation().v());...}
} catch (const std::exception& e) {ZRY_LOG_ERROR(..., e.what()); // 这里的e是异常对象,而不是外部的e
}
但是,在蒸发数据部分,我们有一个变量e
(代表蒸发数据),而在宏的catch
块中又有一个变量e
(代表异常),这在同一个作用域内会导致冲突吗?实际上,在catch
块中,e
的作用域仅限于该块内,不会影响外部作用域。但是,在宏参数中我们使用了e.evaporation().v()
,而宏内部在catch
块中又使用了e
(异常对象),那么在catch
块中,我们无法再访问外部的e
(因为被异常对象e
覆盖了)。
然而,在catch
块中我们并没有使用外部的e
,所以问题应该不大。但是,错误信息中却出现了const class std::exception’ has no member named ‘evaporation’
,这提示在宏内部,当我们在try
块中使用e
(例如e.evaporation().v()
)时,编译器认为e
是std::exception
类型,这是不可能的。
另一种可能是,在宏展开时,由于宏参数中使用了e
,而宏内部catch
块中也使用了e
,导致预处理器产生了混淆。但是,预处理器并不理解C++作用域,这种可能性不大。
实际上,错误信息是在编译阶段报出的,提示在蒸发数据的e.evaporation()
中,e
是const std::exception
类型。这很奇怪,因为e
应该是data.evaporation()
返回的类型,该类型是协议缓冲区中的一个消息类型。
重新考虑错误信息:
/home/zry/src/modules/web/OpWebMD.cpp:11680:47: error: ‘const class std::exception’ has no member named ‘evaporation’
11680 | TRY_CONVERT_DOUBLE(sData.EVAPB, e.evaporation().v(), "EVAPB");
这行代码中,e
应该是一个协议缓冲区的消息,但编译器却认为它是std::exception
。这可能是由于宏展开后,在某些情况下,e
被错误地认为是其他类型。
仔细观察错误位置:11680行。在宏展开后,实际代码被展开成多行,编译器报错指出在11680行(也就是宏调用的那一行)的e.evaporation()
有错。但这时,e
应该是蒸发数据消息,为什么会被认为是std::exception
呢?
一个可能的解释是,在宏展开过程中,由于宏内部使用了e
作为异常变量,而展开后的代码中,在catch
块之前,e
仍然被当作蒸发数据消息,但是在catch
块中,e
被重新定义。然而,这不应该影响try
块中的e
。
我猜测实际的问题可能是在宏的调用参数中,e.evaporation().v()
中的e
被编译器错误地解析了。但这种情况很少见。
另一种可能性:在包含这个宏调用的作用域内,之前已经定义了一个名为e
的变量,其类型是std::exception
?这不太可能,因为我们在蒸发数据部分刚刚定义了const auto& e = ...
。
我们来看一下在蒸发数据部分之前的代码,但根据错误信息,问题出现在11680行,这一行就在定义e
之后。
结论: 这个错误表明在宏展开过程中出现了符号冲突。问题在于宏内部的 e
被误解释为 std::exception
对象,而不是我们期望的协议缓冲区对象。
修改方案
我们可以尝试修改宏,将异常对象重命名为其他名字,以避免任何潜在的冲突:
修改宏:
// 修改安全转换宏,使用不同的异常变量名
#define TRY_CONVERT_DOUBLE(target, source, field_name) \try { \if (!source.empty()) { \ZRY_LOG_DEBUG("Converting double for {}: source='{}'", field_name, source); \target = std::stod(source); \ZRY_LOG_DEBUG("Converted double for {}: success={}", field_name, target); \} else { \ZRY_LOG_DEBUG("Skipping double conversion for {}: source is empty", field_name); \} \} catch (const std::exception& ex) { \ // 将 e 改为 ex 避免冲突ZRY_LOG_ERROR("{} double conversion failed! Source='{}' Error: {}", field_name, source, ex.what()); \}#define TRY_CONVERT_INT(target, source, field_name) \try { \if (!source.empty()) { \ZRY_LOG_DEBUG("Converting int for {}: source='{}'", field_name, source); \target = std::stoi(source); \ZRY_LOG_DEBUG("Converted int for {}: success={}", field_name, target); \} else { \ZRY_LOG_DEBUG("Skipping int conversion for {}: source is empty", field_name); \} \} catch (const std::exception& ex) { \ // 将 e 改为 ex 避免冲突ZRY_LOG_ERROR("{} int conversion failed! Source='{}' Error: {}", field_name, source, ex.what()); \}
同时,在蒸发数据处理部分,你需要使用协议缓冲区对象而不是 std::exception
。在完整代码中应该这样处理:
// 6. 蒸发数据
if (data.has_evaporation()) {ZRY_LOG_DEBUG("Processing evaporation data");// 从协议缓冲区获取蒸发数据对象const auto& evaporationData = data.evaporation(); // 使用更有描述性的变量名// 处理蒸发量TRY_CONVERT_DOUBLE(sData.EVAPB, evaporationData.evaporation().v(), "EVAPB");// 处理总蒸发量TRY_CONVERT_DOUBLE(sData.EVAPB_ddaccu, evaporationData.totalevap().v(), "EVAPB_ddaccu");
}
主要修改点:
- 将宏中的异常对象变量名从
e
改为ex
,避免与局部变量e
冲突 - 在蒸发数据处理部分,使用更具描述性的变量名
evaporationData
代替e
- 确保通过
evaporationData
访问协议缓冲区的字段
总结
- 当使用
try catch
语块时应当避免 try 中的 变量名 和 错误捕获异常对象变量名 一致。 - 频繁的
try catch
语块应该改为 函数调用,并在层级结构中合理的捕获错误或者 抛回上级错误。