Std::string форматирование, подобное sprintf

Мне нужно отформатировать std::string с sprintf и отправить его в поток файлов. Как я могу это сделать?

Ответ 1

Вы не можете сделать это напрямую, потому что у вас нет доступа на запись к базовому буферу (до С++ 11, см. Dietrich Epp comment). Сначала вам нужно сделать это в 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();

Ответ 2

С++ 11, которое использует vsnprintf() внутри:

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

ПРИМЕЧАНИЕ. В некоторых системах "безопасная" и "более быстрая" версия не работает. Следовательно, оба они все еще перечислены. Кроме того, "быстрее" полностью зависит от правильного выбора preallocation, иначе strcpy делает его медленнее.

Ответ 3

Используя С++ 11 std::snprintf, это становится довольно простой и безопасной задачей. Я вижу множество ответов на этот вопрос, которые, по-видимому, были написаны до С++ 11, в которых используются фиксированные длины буфера и переменные, что я бы не рекомендовал из соображений безопасности, эффективности и ясности.

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

Вышеприведенный фрагмент кода распространяется под лицензией CC0 1.0.

Построчное объяснение:

Цель: записать в char* с помощью std::snprintf а затем преобразовать его в std::string.

Сначала мы определим желаемую длину массива char.

С cppreference.com:

Возвращаемое значение

[...] Если полученная строка обрезается из-за ограничения buf_size, функция возвращает общее количество символов (не включая завершающий нулевой байт), которые были бы записаны, если бы ограничение не было наложено.

Это означает, что желаемый размер равен числу символов плюс один, так что нулевой терминатор будет располагаться после всех других символов и что он может быть снова обрезан конструктором строки. Эта проблема была объяснена @alexk7 в комментариях.

Затем мы выделяем новый массив символов и назначаем его в std::unique_ptr. Обычно это рекомендуется, так как вам не придется снова delete его вручную.

Обратите внимание, что это не безопасный способ выделить unique_ptr с пользовательскими типами, так как вы не можете освободить память, если конструктор выдает исключение!

После этого мы, конечно, можем просто использовать snprintf по назначению и записать отформатированную строку в char[] а затем создать и вернуть новую std::string из этого.


Вы можете увидеть пример в действии здесь.


Если вы также хотите использовать std::string в списке аргументов, взгляните на эту суть.


Дополнительная информация для пользователей Visual Studio:

Как объясняется в этом ответе, Microsoft переименовала std::snprintf в _snprintf (да, без std:: _snprintf. Далее MS устанавливает его как устаревшее и рекомендует вместо этого использовать _snprintf_s, однако _snprintf_s не примет буфер равным нулю или меньше отформатированного вывода и не вычислит длину выходных данных, если это произойдет. Таким образом, чтобы избавиться от предупреждений об устаревании во время компиляции, вы можете вставить следующую строку вверху файла, которая содержит использование _snprintf:

#pragma warning(disable : 4996)

Ответ 4

boost::format() предоставляет требуемые функции:

Как видно из библиотек формата Boost:

Объект формата строится из форматированной строки и затем задается аргумент посредством повторных вызовов оператору%. Каждый из этих аргументов затем преобразуется в строки, которые в свою очередь объединены в одну строку, в соответствии с форматированной строкой.

#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

С++ 20 будет включать в себя std::format, который с точки зрения API напоминает sprintf, но полностью безопасен для типов, работает с пользовательскими типами и использует синтаксис строки формата, подобный Python:

#include <format>

std::string result = std::format("The answer is {}.", 42);

Как и для большинства других ответов здесь, к сожалению, они используют varargs и по своей сути небезопасны, если вы не используете что-то вроде атрибута GCC format, который работает только со строками литерального формата. Вы можете понять, почему эти функции небезопасны, на следующем примере:

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

где string_format - реализация из ответа Эрика Аронести. Этот код компилируется, но, скорее всего, он потерпит крах при попытке его запустить:

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

Можно реализовать безопасный printf и расширить его до формата std::string, используя (вариационные) шаблоны. Это было сделано в {fmt} библиотеке и std::format, которые предоставляют безопасную альтернативу sprintf, возвращая std::string (или записывая в предоставленный буфер):

#include <fmt/core.h>

std::string format_str = "The answer is {}.";
std::string result = fmt::format(format_str, 42);

{fmt} отслеживает типы аргументов, и если тип не соответствует спецификации формата, то нет ошибки сегментации, только исключение или ошибка времени компиляции, если используются проверки строки формата constexpr.

Отказ от ответственности: я являюсь автором {fmt} и С++ 20 std::format.

Ответ 6

Если вам нужен только синтаксис типа printf (без вызова printf самостоятельно), посмотрите Boost Format.

Ответ 7

Я написал свой собственный, используя 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);

