当前位置: 首页 > news >正文

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并没有evaporationtotalevap成员。这显然是不正确的,因为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())时,编译器认为estd::exception类型,这是不可能的。

另一种可能是,在宏展开时,由于宏参数中使用了e,而宏内部catch块中也使用了e,导致预处理器产生了混淆。但是,预处理器并不理解C++作用域,这种可能性不大。

实际上,错误信息是在编译阶段报出的,提示在蒸发数据的e.evaporation()中,econst 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");
}

主要修改点:

  1. 将宏中的异常对象变量名从 e 改为 ex,避免与局部变量 e 冲突
  2. 在蒸发数据处理部分,使用更具描述性的变量名 evaporationData 代替 e
  3. 确保通过 evaporationData 访问协议缓冲区的字段

总结

  1. 当使用 try catch 语块时应当避免 try 中的 变量名错误捕获异常对象变量名 一致。
  2. 频繁的 try catch 语块应该改为 函数调用,并在层级结构中合理的捕获错误或者 抛回上级错误。
http://www.xdnf.cn/news/780319.html

相关文章:

  • CSS(2)
  • Ajax技术分析方法全解:从基础到企业级实践(2025最新版)
  • MySQL的备份和恢复
  • 【Spring AI】如何实现文生图功能
  • ArcGIS Pro字段计算器与计算几何不可用,显示灰色
  • 手摸手还原vue3中reactive的get陷阱以及receiver的作用
  • 高通SoC阵列服务器
  • APM32芯得 EP.07 | 探索使用以太网(ETH),搭建一个简单的本地HTTP服务器
  • 基于Linux系统docker封装exe
  • CentOS 7.9 安装 宝塔面板
  • 【leetcode】15.三数之和
  • 机器学习:集成学习概念、分类、随机森林
  • 24.【.NET8 实战--孢子记账--从单体到微服务--转向微服务】--单体转微服务--认证微服务
  • 使用 C++/OpenCV 图像直方图比较两个图片相似度
  • 奥威BI+AI数据分析:企业数智化转型的加速器
  • Redis 缓存粒度如何控制?缓存整个对象还是部分字段?
  • 三目标微网对经济性进行优化调度
  • Hadoop HDFS 体系结构与文件读写流程剖析
  • 宝塔面板安装nodejs后,通过node -v获取不到版本号,报错node: command not found
  • Qwen与Llama分词器核心差异解析
  • 【RabbitMQ】- Channel和Delivery Tag机制
  • 【mysql】BIGINT UNSIGNED字段被表示为float科学计数法 丢失精度问题
  • 学习路之PHP--easyswoole使用视图和模板
  • MFC Resource.h 文件详解与修改指南
  • nginx+tomcat动静分离、负载均衡
  • JavaScript性能优化实战:从核心原理到工程实践的全流程解析
  • 【大模型:知识图谱】--1.py2neo连接图数据库neo4j
  • Neo4j 数据建模:原理、技术与实践指南
  • Java详解LeetCode 热题 100(25):LeetCode 141. 环形链表(Linked List Cycle)详解
  • JVM—垃圾收集算法和HotSpot算法实现细节