Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 function _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.

like image 360
zaharpopov Avatar asked Nov 15 '10 06:11

zaharpopov


Video Answer


2 Answers

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.

like image 174
Tony Delroy Avatar answered Oct 02 '22 17:10

Tony Delroy


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.

like image 37
vitaut Avatar answered Oct 02 '22 16:10

vitaut