关于Unix:C ++:如何在没有sprintf的情况下将fprintf结果作为std :: string获得

关于Unix:C ++:如何在没有sprintf的情况下将fprintf结果作为std :: string获得

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

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

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

用伪代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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()的方法,每种方法都有其优点和缺点:

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

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

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

    • 更多的外部依赖性。 uck
    • 格式的语法与printf()的区别足以使人烦恼:

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

  • 使用Qt的QString :: asprintf()

    • 不同的外部依赖性。
  • 那么,我是否穷尽了所有可能的选择?如果是这样,您认为我的最佳选择是什么?如果没有,我忽略了什么?

    谢谢。


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    #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,而不是确定多少空间是必需的。这是我修改后的代码,大家可以看看吗,如果您认为还可以,我将再次编辑以列出唯一的成本:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    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];
        }
    }

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

    对我而言,它的工作就像一个魅力-外部依赖可能会更糟(一个非常稳定的库)

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

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

    boost :: format库将是这样的:

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

    希望这有助于阐明boost :: format的用法

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

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


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


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

    我希望这有帮助!


    以下是替代解决方案:

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

    (B::printto类似),并定义

    1
    2
    3
    4
    5
    6
    7
    8
    9
    void A::print(FILE *f) {
        printto(ofstream(f));
    }

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

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


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

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

    免责声明:我是这个图书馆的作者。


    这是有关序列化的吗?还是打印正确?
    如果是前者,请考虑也使用boost :: serialization。这都是关于对象和子对象的"递归"序列化。


    推荐阅读