std :: string格式如sprintf


454

我不得不格式std::stringsprintf和发送到文件流。我怎样才能做到这一点?


6
长话短说boost::format(如kennytm的解决方案在此使用)。boost::format也已经支持C ++流运算符!例如:cout << format("helloworld. a=%s, b=%s, c=%s") % 123 % 123.123 % "this is a test" << endl;boost::format拥有最少的代码行...经过同行评审,并且与C ++流很好地集成。
Trevor Boyd Smith,

@Ockonal-为了社区的利益(我对自己的代表不关心),建议您更改选择。当前选择的第一个代码段中,存在一个使用任意最大长度等待发生的错误。第二个片段完全忽略了您使用sprintf之类的vargs的意愿。我建议您在这里选择唯一,干净,安全,仅依赖于C ++标准,经过测试并得到充分评论的答案。那是我的无关紧要。客观上是正确的。参见stackoverflow.com/questions/2342162/…
Douglas Daseeco '18

@TrevorBoydSmith a std::format已添加到C ++ 20 BTW:stackoverflow.com/a/57286312/895245太棒了!
西罗Santilli郝海东冠状病六四事件法轮功

1
@CiroSantilli我读到一篇文章C++20就在昨天,我看到的是C++20复制boost通过添加(现为百万分之一时间)std::formatC++20规格!我当时非常开心!在过去的9年中,我几乎编写的每个C ++文件都使用过boost::format。在C ++中向流中添加正式的printf样式输出将对所有C ++的IMO产生很大的帮助。
Trevor Boyd Smith,

Answers:


332

您不能直接执行此操作,因为您没有对基础缓冲区的写访问权限(直到C ++ 11;请参见Dietrich Epp的注释)。您必须首先在c字符串中执行此操作,然后将其复制到std :: string中:

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

但是我不确定为什么不只使用字符串流?我假设您有特定的原因不只是这样做:

  std::ostringstream stringStream;
  stringStream << "Hello";
  std::string copyOfStr = stringStream.str();

16
魔术cookie char buf[100];使此解决方案不是很可靠。但是基本思想在那里。
John Dibling

18
约翰,流并不慢。流看上去很慢的唯一原因是,默认情况下,iostream与C FILE输出同步,以便正确输出混合的cout和printfs。禁用此链接(通过调用cout.sync_with_stdio(false))至少在MSVC10之前会导致c ++的流优于stdio。
Jimbo

72
使用格式的原因是让本地化人员为外语重建句子的结构,而不是对句子的语法进行硬编码。
Martijn Courteaux 2013年

215
出于某种原因,其他语言也使用类似printf的语法:Java,Python(新语法更接近printf而不是流)。只有C ++对无辜的人类造成了这种冗长的可恶。
quant_dev

9
更好的方法是使用asprintf,它会分配一个具有足够空间来容纳结果的新字符串。然后将其复制到std::string您喜欢的位置,并记住free原始位置。另外,可以将其放在宏中,以便任何好的编译器都可以帮助您验证格式-您不想将a double放在%s期望的位置
Aaron McDaid 2015年

285

现代C ++使此超级简单。

C ++ 20

C ++ 20引入了std::format,它使您能够做到这一点。它使用类似于python中的替换字段:

#include <iostream>
#include <format>

int main() {
    std::cout << std::format("Hello {}!\n", "world");
}

查看完整的文档!这是巨大的生活质量改善。


C ++ 11

使用C ++ 11 s std::snprintf,这已经成为一个相当容易和安全的任务。

#include <memory>
#include <string>
#include <stdexcept>

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'
    if( size <= 0 ){ throw std::runtime_error( "Error during formatting." ); }
    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
}

上面的代码段是根据CC0 1.0授权的。

逐行说明:

目的:写入char*std::snprintf,然后将其转换成一个std::string

首先,我们使用中的特殊条件确定char数组的所需长度snprintf。来自cppreference.com

返回值

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

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

size_t size = snprintf( nullptr, 0, format.c_str(), args ... ) + 1;

snprintf如果发生错误,将返回负数,因此我们将检查格式化是否按要求工作。如@ead在评论中指出的那样,不这样做可能会导致无提示错误或分配巨大的缓冲区。

if( size <= 0 ){ throw std::runtime_error( "Error during formatting." ); }

接下来,我们分配一个新的字符数组并将其分配给一个std::unique_ptr。通常建议这样做,因为您无需delete再次手动进行操作。

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

std::unique_ptr<char[]> buf( new char[ size ] );

之后,我们当然可以使用snprintf它的预期用途,并将格式化后的字符串写入char[]

snprintf( buf.get(), size, format.c_str(), args ... );

最后,我们std::string从中创建并返回一个新值,确保最后省略空终止符。

return std::string( buf.get(), buf.get() + size - 1 );

您可以在此处查看示例。


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


适用于Visual Studio用户的其他信息:

