如何实现支持错误检查与返回值获取的C++函数调用宏?
C++ errno类函数调用校验封装方案
C++开发中经常需要调用一批行为逻辑相似的函数:这类函数执行失败时会返回负值,同时设置errno变量记录错误详情。为了避免重复编写冗余的返回值校验逻辑,通常会把校验逻辑封装为宏,原有实现如下(已修正原代码的语法错误):
#define CHECKED_CALL( func, ... ) \ do {\ auto ret = func( __VA_ARGS__ );\ if( ret < 0 ) {\ std::cerr << #func "() failed with ret " << ret << " errno " << errno << std::endl;\ throw std::runtime_error( #func "() failed" );\ }\ } while(false)
这个宏的基础使用方式非常简便:
CHECKED_CALL( func1, 1, 2, 3 ); CHECKED_CALL( func2, "foobar" );
但原有宏存在使用局限:当需要在调用成功时获取函数的返回值,例如编写如下代码时,原有宏无法支持:
auto r = CHECKED_CALL( func3, 1.5 );
虽然可以额外编写一个支持返回值的新宏,但完全可以用更低成本的方案实现双场景兼容,同时避免维护多套冗余逻辑,具体可以选择以下两种实现:
方案1:改进宏实现,兼容两种调用场景
直接将原有do-while语句块替换为编译器广泛支持的语句表达式,即可让宏同时支持独立语句调用和返回值赋值:
#define CHECKED_CALL(func, ...) \ ({\ auto ret = func(__VA_ARGS__);\ if (ret < 0) {\ std::cerr << #func "() failed with ret " << ret << " errno " << errno << std::endl;\ throw std::runtime_error(#func "() failed");\ }\ ret;\ })
这个实现的特点:
- 完全兼容原有无返回值的调用写法,不需要修改存量代码
- 支持直接赋值获取成功时的返回值
- 不需要返回值时,编译器会自动优化掉未使用的返回值,不会触发未使用变量/返回值的编译警告
注意:语句表达式属于编译器扩展,GCC、Clang、MSVC 2022及以上版本均已支持,若需要严格遵循标准C++,可以使用下面的非宏模板方案。
方案2:标准C++兼容的非宏模板实现
基于C++11及以上版本的可变参数模板和完美转发特性,可以完全脱离宏实现零开销的校验逻辑,不会触发任何编译警告:
#include <stdexcept> #include <iostream> #include <cerrno> #include <type_traits> #include <utility> #include <string> template <typename Func, typename... Args> auto checked_call(const char* func_name, Func&& func, Args&&... args) -> std::invoke_result_t<Func, Args...> { auto ret = std::forward<Func>(func)(std::forward<Args>(args)...); if (ret < 0) { std::cerr << func_name << "() failed with ret " << ret << " errno " << errno << std::endl; throw std::runtime_error(std::string(func_name) + "() failed"); } return ret; } // 仅保留极轻量的宏做函数名字符串化,核心逻辑全部复用模板实现 #define CHECKED_CALL(func, ...) checked_call(#func, func, __VA_ARGS__)
这个实现的特点:
- 调用方式和原有宏完全一致,存量代码无需修改
- 同时支持无返回值调用和返回值赋值场景
- 完美转发参数和返回值,没有额外运行时开销
- 核心逻辑用普通C++代码编写,避免了宏的变量名冲突、运算符优先级等常见问题
- 不需要返回值时编译器会自动优化丢弃返回值,无编译警告
- 兼容所有C++11及以上标准版本,不依赖任何编译器扩展
内容的提问来源于stack exchange,提问作者Slava
相关产品推荐
相关产品推荐

