написать c ++ функцию format_string для форматирования как sprintf из std :: string - PullRequest
6 голосов
/ 15 ноября 2010

Для удобства использования я хочу написать функцию форматирования, похожую на sprintf, просто возвращающую std :: string, например:

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

Я могу использовать vsnprintf, но есть проблема - я не знаюзаранее, как долго должен быть временный буфер.В Microsoft есть функция _vscprintf, которая может это делать, но я думаю, что она не переносимая?

Один из вариантов - это запустить временный буфер и установить известный размер, а затем увеличить его, если с помощью vsnprintf увидеть его недостаточно.Есть ли лучший подход?Спасибо


PS Пожалуйста, дайте ответ без повышения.Я знаю о Boost, но мне любопытно, как реализовать это без.

Ответы [ 4 ]

4 голосов
/ 15 ноября 2010

Одна из опций - запуск временного буфера некоторого известного размера, а затем его увеличение, если вы видите, что этого недостаточно с vsnprintf. Есть ли лучший подход? Спасибо

Вы можете использовать vasprintf(), но это приводит к ненужному выделению кучи - в среднем это вряд ли будет быстрее. Используя alloca, вы можете избежать кучи. Или вы можете записать непосредственно в возвращаемое string: NRVO должен избегать копирования, а с C ++ 11 семантика перемещения будет ограничивать стоимость sans-NRVO несколькими заменами указателей.

#include <cstdio>
#include <cstdarg>
#include <alloca.h>

#include <string>
#include <iostream>

std::string stringf(const char* format, ...)
{
    va_list arg_list;                                                           
    va_start(arg_list, format);                                                 

    // SUSv2 version doesn't work for buf NULL/size 0, so try printing
    // into a small buffer that avoids the double-rendering and alloca path too...
    char short_buf[256];                                                        
    const size_t needed = vsnprintf(short_buf, sizeof short_buf,
                                    format, arg_list) + 1;
    if (needed <= sizeof short_buf)
        return short_buf;

    // need more space...

    // OPTION 1
    std::string result(needed, ' ');
    vsnprintf(result.data(), needed, format, arg_list);
    return result;  // RVO ensures this is cheap
 OR
    // OPTION 2
    char* p = static_cast<char*>(alloca(needed)); // on stack
    vsnprintf(p, needed, format, arg_list);
    return p;  // text copied into returned string
}

int main()                                                                      
{                                                                               
    std::string s = stringf("test '%s', n %8.2f\n", "hello world", 3.14);       
    std::cout << s;                                                             
}

Более простой и изначально более быстрый вариант:

    std::string result(255, ' ');  // 255 spaces + NUL
    const size_t needed = vsnprintf(result.data(), result.size() + 1,
                                    format, arg_list);
    result.resize(needed); // may truncate, leave or extend...
    if (needed > 255) // needed doesn't count NUL
        vsnprintf(result.data(), needed + 1, format, arg_list);
    return result;

Потенциальная проблема заключается в том, что вы выделяете не менее 256 символов, как бы кратко они не сохраняли фактический текст: это может привести к увеличению производительности, связанной с памятью / кэшем. Возможно, вы сможете обойти эту проблему, используя [shrink_to_fit] http://en.cppreference.com/w/cpp/string/basic_string/shrink_to_fit),, но стандарт не требует, чтобы он действительно что-то делал (требования «не являются обязательными»). Если вам в итоге придется скопировать в новую строку с точным размером, вы могли бы также использовать локальный массив символов.

3 голосов
/ 15 ноября 2010

C99 представил snprintf и, возможно, vsnprintf.Существует несколько портативных реализаций с открытым исходным кодом (v)snprintf, таких как this .Последний также реализует vasprintf, который динамически распределяет хранилище.

Также рассмотрим библиотеку C ++ Format , которая обеспечивает безопасную реализацию printf, аналогичную Boost Format, но гораздо быстрее.

0 голосов
/ 15 ноября 2010

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

Это не предложение "все или ничего"; вы все еще можете использовать sprintf для выбранных типов. Например. может быть проще использовать sprintf(buf, "%6.4f", dbltemp);, когда строка входного формата содержит аргумент %6.4f, но %s лучше обрабатывать самостоятельно (простой memcpy).

0 голосов
/ 15 ноября 2010

Если вас не волнует использование нестандартных функций (т. Е. Использование другой функции для любой платформы, как я понял, вы делаете из своего вопроса) и хотите быструю работу, вы найдете asprintf иvasprintf среди расширений GNU (вот и все: ни C, ни POSIX, но поддерживается GCC и glibc).

Они работают как printf и vsprintf, но занимаются распределением буферной памяти, облегчая вашу работу.

int asprintf( char **strp, const char *fmt, ... );
int vasprintf( char **strp, const char *fmt, va_list ap );

Возможно, вы найдете подобные функции в любой системе.Для других вы можете просто написать некоторый код для выделения буфера и передать его snprintf.

...