Ответ 8

Чтобы отформатировать std::string способом "sprintf", вызовите snprintf (arguments nullptr и 0), чтобы получить необходимую длину буфера. Напишите свою функцию, используя шаблон С++ 11 variadic следующим образом:

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

Компилировать с поддержкой С++ 11, например, в GCC: g++ -std=c++11

Использование:

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

Ответ 9

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


[править] Адаптировано, чтобы использовать технику в ответе Эрика (выше):

#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; те, что в настоящее время достаточны для моих нужд. Вы используете stringf() и stringfappend() так же, как и sprintf. Просто помните, что параметры для... должны быть типами 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;
}

Ответ 10

Вот как это делает google: StringPrintf (лицензия BSD)
и facebook делает это совершенно так: StringPrintf (лицензия Apache)
Оба обеспечивают также удобный StringAppendF.

Ответ 11

Мои два цента по этому очень популярному вопросу.

Чтобы процитировать manpage printf -подобных функций:

При успешном возврате эти функции возвращают количество напечатанных символов (исключая нулевой байт, используемый для завершения вывода на строки).

Функции snprintf() и vsnprintf() не записывают больше байтов размера (включая завершающий нулевой байт ('\ 0')). Если результат был усечен из-за этого предела, возвращаемым значением является количество символов (исключая завершающий нулевой байт), которые были бы записаны в финальную строку, если бы было достаточно свободного места. Таким образом, возвращаемое значение размера или больше означает, что выход был усечен.

Другими словами, правильная реализация С++ 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);
}

Хорошо работает:)

Шаблоны Variadic поддерживаются только в С++ 11. Ответ с пиксельной точки показывает аналогичную технику, используя более старые стили программирования.

Странно, что С++ не имеет такой вещи из коробки. Недавно они добавили to_string(), что, на мой взгляд, является большим шагом вперед. Мне интересно, добавят ли они оператор .format в std::string...

Изменить

Как указывал alexk7, для возвращаемого значения std::snprintf требуется A +1, так как нам нужно иметь пробел для байта \0. Интуитивно, на большинстве архитектур, отсутствующих в +1, число required будет частично перезаписано с помощью 0. Это произойдет после оценки required как фактического параметра для std::snprintf, поэтому эффект не должен быть видимым.

Однако эта проблема может измениться, например, при оптимизации компилятора: что, если компилятор решает использовать регистр для переменной required? Это ошибки, которые иногда приводят к проблемам безопасности.

Ответ 12

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 и С++ 11

Ответ 13

Проверено, ответ качества продукции

Этот ответ обрабатывает общий случай с помощью методов, соответствующих стандартам. Тот же подход приведен в качестве примера на 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 в многократно используемой функции является плохим стилем программирования и его следует избегать. В этом случае произвольно большое представление переменной переменной vargs является ключевым фактором при выборе алгоритма.

Повторная попытка при переполнении экспоненциально неэффективна, что является еще одной причиной, обсуждаемой, когда комитет по стандартам С++ 11 обсуждал вышеупомянутое предложение, чтобы обеспечить пробный запуск, когда буфер записи равен нулю.

