форматирование std :: string как sprintf


454

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


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 ++.
Тревор Бойд Смит

@Ockonal - Ради сообщества (мне было наплевать на мою репутацию), я предлагаю вам изменить свой выбор. Тот, который в данный момент выбран в первом фрагменте, представляет ошибку, ожидающую при использовании произвольной максимальной длины. Второй фрагмент полностью игнорирует ваше заявленное желание использовать vargs, например sprintf. Я предлагаю вам выбрать ЕДИНСТВЕННЫЙ ответ здесь, который является чистым, безопасным, опирается только на стандарты C ++, протестирован и хорошо прокомментирован. То, что это мое, не имеет значения. Это объективно верно. См. Stackoverflow.com/questions/2342162/… .
Дуглас Дасеко

@TrevorBoydSmith a std::formatбыл добавлен в C ++ 20 Кстати: stackoverflow.com/a/57286312/895245 Круто!
Сиро Сантилли 郝海东 冠状 病 六四 事件 法轮功

1
@CiroSantilli Я прочитал статью о C++20вчерашнем дне и увидел, что она C++20скопирована boost(в миллионный раз), добавив std::formatв C++20спецификацию! Я был очень, очень счастлив! Почти все файлы C ++, которые я написал за последние 9 лет, были использованы boost::format. Добавление официального вывода в стиле printf к потокам в C ++ будет иметь большое значение для всего C ++.
Тревор Бойд Смит

Ответы:


333

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

17
Волшебное печенье в char buf[100];делает это решение не очень надежным. Но основная идея есть.
Джон Диблинг

18
Джон, потоки не медленные. Единственная причина, по которой потоки кажутся медленными, заключается в том, что по умолчанию iostreams синхронизируются с выходом C FILE, так что смешанные cout и printfs выводятся правильно. Отключение этой ссылки (с вызовом cout.sync_with_stdio (false)) приводит к тому, что потоки c ++ превосходят stdio, по крайней мере, начиная с MSVC10.
Джимбо

72
Причина использования форматов состоит в том, чтобы позволить локализатору перестроить структуру предложения для иностранных языков вместо жесткого кодирования грамматики предложения.
Мартин Курто

216
По некоторым причинам, другие языки используют синтаксис, похожий на printf: Java, Python (новый синтаксис все еще ближе к printf, чем к потокам). Только C ++ причиняет эту многословную мерзость невинным людям.
Quant_Dev

9
Еще лучше использовать asprintf, что выделяет новую строку с достаточным пространством для хранения результата. Затем скопируйте это, std::stringесли хотите, и запомните freeоригинал. Кроме того, можно поместить это в макрос, чтобы любой хороший компилятор помог вам проверить формат - вы не хотите помещать туда, doubleгде %sожидается
Aaron McDaid

287

Современный 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 с 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, функция возвращает общее количество символов (не включая завершающий нулевой байт), которое было бы записано, если бы ограничение не было наложено.

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

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 возвращается как количество символов, необходимое для форматирования вывода, не включая завершающий ноль. Чтобы сделать успешный вызов с тем же параметром и параметрами локали, выделите буфер, содержащий не менее len + 1 символов.
Ч

3
@moooeeeep Множество причин. Во-первых, цель здесь - вернуть std :: string, а не c-строку, так что вы, вероятно, имели в виду return string(&buf[0], size);или что-то подобное. Во-вторых, если бы вы возвращали c-строку подобным образом, это вызвало бы неопределенное поведение, потому что вектор, содержащий значения, на которые вы указываете, будет недействительным при возврате. В-третьих, когда я начал изучать C ++, стандарт не определял, в каком порядке элементы должны храниться внутри std::vector, поэтому доступ к его хранилищу через указатель был неопределенным поведением. Теперь это будет работать, но я не вижу никакой выгоды в этом.
iFreilicht

