write c++ function format_string for formatting like sprintf of std::string
For convenient usage I want to write formatting function similar to sprintf just returning std::string, like this:
std::string format_string(const char* format, ...)
I can use vsnprintf
there but have problem - I don't know in advance how long temp buffer should be. On Microsoft have f开发者_JAVA百科unction _vscprintf
that can do it but I think it not portable?
One option is have temp buffer start some known size then increase it if see it's not enough with vsnprintf
. Are there better approach? Thanks
P.S. Please give answer without Boost. I know about Boost, but I'm curious how implement it without.
One option is have temp buffer start some known size then increase it if see it's not enough with
vsnprintf
. Are there better approach? Thanks
You can use vasprintf()
, but that does an unnecessary heap allocation - it's unlikely to be faster on average. Using alloca
you can avoid the heap. Or, you can write directly into the string
that's returned: NRVO should avoid a copy, and as of C++11 move semantics would limit the cost sans-NRVO to a few pointer swaps.
#include <cstdio>
#include <cstdarg>
#include <alloca.h>
#include <string>
#include <iostream>
std::string stringf(const char* format, ...)
{
va_list arg_list;
va_start(arg_list, format);
// SUSv2 version doesn't work for buf NULL/size 0, so try printing
// into a small buffer that avoids the double-rendering and alloca path too...
char short_buf[256];
const size_t needed = vsnprintf(short_buf, sizeof short_buf,
format, arg_list) + 1;
if (needed <= sizeof short_buf)
return short_buf;
// need more space...
// OPTION 1
std::string result(needed, ' ');
vsnprintf(result.data(), needed, format, arg_list);
return result; // RVO ensures this is cheap
OR
// OPTION 2
char* p = static_cast<char*>(alloca(needed)); // on stack
vsnprintf(p, needed, format, arg_list);
return p; // text copied into returned string
}
int main()
{
std::string s = stringf("test '%s', n %8.2f\n", "hello world", 3.14);
std::cout << s;
}
An simpler and initially faster option would be:
std::string result(255, ' '); // 255 spaces + NUL
const size_t needed = vsnprintf(result.data(), result.size() + 1,
format, arg_list);
result.resize(needed); // may truncate, leave or extend...
if (needed > 255) // needed doesn't count NUL
vsnprintf(result.data(), needed + 1, format, arg_list);
return result;
The potential problem is that you're allocating at least 256 characters however short the actual text stored: that could add up and cost you in memory/cache related performance. You might be able to work around the issue using [shrink_to_fit
]http://en.cppreference.com/w/cpp/string/basic_string/shrink_to_fit), but the Standard doesn't require it to actually do anything (the requirements are "non binding"). If you end up having to copy to a new exactly-sized string, you might as well have used the local char array.
C99 introduced snprintf and possibly vsnprintf
as well. There are several portable open source implementations of (v)snprintf
such as this one. The latter also implements vasprintf
which dynamically allocates storage.
Also consider the C++ Format library which provides a safe printf
implementation similar to Boost Format, but much faster.
If you don't care using non-standard functions (ie: using a different function for any platform, like I got you're doing from your question), and want a fast job, you will find asprintf
and vasprintf
amongst GNU extension (that's it: nor C nor POSIX, but supported by GCC and glibc).
They works like printf
and vsprintf
, but take care of allocating buffer memory easing your work.
int asprintf( char **strp, const char *fmt, ... );
int vasprintf( char **strp, const char *fmt, va_list ap );
You'll probably found similar functions on any system. For the others you could just write some code to allocate some buffer and pass it to snprintf
.
The robust way to do this is by writing the entire function yourself. That is to say, don't forward to other printf
-like function, but parse and print all arguments yourself. Scan the whole format string, and check the arguments to determine the necessary buffer size. Subsequently, print to that buffer
This is not an all-or-nothing suggestion; you can still use sprintf
for selected types. E.g. it may be easier to use sprintf(buf, "%6.4f", dbltemp);
when your input format string contains a %6.4f
argument, but %s
is better handled yourself (simple memcpy).
精彩评论