素槿
Published on 2025-08-22 / 4 Visits
0

基于source_location的调试接口

前言

平时一般简单的调试可以直接cout,但是不方便使用。
希望封装一个简单易用的接口,可以打印不同的数据,且可以打印出对应的文件,函数名和所在行数。

没有source_location之前想要打印文件,函数名和所在行数等信息。就是封装相关的宏和使用C可变参数宏打印。直到source_location出现,才有C++式的获取文件,函数名和所在行数的方法。

编译环境:

$ uname -a
Linux DESKTOP-L4NCHLS 5.10.16.3-microsoft-standard-WSL2 #1 SMP Fri Apr 2 22:23:49 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux

$ g++ -v
Using built-in specs.
COLLECT_GCC=g++
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/11/lto-wrapper
OFFLOAD_TARGET_NAMES=nvptx-none:amdgcn-amdhsa
OFFLOAD_TARGET_DEFAULT=1
Target: x86_64-linux-gnu
... ...
Thread model: posix
Supported LTO compression algorithms: zlib zstd
gcc version 11.1.0 (Ubuntu 11.1.0-1ubuntu1~20.04)

代码:

module;
#include <iostream>
#include <source_location>
#include <string_view>
#include <filesystem>
#include <mutex>
export module Loger;

namespace fs = std::filesystem;
using sl     = std::source_location;

std::mutex lock;

inline void loger()
{
    std::cout << "\n";
    return;
}

template <typename T, typename... Args>
void loger(T&& t, Args&&... args)
{
    std::cout << t << " ";
    return loger(args...);
}

export template <typename... Args>
struct debug {
    debug(Args&&... args, const sl& loc = sl::current())
    {
#ifdef NDEBUG
#else
        std::lock_guard l{lock}; /* useful in multithread debug */
        std::cout << "{ in File: [" << loc.file_name() << "], Function: ["
                  << loc.function_name() << "], Line: [" << loc.line()
                  << "] }: ";
        loger(args...);
#endif
    }
};

export template <typename... Args>
debug(Args&&...) -> debug<Args...>;

在上面的代码中,使用模板来提供基础的多类型支持。可以根据需要修改输出格式。

一般来说,最简单方法的是定义一个模板函数,以source_location为最后一个默认实参:

template <typename T, typename... Args>
void loger(T&& t, Args&&... args, const sl& loc = sl::current())
{/* ... */}

但是编译器的模板推导并不能匹配到具体的函数。

所以只能另辟蹊径,使用类模板配合C++17的新特性模板推导指引。如上述代码中所示。同时使用一个模板函数 loger来展开可变模板参数。

同时使用C++20最新特性modules,将整个文件模块化。所以需要支持C++20标准的编译器。
也可以使用传统的头文件的形式,只需去除相关关键字和语句即可。但是C++推导指引至少需要支持C++17。

同时为了防止多线程调试输出时错乱,使用了锁。简单输出可以删除以提高一点性能。

同时加上了调试宏,如果不想输出了只要在编译时加上 -DNDEBUG选项,即可禁用所有输出。

测试代码:

#include <iostream>
#include <source_location>
import Loger;

int main(int argc, char const* argv[])
{
    debug("123", "223", "223", "223", "223");
    debug(1, "2", 3.4);
    int var = 1;
    debug{var};
    return 0;
}

上面的代码的两个include语句不能删除,有可能是G++编译器的支持或者其他原因,删除之后不能通过编译,根据错误信息猜测是因为实例化模板时 coutsource_location需要。(若是其他原因恳请告知),需要注意的是第三个输出需要使用统一初始化以避免声明冲突。

编译输出:

$  g++ -std=c++20 -fmodules-ts Loger.cpp main.cc
./a.out 
{ in File: [main.cc], Function: [int main(int, const char**)], Line: [7] }: 123 223 223 223 223 
{ in File: [main.cc], Function: [int main(int, const char**)], Line: [8] }: 1 2 3.4
{ in File: [main.cc], Function: [int main(int, const char**)], Line: [10] }: 1