2
@iFreilicht Новый std::stringбудет создан из неявно преобразованного вектора ( инициализация копии ), который затем возвращается как копия, как предполагает сигнатура функции. Кроме того, элементы a std::vectorхранятся и должны были храниться непрерывно . Но я понимаю, что в этом нет никакой пользы.
moooeeeep

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>' через функцию variadic; вызов будет прерван во время выполнения [-Wnon-pod -varargs] ) в clang 3.9.1, но в CL 19 он прекрасно компилируется и вместо этого вылетает во время выполнения. Любой предупреждающий флаг, который я могу включить, чтобы иметь эту информацию во время компиляции в cl тоже?
Цитракс

241

C ++ 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.

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


3
медленный. зачем увеличивать размер на 1? И когда эта функция возвращает -1?
0xDEAD BEEF

27
Вы перезаписываете str.c_str ()? Разве это не опасно?
Квантовая

8
va_start со ссылочным аргументом имеет проблемы в MSVC. Он молча терпит неудачу и возвращает указатели на случайную память. В качестве обходного пути используйте std :: string fmt вместо std :: string & fmt или напишите объект-оболочку.
Стив Ханов

6
Я + 1, потому что знаю, что это, вероятно, будет работать в зависимости от того, как реализовано большинство std :: strings, однако c_str на самом деле не предназначен для изменения базовой строки. Это должно быть только для чтения.
Дуг Т.

6
И чтобы заранее получить полученную длину строки, см .: stackoverflow.com/a/7825892/908336 Я не вижу смысла в увеличении sizeна каждой итерации, когда вы можете получить ее при первом вызове vsnprintf().
Массуд Хаари

107

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
Вы также можете удалить нужные библиотеки из-за наддува. Использование прилагаемого инструмента.
Хасан Сайед

7
Boost Format не только большой, но и очень медленный. См. Zverovich.net/2013/09/07/… и boost.org/doc/libs/1_52_0/libs/spirit/doc/html/spirit/karma/…
vitaut

14
Включение boost в любом месте вашего проекта сразу значительно увеличивает время компиляции. Для крупных проектов это, скорее всего, не имеет значения. Для небольших проектов повышение - это сопротивление.
Quant_dev

2
@vitaut Хотя это ужасно много ресурсов по сравнению с альтернативами. Как часто вы форматируете строки? Учитывая, что это занимает всего несколько микросекунд, и большинство проектов, вероятно, используют его всего несколько десятков раз, это не заметно в проекте, который не слишком сосредоточен на форматировании строк, верно?
AturSams

2
К сожалению, boost :: format не работает так же: не принимает var_args. Некоторым людям нравится, чтобы весь код, связанный с одной программой, выглядел одинаково / использовал одинаковые идиомы.
xor007

88

C ++ 20 будет включать в себя то, std::formatчто похоже sprintfна API, но полностью типобезопасно, работает с пользовательскими типами и использует синтаксис строки формата, подобный Python. Вот как вы сможете отформатировать 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или большинства других ответов здесь, к сожалению, они используют переменные и по своей сути небезопасны, если вы не используете что-то вроде formatатрибута GCC, который работает только со строками литерального формата. Вы можете увидеть, почему эти функции небезопасны на следующем примере:

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

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


ИМХО ты пропустил включение error: 'fmt' has not been declared
Sérgio

Это всего лишь фрагмент, а не полный код. Очевидно, что вам нужно включить <fmt / format.h> и поместить код в функцию.
Витаут

для меня это не так очевидно, ИМХО, вы должны включить его в сниппет, спасибо за отзыв
Sérgio

1
Подобная fmtреализация была добавлена ​​в C ++ 20! stackoverflow.com/a/57286312/895245 fmt в настоящее время заявляет о поддержке его. Отличная работа!
Сиро Сантилли 郝海东 冠状 病 六四 事件 法轮功

2
@vitaut Спасибо за вашу работу над этим!
Курт Николс


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

1
Используйте код в stackoverflow.com/a/7825892/908336, чтобы получить результирующую длину строки заранее. И вы можете использовать умные указатели для безопасного кода:std::unique_ptr<char[]> buffer (new char[size]);
Massood Khaari

