编写c ++函数format_string用于格式化std :: string的sprintf

时间:2010-11-15 06:54:55

标签: c++ string string-formatting

为了方便使用,我想编写类似于sprintf的格式化函数,只需返回std :: string,如下所示:

std::string format_string(const char* format, ...)

我可以在那里使用vsnprintf但是有问题 - 我事先并不知道临时缓冲区应该有多长。在微软有功能_vscprintf可以做到,但我认为它不可移植?

一个选项是让临时缓冲区启动一些已知大小,然后在vsnprintf看到它不够时增加它。有更好的方法吗?感谢


P.S。请不要Boost给出答案。我知道Boost,但我很好奇如何实现它。

4 个答案:

答案 0 :(得分:3)

C99也引入了snprintfvsnprintf(v)snprintf有几个可移植的开源实现,例如this one。后者还实现了动态分配存储的vasprintf

另请考虑C++ Format library提供类似于Boost格式的安全printf实现,但要快得多。

答案 1 :(得分:3)

  

一个选项是让临时缓冲区启动一些已知大小,然后在vsnprintf看到它不够时增加它。有更好的方法吗?感谢

您可以使用vasprintf(),但这会产生不必要的堆分配 - 平均来说不太可能更快。使用alloca可以避免堆。或者,您可以直接写入返回的string:NRVO应该避免复制,并且从C ++ 11开始,移动语义会将成本sans-NRVO限制为几个指针交换。

#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;                                                             
}

更简单且最初更快的选项是:

    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;

潜在的问题是你要分配至少256个字符,但是要存储的实际文本很短:这可能会增加你的内存/缓存相关性能。您可以使用[shrink_to_fit] http://en.cppreference.com/w/cpp/string/basic_string/shrink_to_fit)解决问题,但标准并不要求它实际执行任何操作(要求是“非绑定”)。如果您最终必须复制到一个新的精确大小的字符串,您可能也使用了本地char数组。

答案 2 :(得分:0)

如果你不关心使用非标准功能(即:对任何平台使用不同的功能,就像我从你的问题中得到的那样),并且想要快速的工作,你会发现{{1}和GNU扩展中的asprintf(也就是它:C也不是POSIX,但是由GCC和glibc支持)。

它们的工作方式类似于vasprintfprintf,但需要注意分配缓冲区内存以简化工作。

vsprintf

您可能会在任何系统上找到类似的功能。对于其他人,您可以编写一些代码来分配一些缓冲区并将其传递给int asprintf( char **strp, const char *fmt, ... ); int vasprintf( char **strp, const char *fmt, va_list ap );

答案 3 :(得分:0)

执行此操作的有效方法是自己编写整个函数。也就是说,不要转发到其他类似printf的函数,而是自己解析并打印所有参数。扫描整个格式字符串,并检查参数以确定必要的缓冲区大小。随后,打印到该缓冲区

这不是一个全有或全无的建议;您仍然可以对所选类型使用sprintf。例如。当您的输入格式字符串包含sprintf(buf, "%6.4f", dbltemp);参数时,使用%6.4f可能更容易,但%s可以更好地处理(简单的memcpy)。