C++:如何在不使用 sprintf 的情况下将 fprintf 结果作为 std::string 获取

2024-02-28

我正在使用一个用 C++ 实现的开源 UNIX 工具,我需要更改一些代码以使其执行我想要的操作。我想做尽可能小的改变,希望我的补丁能够被上游接受。首选可在标准 C++ 中实现且不会创建更多外部依赖项的解决方案。

这是我的问题。我有一个 C++ 类(我们称之为“A”),当前使用 fprintf() 将其严格格式化的数据结构打印到文件指针。在它的打印函数中,它还递归地调用几个成员类的相同定义的打印函数(“B”是一个例子)。还有另一个类 C,它有一个成员 std::string "foo",需要将其设置为 A 实例的 print() 结果。将其视为 A 的 to_str() 成员函数。

在伪代码中:

class A {
public:
  ...

  void print(FILE* f);
  B b;

  ...  
};

...

void A::print(FILE *f)
{
  std::string s = "stuff";
  fprintf(f, "some %s", s);
  b.print(f);
}

class C {
  ...
  std::string foo;
  bool set_foo(std::str);
  ...
}

...

A a = new A();
C c = new C();

...

// wish i knew how to write A's to_str()
c.set_foo(a.to_str());

我应该提到 C 相当稳定,但 A 和 B(以及 A 的其余依赖项)处于不断变化的状态,因此所需的代码更改越少越好。当前的 print(FILE* F) 接口也需要保留。我考虑了几种实现 A::to_str() 的方法,每种方法都有优点和缺点:

  1. 将对 fprintf() 的调用更改为 sprintf()

    • 我不必重写任何格式字符串
    • print() 可以重新实现为: fprint(f, this.to_str());
    • 但我需要手动分配 char[],合并很多 c 字符串,最后将字符数组转换为 std::string
  2. 尝试在字符串流中捕获 a.print() 的结果

    • 我必须将所有格式字符串转换为
    • print() 必须重写,因为据我所知,没有标准方法可以从 UNIX 文件句柄创建输出流(尽管这家伙说这也许是可能的 http://synflood.at/blog/index.php?/archives/456-One-word-of-warning-about-stdio_filebuf.html).
  3. 使用Boost的字符串格式库 http://www.boost.org/doc/libs/1_36_0/libs/format/doc/format.html

    • 更多的外部依赖。恶心。
    • Format 的语法与 printf() 的不同之处足以令人烦恼:

    printf(format_str, args) -> cout

  4. 使用Qt的QString::asprintf() https://doc.qt.io/qt-5/qstring.html#asprintf%3E

    • 不同的外部依赖。

那么,我是否已经用尽了所有可能的选择?如果是这样,你认为我最好的选择是什么?如果没有,我忽略了什么?

Thanks.


这是我喜欢的习惯用法,它使功能与“sprintf”相同,但返回 std::string,并且不受缓冲区溢出问题的影响。该代码是我正在编写的开源项目的一部分(BSD 许可证),因此每个人都可以随意使用它。

#include <string>
#include <cstdarg>
#include <vector>
#include <string>

std::string
format (const char *fmt, ...)
{
    va_list ap;
    va_start (ap, fmt);
    std::string buf = vformat (fmt, ap);
    va_end (ap);
    return buf;
}



std::string
vformat (const char *fmt, va_list ap)
{
    // Allocate a buffer on the stack that's big enough for us almost
    // all the time.
    size_t size = 1024;
    char buf[size];

    // Try to vsnprintf into our buffer.
    va_list apcopy;
    va_copy (apcopy, ap);
    int needed = vsnprintf (&buf[0], size, fmt, ap);
    // NB. On Windows, vsnprintf returns -1 if the string didn't fit the
    // buffer.  On Linux & OSX, it returns the length it would have needed.

    if (needed <= size && needed >= 0) {
        // It fit fine the first time, we're done.
        return std::string (&buf[0]);
    } else {
        // vsnprintf reported that it wanted to write more characters
        // than we allotted.  So do a malloc of the right size and try again.
        // This doesn't happen very often if we chose our initial size
        // well.
        std::vector <char> buf;
        size = needed;
        buf.resize (size);
        needed = vsnprintf (&buf[0], size, fmt, apcopy);
        return std::string (&buf[0]);
    }
}

编辑:当我编写这段代码时,我不知道这需要 C99 一致性,并且 Windows(以及旧版 glibc)具有不同的 vsnprintf 行为,其中失败时返回 -1,而不是确定多少空间的度量是需要的。这是我修改后的代码,大家可以看一下吗?如果您认为可以,我将再次编辑以使其成为列出的唯一成本:

std::string
Strutil::vformat (const char *fmt, va_list ap)
{
    // Allocate a buffer on the stack that's big enough for us almost
    // all the time.  Be prepared to allocate dynamically if it doesn't fit.
    size_t size = 1024;
    char stackbuf[1024];
    std::vector<char> dynamicbuf;
    char *buf = &stackbuf[0];
    va_list ap_copy;

    while (1) {
        // Try to vsnprintf into our buffer.
        va_copy(ap_copy, ap);
        int needed = vsnprintf (buf, size, fmt, ap);
        va_end(ap_copy);

        // NB. C99 (which modern Linux and OS X follow) says vsnprintf
        // failure returns the length it would have needed.  But older
        // glibc and current Windows return -1 for failure, i.e., not
        // telling us how much was needed.

        if (needed <= (int)size && needed >= 0) {
            // It fit fine so we're done.
            return std::string (buf, (size_t) needed);
        }

        // vsnprintf reported that it wanted to write more characters
        // than we allotted.  So try again using a dynamic buffer.  This
        // doesn't happen very often if we chose our initial size well.
        size = (needed > 0) ? (needed+1) : (size*2);
        dynamicbuf.resize (size);
        buf = &dynamicbuf[0];
    }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++:如何在不使用 sprintf 的情况下将 fprintf 结果作为 std::string 获取 的相关文章

  • 是否存在用于编辑 doxygen 评论的“wiki”? [关闭]

    Closed 此问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 我正在开发一个相当大的开源 RTS 游戏引擎 Spring http springrts com 我最近
  • 获取 Bash 和 KornShell (ksh) 中命令的退出代码

    我想写这样的代码 command some command safeRunCommand command safeRunCommand cmnd 1 cmnd if 0 then printf Error when executing co
  • 您应该通过属性访问同一类中的变量吗?

    如果您有一个获取和设置实例变量的属性 那么通常您总是使用该类外部的属性来访问它 我的问题是你也应该在课堂上这样做吗 如果有的话 我总是使用该属性 即使是在班级内 但我想听到一些支持和反对的论据 以确定哪个是最正确的以及为什么 或者这只是项目
  • 对“组件”类型的引用声明它是在“系统”中定义的

    尝试在 UWP 应用程序中获取一些 WMI 对象 在 net 4 6 上运行 VS2015 我收到 ForEach 和方法调用错误 指出 引用类型 组件 声明它是在 系统 中定义的 错误为 CS7069 using System using
  • 如何将 Boost 库添加到 XCode 6.0 中的 C++ 程序?

    我在用着XCode6 0并且需 要boost程序库 我已经下载了boost 1 57 0 tar gz from http sourceforge net projects boost files boost 1 57 0 http sou
  • C++变量声明和初始化规则

    考虑以下声明和初始化类型变量的方法C C c1 C c2 c2 C C c3 C C c4 C 所有这些是否完全等同 或者其中一些可以根据确切的定义而有所不同C 假设它有公共默认值和复制构造函数 这些意味着 C c1 default con
  • C#等待串口数据

    我试图通过 C 应用程序从指纹扫描仪获取数据 但在指纹发送之前 我的整个代码都会执行 我尝试使用延迟功能System Threading Thread Sleep 1000 因此它可以在下一步执行之前获取数据 但这一切似乎都是徒劳的 任何人
  • Azure 函数 - 如何读取表单数据

    如何阅读表单数据 in Azure 函数 我尝试了多种方法 但总是出现错误 例如 using System Net public static async Task
  • HttpCookie 和 Cookie 的区别?

    所以我很困惑 因为 msdn 和其他教程告诉我使用 HttpCookies 通过 Response Cookies Add cookie 添加 cookie 但这就是问题所在 Response Cookies Add 只接受 Cookie
  • 为什么 ReadKey 在从 Git Bash 运行 .net-core 控制台应用程序时会抛出异常?

    这是代码 ConsoleKeyInfo cki while cki Console ReadKey true Key ConsoleKey Escape Console WriteLine cki Key 当我使用 dotnet run 从
  • 如何结束用户会话并确保用户已注销?

    我是 aspx 的新手 现在的问题是 因为我正在做一个支持网络的项目 所以我从用户那里登录了 我拖放登录模板 然后使用 Session Authentication username Tostring 存储当前登录用户的信息等 现在我什至使
  • 命令绑定到 ViewModel,并在 View 中使用确认逻辑

    寻找最优雅的解决方案将按钮命令绑定到 ViewModel ICommand 属性 同时允许在视图中进行确认 我想做的事 仅允许用户在应该时单击按钮 单击按钮时 要求确认 如果确认 则在 ViewModel 中进行工作 否则取消 不要破坏MV
  • 如何将格式化的电子邮件地址解析为显示名称和电子邮件地址?

    给定电子邮件地址 Jim 电子邮件受保护 gt 如果我尝试将其传递给 MailAddress 我会得到异常 指定的字符串不符合电子邮件地址所需的格式 如何将此地址解析为显示名称 Jim 和电子邮件地址 电子邮件受保护 cdn cgi l e
  • #region 描述编译到.net 中的.exe 中?

    region endregion 指令 描述 是否编译到 NET 中的 EXE 中 我知道注释不是 但我经常在一个区域内对代码组进行分块并给出有用的描述 我想确保这些描述在我编译的代码中不可见 我不是在寻找混淆信息 不过 谢谢 不 他们不是
  • 让 clang-tidy 修复头文件

    我正在将当前使用 gcc 编译的项目移至 clang 并有一堆 gcc 没有生成的警告 Winconsistent missing override clang tidy致力于修复这些错误 cpp文件 但是它不触及hpp文件 因为在数据库中
  • 如何在 C++ 中检查文件是否已被另一个应用程序打开?

    我知道 有is open C 中的函数 但我希望一个程序检查文件是否尚未被另一个应用程序打开 有没有办法使用标准库来做到这一点 编辑 在答案中澄清这是针对 Linux 应用程序的 不仅标准库没有这个功能 一般来说也是不可能的 你可以 在li
  • ARM + gcc:不要使用一大块 .rodata 部分

    我想使用 gcc 编译一个程序 并针对 ARM 处理器进行链接时间优化 当我在没有 LTO 的情况下编译时 系统会被编译 当我启用 LTO 时 使用 flto 我收到以下汇编错误 错误 无效的文字常量 池需要更近 环顾网络 我发现这与我系统
  • 是否可以在 Visual Studio 2010 项目中使用多个“字符集”?

    如您所知 在 Visual Studio 2010 c 中 我们有 noset unicode 和 MBCS 字符集 我们可以通过菜单或预处理器指令 如 define UNICODE 来设置它 我正在开发一个项目 它有一个使用 MBCS 字
  • qt 读取就绪信号

    我正在尝试与运行 1996 年处理器的设备建立串行连接 这意味着数据传输回我可能需要几秒钟的时间 我知道readyRead每次有新数据可用时都会生成信号 但我的问题是生成多长时间 这也是我可以测试就绪读取是否较低的一种方法 因为如果当它们不
  • 可选地支持模板的initializer_list构造可能包装容器

    如果我有一个包装标准容器的模板 似乎我可以相当轻松地委托初始化器列表构造函数 template

随机推荐