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

如何定义仅接受字符串字面量的函数以适配C库日志宏?

针对你的问题,这里提供几种符合要求的解决方案,兼顾函数的类型安全、原日志宏的功能保留,以及解决缓冲区限制和重复格式化的问题:


方案1:动态缓冲区解决长度限制(简单跨平台)

这个方案先计算格式化后字符串的实际长度,再动态分配缓冲区,彻底解决固定长度数组的截断问题,同时保留原函数的类型安全和可调试性。

#include <stdexcept>
#include <cstdarg>
#include <cstdio>
#include <vector>

void throw_on_err(err_t err, const char* format, ...) {
    if (err != OK) {
        va_list args;
        va_start(args, format);
        
        // 计算格式化后字符串的长度(不分配缓冲区)
        int msg_len = vsnprintf(nullptr, 0, format, args);
        va_end(args);

        if (msg_len <= 0) {
            MACRO("Failed to format error message");
            throw std::runtime_error("Failed to format error message");
        }

        // 分配足够的缓冲区(+1 用于存储终止符)
        std::vector<char> buffer(static_cast<size_t>(msg_len) + 1);
        
        // 执行格式化
        va_start(args, format);
        vsnprintf(buffer.data(), buffer.size(), format, args);
        va_end(args);

        // 调用原日志宏并抛出异常
        MACRO("%s", buffer.data());
        throw std::runtime_error(buffer.data());
    }
}

特点:

  • 实现简单,完全跨平台
  • 消除固定长度缓冲区的限制
  • 仍存在两次格式化(一次生成异常消息,一次日志宏输出),但性能开销在多数场景下可接受

方案2:可变参数模板+字面量参数(兼容原宏特性)

利用C++模板特性,将格式字符串作为模板参数传递,直接复用原日志宏的字符串拼接、着色、日志级别检查等功能,同时减少不必要的格式化操作。

#include <stdexcept>
#include <cstdarg>
#include <cstdio>
#include <vector>
#include <string>

namespace detail {
    // 辅助函数:根据格式字符串和va_list生成格式化后的字符串
    std::string format_from_va(const char* format, va_list args) {
        va_list args_copy;
        va_copy(args_copy, args);
        const int len = vsnprintf(nullptr, 0, format, args_copy);
        va_end(args_copy);

        if (len <= 0) {
            return "Failed to format error message";
        }

        std::vector<char> buffer(static_cast<size_t>(len) + 1);
        vsnprintf(buffer.data(), buffer.size(), format, args);
        return std::string(buffer.data());
    }

    // 辅助函数:接收C风格可变参数并生成字符串
    std::string format_message(const char* format, ...) {
        va_list args;
        va_start(args, format);
        const std::string msg = format_from_va(format, args);
        va_end(args);
        return msg;
    }
}

// 模板函数:字符串字面量作为模板参数,避免宏的缺点
template <const char* Format>
void throw_on_err(err_t err, auto&&... args) {
    if (err != OK) {
        // 直接调用原日志宏,使用原始格式字符串和参数
        MACRO(Format, std::forward<decltype(args)>(args)...);
        
        // 生成异常消息:借助小宏转发模板参数到C风格可变参数函数
#define FORWARD_ARGS(...) __VA_ARGS__
        const std::string err_msg = detail::format_message(Format, FORWARD_ARGS(args)...);
#undef FORWARD_ARGS

        throw std::runtime_error(err_msg);
    }
}

// 定义字符串字面量模板参数实例(C++17及以上支持)
constexpr const char network_err[] = "network error %d";
constexpr const char file_err[] = "file open failed: %s";

// 调用示例
// throw_on_err<network_err>(errno, errno);
// throw_on_err<file_err>(file_err_code, filename);

特点:

  • 主逻辑为函数,保留类型安全和可调试性
  • 直接传递原始格式字符串给日志宏,完美兼容其所有特性
  • 日志宏的输出为一次格式化,异常消息的格式化复用同一逻辑,相对方案1减少了部分冗余

方案3:平台相关输出捕获(彻底消除重复格式化)

如果必须彻底避免两次格式化,可以临时重定向标准输出到内存缓冲区,捕获日志宏的输出内容作为异常消息。以下是Linux平台的实现:

#include <stdexcept>
#include <cstdarg>
#include <cstdio>
#include <vector>
#include <string>
#include <unistd.h>

void throw_on_err(err_t err, const char* format, ...) {
    if (err != OK) {
        int pipe_fds[2];
        if (pipe(pipe_fds) == -1) {
            perror("pipe failed");
            throw std::runtime_error("Failed to create pipe for logging");
        }

        // 保存原始stdout文件描述符
        const int original_stdout = dup(STDOUT_FILENO);
        if (original_stdout == -1) {
            close(pipe_fds[0]);
            close(pipe_fds[1]);
            perror("dup failed");
            throw std::runtime_error("Failed to duplicate stdout");
        }

        // 将stdout重定向到管道写端
        if (dup2(pipe_fds[1], STDOUT_FILENO) == -1) {
            close(pipe_fds[0]);
            close(pipe_fds[1]);
            close(original_stdout);
            perror("dup2 failed");
            throw std::runtime_error("Failed to redirect stdout");
        }
        close(pipe_fds[1]);

        // 调用日志宏,输出被捕获到管道
        va_list args;
        va_start(args, format);
        printf("log: " format "\n", args);
        va_end(args);

        // 恢复原始stdout
        dup2(original_stdout, STDOUT_FILENO);
        close(original_stdout);

        // 读取捕获的日志内容
        std::vector<char> buffer(1024);
        ssize_t bytes_read = read(pipe_fds[0], buffer.data(), buffer.size() - 1);
        close(pipe_fds[0]);

        std::string err_msg;
        if (bytes_read > 0) {
            buffer[bytes_read] = '\0';
            err_msg = buffer.data();
            // 去掉末尾换行符
            if (!err_msg.empty() && err_msg.back() == '\n') {
                err_msg.pop_back();
            }
        } else {
            err_msg = "Failed to capture log message";
        }

        throw std::runtime_error(err_msg);
    }
}

特点:

  • 彻底消除重复格式化,仅执行一次格式化操作
  • 平台相关(Windows需使用CreatePipe/SetStdHandle等API)
  • 多线程环境下需加锁避免并发冲突

内容的提问来源于stack exchange,提问作者N. I.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 08:37:05