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

我正在用C ++实现的一个开源的UNIX工具,我需要改变一些代码来实现我想要的function。 我希望做出尽可能小的改变,希望能够在上游接受我的补丁。 可以在标准C ++中实现的解决scheme,不要创build更多的外部依赖性。

这是我的问题。 我有一个C ++类 – 我们称之为“A” – 目前使用fprintf()将其格式严格的数据结构打印到文件指针。 在它的打印函数中,它也recursion地调用几个成员类的相同定义的打印函数(“B”是一个例子)。 还有另外一个类C有一个成员std :: string“foo”,需要设置为A的一个实例的print()结果。把它看作是一个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的其他依赖者)处于不断变化的状态,所以代码更改越less越好。 当前打印(FILE * F)界面也需要保留。 我已经考虑了几种实现A :: to_str()的方法,每种方法都有优缺点:

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

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

    • 我将不得不将所有的格式string转换为<<输出格式。 有数百个fprintf()可以转换: – {
    • print()将不得不被重写,因为我不知道从UNIX文件句柄创build输出stream的标准方式(虽然这个人说这可能是可能的 )。
  3. 使用Boost的string格式库

    • 更多的外部依赖。 呸。
    • 格式的语法和printf()不一样,很烦人:

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

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

    • 不同的外部依赖。

那么,我用尽了所有可能的select吗? 如果是这样,你认为哪个是我最好的select? 如果不是,我忽略了什么?

谢谢。

我正在使用#3:提升字符串格式库 – 但我不得不承认,我从来没有任何问题的格式规格的差异。

对我来说就像一个魅力 – 而外部依赖可能会更糟糕(一个非常稳定的图书馆)

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

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

就像boost :: format库一样:

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

相比之下,我有一些格式规范,我不能真正用流(尽我所知)

这里是我喜欢使用与'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, ie, 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]; } } 

你可以使用std :: string和iostreams格式,比如setw()和其他的iomanip

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

 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来避免缓冲区溢出。 您也可以有选择地将更具风险的冲刺文件更改为<<格式,以便更安全,但尽可能少地改变。

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

我希望这有帮助!

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