В приведенной выше реализации готового производства первый прогон является таким пробным прогоном для определения размера распределения. Распределение не происходит. Синтаксический анализ директив printf и чтение vargs стали чрезвычайно эффективными в течение десятилетий. Повторно используемый код должен быть предсказуемым, даже если нужно пожертвовать небольшой неэффективностью для тривиальных случаев.

Безопасность и Надежность

Эндрю Кениг сказал небольшой группе из нас после своей лекции на мероприятии в Кембридже: "Функции пользователя не должны полагаться на использование отказа из-за исключительной функциональности". Как обычно, его мудрость была показана в записи с тех пор. Исправленные и закрытые ошибки безопасности часто указывают на повторные попытки в описании дыры, использованной до исправления.

Это упоминается в официальном предложении о пересмотре стандартов для функции нулевого буфера в альтернативе sprintf, предложении о пересмотре C9X, документе ИСО МЭК WG14 N645/X3J11 96-008. Строка произвольной длины, вставляемая в директиву печати "% s" в рамках ограничений доступности динамической памяти, не является исключением и не должна использоваться для создания "Необычайной функциональности".

Рассмотрим предложение вместе с примером кода, приведенным внизу страницы С++ Reference.org, на которую есть ссылка в первом абзаце этого ответа.

Кроме того, тестирование случаев неудачи редко бывает таким же успешным.

портативность

Все основные поставщики ОС предоставляют компиляторы, которые полностью поддерживают std :: vsnprintf как часть стандартов С++ 11. Хосты, использующие продукты поставщиков, которые больше не поддерживают дистрибутивы, должны быть снабжены g++ или кланом g++ по многим причинам.

Использование стека

Использование стека при первом вызове std :: vsnprintf будет меньше или равно использованию второго, и оно будет освобождено до начала второго вызова. Если первый вызов превышает доступность стека, то std :: fprintf также потерпит неудачу.

Ответ 14

С++ 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

Implementation of C++20 std::format.

и был ранее упомянут в: std::string форматирование как sprintf

Ответ 15

Основываясь на ответе Эрика Аронстисти:

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(), который был в исходном ответе.

Ответ 16

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

Ответ 17

Строка

не имеет того, что вам нужно, но std:: stringstream делает. Используйте stringstream для создания строки, а затем извлеките строку. Здесь - полный список того, что вы можете сделать. Например:

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

даст вам 10 десятичных знаков точности при печати двойного или плавающего.

Ответ 18

Вы можете попробовать следующее:

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

Ответ 19

Если вы находитесь в системе с 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;
}

Ответ 20

Это код, который я использую для этого в моей программе... Это ничего не притворяется, но это делает трюк... Обратите внимание: вам нужно будет приспособить свой размер по мере необходимости. 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;
}

Ответ 21

Взял идею из Dacav и ответ пиксельной точки. Я немного поиграл и получил это:

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

С практикой программирования разумной я считаю, что кода должно быть достаточно, однако я по-прежнему открыт для более безопасных альтернатив, которые все еще достаточно просты и не требуют С++ 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, только то, что она не использует new и delete[], а также указывает размер при создании std::string.

Идея не использовать new и delete[] заключается в том, чтобы использовать стек над кучей, поскольку ему не нужно вызывать функции выделения и освобождения, однако, если они неправильно используются, это может быть опасно для переполнение буфера в некоторых (возможно, старых или, возможно, только уязвимых) системах. Если это вызывает беспокойство, я настоятельно рекомендую использовать new и delete[]. Обратите внимание, что речь идет только о распределении, поскольку vsnprintf() уже вызывается с ограничениями, поэтому указание ограничения, основанного на размере, выделенном во втором буфере, также предотвратит их.)

Ответ 22

Я обычно использую это:

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

Ответ 23

Ниже слегка измененная версия ответа @iFreilicht, обновленная до С++ 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

Не стесняйтесь объединить этот ответ с оригинальным, если хотите.

