开发者

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).

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