You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

定义易维护函数出入日志器:如何免宏实现?或宏是否适用?

如何不使用宏实现函数出入日志器,或该场景是否适合使用宏?

我想要实现一个函数出入日志器,核心目标有两个:

  1. 无需在每个函数中显式引用__func__这类隐式变量(比如后续切换为GCC的__PRETTY_FUNCTION__时不用大量修改代码);
  2. 尽量避免使用宏。

但目前我没办法达成第二个目标,当前的实现是通过宏结合RAII类来完成的:

#define LOG_ENTRY_EXIT() entry_exit_logger e(__func__);
using func_name_type = const char*;
class entry_exit_logger{
public:
    entry_exit_logger(const func_name_type fname) : func_name(fname){
        std::cout << "-------Enter: " << func_name << "------" << std::endl;
    }
    ~entry_exit_logger(){
        std::cout << "-------Exit: " << func_name << "------" << std::endl;
    }
private:
    const func_name_type func_name;
};
void test_entry_exit(){
    LOG_ENTRY_EXIT();
}
int main(){
    test_entry_exit();
    return 0;
}

想请教一下,有没有办法不使用宏来实现这个日志器?或者说,这种场景其实更适合使用宏?


先直接给你结论:这个场景其实非常适合用宏,但如果实在想规避宏,也有一些(代价不小的)替代方案。

为什么这个场景适合用宏?

宏是少数能在调用点自动捕获__func__、__PRETTY_FUNCTION__这类编译器隐式变量的工具——这些变量的作用域是当前函数,只有在函数内部直接展开的代码才能拿到正确的值。你的第一个需求是"后续切换这类变量不用大量修改",宏刚好能完美满足:只需要修改宏定义里的变量名,所有调用点都会自动更新,完全不需要改动业务代码。

而且宏的写法非常简洁,业务函数里只需要一行LOG_ENTRY_EXIT()就能完成日志注入,对代码侵入性极低。

如果硬要规避宏,有哪些替代方案?

这些方案都有明显的缺点,你需要权衡是否接受:

  • 模板函数 + 手动传递函数名
    你可以写一个模板函数返回RAII对象,但必须手动在每个函数里传入__func__:

    using func_name_type = const char*;
    class entry_exit_logger{
    public:
        entry_exit_logger(const func_name_type fname) : func_name(fname){
            std::cout << "-------Enter: " << func_name << "------" << std::endl;
        }
        ~entry_exit_logger(){
            std::cout << "-------Exit: " << func_name << "------" << std::endl;
        }
    private:
        const func_name_type func_name;
    };
    
    template<typename FuncName>
    auto make_entry_exit_logger(FuncName&& fname) {
        return entry_exit_logger(std::forward<FuncName>(fname));
    }
    
    void test_entry_exit(){
        auto logger = make_entry_exit_logger(__func__);
    }
    

    缺点很明显:违背了你第一个需求的初衷——后续要切换__PRETTY_FUNCTION__时,你得逐个修改所有函数里的参数,工作量巨大。

  • 利用C++20的std::source_location
    C++20引入了std::source_location,可以在函数里获取调用点的信息,包括函数名:

    #include <source_location>
    
    using func_name_type = const char*;
    class entry_exit_logger{
    public:
        entry_exit_logger(const std::source_location& loc = std::source_location::current()) 
            : func_name(loc.function_name()){
            std::cout << "-------Enter: " << func_name << "------" << std::endl;
        }
        ~entry_exit_logger(){
            std::cout << "-------Exit: " << func_name << "------" << std::endl;
        }
    private:
        const func_name_type func_name;
    };
    
    void test_entry_exit(){
        entry_exit_logger logger;
    }
    

    这个方案看起来完美:不需要宏,也不用手动传函数名,后续如果需要调整获取函数名的方式,只需要修改entry_exit_logger的构造函数即可。但它有两个限制:

    1. 必须使用C++20及以上的标准;
    2. std::source_location::function_name()返回的函数名格式可能和__func__、__PRETTY_FUNCTION__略有不同,比如会包含更多的命名空间信息,需要确认是否符合你的日志格式要求。

总结

如果你的项目可以使用C20,std::source_location是替代宏的最佳方案;如果只能用C17及以下,那宏就是最简洁、最符合你需求的选择——完全没必要为了规避宏而增加代码维护成本。毕竟宏在这种"捕获编译期隐式变量+统一修改入口"的场景下,是C++里最顺手的工具之一。

内容的提问来源于stack exchange,提问作者curios_case

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 08:22:40