Ответ 25

Вы можете форматировать вывод С++ в cout с помощью файла заголовка iomanip. Убедитесь, что вы включили заголовочный файл iomanip, прежде чем использовать какие-либо вспомогательные функции, например setprecision, setfill и т.д.

Вот фрагмент кода, который я использовал в прошлом, чтобы напечатать среднее время ожидания в векторе, который я "скопировал".

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

Вот краткое описание того, как мы можем форматировать потоки С++. http://www.cprogramming.com/tutorial/iomanip.html

Ответ 26

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

Ответ 27

Очень-очень простое решение.

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

Ответ 28

Все ответы до сих пор, похоже, имеют проблемы: (1) он может не работать на VС++ (2), он требует дополнительных зависимостей, таких как boost или fmt (3), слишком сложной пользовательской реализации и, вероятно, недостаточно хорошо тестировался.

Ниже приведен код всех вышеперечисленных проблем.

#include <string>
#include <cstdarg>
#include <memory>

std::string format(const char* format, ...)
{
    va_list args;
    va_start(args, format);
    #ifndef _MSC_VER
        size_t size = std::snprintf( nullptr, 0, format, args) + 1; // Extra space for '\0'
        std::unique_ptr<char[]> buf( new char[ size ] ); 
        std::vsnprintf( buf.get(), size, format, args);
        return std::string(buf.get(), buf.get() + size - 1 ); // We don't want the '\0' inside
    #else
        int size = _vscprintf(format, args);
        std::string result(++size, 0);
        vsnprintf_s((char*)result.data(), size, _TRUNCATE, format, args);
        return result;
    #endif
    va_end(args);
}

int main() {
    float f = 3.f;
    int i = 5;
    std::string s = "hello!";
    auto rs = format("i=%d, f=%f, s=%s", i, f, s.c_str());
    printf("%s", rs.c_str());
    return 0;
}

Примечания:

  • Отдельная ветка кода VС++ необходима из-за несоблюдения стандартов.
  • Функция принимает char * вместо std::string. Это потому, что большую часть времени эта функция вызывается с буквальной строкой, которая действительно char *, а не std::string. Если у вас есть параметр std::string в качестве параметра формата, просто вызовите .c_str().
  • Имя функции - это формат, а не такие, как string_format, потому что параметры и тип возвращаемого значения уже сообщают вам, что он форматирует.

Протестировано,

Ответ 29

ОБНОВЛЕНИЕ 1: добавлены тесты fmt::format

Я провел собственное исследование вокруг методов, введенных здесь, и получил диаметрально противоположные результаты по сравнению с упомянутыми здесь.

Я использовал 4 функции по 4 методам:

  • вариационная функция + vsnprintf + std::unique_ptr
  • variadic function + vsnprintf + std::string
  • вариационная функция шаблона + std::ostringstream + std::tuple + utility::for_each
  • Функция fmt::format из библиотеки fmt

Для тестового бэкэнда использовался 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);

}

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

Ответ 30

Обновление некоторого ответа вокруг, разница - функция будет правильно принимать std :: string для% s

namespace format_helper
{

    template <class Src>
    inline Src cast(Src v)
    {
        return v;
    }

    inline const char *cast(const std::string& v)
    {
        return v.c_str();
    }
};

template <typename... Ts>
inline std::string stringfmt (const std::string &fmt, Ts&&... vs)
{
    using namespace format_helper;
    char b;
    size_t required = std::snprintf(&b, 0, fmt.c_str(), cast(std::forward<Ts>(vs))...);//not counting the terminating null character.
    std::string result;
    //because we use string as container, it adds extra 0 automatically
    result.resize(required , 0);
    //and snprintf will use n-1 bytes supplied
    std::snprintf(const_cast<char*>(result.data()), required + 1, fmt.c_str(), cast(std::forward<Ts>(vs))...);

    return result;
}

Live: http://cpp.sh/5ajsv