Я не уверен, что это правильно в случае отказа; Я думаю, что вам нужно сделать va_copy для vl для второго vsnprintf (), чтобы правильно увидеть аргументы. Для примера см .: github.com/haberman/upb/blob/…
Джош Хаберман,

15

Чтобы выполнить форматирование std::stringв стиле sprintf, вызовите snprintf(arguments nullptrи 0), чтобы получить необходимую длину буфера. Напишите свою функцию, используя шаблон C ++ 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;
}

Компилировать с поддержкой 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.
Шиталь Шах

почему вы не используете char buf[length + 1];вместо char* buf = new char[length + 1];?
Behrouz.M

Разница между использованием char[]и char*с новым заключается в том, что в первом случае buf будет размещен в стеке. Это нормально для небольших буферов, но поскольку мы не можем гарантировать размер результирующей строки, использовать ее немного лучше new. Например, на моем компьютере string_sprintf("value: %020000000d",5)выведите огромное количество new char[length + 1]
начальных

очень умная идея получить фактический размер баффа, необходимый для форматированного вывода
Chris

1
@ user2622016: Спасибо за решение! Пожалуйста, обратите внимание, что std::move это лишнее .
Михай Тодор

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)-Функция для обслуживания графического интерфейса или терминала или любые потребности специального вывода с использованием #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);


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

#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: я написал и использую следующие функции. Если вам это нравится, вы можете расширить% -опции, чтобы они более близко подходили к 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;
}

@MooingDuck: изменен параметр функции согласно комментарию Дэна к ответу Аронести. Я использую только Linux / GCC, и в fmtкачестве справки он работает нормально. (Но я полагаю, что люди захотят поиграть с игрушками, так что ...) Если есть какие-то другие предполагаемые «ошибки», не могли бы вы уточнить?
slashmais

Я неправильно понял, как работает часть его кода, и подумал, что это влияет на многие изменения размера. Пересмотр показывает, что я ошибся. Ваш код правильный.
Mooing Duck

Построение ответа Эрика Аронести - красная сельдь. Его первый пример кода небезопасен, а второй - неэффективен и неуклюж. Чистая реализация четко обозначена тем фактом, что, если buf_siz какой-либо из функций семейства vprintf равен нулю, ничего не записывается, а буфер может быть нулевым указателем, однако возвращаемое значение (число байтов, которое будет записано, не включая нулевой терминатор) все еще вычисляется и возвращается. Ответ о качестве продукции здесь: stackoverflow.com/questions/2342162/…
Дуглас Дасико


10

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

Чтобы процитировать man-страницу 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);
}

Работает довольно хорошо :)

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

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

редактировать

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

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


1
snprintf всегда добавляет завершающий нулевой байт, но возвращает количество символов без него. Разве этот код не всегда пропускает последний символ?
alexk7

@ alexk7, Хороший улов! Я обновляю ответ. Код не пропускает последний символ, но записывает за пределы bytesбуфера, возможно, через requiredцелое число (которое, к счастью, в этот момент уже вычислено).
Дакав

1
Небольшая подсказка: с размером буфера 0 вы можете передать в nullptrкачестве аргумента буфера, исключив char b;строку в вашем коде. ( Источник )
iFreilicht

@iFreilicht, fix'd. Также +1
Dacav

2
Использование «char bytes [required]» будет размещено в стеке вместо кучи, это может быть опасно для строк большого формата. Попробуйте использовать вместо этого новый. 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


9

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

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

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

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

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

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

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

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

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

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

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

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

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


Кратко и надежно. Может произойти сбой на HP-UX, IRIX, Tru64, которые имеют несоответствующие vsnprintf-s. РЕДАКТИРОВАТЬ: также, учитывая, как два прохода может повлиять на производительность, особенно для наиболее распространенного форматирования небольших строк, вы рассматривали предположение для начального прохода, которое может быть достаточно большим?
инженер

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

