C++:如何将 fprintf 结果作为 std::string w/o sprintf

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/69738/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 12:37:50  来源:igfitidea点击:

C++: how to get fprintf results as a std::string w/o sprintf

c++unixformatprintfstdstring

提问by underspecified

I am working with an open-source UNIX tool that is implemented in C++, and I need to change some code to get it to do what I want. I would like to make the smallest possible change in hopes of getting my patch accepted upstream. Solutions that are implementable in standard C++ and do not create more external dependencies are preferred.

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

Here is my problem. I have a C++ class -- let's call it "A" -- that currently uses fprintf() to print its heavily formatted data structures to a file pointer. In its print function, it also recursively calls the identically defined print functions of several member classes ("B" is an example). There is another class C that has a member std::string "foo" that needs to be set to the print() results of an instance of A. Think of it as a to_str() member function for A.

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

In pseudocode:

在伪代码中:

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());

I should mention that C is fairly stable, but A and B (and the rest of A's dependents) are in a state of flux, so the less code changes necessary the better. The current print(FILE* F) interface also needs to be preserved. I have considered several approaches to implementing A::to_str(), each with advantages and disadvantages:

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

  1. Change the calls to fprintf() to sprintf()

    • I wouldn't have to rewrite any format strings
    • print() could be reimplemented as: fprint(f, this.to_str());
    • But I would need to manually allocate char[]s, merge a lot of c strings , and finally convert the character array to a std::string
  2. Try to catch the results of a.print() in a string stream

    • I would have to convert all of the format strings to << output format. There are hundreds of fprintf()s to convert :-{
    • print() would have to be rewritten because there is no standard way that I know of to create an output stream from a UNIX file handle (though this guy says it may be possible).
  3. Use Boost's string format library

    • More external dependencies. Yuck.
    • Format's syntax is different enough from printf() to be annoying:

    printf(format_str, args) -> cout << boost::format(format_str) % arg1 % arg2 % etc

  4. Use Qt's QString::asprintf()

    • A different external dependency.
  1. 将对 fprintf() 的调用更改为 sprintf()

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

    • 我必须将所有格式字符串转换为 << 输出格式。有数百个 fprintf() 需要转换:-{
    • print() 必须重写,因为我知道没有标准方法可以从 UNIX 文件句柄创建输出流(尽管这个人说这可能是可能的)。
  3. 使用 Boost 的字符串格式库

    • 更多的外部依赖。哎呀。
    • Format 的语法与 printf() 的不同很烦人:

    printf(format_str, args) -> cout << boost::format(format_str) % arg1 % arg2 % 等

  4. 使用 Qt 的QString::asprintf()

    • 不同的外部依赖。

So, have I exhausted all possible options? If so, which do you think is my best bet? If not, what have I overlooked?

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

Thanks.

谢谢。

采纳答案by bernhardrusch

I am using #3: the boost string format library - but I have to admit that I've never had any problem with the differences in format specifications.

我正在使用 #3:boost 字符串格式库 - 但我必须承认我从来没有遇到过格式规范差异的任何问题。

Works like a charm for me - and the external dependencies could be worse (a very stable library)

对我来说就像一个魅力 - 外部依赖可能更糟(一个非常稳定的库)

Edited: adding an example how to use boost::format instead of printf:

编辑:添加一个示例如何使用 boost::format 而不是 printf:

sprintf(buffer, "This is a string with some %s and %d numbers", "strings", 42);

would be something like this with the boost::format library:

使用 boost::format 库会是这样的:

string = boost::str(boost::format("This is a string with some %s and %d numbers") %"strings" %42);

Hope this helps clarify the usage of boost::format

希望这有助于澄清 boost::format 的用法

I've used boost::format as a sprintf / printf replacement in 4 or 5 applications (writing formatted strings to files, or custom output to logfiles) and never had problems with format differences. There may be some (more or less obscure) format specifiers which are differently - but I never had a problem.

我在 4 或 5 个应用程序中使用 boost::format 作为 sprintf/printf 替换(将格式化字符串写入文件,或自定义输出到日志文件),并且从未遇到过格式差异问题。可能有一些(或多或少晦涩的)格式说明符有所不同 - 但我从来没有遇到过问题。

In contrast I had some format specifications I couldn't really do with streams (as much as I remember)

相比之下,我有一些格式规范,我无法真正使用流(据我所知)

回答by Larry Gritz

Here's the idiom I like for making functionality identical to 'sprintf', but returning a std::string, and immune to buffer overflow problems. This code is part of an open source project that I'm writing (BSD license), so everybody feel free to use this as you wish.

这是我喜欢使功能与 '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]);
    }
}

EDIT: when I wrote this code, I had no idea that this required C99 conformance and that Windows (as well as older glibc) had different vsnprintf behavior, in which it returns -1 for failure, rather than a definitive measure of how much space is needed. Here is my revised code, could everybody look it over and if you think it's ok, I will edit again to make that the only cost listed:

编辑:当我编写这段代码时,我不知道这需要符合 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];
    }
}

回答by Yann Ramin

You can use std::string and iostreams with formatting, such as the setw() call and others in iomanip

您可以使用 std::string 和 iostreams 进行格式化,例如 setw() 调用和 iomanip 中的其他调用

回答by Jan de Vos

The following might be an alternative solution:

以下可能是替代解决方案:

void A::printto(ostream outputstream) {
    char buffer[100];
    string s = "stuff";
    sprintf(buffer, "some %s", s);
    outputstream << buffer << endl;
    b.printto(outputstream);
}

(B::printtosimilar), and define

B::printto相似),并定义

void A::print(FILE *f) {
    printto(ofstream(f));
}

string A::to_str() {
    ostringstream os;
    printto(os);
    return os.str();
}

Of course, you should really use snprintf instead of sprintf to avoid buffer overflows. You could also selectively change the more risky sprintfs to << format, to be safer and yet change as little as possible.

当然,您应该真正使用 snprintf 而不是 sprintf 来避免缓冲区溢出。您还可以有选择地将风险更大的 sprintfs 更改为 << 格式,以提高安全性并尽可能减少更改。

回答by Kevin

You should try the Loki library's SafeFormat header file (http://loki-lib.sourceforge.net/index.php?n=Idioms.Printf). It's similar to boost's string format library, but keeps the syntax of the printf(...) functions.

您应该尝试 Loki 库的 SafeFormat 头文件 ( http://loki-lib.sourceforge.net/index.php?n=Idioms.Printf)。它类似于 boost 的字符串格式库,但保留了 printf(...) 函数的语法。

I hope this helps!

我希望这有帮助!

回答by vitaut

The {fmt} libraryprovides fmt::sprintffunction that performs printf-compatible formatting (including positional arguments according to POSIX specification) and returns the result as std::string:

{fmt} 库提供fmt::sprintf了执行printf兼容格式(包括根据POSIX 规范的位置参数)并返回结果的函数std::string

std::string s = fmt::sprintf("The answer is %d.", 42);

Disclaimer: I'm the author of this library.

免责声明:我是这个库的作者。

回答by Assaf Lavie

Is this about serialization? Or printing proper? If the former, consider boost::serialization as well. It's all about "recursive" serialization of objects and sub-object.

这是关于序列化吗?还是打印正常?如果是前者,还要考虑 boost::serialization。这都是关于对象和子对象的“递归”序列化。