该答案所述,Microsoft重命名std::snprintf_snprintf(是,不带std::)。MS进一步将其设置为不推荐使用,并建议改为使用它_snprintf_s,但是_snprintf_s不会接受该缓冲区为零或小于格式化输出的缓冲区,并且如果出现这种情况,将不计算输出长度。因此,为了消除编译期间的弃用警告,您可以在文件的顶部插入以下行,其中包含_snprintf

#pragma warning(disable : 4996)

最后的想法

这个问题的很多答案是在C ++ 11之前编写的,并且使用固定的缓冲区长度或vargs。除非您坚持使用旧版本的C ++,否则我不建议您使用这些解决方案。理想情况下,采用C ++ 20方式。

因为此答案中的C ++ 11解决方案使用模板,所以如果使用很多,它可以生成大量代码。但是,除非您要为二进制文件空间非常有限的环境进行开发,否则这将不是问题,并且在清晰度和安全性方面仍将是对其他解决方案的巨大改进。

如果空间效率非常重要,那么使用vargs和vsnprintf的这两个解决方案可能会有用。 不要使用固定缓冲区长度的任何解决方案,这只是自找麻烦。


2
请你的答案为Visual Studio用户是VS的版本必须是至少2013年从强调:文章中,你可以看到,它仅适用于VS2013的版本。如果缓冲区是一个空指针和计数为零,len为返回格式化输出所需的字符数,不包括终止null。要使用相同的参数和语言环境参数成功调用,请分配一个缓冲区,该缓冲区至少包含len + 1个字符。

3
@moooeeeep多个原因。首先,这里的目标是返回一个std :: string,而不是c字符串,所以您可能是想说return string(&buf[0], size);类似的话。其次,如果您要返回这样的C字符串,则会导致未定义的行为,因为保存指向您的值的向量将在返回时失效。第三,当我开始学习C ++时,该标准并未定义元素必须以什么顺序存储在内std::vector,因此通过指针访问其存储是未定义的行为。现在可以了,但是我认为这样做没有任何好处。
iFreilicht 2015年

2
@iFreilicht std::string从隐式转换的向量(副本初始化)构造一个新的对象,然后将其作为副本返回,如功能签名所示。同样,a的元素std::vector是(并且总是希望)连续存储。但我认为你这样做可能没有任何好处。
moooeeeep 2015年

4
我真的很喜欢这种解决方案,但是我认为这行return string(buf.get(), buf.get() + size);应该是return string(buf.get(), buf.get() + size - 1);其他方式,否则您将得到一个结尾为空字符的字符串。我发现gcc 4.9就是这种情况。
菲尔·威廉姆斯

3
将std :: string传递给%s会导致编译错误(错误:无法通过可变参数函数传递非平凡类型'std :: __ cxx11 :: basic_string <char>'的对象;调用将在运行时中止[-Wnon-pod -varargs]),但是在CL 19中,它可以正常编译并在运行时崩溃。我也可以在cl的编译时打开任何警告标志来进行处理吗?
Zitrax '17

241

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

一种更安全,更有效(我测试了它,而且速度更快)的方法:

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

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

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


3
慢。为什么将大小增加1?并且该函数何时返回-1?
0xDEAD BEEF,2012年

27
您要覆盖str.c_str()吗?那不是很危险吗?
量子

8
带有参考参数的va_start在MSVC上有问题。它静默失败,并返回指向随机内存的指针。解决方法是,使用std :: string fmt代替std :: string&fmt,或编写包装对象。
史蒂夫·哈诺夫

6
我+1是因为我知道这可能会根据大多数std :: strings的实现方式而起作用,但是c_str并不是真正旨在用作修改基础字符串的地方。它应该是只读的。
Doug T.

6
要预先获得结果字符串的长度,请参见:stackoverflow.com/a/7825892/908336 我看不到size每次迭代都有增加的意义,而您可以通过第一次调用获得它vsnprintf()
Massood Khaari

107

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"

5
您也可以从增强中修剪所需的库。使用提供的工具。
哈桑·赛义德


13
在项目中的任何地方包含boost都会立即增加编译时间。对于大型项目,这很可能无关紧要。对于小型项目,提振是阻力。
quant_dev

2
@vitaut 与替代品相比,它非常耗资源。您多久格式化一次字符串?考虑到它只需要几微秒,并且大多数项目可能只使用了几十次,因此在不十分关注字符串格式的项目中并不引人注意,对吗?
AturSams,2015年

2
不幸的是,boost :: format不能以相同的方式工作:不接受var_args。有些人喜欢使所有与单个程序相关的代码看起来相同/使用相同的习惯用法。
xor007 2015年

88

C ++ 20将包括std::formatsprintfAPI 相似但完全类型安全,可与用户定义类型一起使用并使用类似Python的格式字符串语法的C ++ 20 。这是您将如何格式化std::string并将其写入流中的方法:

std::string s = "foo";
std::cout << std::format("Look, a string: {}", s);

