代码之家  ›  专栏  ›  技术社区  ›  underspecified

C++:如何获得fPRTFF结果作为STD::String W/O Simulf

  •  17
  • underspecified  · 技术社区  · 16 年前

    我正在使用一个开源的UNIX工具,它是用C++实现的,我需要改变一些代码来让它做我想做的事情。我想做最小可能的改变,希望我的补丁能在上游被接受。可在标准C++中实现且不创建更多外部依赖的解决方案是首选的。

    这是我的问题。我有一个C++类——我们称之为“A”——当前使用fPrftFe()来将其重格式的数据结构打印到一个文件指针。在其print函数中,它还递归地调用几个成员类的相同定义的print函数(“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的其余依赖项)处于一种不断变化的状态,所以所需的代码更改越少越好。还需要保留当前的打印(file*f)接口。我考虑了几种实现A::to_str()的方法,每种方法都有其优缺点:

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

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

      • 我必须将所有格式字符串转换为<<输出格式。有数百个fprintf()要转换:-{
      • print()必须重写,因为我不知道从Unix文件句柄创建输出流的标准方法(尽管 this guy says it may be possible )
    3. 使用Boost的字符串 format library

      • 更多外部依赖项。讨厌。
      • 格式的语法与printf()完全不同,这让人恼火:

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

    4. 使用QT QString::asprintf()

      • 不同的外部依赖。

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

    谢谢。

    6 回复  |  直到 8 年前
        1
  •  13
  •   bernhardrusch    16 年前

    我正在使用3:BoostString格式库-但我必须承认,我从来没有对格式规范的差异有过任何问题。

    对我来说工作就像是一种魅力——外部依赖性可能更糟(一个非常稳定的库)

    已编辑:添加如何使用boost::格式而不是printf的示例:

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

    在boost::格式库中应该是这样的:

    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替换(将格式化字符串写入文件,或将自定义输出写入日志文件),并且从未遇到格式差异问题。可能有一些(或多或少有些模糊)格式说明符是不同的-但我从来没有遇到过问题。

    相比之下,我有一些格式规范,我不能真正处理流(正如我记得的那样)。

        2
  •  36
  •   Michael Anderson    9 年前

    这是我喜欢的习惯用法,它使功能与“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];
        }
    }
    
        3
  •  1
  •   Yann Ramin    16 年前

    可以使用带格式的std::string和iostreams,例如setw()调用和iomanip中的其他调用。

        4
  •  1
  •   Jan de Vos    16 年前

    以下可能是另一种解决方案:

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

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

    void A::print(FILE *f) {
        printto(ofstream(f));
    }
    
    string A::to_str() {
        ostringstream os;
        printto(os);
        return os.str();
    }
    

    当然,您应该使用snprintf而不是sprintf来避免缓冲区溢出。您还可以有选择地将风险更大的sprintfs更改为<<格式,以更安全,但更改的次数越少越好。

        5
  •  1
  •   Kevin    16 年前

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

    希望这有帮助!

        6
  •  0
  •   Assaf Lavie    16 年前

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