我正在使用一个用C ++实现的开源UNIX工具,我需要更改一些代码才能让它做我想做的事情。我想做出尽可能小的改变,希望上传接受我的补丁。在标准C ++中可实现且不创建更多外部依赖关系的解决方案是首选。
这是我的问题。我有一个C ++类 - 我们称之为“A” - 目前使用fprintf()将其格式严格的数据结构打印到文件指针。在其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()的方法,每种方法都有优点和缺点:
将对fprintf()的调用更改为sprintf()
尝试在字符串流中捕获a.print()的结果
使用Boost的字符串format library
printf(format_str,args) - > cout<< boost :: format(format_str)%arg1%arg2%etc
使用Qt的QString::asprintf()
那么,我已经用尽了所有可能的选择吗?如果是这样,你认为哪个是我最好的选择?如果没有,我忽略了什么?
感谢。
答案 0 :(得分:37)
这是我喜欢的功能,它使功能与'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];
}
}
答案 1 :(得分:13)
我正在使用#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替换(将格式化的字符串写入文件,或将自定义输出写入日志文件),并且从未遇到过格式差异的问题。可能有一些(或多或少模糊)格式说明符不同 - 但我从来没有遇到过问题。
相比之下,我有一些格式规范,我不能真正使用流(我记得很多)
答案 2 :(得分:1)
您可以将std :: string和iostreams与格式化一起使用,例如setw()调用以及iomanip中的其他调用
答案 3 :(得分:1)
以下可能是另一种解决方案:
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来避免缓冲区溢出。您还可以选择性地将风险更高的sprintf更改为&lt;&lt;格式,更安全,但尽可能少地改变。
答案 4 :(得分:1)
您应该尝试使用Loki库的SafeFormat头文件(http://loki-lib.sourceforge.net/index.php?n=Idioms.Printf)。它类似于boost的字符串格式库,但保留了printf(...)函数的语法。
我希望这有帮助!
答案 5 :(得分:0)
这是关于序列化的吗?还是印刷得当? 如果是前者,也考虑boost :: serialization。这完全是关于对象和子对象的“递归”序列化。
答案 6 :(得分:0)
The {fmt} library提供了fmt::sprintf
函数,该函数执行与printf
兼容的格式(包括根据POSIX specification的位置参数),并以std::string
的形式返回结果:>
std::string s = fmt::sprintf("The answer is %d.", 42);
免责声明:我是该库的作者。