要么

std::string s = "foo";
puts(std::format("Look, a string: {}", s).c_str());

或者,您可以使用{fmt}库来格式化字符串并将其stdout一次性写入文件流或将其写入文件流:

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

至于这里的sprintf或大多数其他答案,不幸的是,它们使用varargs并且本质上是不安全的,除非您使用类似GCC的format属性,该属性仅适用于文字格式字符串。您可以在以下示例中查看为什么这些功能不安全:

std::string format_str = "%s";
string_format(format_str, format_str[0]);

string_formatErik Aronesty的答案在哪里实现。这段代码可以编译,但是当您尝试运行它时,很可能会崩溃:

$ g++ -Wall -Wextra -pedantic test.cc 
$ ./a.out 
Segmentation fault: 11

免责声明:我是{fmt}和C ++ 20的作者std::format


恕我直言,你错过了包括 error: 'fmt' has not been declared
塞尔吉奥

这只是一个片段,而不是完整的代码。显然,您需要包括<fmt / format.h>并将代码放入函数中。
vitaut

对我来说不是那么明显,恕我直言,你应该包括在片段中,感谢您的反馈
塞尔吉奥

1
一个fmt类似的实现添加到C ++ 20!stackoverflow.com/a/57286312/895245 fmt当前声称支持它。很棒的工作!
西罗Santilli郝海东冠状病六四事件法轮功

2
@vitaut感谢您在此方面的工作!
Curt Nichols


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

所以你可以像这样使用它

std::string mystr = format("%s %d %10.5f", "omg", 1, 10.5);

这将对数据进行完整的额外复制,可以vsnprintf直接在字符串中使用。
Mooing Duck 2013年

1
请使用stackoverflow.com/a/7825892/908336中的代码预先获取生成的字符串长度。您可以将智能指针用于异常安全代码:std::unique_ptr<char[]> buffer (new char[size]);
Massood Khaari

在后备情况下,我不确定这是否正确;我认为您需要为第二个vsnprintf()做一个vl的va_copy才能正确看到参数。举一个例子,请参阅:github.com/haberman/upb/blob/...
乔希哈伯曼

15

为了以std::string“ sprintf”方式格式化,请调用snprintf(参数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;
}

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

用法:

  std::cout << string_sprintf("%g, %g\n", 1.23, 0.001);

std :: snprintf在VC ++ 12(Visual Studio 2013)中不可用。替换为_snprintf。
Shital Shah

为什么不使用char buf[length + 1];代替char* buf = new char[length + 1];
Behrouz.M,2016年

using char[]char*with new 之间的区别在于,在前一种情况下,buf将分配在堆栈上。小型缓冲区是可以的,但是由于我们不能保证结果字符串的大小,因此使用会更好new。例如,在我的机器上string_sprintf("value: %020000000d",5),在堆栈5上打印出new char[length + 1]
令人讨厌

一个非常聪明的主意,可以获取格式化输出所需的实际增益大小
克里斯(Chris

1
@ user2622016:感谢您的解决方案!请注意,这std::move 是多余的
Mihai Todor

14

[edit:20/05/25]更好......
在标题中:

// `say` prints the values
// `says` returns a string instead of printing
// `sayss` appends the values to it's first argument instead of printing
// `sayerr` prints the values and returns `false` (useful for return statement fail-report)<br/>

void PRINTSTRING(const std::string &s); //cater for GUI, terminal, whatever..
template<typename...P> void say(P...p) { std::string r{}; std::stringstream ss(""); (ss<<...<<p); r=ss.str(); PRINTSTRING(r); }
template<typename...P> std::string says(P...p) { std::string r{}; std::stringstream ss(""); (ss<<...<<p); r=ss.str(); return r; }
template<typename...P> void sayss(std::string &s, P...p) { std::string r{}; std::stringstream ss(""); (ss<<...<<p); r=ss.str();  s+=r; } //APPENDS! to s!
template<typename...P> bool sayerr(P...p) { std::string r{}; std::stringstream ss("ERROR: "); (ss<<...<<p); r=ss.str(); PRINTSTRING(r); return false; }

所述PRINTSTRING(r)-function是为应付GUI或终端或使用任何特殊的输出需求#ifdef _some_flag_,默认为:

void PRINTSTRING(const std::string &s) { std::cout << s << std::flush; }

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

它实际上是有时受阻的- <<运算符的逗号分隔版本(代替),其用法如下:

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


[编辑]改编为在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;
}

[上一个答案]
一个很晚的答案,但是对于那些像我一样喜欢'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;
}

@MooingDuck:根据Dan对Aronesty的回答的注释,更改了函数参数。我仅使用Linux / gcc,fmt作为参考,它可以正常工作。(但是我想人们会想要玩玩具的,所以...)如果还有其他所谓的“小虫”,您能详细说明一下吗?
slashmais

