"); //-->
1.引言
在上一篇文章中,介绍了 VP 是什么,并以单算子 rotate 为例,介绍了 VP API 使用方法,其中有一些日志打印的代码显得特别高大上
LOGE_AND_RETURN_IF(src_mat.empty(), HB_UCP_INVALID_ARGUMENT,
"Read image {} failed", src_img.c_str());
由于本人是属于对 C++不那么熟悉的同学,下面会从我的视角来介绍这个问题,如果其中有错误或表述不当的地方,欢迎评论指正。
其实本文是为了服务于另外一篇文章:
2.log_util.h 代码解读
LOGE_AND_RETURN_IF 是在 log_util.h 定义的一个宏,下面来具体看下 log_util.h 中的代码:
#ifndef VP_CODE_07_ROTATE_INCLUDE_UTIL_LOG_UTIL_H_
#define VP_CODE_07_ROTATE_INCLUDE_UTIL_LOG_UTIL_H_
#include <sstream> //C++中标准头文件,不做解释
#include <utility> //C++中标准头文件,不做解释
#include "hlog/logging.h" // 地平线提供的头文件,并不在当前目录下,为什么能包含?
#define LOGI(err_msg, ...) HFLOGM_I("VP_TRANSFORMATION", err_msg, ##__VA_ARGS__)
#define LOGD(err_msg, ...) HFLOGM_D("VP_TRANSFORMATION", err_msg, ##__VA_ARGS__)
#define LOGE(err_msg, ...) HFLOGM_E("VP_TRANSFORMATION", err_msg, ##__VA_ARGS__)
#define LOGW(err_msg, ...) HFLOGM_W("VP_TRANSFORMATION", err_msg, ##__VA_ARGS__)
#define LOGE_AND_RETURN_IF(condition, code, err_msg, ...) \
do { \
if (condition) { \
LOGE(err_msg, ##__VA_ARGS__); \
return code; \
} \
} while (0)
#define LOGE_AND_RETURN_IF_NULL(ptr, code) \
LOGE_AND_RETURN_IF(ptr == nullptr, code, NULLPTR_ERR(ptr))
#endif // VP_CODE_07_ROTATE_INCLUDE_UTIL_LOG_UTIL_H_
2.1 LOGx
来看一下日志宏定义
#define LOGI(err_msg, ...) HFLOGM_I("VP_TRANSFORMATION", err_msg, ##__VA_ARGS__)
#define LOGD(err_msg, ...) HFLOGM_D("VP_TRANSFORMATION", err_msg, ##__VA_ARGS__)
#define LOGE(err_msg, ...) HFLOGM_E("VP_TRANSFORMATION", err_msg, ##__VA_ARGS__)
#define LOGW(err_msg, ...) HFLOGM_W("VP_TRANSFORMATION", err_msg, ##__VA_ARGS__)
这些宏封装了 日志打印函数:
LOGI(Info):信息日志
LOGD(Debug):调试日志
LOGE(Error):错误日志
LOGW(Warning):警告日志
具体解释下其中一行:
#define LOGI(err_msg, ...) HFLOGM_I("VP_TRANSFORMATION", err_msg, ##__VA_ARGS__)
LOGI(err_msg, …) 定义了一个宏:
err_msg 是第一个参数
… 表示可以接受变长参数。但… 仅是一个 占位符,它不能直接用于宏的展开。(在下面介绍个展开的例子)
HFLOGM_I(“VP_TRANSFORMATION”, err_msg, ##VA_ARGS):
HFLOGM_I 是 hlog/logging.h 中提供的 具体日志实现
“VP_TRANSFORMATION” 是 日志模块名称,用于分类日志来源。
err_msg 就是前面的 LOGI 中的 err_msg
VA_ARGS 是一个 占位符的替代符,用于 填充 … 传递的实际参数。
##VA_ARGS 的作用是 在 VA_ARGS 为空时去掉前面的逗号,防止编译错误。
当调用:LOGI(“Error code: %d”, 404);
它会展开为:HFLOGM_I(“VP_TRANSFORMATION”, “Error code: %d”, 404);
如果调用:LOGI(“Simple message”);
它会展开为:HFLOGM_I(“VP_TRANSFORMATION”, “Simple message”);
其中 ##VA_ARGS 确保了 VA_ARGS 为空时不会出现额外的 ,。(在下面介绍个展开的例子)
2.2 LOGE_AND_RETURN_IF 宏
下面来解读 LOGE_AND_RETURN_IF 宏的写法:
#define LOGE_AND_RETURN_IF(condition, code, err_msg, ...) \
do { \
if (condition) { \
LOGE(err_msg, ##__VA_ARGS__); \
return code; \
} \
} while (0)
这个宏 LOGE_AND_RETURN_IF 是一个常见的 防御式编程(Defensive Programming) 宏,如果 condition 为真,则:
记录错误日志 LOGE(err_msg, ##VA_ARGS)
返回 code
定义一个 LOGE_AND_RETURN_IF 宏时,do { … } while (0)起到什么作用?
do { … } while (0) 的作用是让整个宏块结构化,使它在语法上表现得像一个普通的语句,这样:确保 if 语句和 return 被视为一个整体,不会因为 if-else 结构导致错误。
条件满足,触发错误日志并返回;当 … 为空时,去掉前面的 ,,防止编译错误,示例(无可变参数)
LOGE_AND_RETURN_IF(error, -1, "An error occurred");
展开后
do {
if (error) {
LOGE("An error occurred");
return -1;
}
} while (0);
##VA_ARGS 确保了 没有多余的 , 影响 LOGE 语法。
(带可变参数)
LOGE_AND_RETURN_IF(value < 0, -1, "Invalid value: %d", value);
展开后
do {
if (value < 0) {
LOGE("Invalid value: %d", value);
return -1;
}
} while (0);
2.3 do { … } while (0)作用
在前面有说 do { … } while (0) 可以:确保 if 语句和 return 被视为一个整体,不会因为 if-else 结构导致错误。
为什么 do { … } while (0) 能避免 if-else 结构导致的错误?if-else 结构有什么问题?下面来看一下
do { … } while (0) 的核心作用是让整个宏块在语法上表现得像一个单一语句,从而避免 if-else 结构中的 悬空 else(dangling else) 和 单行 if 语句的 bug。
2.3.1 if-else 结构的问题
如果 没有 do { … } while (0),在 if-else 语句中 直接用 if 语句包裹宏时,可能导致 else 结构错误。错误示例如下:
未使用 do { … } while (0) 定义宏
#define LOGE_AND_RETURN_IF(condition, code, err_msg, ...) \
if (condition) { \
LOGE(err_msg, ##__VA_ARGS__); \
return code; \
}
代码中调用宏
if (x < 0)
LOGE_AND_RETURN_IF(x < -10, -1, "x is too small");
else
printf("x is positive\n");
错误展开:
if (x < 0)
if (x < -10) {
LOGE("x is too small");
return -1;
} // 这里的 `if` 结束
else // 这个 else 可能会匹配到错误的 if!
printf("x is positive\n");
此时就会引入悬空 else 的问题。
2.3.2 悬空 else 问题
错误现象:
x = -5:
x is positive // 这个输出是不应该出现的!
C/C++ 规定:else 总是匹配最近的 if。
上一节中 else 错误地匹配了 if (x < -10),而不是 if (x < 0),导致:
x = -5 时,if (x < 0) 为真,但 if (x < -10) 为假,所以 LOGE_AND_RETURN_IF 不执行。
然而 else 仍然执行,最终 printf(“x is positive\n”); 被错误地执行!
解决方案:使用 do { … } while (0)结构。
2.4 NULLPTR_ERR(ptr) 宏
#define NULLPTR_ERR(ptr) #ptr " is null pointer"
#ptr 是 字符串化(stringizing)运算符,它会把 ptr 变成字符串。
示例:
printf("%s\n", NULLPTR_ERR(my_ptr));
展开后:
printf("%s\n", "my_ptr is null pointer");
输出:my_ptr is null pointer
2.5 LOGE_AND_RETURN_IF_NULL(ptr, code) 宏
#define LOGE_AND_RETURN_IF_NULL(ptr, code) \
LOGE_AND_RETURN_IF(ptr == nullptr, code, NULLPTR_ERR(ptr))
这个宏会:
检查 ptr 是否为 nullptr。
如果 ptr == nullptr:
记录日志,日志信息由 NULLPTR_ERR(ptr) 生成,例如 “ptr is null pointer”。
返回 code 作为错误码。
3.总结
*博客内容为网友个人发布,仅代表博主个人观点,如有侵权请联系工作人员删除。