像sprintf一样的std :: string格式

Max*_*rai 412 c++ string formatting stl

我必须格式化std::stringsprintf,并将其发送到文件流.我怎样才能做到这一点?

Dou*_* T. 304

你不能直接这样做,因为你没有对底层缓冲区的写权限(直到C++ 11;参见Dietrich Epp的评论).您必须先在c-string中执行此操作,然后将其复制到std :: string中:

  char buff[100];
  snprintf(buff, sizeof(buff), "%s", "Hello");
  std::string buffAsStdStr = buff;
Run Code Online (Sandbox Code Playgroud)

但我不确定为什么你不会只使用字符串流?我假设你有特定的理由不只是这样做:

  std::ostringstream stringStream;
  stringStream << "Hello";
  std::string copyOfStr = stringStream.str();
Run Code Online (Sandbox Code Playgroud)

  • 出于某种原因,其他语言使用类似printf的语法:Java,Python(新语法仍然更接近printf而不是流).只有C++才会对无辜的人类造成这种冗长的憎恶. (196认同)
  • 使用格式的原因是让本地化程序重建外语句子的结构,而不是硬编码句子的语法. (66认同)
  • 约翰,溪流并不慢.流看起来很慢的唯一原因是默认情况下,iostream正在与C FILE输出同步,以便正确输出混合的cout和printfs.禁用此链接(通过调用cout.sync_with_stdio(false))会导致c ++的流优于stdio,至少从MSVC10开始. (17认同)
  • `char buf [100];`中的神奇cookie使得这个解决方案不是很强大.但基本的想法是存在的. (12认同)
  • 更好的是,使用`asprintf`,它分配一个具有足够空间的新字符串来保存结果.然后如果你愿意的话,将它复制到`std :: string`,并记住`free`原文.此外,可以把它放在一个宏中,这样任何好的编译器都可以帮助你验证格式 - 你不想把'double`放在'%s`的预期位置 (8认同)
  • @villapx:你有一些缺失.请参阅此答案:http://stackoverflow.com/questions/25169915/is-writing-to-str0-buffer-of-a-stdstring-well-defined-behaviour-in-c11 (4认同)
  • 要使用 osstream,请在程序开头添加“#include &lt;sstream&gt;”。 (2认同)
  • @AaronMcDaid`asprintf`是GNU扩展,因此不可移植。 (2认同)
  • `但我不确定为什么你不会只使用字符串流......`代码膨胀在呼叫站点?这不仅仅是代码臃肿,而是[疯狂代码臃肿](https://godbolt.org/g/HnvnEi).以下是[printf版本的样子](https://godbolt.org/g/wuH485) (2认同)
  • 为什么存在一种削弱简洁语义重要性的文化?从字面上看,字符串流是一个使用起来非常痛苦的对象。如果你的工作是每天不间断地编写代码,那么 C++ 代码写起来会非常痛苦。就像:它伤害了你的手。 (2认同)

Eri*_*sty 239

vsnprintf()内部使用的C++ 11解决方案:

#include <stdarg.h>  // For va_start, etc.

std::string string_format(const std::string fmt, ...) {
    int size = ((int)fmt.size()) * 2 + 50;   // Use a rubric appropriate for your code
    std::string str;
    va_list ap;
    while (1) {     // Maximum two passes on a POSIX system...
        str.resize(size);
        va_start(ap, fmt);
        int n = vsnprintf((char *)str.data(), size, fmt.c_str(), ap);
        va_end(ap);
        if (n > -1 && n < size) {  // Everything worked
            str.resize(n);
            return str;
        }
        if (n > -1)  // Needed size returned
            size = n + 1;   // For null char
        else
            size *= 2;      // Guess at a larger size (OS specific)
    }
    return str;
}
Run Code Online (Sandbox Code Playgroud)

更安全,更高效(我测试它,它更快)方法:

#include <stdarg.h>  // For va_start, etc.
#include <memory>    // For std::unique_ptr

std::string string_format(const std::string fmt_str, ...) {
    int final_n, n = ((int)fmt_str.size()) * 2; /* Reserve two times as much as the length of the fmt_str */
    std::unique_ptr<char[]> formatted;
    va_list ap;
    while(1) {
        formatted.reset(new char[n]); /* Wrap the plain char array into the unique_ptr */
        strcpy(&formatted[0], fmt_str.c_str());
        va_start(ap, fmt_str);
        final_n = vsnprintf(&formatted[0], n, fmt_str.c_str(), ap);
        va_end(ap);
        if (final_n < 0 || final_n >= n)
            n += abs(final_n - n + 1);
        else
            break;
    }
    return std::string(formatted.get());
}
Run Code Online (Sandbox Code Playgroud)

fmt_str是按值传递与要求相符va_start.

注意:"更安全"和"更快"的版本在某些系统上不起作用.因此两者仍然列出.此外,"更快"完全取决于预分配步骤是否正确,否则strcpy会使其变慢.

  • 你正在覆盖str.c_str()?这不危险吗? (27认同)
  • 带参考参数的va_start在MSVC上存在问题.它以静默方式失败并返回指向随机内存的指针.作为解决方法,使用std :: string fmt而不是std :: string&fmt,或者编写包装器对象. (8认同)
  • I + 1因为我知道这可能会根据大多数std :: strings的实现方式而有效,但是c_str实际上并不打算成为修改底层字符串的地方.它应该是只读的. (6认同)
  • 并且为了获得预先得到的字符串长度,请参阅:http://stackoverflow.com/a/7825892/908336我没有看到每次迭代中增加`size`的重点,当你可以通过第一次调用获得它时`vsnprintf()`. (6认同)
  • 慢.为什么增加1?什么时候这个功能返回-1? (3认同)
  • @MassoodKhaari让vsnprintf首先返回大小的速度慢了两倍.并且在某些操作系统上它是不可靠的.最好做出好的猜测,然后,如果vsnprintf失败,加倍.由于指数,这导致O(1)算法.大多数时候,你不会做超过1次传球. (3认同)
  • @死牛肉:此代码的来源来自 vsnprintf ( http://www.tin.org/bin/man.cgi?section=3&amp;topic=vsnprintf ) 的文档,在评论中您的问题得到了回答。 (2认同)
  • 对于参考参数,它不是MSVC问题.它是标准所要求的:http://www.cplusplus.com/reference/cstdarg/va_start/(参见C++中`paramN`的要求.) (2认同)
  • @MassoodKhaari 没有真正的迭代......它只在遵守 POSIX 标准的操作系统上发生一次。预先正确猜测可以将性能提高 2 倍。如果您在有或没有猜测的情况下进行测试...您会发现所有性能都依赖于猜测的准确性。 (2认同)
  • 对于那些关心使用字符串作为可写缓冲区的人,请参阅:http://stackoverflow.com/questions/25169915/is-writing-to-str0-buffer-of-a-stdstring-well-define-behaviour-in -c11 (2认同)

iFr*_*cht 214

利用C++ 11 std::snprintf,这变得非常简单和安全.我看到很多关于这个问题的答案显然是在C++ 11之前编写的,它使用固定的缓冲区长度和varg,我不建议出于安全性,效率和清晰度的原因.

#include <memory>
#include <iostream>
#include <string>
#include <cstdio>

template<typename ... Args>
std::string string_format( const std::string& format, Args ... args )
{
    size_t size = snprintf( nullptr, 0, format.c_str(), args ... ) + 1; // Extra space for '\0'
    std::unique_ptr<char[]> buf( new char[ size ] ); 
    snprintf( buf.get(), size, format.c_str(), args ... );
    return std::string( buf.get(), buf.get() + size - 1 ); // We don't want the '\0' inside
}
Run Code Online (Sandbox Code Playgroud)

上面的代码片段在CC0 1.0下获得许可.

逐行说明:

目标:char*通过使用 写入astd::snprintf然后将其转换为astd::string.

首先,我们确定char数组的所需长度.

来自cppreference.com:

返回值

[...]如果由于buf_size限制而导致结果字符串被截断,则函数将返回已写入的字符总数(不包括终止空字节),如果未强制执行限制.

这意味着所需的大小是字符数加1,因此空终止符将位于所有其他字符之后,并且可以再次被字符串构造函数截断.@ alexk7在评论中解释了这个问题.

然后,我们分配一个新的字符数组并将其分配给a std::unique_ptr.通常建议这样做,因为您不必delete再次手动操作.

请注意,这不是一种unique_ptr使用用户定义类型分配的安全方法,因为如果构造函数抛出异常,则无法释放内存!

在那之后,我们当然可以使用snprintf它的预期用途并将格式化的字符串写入char[],然后创建并返回一个新的std::string.


你可以在这里看到一个实例.


如果您还想std::string在参数列表中使用,请查看此要点.


Visual Studio用户的其他信息:

正如在这个答案中解释的那样,微软改名std::snprintf_snprintf(是的,没有std::).MS进一步将其设置为已弃用并建议使用_snprintf_s,但_snprintf_s不会接受缓冲区为零或小于格式化输出,并且如果发生这种情况则不会计算输出长度.因此,为了在编译期间摆脱弃用警告,您可以在文件顶部插入以下行,其中包含以下内容_snprintf:

#pragma warning(disable : 4996)
Run Code Online (Sandbox Code Playgroud)

  • 我真的很喜欢这个解决方案,但我认为行`return string(buf.get(),buf.get()+ size);`应该是`return string(buf.get(),buf.get()+ size - 1);`否则你得到一个末尾带有空字符的字符串.我发现这是gcc 4.9的情况. (4认同)
  • @moooeeeep多个原因.首先,这里的目标是返回一个std :: string,而不是一个c-string,所以你可能意味着`return string(&buf [0],size);`或者类似的东西.其次,如果你要返回一个这样的c字符串,它会导致未定义的行为,因为保存指向的值的向量在返回时将失效.第三,当我开始学习C++时,标准没有定义元素必须存储在`std :: vector`中的顺序,因此通过指针访问其存储是未定义的行为.现在它起作用,但我认为这样做没有任何好处. (3认同)
  • 将std :: string传递给%s会导致编译错误(_error:无法通过可变参数函数传递非平凡类型'std :: __ cxx11 :: basic_string <char>'的对象;调用将在运行时中止[-Wnon-pod -varargs] _)在clang 3.9.1中,但在CL 19中,它在运行时编译精细和崩溃.我可以打开任何警告标志,在编译时也可以在cl中使用它吗? (3认同)
  • 请在Visual Studio用户的答案中强调VS的版本必须至少为2013.来自[this](https://msdn.microsoft.com/en-us/library/2ts7cx93%28v=vs.120% 29.aspx)文章你可以看到它只适用于VS2013版本:_If buffer是一个空指针,count是零,len返回格式化输出所需的字符数,不包括终止null.要使用相同的参数和语言环境参数进行成功调用,请分配一个至少包含len + 1个字符的缓冲区._ (2认同)
  • 为什么不使用 `std::vector&lt;char&gt; buf(size);` 作为缓冲区并简单地返回 `return &amp;buf[0]` ? (2认同)
  • @iFreilicht从隐式转换的向量([复制初始化](http://en.cppreference.com/w/cpp/language/copy_initialization))中构造一个新的`std :: string`,然后将其返回为复制,如功能签名所示。而且,“ std :: vector”的元素一直(并且一直打算)被[连续地]存储(http://herbsutter.com/2008/04/07/cringe-not-vectors-are-guaranteed-连续)。但我认为你这样做可能没有任何好处。 (2认同)
  • @AnhTuan这是真的,但是我想避免任何可能的错误,并且没有很多C经验,所以我用C++方式做.这样做没有显着的性能影响,我觉得它更安全. (2认同)
  • 我的编译器抱怨这个解决方案(gcc version 5.4.0 20160609 Ubuntu 5.4.0-6ubuntu1~16.04.1):`error: format not a string literal and no format arguments [-Werror=format-security]`。这是有道理的,因为 `snprintf()` 的 `format` 参数是一个变量,而不是文字,并且可能导致安全问题。更多信息:http://stackoverflow.com/q/9707569/143504 和 http://stackoverflow.com/q/9306175/143504。 (2认同)
  • 编译器将为每个参数类型元组生成模板特化.如果使用不同参数多次使用此函数,则会生成并编译大量代码.(代号臃肿) (2认同)
  • 这个有用的解决方案有一个小问题:snprintf 返回一个 int (错误为负) - 添加 1 并转换为 size_t “吞掉”可能的问题,甚至可能导致分配大量内存(例如,如果返回 -2)。 (2认同)

ken*_*ytm 106

boost::format() 提供您想要的功能:

从Boost格式库概要:

格式对象由格式字符串构造,然后通过重复调用operator%给出参数.然后根据格式字符串将每个参数转换为字符串,然后将字符串组合成一个字符串.

#include <boost/format.hpp>

cout << boost::format("writing %1%,  x=%2% : %3%-th try") % "toto" % 40.23 % 50; 
// prints "writing toto,  x=40.230 : 50-th try"
Run Code Online (Sandbox Code Playgroud)

  • 在项目的任何位置包含boost会立即显着增加编译时间.对于大型项目,它很可能并不重要.对于小型项目,提升是一个拖累. (12认同)
  • Boost Format不仅很大,而且速度很慢.见http://zverovich.net/2013/09/07/integer-to-string-conversion-in-cplusplus.html和http://www.boost.org/doc/libs/1_52_0/libs/spirit/doc /html/spirit/karma/performance_measurements/numeric_performance/double_performance.html (7认同)
  • 你也可以修改你需要的库.使用提供的工具. (5认同)
  • @vitaut虽然与替代品相比,它非常耗费资源.你经常格式化字符串?考虑到它只需要几微秒,而且大多数项目可能只使用它几十次,在一个不重点关注字符串格式的项目中并不明显,对吧? (2认同)
  • 不幸的是,boost :: format的工作方式不同:不接受var_args.有些人喜欢让所有与单个程序相关的代码看起来相同/使用相同的习语. (2认同)

vit*_*aut 73

不幸的是,这里的大多数答案都使用了varargs,除非你使用GCC的std::format属性,它只适用于文字格式字符串,否则它本身就是不安全的.您可以在以下示例中看到这些函数为何不安全:

std::string s = "foo";
std::cout << std::format("Look, a string: {}", s);
Run Code Online (Sandbox Code Playgroud)

sprintfErik Aronesty的答案在哪里实施.此代码编译,但当您尝试运行它时,它很可能会崩溃:

std::string s = "foo";
puts(std::format("Look, a string: {}", s).c_str());
Run Code Online (Sandbox Code Playgroud)

可以实现安全性std::string并将其扩展为stdout使用(可变参数)模板进行格式化.这已经在{fmt}库中完成,它为sprintf返回提供了一个安全的替代方法format:

fmt::print(f, "Look, a string: {}", s); // where f is a file stream
Run Code Online (Sandbox Code Playgroud)

{fmt}跟踪参数类型,如果类型与格式规范不匹配,则不存在分段错误,只有异常或编译时错误(如果使用string_format格式字符串检查).

免责声明:我是{fmt}的作者.


Tim*_*sch 18

如果您只想要类似printf的语法(不自行调用printf),请查看Boost Format.


Pit*_*kul 15

我使用vsnprintf编写了自己的,所以它返回字符串而不必创建自己的缓冲区.

#include <string>
#include <cstdarg>

//missing string printf
//this is safe and convenient but not exactly efficient
inline std::string format(const char* fmt, ...){
    int size = 512;
    char* buffer = 0;
    buffer = new char[size];
    va_list vl;
    va_start(vl, fmt);
    int nsize = vsnprintf(buffer, size, fmt, vl);
    if(size<=nsize){ //fail delete buffer and try again
        delete[] buffer;
        buffer = 0;
        buffer = new char[nsize+1]; //+1 for /0
        nsize = vsnprintf(buffer, size, fmt, vl);
    }
    std::string ret(buffer);
    va_end(vl);
    delete[] buffer;
    return ret;
}
Run Code Online (Sandbox Code Playgroud)

所以你可以像使用它一样

std::string mystr = format("%s %d %10.5f", "omg", 1, 10.5);
Run Code Online (Sandbox Code Playgroud)

  • 我不确定这在后备情况下是否正确;我认为您需要为第二个 vsnprintf() 执行 vl 的 va_copy 才能正确查看参数。有关示例,请参阅:https://github.com/haberman/upb/blob/26d98ca94f2f049e8767b4a9a33d185a3d7ea0fd/upb/upb.c#L17 (2认同)

use*_*016 15

为了以std::string'sprintf'方式格式化,调用snprintf(arguments nullptr0)以获得所需的缓冲区长度.使用C++ 11可变参数模板编写函数,如下所示:

#include <cstdio>
#include <string>
#include <cassert>

template< typename... Args >
std::string string_sprintf( const char* format, Args... args ) {
  int length = std::snprintf( nullptr, 0, format, args... );
  assert( length >= 0 );

  char* buf = new char[length + 1];
  std::snprintf( buf, length + 1, format, args... );

  std::string str( buf );
  delete[] buf;
  return str;
}
Run Code Online (Sandbox Code Playgroud)

编译C++ 11支持,例如在GCC中: g++ -std=c++11

用法:

  std::cout << string_sprintf("%g, %g\n", 1.23, 0.001);
Run Code Online (Sandbox Code Playgroud)


sla*_*ais 14

[edit '17/8/31]添加一个可变模板版'vtspf(..)':

template<typename T> const std::string type_to_string(const T &v)
{
    std::ostringstream ss;
    ss << v;
    return ss.str();
};

template<typename T> const T string_to_type(const std::string &str)
{
    std::istringstream ss(str);
    T ret;
    ss >> ret;
    return ret;
};

template<typename...P> void vtspf_priv(std::string &s) {}

template<typename H, typename...P> void vtspf_priv(std::string &s, H h, P...p)
{
    s+=type_to_string(h);
    vtspf_priv(s, p...);
}

template<typename...P> std::string temp_vtspf(P...p)
{
    std::string s("");
    vtspf_priv(s, p...);
    return s;
}
Run Code Online (Sandbox Code Playgroud)

这实际上是一个逗号分隔的版本(相反)有时阻碍 - 操作<<符,使用如下:

char chSpace=' ';
double pi=3.1415;
std::string sWorld="World", str_var;
str_var = vtspf("Hello", ',', chSpace, sWorld, ", pi=", pi);
Run Code Online (Sandbox Code Playgroud)


[编辑]适应在Erik Aronesty的答案中使用该技术(上图):

#include <string>
#include <cstdarg>
#include <cstdio>

//=============================================================================
void spf(std::string &s, const std::string fmt, ...)
{
    int n, size=100;
    bool b=false;
    va_list marker;

    while (!b)
    {
        s.resize(size);
        va_start(marker, fmt);
        n = vsnprintf((char*)s.c_str(), size, fmt.c_str(), marker);
        va_end(marker);
        if ((n>0) && ((b=(n<size))==true)) s.resize(n); else size*=2;
    }
}

//=============================================================================
void spfa(std::string &s, const std::string fmt, ...)
{
    std::string ss;
    int n, size=100;
    bool b=false;
    va_list marker;

    while (!b)
    {
        ss.resize(size);
        va_start(marker, fmt);
        n = vsnprintf((char*)ss.c_str(), size, fmt.c_str(), marker);
        va_end(marker);
        if ((n>0) && ((b=(n<size))==true)) ss.resize(n); else size*=2;
    }
    s += ss;
}
Run Code Online (Sandbox Code Playgroud)

[上一个答案]
一个非常晚的答案,但对于那些像我一样喜欢'sprintf'方式的人:我写过并且正在使用以下功能.如果你喜欢它,你可以扩展%-options以更接近sprintf的那些; 目前那些足以满足我的需求.你使用与sprintf相同的stringf()和stringfappend().请记住,...的参数必须是POD类型.

//=============================================================================
void DoFormatting(std::string& sF, const char* sformat, va_list marker)
{
    char *s, ch=0;
    int n, i=0, m;
    long l;
    double d;
    std::string sf = sformat;
    std::stringstream ss;

    m = sf.length();
    while (i<m)
    {
        ch = sf.at(i);
        if (ch == '%')
        {
            i++;
            if (i<m)
            {
                ch = sf.at(i);
                switch(ch)
                {
                    case 's': { s = va_arg(marker, char*);  ss << s;         } break;
                    case 'c': { n = va_arg(marker, int);    ss << (char)n;   } break;
                    case 'd': { n = va_arg(marker, int);    ss << (int)n;    } break;
                    case 'l': { l = va_arg(marker, long);   ss << (long)l;   } break;
                    case 'f': { d = va_arg(marker, double); ss << (float)d;  } break;
                    case 'e': { d = va_arg(marker, double); ss << (double)d; } break;
                    case 'X':
                    case 'x':
                        {
                            if (++i<m)
                            {
                                ss << std::hex << std::setiosflags (std::ios_base::showbase);
                                if (ch == 'X') ss << std::setiosflags (std::ios_base::uppercase);
                                char ch2 = sf.at(i);
                                if (ch2 == 'c') { n = va_arg(marker, int);  ss << std::hex << (char)n; }
                                else if (ch2 == 'd') { n = va_arg(marker, int); ss << std::hex << (int)n; }
                                else if (ch2 == 'l') { l = va_arg(marker, long);    ss << std::hex << (long)l; }
                                else ss << '%' << ch << ch2;
                                ss << std::resetiosflags (std::ios_base::showbase | std::ios_base::uppercase) << std::dec;
                            }
                        } break;
                    case '%': { ss << '%'; } break;
                    default:
                    {
                        ss << "%" << ch;
                        //i = m; //get out of loop
                    }
                }
            }
        }
        else ss << ch;
        i++;
    }
    va_end(marker);
    sF = ss.str();
}

//=============================================================================
void stringf(string& stgt,const char *sformat, ... )
{
    va_list marker;
    va_start(marker, sformat);
    DoFormatting(stgt, sformat, marker);
}

//=============================================================================
void stringfappend(string& stgt,const char *sformat, ... )
{
    string sF = "";
    va_list marker;
    va_start(marker, sformat);
    DoFormatting(sF, sformat, marker);
    stgt += sF;
}
Run Code Online (Sandbox Code Playgroud)


PW.*_*PW. 10

这就是谷歌的做法:( StringPrintfBSD许可证)
和Facebook以非常相似的方式做到:( StringPrintfApache许可证)
两者都提供了方便StringAppendF.


Dac*_*cav 9

我对这个非常受欢迎的问题的两分钱.

引用类似函数联机帮助页printf:

成功返回后,这些函数返回打印的字符数(不包括用于结束输出到字符串的空字节).

函数snprintf()和vsnprintf()写入的字节数不超过大小(包括终止空字节('\ 0')).如果输出由于此限制而被截断,则返回值是字符数(不包括终止空字节),如果有足够的空间,则该字符数将被写入最终字符串.因此,大小或更大的返回值意味着输出被截断.

换句话说,理智的C++ 11实现应该如下:

#include <string>
#include <cstdio>

template <typename... Ts>
std::string fmt (const std::string &fmt, Ts... vs)
{
    char b;
    size_t required = std::snprintf(&b, 0, fmt.c_str(), vs...) + 1;
        // See comments: the +1 is necessary, while the first parameter
        //               can also be set to nullptr

    char bytes[required];
    std::snprintf(bytes, required, fmt.c_str(), vs...);

    return std::string(bytes);
}
Run Code Online (Sandbox Code Playgroud)

它运作得很好:)

仅在C++ 11中支持变量模板.pixelpoint的答案显示了使用较旧编程风格的类似技术.

很奇怪C++没有开箱即用的东西.他们最近补充说to_string(),我认为这是向前迈出的一大步.我想知道他们是否会最终添加一个.format运算符std::string...

编辑

正如alexk7指出的那样,+1返回值需要A std::snprintf,因为我们需要为\0字节留出空间.直观地说,在大多数缺少的架构上+1都会导致required整数被a部分覆盖0.这将评估required为实际参数发生std::snprintf,因此效果不应该是可见的.

然而,这个问题可能会改变,例如编译器优化:如果编译器决定使用寄存器作为required变量怎么办?这种错误有时会导致安全问题.

  • 使用"char bytes [required]"将在堆栈而不是堆上分配,在大型格式字符串上可能会有危险.考虑使用新的代替.晏 (2认同)

小智 8

template<typename... Args>
std::string string_format(const char* fmt, Args... args)
{
    size_t size = snprintf(nullptr, 0, fmt, args...);
    std::string buf;
    buf.reserve(size + 1);
    buf.resize(size);
    snprintf(&buf[0], size + 1, fmt, args...);
    return buf;
}
Run Code Online (Sandbox Code Playgroud)

使用C99 snprintf和C++ 11


Dou*_*eco 7

测试,生产质量答案

这个答案处理符合标准的技术的一般情况.在其页面底部附近的CppReference.com上给出了相同的方法作为示例.与他们的示例不同,此代码符合问题的要求,并在机器人和卫星应用程序中进行现场测试.它还改进了评论.设计质量将在下面进一步讨论.

#include <string>
#include <cstdarg>
#include <vector>

// requires at least C++11
const std::string vformat(const char * const zcFormat, ...) {

    // initialize use of the variable argument array
    va_list vaArgs;
    va_start(vaArgs, zcFormat);

    // reliably acquire the size
    // from a copy of the variable argument array
    // and a functionally reliable call to mock the formatting
    va_list vaArgsCopy;
    va_copy(vaArgsCopy, vaArgs);
    const int iLen = std::vsnprintf(NULL, 0, zcFormat, vaArgsCopy);
    va_end(vaArgsCopy);

    // return a formatted string without risking memory mismanagement
    // and without assuming any compiler or platform specific behavior
    std::vector<char> zc(iLen + 1);
    std::vsnprintf(zc.data(), zc.size(), zcFormat, vaArgs);
    va_end(vaArgs);
    return std::string(zc.data(), iLen); }

#include <ctime>
#include <iostream>
#include <iomanip>

// demonstration of use
int main() {

    std::time_t t = std::time(nullptr);
    std::cerr
        << std::put_time(std::localtime(& t), "%D %T")
        << " [debug]: "
        << vformat("Int 1 is %d, Int 2 is %d, Int 3 is %d", 11, 22, 33)
        << std::endl;
    return 0; }
Run Code Online (Sandbox Code Playgroud)

可预测的线性效率

根据问题规范,两次通过是安全,可靠和可预测的可重用功能的必需品.关于可重用函数中vargs大小分布的假设是糟糕的编程风格,应该避免.在这种情况下,vargs的任意大的可变长度表示是算法选择的关键因素.

在溢出时重试是指数效率低的,这是当C++ 11标准委员会讨论上述提议在写缓冲区为空时提供干运行时讨论的另一个原因.

在上面的生产就绪实现中,第一次运行是这样的干运行以确定分配大小.没有分配.几十年来,printf指令的解析和vargs的读取已经非常有效.可重复使用的代码应该是可预测的,即使必须牺牲一些微不足道的低效率.

安全性和可靠性

安德鲁·科尼格在剑桥大学的一次活动讲座后对我们的一小部分人说:"用户功能不应该依赖于利用失败来实现无与伦比的功能." 像往常一样,自那以后,他的智慧在记录中得到了证实.固定和封闭的安全性错误问题通常表示在修复之前利用漏洞的描述中重试黑客.

这在sprintf的替代品中的空缓冲特征的正式标准修订提案,C9X修订提案,ISO IEC文件WG14 N6​​45/X3J11 96-008中提到.在动态内存可用性的约束下,每个print指令插入一个任意长的字符串"%s",这不是一个例外,不应该被利用来产生"Unexceptional functions".

请考虑与本答案第一段中链接的C++ Reference.org页面底部给出的示例代码一起提出的建议.

此外,对失败案例的测试很少能够成功案例.

可移植性

所有主要操作系统供应商都提供完全支持std :: vsnprintf的编译器,作为c ++ 11标准的一部分.运行不再维护发行版的供应商产品的主机应该提供g ++或clang ++,原因有很多.

堆栈使用

第一次调用std :: vsnprintf时的堆栈使用将小于或等于第二次调用,并且它将在第二次调用开始之前释放.如果第一个调用超过堆栈可用性,那么std :: fprintf也会失败.

  • FWIW,我指的猜测是在第一次运行发生时使用堆栈分配的缓冲区。如果合适,它可以节省第二次运行的成本以及在那里发生的动态分配。据推测,小字符串比大字符串更常用。在我的粗略基准中,该策略(几乎)将小字符串的运行时间减半,并且在上述策略的百分之几内(也许是固定开销?)。您能详细说明一下采用试运行等方式的 C++11 设计吗?我想读一下有关它的内容。 (2认同)
  • 那么为什么使用 `std::vector&lt;char&gt;` 而不是 `std::string` 呢? (2认同)
  • 这些括号才是真正的犯罪 (2认同)

Tho*_*erl 6

如果您使用的系统有asprintf(3),您可以轻松地包装它:

#include <iostream>
#include <cstdarg>
#include <cstdio>

std::string format(const char *fmt, ...) __attribute__ ((format (printf, 1, 2)));

std::string format(const char *fmt, ...)
{
    std::string result;

    va_list ap;
    va_start(ap, fmt);

    char *tmp = 0;
    int res = vasprintf(&tmp, fmt, ap);
    va_end(ap);

    if (res != -1) {
        result = tmp;
        free(tmp);
    } else {
        // The vasprintf call failed, either do nothing and
        // fall through (will return empty string) or
        // throw an exception, if your code uses those
    }

    return result;
}

int main(int argc, char *argv[]) {
    std::string username = "you";
    std::cout << format("Hello %s! %d", username.c_str(), 123) << std::endl;
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

  • 我会在 `format` 之前添加这一行作为声明,因为它告诉 gcc 检查参数的类型并使用 -Wall 给出适当的警告: `std::string format(const char *fmt, ...) __attribute__ ((格式(printf, 1, 2)));` (2认同)
  • 我刚刚添加了对 `va_end` 的调用。*“如果在调用 va_start 或 va_copy 的函数返回之前未调用 va_end,则行为未定义。”* - [docs](http://en.cppreference.com/w/cpp/utility/variadic/va_end) (2认同)

Cir*_*四事件 6

C ++ 20 std::format

已经到了!该功能在http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0645r9.html中进行了描述,并使用类似Python的.format()语法。

我希望用法将是这样的:

#include <format>
#include <string>

int main() {
    std::string message = std::format("The answer is {}.", 42);
}
Run Code Online (Sandbox Code Playgroud)

当GCC支持到达时,我会尝试一下,GCC 9.1.0 g++-9 -std=c++2a仍然不支持它。

该API将添加一个新的std::format标头:

建议的格式API在新的标头中定义,<format>对现有代码没有影响。

fmt如果需要polyfill ,现有的库声称可以实现它:https : //github.com/fmtlib/fmt

C ++ 20的实现std::format

并在前面提到过:std :: string格式如sprintf


Che*_*etS 5

根据Erik Aronesty提供的答案:

std::string string_format(const std::string &fmt, ...) {
    std::vector<char> str(100,'\0');
    va_list ap;
    while (1) {
        va_start(ap, fmt);
        auto n = vsnprintf(str.data(), str.size(), fmt.c_str(), ap);
        va_end(ap);
        if ((n > -1) && (size_t(n) < str.size())) {
            return str.data();
        }
        if (n > -1)
            str.resize( n + 1 );
        else
            str.resize( str.size() * 2);
    }
    return str.data();
}
Run Code Online (Sandbox Code Playgroud)

这避免了抛弃原始答案中const的结果的需要.c_str().


小智 5

inline void format(string& a_string, const char* fmt, ...)
{
    va_list vl;
    va_start(vl, fmt);
    int size = _vscprintf( fmt, vl );
    a_string.resize( ++size );
    vsnprintf_s((char*)a_string.data(), size, _TRUNCATE, fmt, vl);
    va_end(vl);
}
Run Code Online (Sandbox Code Playgroud)

  • +1这个聪明的想法,但不太清楚`_vscprintf`是什么。我认为你应该详细说明这个答案。 (2认同)

归档时间:

查看次数:

770820 次

最近记录:

5 年,11 月 前