我误解了他的代码的一部分是如何工作的,并认为它正在对许多大小进行调整。重新检查表明我弄错了。您的代码是正确的。
Mooing Duck

建立在埃里克·阿隆斯蒂的答案之上的是一条红鲱鱼。他的第一个代码示例不安全,而第二个示例效率低下且笨拙。干净的实现清晰地表明了以下事实:如果任何vprintf函数系列的buf_siz为零,则不写入任何内容,并且缓冲区可能是空指针,但是返回值(将被写入的字节数不包括空终止符)仍会计算并返回。生产质量答案就在这里:stackoverflow.com/questions/2342162/...
道格拉斯Daseeco

10

这是google的工作方式:StringPrintf(BSD许可证)
和facebook的执行方式非常相似:StringPrintf(Apache许可证)
两者都提供了便利StringAppendF


10

我在这个非常受欢迎的问题上花了两美分。

引用-like函数联机帮助页printf

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

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

换句话说,合理的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);
}

它很好用:)

可变参数模板仅在C ++ 11中受支持。来自pixelpoint的答案显示了使用较旧编程风格的类似技术。

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

编辑

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

但是,例如通过编译器优化,此问题可能会改变:如果编译器决定对required变量使用寄存器,该怎么办?这种错误有时会导致安全问题。


1
snprintf始终附加一个终止的空字节,但返回不包含该字符的字符数。这段代码不总是跳过最后一个字符吗?
alexk7 2015年

@ alexk7,不错!我正在更新答案。该代码不会跳过最后一个字符,但是会写到bytes缓冲区的末尾,可能会覆盖required整数(幸运的是,此时整数已经被求值了)。
Dacav

1
只是一个小提示:缓冲区大小为0时,您可以传递a nullptr作为缓冲区参数,从而消除char b;代码中的行。(来源
iFreilicht 2015年

@iFreilicht,已修复。另外+1
Dacav

2
使用“ char bytes [required]”将在堆栈而不是堆上分配,这在大格式字符串上可能很危险。考虑使用use new代替。Yann
Yannuth

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

使用C99 snprintf和C ++ 11


8

经过测试,生产质量答案

该答案使用符合标准的技术处理一般情况。在页面底部附近的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; }

可预测的线性效率

根据问题规范,必须两次通过才能确保安全,可靠和可预测的可重用功能。关于可重用函数中vargs大小分布的假设是不好的编程风格,应避免使用。在这种情况下,可变参数的任意大的可变长度表示形式是选择算法的关键因素。

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

在以上生产就绪实施中,第一轮运行是这样的空运行以确定分配大小。没有分配发生。数十年来,printf指令的解析和vargs的读取已变得极为高效。即使必须牺牲一些琐碎的琐事,可重用的代码也应该是可预测的。

安全性与可靠性

在剑桥活动上的演讲后,安德鲁·科尼格(Andrew Koenig)对我们中的一小部分人说:“用户功能不应依赖对失败的利用而获得无与伦比的功能。” 像往常一样,他的智慧从那以后就被证明是正确的。已修复和已关闭的安全漏洞问题通常表示在修复之前对漏洞的描述中存在重试漏洞。

sprintf的替代方案,C9X修订建议,ISO IEC文档WG14 N6​​45 / X3J11 96-008中关于空缓冲区功能的正式标准修订建议中提到了这一点。在动态内存可用性的约束下,每个打印指令“%s”插入的任意长字符串也不例外,也不应该被利用来产生“非凡功能”。

在提案的第一段中,将C ++ Reference.org页面底部给出的示例代码与示例代码一起考虑。

同样,失败案例的测试很少像成功案例那样健壮。

可移植性

所有主要的OS供应商都提供了完全支持std :: vsnprintf的编译器,并将其作为c ++ 11标准的一部分。出于许多原因,应为运行不再维护发行版的供应商产品的主机提供g ++或clang ++。

堆叠使用

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


简短而强大。在具有不合格vsnprintf-s的HP-UX,IRIX,Tru64上可能会失败。编辑:此外,考虑两次通过可能对性能产生的影响,尤其是。对于最常见的小字符串格式,您是否考虑过初始通过的猜测,可能足够大?
工程师

FWIW,我指的是在第一次运行时使用的堆栈分配缓冲区。如果合适的话,可以节省第二次运行的成本以及在那里进行的动态分配。据推测,小字符串比大字符串更常用。在我的粗略基准测试中,该策略(几乎)将小字符串的运行时间减少了一半,并且仅在上述策略的百分之几(固定的开销?)之内。您能否详细说明采用空运行等的C ++ 11设计?我想了解一下。
工程师

@Engineerist,您的问题已在代码上方和下方的答案正文中得到解决。可以使子主题更易于阅读。
道格拉斯·达西斯科

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

当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


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();
}

这避免了需要抛弃const的结果.c_str()这是在原来的答案。


