C ++:如何以不带sprintf的std :: string形式获取fprintf结果
我正在使用以C ++实现的开源UNIX工具,我需要更改一些代码才能使其执行我想要的操作。我希望进行最小的更改,以期使我的补丁程序被上游接受。首选可在标准C ++中实现且不会创建更多外部依赖项的解决方案。
这是我的问题。我有一个C ++类-让我们将其称为" A"-当前使用fprintf()将其格式繁重的数据结构打印到文件指针。在其打印功能中,它还递归调用几个成员类的相同定义的打印功能(" B"为示例)。还有另一个具有成员std :: string" foo"的类C,需要将其设置为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()的方法,每种方法都有其优点和缺点:
- print()可以重新实现为:fprint(f,this.to_str());
- 但是我需要手动分配char [] s,合并很多c字符串,最后将字符数组转换为std :: string
- print()必须重写,因为我不知道从UNIX文件句柄创建输出流的标准方法(尽管此人说这是可能的)。
- 格式的语法与printf()的区别足以使人烦恼:
printf(format_str,args)-> cout << boost :: format(format_str)%arg1%arg2%等
那么,我是否穷尽了所有可能的选择?如果是这样,我们认为我的最佳选择是什么?如果没有,我忽略了什么?
谢谢。
解决方案
回答
这是有关序列化的吗?还是打印正确?
如果是前者,请考虑也使用boost :: serialization。都是关于对象和子对象的"递归"序列化。
回答
我正在使用#3:boost字符串格式库,但我必须承认,格式规范的差异从来没有任何问题。
对我而言,它的工作就像一个魅力,外部依赖可能会更糟(一个非常稳定的库)
编辑:添加示例如何使用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的替代品(将格式化的字符串写入文件,或者将自定义输出写入日志文件),并且从未遇到格式差异的问题。可能有一些(或者多或者少晦涩的)格式说明符,它们是不同的,但我从来没有遇到过问题。
相比之下,我有一些我无法真正使用流的格式规范(据我所记得)
回答
我们可以使用std :: string和iostream进行格式化,例如setw()调用以及iomanip中的其他格式
回答
这是我喜欢使功能与'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]; } }
回答
以下是替代解决方案:
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更改为<<格式,以确保安全,但更改尽可能少。
回答
我们应该尝试使用Loki库的SafeFormat头文件(http://loki-lib.sourceforge.net/index.php?n=Idioms.Printf)。它类似于boost的字符串格式库,但是保留了printf(...)函数的语法。
我希望这有帮助!