Основываясь на ответе, предоставленном Эриком Аронести:

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
Построение ответа Эрика Аронести - красная сельдь. Его первый пример кода небезопасен, а второй - с циклом неэффективным и неуклюжим. Чистая реализация четко обозначена тем фактом, что, если buf_siz какой-либо из функций семейства vprintf равен нулю, ничего не записывается, а буфер может быть нулевым указателем, однако возвращаемое значение (число байтов, которое будет записано, не включая нулевой терминатор) все еще вычисляется и возвращается. Ответ о качестве продукции здесь: stackoverflow.com/questions/2342162/…
Дуглас Дасико

Ответ Эрика Аронести был отредактирован с тех пор, как был добавлен мой. Я хотел выделить вариант использования вектора <char> для хранения строк по мере их сборки. Я часто использую эту технику при вызове функций C из кода C ++. Интересно, что на этот вопрос сейчас 34 ответа.
ChetS

Пример cppreference.com на странице vfprintf был добавлен позже. Я считаю, что лучший ответ - это принятый в настоящее время ответ, использование строковых потоков вместо варианта 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есть. Я думаю, что вы должны уточнить этот ответ.
Дакав

3

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

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

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


8
который все еще не дает вам ничего похожего на элемент управления printf ... но это приятно.
Эрик Аронесты

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)));
Аарон МакДейд

2
Я только что добавил вызов va_end. «если va_end не вызывается до того, как функция, которая вызывает va_start или va_copy, возвращает поведение, оно не определено.» - docs
Аарон МакДейд

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 уже устанавливает весь буфер в ноль. Нет необходимости в memset ...
EricSchaefer

Это делает полную дополнительную копию данных, которую можно использовать vsnprintfнепосредственно в строку.
Mooing Duck

2

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

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

(Оказывается, эта версия просто похожа на ответ Пити Онгмонгколкула , только то, что она не использует newи delete[], а также указывает размер при создании std::string.

Идея здесь не в том, чтобы использовать newи delete[]подразумевать использование стека поверх кучи, поскольку ему не нужно вызывать функции выделения и освобождения, однако, если не использовать должным образом, это может быть опасно для переполнения буфера в некоторых (возможно, старых или возможно просто уязвимые) системы. Если это проблема, я настоятельно рекомендую использовать newи delete[]вместо. Обратите внимание, что единственная проблема здесь связана с распределением, 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 хорош - однако вам нужно проверить код возврата. В буфере -1 будет неопределенное значение. Нужно: if (size == -1) {throw std :: bad_alloc (); }
Нил Макгилл,

2

Ниже слегка измененная версия ответа @iFreilicht, обновлена ​​до C ++ 14 (использование make_uniqueфункции вместо необработанного объявления) и добавлена ​​поддержка std::stringаргументов (на основе статьи Кенни Керра )

#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

Вы можете отформатировать вывод C ++ в 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;

Вот краткое описание того, как мы можем форматировать потоки 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);- безопасно ли предполагать, что в буфере строки есть место для завершающего нулевого символа? Существуют ли реализации, которые не выделяют размер + 1 символов. Было бы безопаснее сделатьdst.resize(length+1); vsnprintf((char *)dst.data(), dst.size(), format, ap); dst.resize(length);
drwatsoncode

Видимо, ответ на мой предыдущий комментарий: Нет, это НЕ безопасно, предполагать, что есть нулевой символ. В частности, в отношении спецификации C ++ 98: «Доступ к значению в data () + size () приводит к неопределенному поведению : нет никаких гарантий, что нулевой символ завершает последовательность символов, указанную значением, возвращаемым этой функцией. См. Строку :: c_str для функции, которая обеспечивает такую ​​гарантию. Программа не должна изменять ни один из символов в этой последовательности. "Однако спецификация C ++ 11 указывает, что dataи c_strявляются синонимами.
drwatsoncode


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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.