1
建立在埃里克·阿隆斯蒂的答案之上的是一条红鲱鱼。他的第一个代码示例是不安全的,而第二个代码示例的循环效率低下且笨拙。干净的实现清晰地表明了以下事实:如果任何vprintf函数系列的buf_siz为零,则不写入任何内容,并且缓冲区可能是空指针,但是返回值(将被写入的字节数不包括空终止符)仍会计算并返回。生产质量答案就在这里:stackoverflow.com/questions/2342162/...
道格拉斯Daseeco

自从我的添加以来,Erik Aronesty的答案已被编辑。我想突出显示在构建字符串时使用vector <char>存储字符串的选项。从C ++代码调用C函数时,我经常使用这种技术。有趣的是,该问题现在有34个答案。
ChetS,

稍后添加了vfprintf页面上的cppreference.com示例。我相信最好的答案是当前接受的答案,使用字符串流而不是printf变体是C ++的方式。但是我的回答在提供时确实增加了价值。它比当时的其他答案逐渐好。现在该标准具有string_view,参数包和Variadic模板,新的答案可能包括这些功能。至于我的回答,尽管它可能不再值得额外的投票,但不应删除或投票,所以我将其保留。
ChetS,

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

1
+1是个聪明的主意,但不清楚是什么_vscprintf。我认为您应该详细说明这个答案。
Dacav

3

string没有您需要的东西,但是std :: stringstream有。使用stringstream创建字符串,然后提取字符串。是您可以执行的操作的完整列表。例如:

cout.setprecision(10); //stringstream is a stream like cout

在打印double或float时将为您提供10位小数精度。


8
这仍然无法给您带来控制printf所能给您的任何好处……但是很好。
Erik Aronesty,2014年

3

您可以尝试以下方法:

string str;
str.resize( _MAX_PATH );

sprintf( &str[0], "%s %s", "hello", "world" );
// optionals
// sprintf_s( &str[0], str.length(), "%s %s", "hello", "world" ); // Microsoft
// #include <stdio.h>
// snprintf( &str[0], str.length(), "%s %s", "hello", "world" ); // c++11

str.resize( strlen( str.data() ) + 1 );

3

如果您使用的系统具有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;
}

2
我会在此行之前添加此行作为声明format,因为它告诉gcc检查参数的类型并使用-Wall发出体面的警告:std::string format(const char *fmt, ...) __attribute__ ((format (printf, 1, 2)));
Aaron McDaid 2015年

2
我刚刚向拨打了电话va_end“如果在调用va_start或va_copy的函数返回前未调用va_end,则行为未定义。” - docs
Aaron McDaid 2015年

1
您应该检查vasprintf的返回结果,因为失败时指针值未定义。因此,可能包括<new>并添加:if(size == -1){throw std :: bad_alloc(); }
尼尔·麦吉尔

好一点,我已经相应地修改了答案,我决定只在此处添加注释而不是进行注释throw std::bad_alloc();,因为我没有在代码库中使用C ++异常,对于这样做的人,他们可以轻松地基于添加关于源评论和您的评论。
托马斯·佩尔

2

这是我在程序中用来执行此操作的代码...没什么花哨的,但是可以解决问题...注意,您将必须根据需要调整大小。我的MAX_BUFFER是1024。

std::string Format ( const char *fmt, ... )
{
    char textString[MAX_BUFFER*5] = {'\0'};

    // -- Empty the buffer properly to ensure no leaks.
    memset(textString, '\0', sizeof(textString));

    va_list args;
    va_start ( args, fmt );
    vsnprintf ( textString, MAX_BUFFER*5, fmt, args );
    va_end ( args );
    std::string retStr = textString;
    return retStr;
}

4
初始化textString已经将整个缓冲区设置为零。无需
记忆

这将对数据进行完整的额外复制,可以vsnprintf直接在字符串中使用。
Mooing Duck

2

Dacavpixelpoint的答案中得出了这个想法。我玩了一下,得到了:

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

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

    va_start(vl, fmt);
    int size = vsnprintf(0, 0, fmt, vl) + sizeof('\0');
    va_end(vl);

    char buffer[size];

    va_start(vl, fmt);
    size = vsnprintf(buffer, size, fmt, vl);
    va_end(vl);

    return std::string(buffer, size);
}

通过合理的编程实践,我相信代码应该足够,但是我仍然对更安全的替代方案持开放态度,这些替代方案仍然足够简单,并且不需要C ++ 11。


这是另一个使用初始缓冲区的版本,以防止vsnprintf()在初始缓冲区已足够时再次调用。

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

    va_list vl;
    int size;

    enum { INITIAL_BUFFER_SIZE = 512 };

    {
        char buffer[INITIAL_BUFFER_SIZE];

        va_start(vl, fmt);
        size = vsnprintf(buffer, INITIAL_BUFFER_SIZE, fmt, vl);
        va_end(vl);

        if (size < INITIAL_BUFFER_SIZE)
            return std::string(buffer, size);
    }

    size += sizeof('\0');

    char buffer[size];

    va_start(vl, fmt);
    size = vsnprintf(buffer, size, fmt, vl);
    va_end(vl);

    return std::string(buffer, size);
}

(事实证明,该版本与Piti Ongmongkolkul的答案类似,只是它不使用newdelete[],并且在创建时指定了大小std::string

这里的想法是不使用newdelete[]暗示在堆栈上使用堆栈,因为它不需要调用分配和释放函数,但是,如果使用不当,缓冲某些(也许是旧的或可能只是脆弱的系统)。如果这是一个问题,我强烈建议使用newdelete[]代替。请注意,这里唯一关心的vsnprintf()是已经使用限制来调用的分配,因此基于第二个缓冲区上分配的大小指定一个限制也可以避免这种情况。)


2

我通常使用这个:

std::string myformat(const char *const fmt, ...)
{
        char *buffer = NULL;
        va_list ap;

        va_start(ap, fmt);
        (void)vasprintf(&buffer, fmt, ap);
        va_end(ap);

        std::string result = buffer;
        free(buffer);

        return result;
}

缺点:并非所有系统都支持vasprint


vasprintf很好-但是您需要检查返回码。On -1缓冲区将具有未定义的值。需要:if(size == -1){throw std :: bad_alloc(); }
尼尔·麦吉尔

2

在@iFreilicht答案的稍作修改的版本下方,更新为C ++ 14(使用make_unique函数代替原始声明)并添加了对std::string参数的支持(基于Kenny Kerr 文章

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

template <typename T>
T process_arg(T value) noexcept
{
    return value;
}

template <typename T>
T const * process_arg(std::basic_string<T> const & value) noexcept
{
    return value.c_str();
}

template<typename ... Args>
std::string string_format(const std::string& format, Args const & ... args)
{
    const auto fmt = format.c_str();
    const size_t size = std::snprintf(nullptr, 0, fmt, process_arg(args) ...) + 1;
    auto buf = std::make_unique<char[]>(size);
    std::snprintf(buf.get(), size, fmt, process_arg(args) ...);
    auto res = std::string(buf.get(), buf.get() + size - 1);
    return res;
}

int main()
{
    int i = 3;
    float f = 5.f;
    char* s0 = "hello";
    std::string s1 = "world";
    std::cout << string_format("i=%d, f=%f, s=%s %s", i, f, s0, s1) << "\n";
}

输出:

i = 3, f = 5.000000, s = hello world

如果需要,可以将此答案与原始答案合并。



1

您可以使用iomanip头文件在cout中格式化C ++输出。在使用任何辅助功能(如setprecision,setfill等)之前,请确保包含iomanip头文件。

这是我过去使用的代码片段,用于打印矢量中的平均等待时间,这些时间我已经“累计”了。

#include<iomanip>
#include<iostream>
#include<vector>
#include<numeric>

...

cout<< "Average waiting times for tasks is " << setprecision(4) << accumulate(all(waitingTimes), 0)/double(waitingTimes.size()) ;
cout << " and " << Q.size() << " tasks remaining" << endl;

这是我们如何格式化C ++流的简要说明。 http://www.cprogramming.com/tutorial/iomanip.html


1

如果缓冲区的大小不足以打印字符串,则可能会出现问题。您必须先确定格式化字符串的长度,然后才能在其中打印格式化消息。我为此提供了自己的帮助(已在Windows和Linux GCC上测试),您可以尝试使用它。

String.cpp:http
://pastebin.com/DnfvzyKP String.h:http://pastebin.com/7U6iCUMa

String.cpp:

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

using ::std::string;

#pragma warning(disable : 4996)

#ifndef va_copy
#ifdef _MSC_VER
#define va_copy(dst, src) dst=src
#elif !(__cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__))
#define va_copy(dst, src) memcpy((void*)dst, (void*)src, sizeof(*src))
#endif
#endif

///
/// \breif Format message
/// \param dst String to store formatted message
/// \param format Format of message
/// \param ap Variable argument list
///
void toString(string &dst, const char *format, va_list ap) throw() {
  int length;
  va_list apStrLen;
  va_copy(apStrLen, ap);
  length = vsnprintf(NULL, 0, format, apStrLen);
  va_end(apStrLen);
  if (length > 0) {
    dst.resize(length);
    vsnprintf((char *)dst.data(), dst.size() + 1, format, ap);
  } else {
    dst = "Format error! format: ";
    dst.append(format);
  }
}

///
/// \breif Format message
/// \param dst String to store formatted message
/// \param format Format of message
/// \param ... Variable argument list
///
void toString(string &dst, const char *format, ...) throw() {
  va_list ap;
  va_start(ap, format);
  toString(dst, format, ap);
  va_end(ap);
}

///
/// \breif Format message
/// \param format Format of message
/// \param ... Variable argument list
///
string toString(const char *format, ...) throw() {
  string dst;
  va_list ap;
  va_start(ap, format);
  toString(dst, format, ap);
  va_end(ap);
  return dst;
}

///
/// \breif Format message
/// \param format Format of message
/// \param ap Variable argument list
///
string toString(const char *format, va_list ap) throw() {
  string dst;
  toString(dst, format, ap);
  return dst;
}


int main() {
  int a = 32;
  const char * str = "This works!";

  string test(toString("\nSome testing: a = %d, %s\n", a, str));
  printf(test.c_str());

  a = 0x7fffffff;
  test = toString("\nMore testing: a = %d, %s\n", a, "This works too..");
  printf(test.c_str());

  a = 0x80000000;
  toString(test, "\nMore testing: a = %d, %s\n", a, "This way is cheaper");
  printf(test.c_str());

  return 0;
}

String.h:

#pragma once
#include <cstdarg>
#include <string>

using ::std::string;

///
/// \breif Format message
/// \param dst String to store formatted message
/// \param format Format of message
/// \param ap Variable argument list
///
void toString(string &dst, const char *format, va_list ap) throw();
///
/// \breif Format message
/// \param dst String to store formatted message
/// \param format Format of message
/// \param ... Variable argument list
///
void toString(string &dst, const char *format, ...) throw();
///
/// \breif Format message
/// \param format Format of message
/// \param ... Variable argument list
///
string toString(const char *format, ...) throw();

///
/// \breif Format message
/// \param format Format of message
/// \param ap Variable argument list
///
string toString(const char *format, va_list ap) throw();

关于行vsnprintf((char *)dst.data(), dst.size() + 1, format, ap);-可以安全地假设字符串的缓冲区中有一个可容纳空字符的空间吗?是否有不分配size + 1个字符的实现。这样做会更安全吗dst.resize(length+1); vsnprintf((char *)dst.data(), dst.size(), format, ap); dst.resize(length);
drwatsoncode

显然,我之前的评论的答案是:不,假设存在空字符并不安全。特别是关于C ++ 98规范:“访问data()+ size()的值会产生未定义的行为无法保证空字符会终止此函数返回的值所指向的字符序列。请参见string :: c_str用于提供此类保证的函数。 程序不得更改此序列中的任何字符。 “但是,C ++ 11规范指出了datac_str是同义词。
drwatsoncode

1
_return.desc = (boost::format("fail to detect. cv_result = %d") % st_result).str();

1

非常非常简单的解决方案。

std::string strBuf;
strBuf.resize(256);
int iCharsPrinted = sprintf_s((char *)strPath.c_str(), strPath.size(), ...);
strBuf.resize(iCharsPrinted);

1

我知道这个问题已经回答了很多次,但这更加简洁:

std::string format(const std::string fmt_str, ...)
{
    va_list ap;
    char *fp = NULL;
    va_start(ap, fmt_str);
    vasprintf(&fp, fmt_str.c_str(), ap);
    va_end(ap);
    std::unique_ptr<char[]> formatted(fp);
    return std::string(formatted.get());
}

例:

#include <iostream>
#include <random>

int main()
{
    std::random_device r;
    std::cout << format("Hello %d!\n", r());
}

另请参阅http://rextester.com/NJB14150


1

更新1:添加fmt::format测试

我对这里介绍的方法进行了自己的调查,得出与这里提到的截然相反的结果。

我已经在4种方法中使用了4种功能:

  • 可变函数vsnprintf++std::unique_ptr
  • 可变函数vsnprintf++std::string
  • 可变参数模板功能+ std::ostringstream+ std::tuple+utility::for_each
  • fmt::formatfmt库功能

对于测试后端,googletest已使用。

#include <string>
#include <cstdarg>
#include <cstdlib>
#include <memory>
#include <algorithm>

#include <fmt/format.h>

inline std::string string_format(size_t string_reserve, const std::string fmt_str, ...)
{
    size_t str_len = (std::max)(fmt_str.size(), string_reserve);

    // plain buffer is a bit faster here than std::string::reserve
    std::unique_ptr<char[]> formatted;

    va_list ap;
    va_start(ap, fmt_str);

    while (true) {
        formatted.reset(new char[str_len]);

        const int final_n = vsnprintf(&formatted[0], str_len, fmt_str.c_str(), ap);

        if (final_n < 0 || final_n >= int(str_len))
            str_len += (std::abs)(final_n - int(str_len) + 1);
        else
            break;
    }

    va_end(ap);

    return std::string(formatted.get());
}

inline std::string string_format2(size_t string_reserve, const std::string fmt_str, ...)
{
    size_t str_len = (std::max)(fmt_str.size(), string_reserve);
    std::string str;

    va_list ap;
    va_start(ap, fmt_str);

    while (true) {
        str.resize(str_len);

        const int final_n = vsnprintf(const_cast<char *>(str.data()), str_len, fmt_str.c_str(), ap);

        if (final_n < 0 || final_n >= int(str_len))
            str_len += (std::abs)(final_n - int(str_len) + 1);
        else {
            str.resize(final_n); // do not forget to shrink the size!
            break;
        }
    }

    va_end(ap);

    return str;
}

template <typename... Args>
inline std::string string_format3(size_t string_reserve, Args... args)
{
    std::ostringstream ss;
    if (string_reserve) {
        ss.rdbuf()->str().reserve(string_reserve);
    }
    std::tuple<Args...> t{ args... };
    utility::for_each(t, [&ss](auto & v)
    {
        ss << v;
    });
    return ss.str();
}

for_each实施就是从这里取:叠代元组

#include <type_traits>
#include <tuple>

namespace utility {

    template <std::size_t I = 0, typename FuncT, typename... Tp>
    inline typename std::enable_if<I == sizeof...(Tp), void>::type
        for_each(std::tuple<Tp...> &, const FuncT &)
    {
    }

    template<std::size_t I = 0, typename FuncT, typename... Tp>
    inline typename std::enable_if<I < sizeof...(Tp), void>::type
        for_each(std::tuple<Tp...> & t, const FuncT & f)
    {
        f(std::get<I>(t));
        for_each<I + 1, FuncT, Tp...>(t, f);
    }

}

测试:

TEST(ExternalFuncs, test_string_format_on_unique_ptr_0)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format(0, "%s+%u\n", "test test test", 12345);
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_unique_ptr_256)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format(256, "%s+%u\n", "test test test", 12345);
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_std_string_0)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format2(0, "%s+%u\n", "test test test", 12345);
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_std_string_256)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format2(256, "%s+%u\n", "test test test", 12345);
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_string_stream_on_variadic_tuple_0)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format3(0, "test test test", "+", 12345, "\n");
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_string_stream_on_variadic_tuple_256)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format3(256, "test test test", "+", 12345, "\n");
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_string_stream_inline_0)
{
    for (size_t i = 0; i < 1000000; i++) {
        std::ostringstream ss;
        ss << "test test test" << "+" << 12345 << "\n";
        const std::string v = ss.str();
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_string_stream_inline_256)
{
    for (size_t i = 0; i < 1000000; i++) {
        std::ostringstream ss;
        ss.rdbuf()->str().reserve(256);
        ss << "test test test" << "+" << 12345 << "\n";
        const std::string v = ss.str();
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_fmt_format_positional)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = fmt::format("{0:s}+{1:d}\n", "test test test", 12345);
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_fmt_format_named)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = fmt::format("{first:s}+{second:d}\n", fmt::arg("first", "test test test"), fmt::arg("second", 12345));
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR

unsued.hpp

#define UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(var)   ::utility::unused_param(&var)

namespace utility {

    extern const volatile void * volatile g_unused_param_storage_ptr;

    extern void
#ifdef __GNUC__
    __attribute__((optimize("O0")))
#endif
        unused_param(const volatile void * p);

}

未使用.cpp

namespace utility {

    const volatile void * volatile g_unused_param_storage_ptr = nullptr;

    void
#ifdef __GNUC__
    __attribute__((optimize("O0")))
#endif
        unused_param(const volatile void * p)
    {
        g_unused_param_storage_ptr = p;
    }

}

结果

[ RUN      ] ExternalFuncs.test_string_format_on_unique_ptr_0
[       OK ] ExternalFuncs.test_string_format_on_unique_ptr_0 (556 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_unique_ptr_256
[       OK ] ExternalFuncs.test_string_format_on_unique_ptr_256 (331 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_std_string_0
[       OK ] ExternalFuncs.test_string_format_on_std_string_0 (457 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_std_string_256
[       OK ] ExternalFuncs.test_string_format_on_std_string_256 (279 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_string_stream_on_variadic_tuple_0
[       OK ] ExternalFuncs.test_string_format_on_string_stream_on_variadic_tuple_0 (1214 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_string_stream_on_variadic_tuple_256
[       OK ] ExternalFuncs.test_string_format_on_string_stream_on_variadic_tuple_256 (1325 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_string_stream_inline_0
[       OK ] ExternalFuncs.test_string_format_on_string_stream_inline_0 (1208 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_string_stream_inline_256
[       OK ] ExternalFuncs.test_string_format_on_string_stream_inline_256 (1302 ms)
[ RUN      ] ExternalFuncs.test_fmt_format_positional
[       OK ] ExternalFuncs.test_fmt_format_positional (288 ms)
[ RUN      ] ExternalFuncs.test_fmt_format_named
[       OK ] ExternalFuncs.test_fmt_format_named (392 ms)

如您所见,通过vsnprintf+ 实现std::string等于fmt::format,但是比通过vsnprintf+实现std::unique_ptr更快,后者比通过+ 更快std::ostringstream

测试在中编译Visual Studio 2015 Update 3并在上运行Windows 7 x64 / Intel Core i7-4820K CPU @ 3.70GHz / 16GB

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.