C ++: как получить результаты fprintf как std :: string без sprintf - PullRequest
18 голосов
/ 16 сентября 2008

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

Вот моя проблема. У меня есть класс C ++ - назовем его «A», который в настоящее время использует fprintf () для печати своих сильно отформатированных структур данных в указатель файла. В своей функции печати он также рекурсивно вызывает идентично определенные функции печати нескольких классов-членов (пример «B»). Есть еще один класс C, который имеет член std :: string "foo", который должен быть установлен на результаты print () экземпляра A. Представьте его как функцию-член to_str () для A.

В псевдокоде:

class A {
public:
  ...

  void print(FILE* f);
  B b;

  ...  
};

...

void A::print(FILE *f)
{
  std::string s = "stuff";
  fprintf(f, "some %s", s);
  b.print(f);
}

class C {
  ...
  std::string foo;
  bool set_foo(std::str);
  ...
}

...

A a = new A();
C c = new C();

...

// wish i knew how to write A's to_str()
c.set_foo(a.to_str());

Я должен упомянуть, что C довольно стабилен, но A и B (и остальные зависимые от A) находятся в состоянии изменения, поэтому чем меньше изменений кода необходимо, тем лучше. Текущий интерфейс печати (FILE * F) также необходимо сохранить. Я рассмотрел несколько подходов к реализации A :: to_str (), каждый из которых имеет свои преимущества и недостатки:

  1. Измените вызовы fprintf () на sprintf ()

    • Мне бы не пришлось переписывать строки формата
    • print () может быть переопределено как: fprint (f, this.to_str ());
    • Но мне нужно было бы вручную выделить char [] s, объединить множество строк c и, наконец, преобразовать массив символов в std :: string
  2. Попробуйте перехватить результаты a.print () в потоке строк

    • Мне бы пришлось преобразовать все строки формата в << выходной формат. Существуют сотни fprintf () для преобразования: - {</li>
    • print () пришлось бы переписать, потому что я не знаю стандартного способа создания потока вывода из дескриптора файла UNIX (хотя этот парень говорит, что это возможно ).
  3. Использовать строку Boost библиотека форматов

    • Больше внешних зависимостей. Тьфу.
    • Синтаксис формата достаточно отличается от printf (), что раздражает:

    printf (format_str, args) -> cout << boost :: format (format_str)% arg1% arg2% и т. Д. </p>

  4. Используйте Qt's QString :: asprintf ()

    • Другая внешняя зависимость.

Итак, я исчерпал все возможные варианты? Если да, то как ты считаешь, моя лучшая ставка? Если нет, то что я упустил из виду?

Спасибо.

Ответы [ 7 ]

37 голосов
/ 16 сентября 2008

Вот идиома, которую мне нравится делать функциональностью, идентичной 'sprintf', но возвращающей std :: string, и невосприимчивой к проблемам переполнения буфера. Этот код является частью проекта с открытым исходным кодом, который я пишу (лицензия BSD), поэтому каждый может использовать его по своему усмотрению.

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

std::string
format (const char *fmt, ...)
{
    va_list ap;
    va_start (ap, fmt);
    std::string buf = vformat (fmt, ap);
    va_end (ap);
    return buf;
}



std::string
vformat (const char *fmt, va_list ap)
{
    // Allocate a buffer on the stack that's big enough for us almost
    // all the time.
    size_t size = 1024;
    char buf[size];

    // Try to vsnprintf into our buffer.
    va_list apcopy;
    va_copy (apcopy, ap);
    int needed = vsnprintf (&buf[0], size, fmt, ap);
    // NB. On Windows, vsnprintf returns -1 if the string didn't fit the
    // buffer.  On Linux & OSX, it returns the length it would have needed.

    if (needed <= size && needed >= 0) {
        // It fit fine the first time, we're done.
        return std::string (&buf[0]);
    } else {
        // vsnprintf reported that it wanted to write more characters
        // than we allotted.  So do a malloc of the right size and try again.
        // This doesn't happen very often if we chose our initial size
        // well.
        std::vector <char> buf;
        size = needed;
        buf.resize (size);
        needed = vsnprintf (&buf[0], size, fmt, apcopy);
        return std::string (&buf[0]);
    }
}

РЕДАКТИРОВАТЬ: когда я писал этот код, я понятия не имел, что для этого требуется соответствие C99 и что Windows (так же как и более старый glibc) имеет другое поведение vsnprintf, в котором он возвращает -1 в случае сбоя, а не окончательный показатель сколько места нужно. Вот мой пересмотренный код, все могут его просмотреть, и если вы считаете, что все в порядке, я снова отредактирую, чтобы указать единственную стоимость:

std::string
Strutil::vformat (const char *fmt, va_list ap)
{
    // Allocate a buffer on the stack that's big enough for us almost
    // all the time.  Be prepared to allocate dynamically if it doesn't fit.
    size_t size = 1024;
    char stackbuf[1024];
    std::vector<char> dynamicbuf;
    char *buf = &stackbuf[0];
    va_list ap_copy;

    while (1) {
        // Try to vsnprintf into our buffer.
        va_copy(ap_copy, ap);
        int needed = vsnprintf (buf, size, fmt, ap);
        va_end(ap_copy);

        // NB. C99 (which modern Linux and OS X follow) says vsnprintf
        // failure returns the length it would have needed.  But older
        // glibc and current Windows return -1 for failure, i.e., not
        // telling us how much was needed.

        if (needed <= (int)size && needed >= 0) {
            // It fit fine so we're done.
            return std::string (buf, (size_t) needed);
        }

        // vsnprintf reported that it wanted to write more characters
        // than we allotted.  So try again using a dynamic buffer.  This
        // doesn't happen very often if we chose our initial size well.
        size = (needed > 0) ? (needed+1) : (size*2);
        dynamicbuf.resize (size);
        buf = &dynamicbuf[0];
    }
}
13 голосов
/ 16 сентября 2008

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

Работает для меня как обаяние - и внешние зависимости могут быть хуже (очень стабильная библиотека)

Отредактировано: добавлен пример использования boost :: format вместо printf:

sprintf(buffer, "This is a string with some %s and %d numbers", "strings", 42);

было бы примерно так с библиотекой boost :: format:

string = boost::str(boost::format("This is a string with some %s and %d numbers") %"strings" %42);

Надеюсь, это поможет прояснить использование boost :: format

Я использовал boost :: format в качестве замены sprintf / printf в 4 или 5 приложениях (запись отформатированных строк в файлы или пользовательский вывод в лог-файлы), и у меня никогда не было проблем с различиями формата. Могут быть некоторые (более или менее неясные) спецификаторы формата, которые отличаются - но у меня никогда не было проблем.

Напротив, у меня были некоторые спецификации формата, которые я не мог сделать с потоками (насколько я помню)

1 голос
/ 16 сентября 2008

Вам следует попробовать файл заголовка SafeFormat библиотеки Loki (http://loki -lib.sourceforge.net / index.php? N = Idioms.Printf ). Это похоже на библиотеку строкового формата boost, но сохраняет синтаксис функций printf (...).

Надеюсь, это поможет!

1 голос
/ 16 сентября 2008

Следующее может быть альтернативным решением:

void A::printto(ostream outputstream) {
    char buffer[100];
    string s = "stuff";
    sprintf(buffer, "some %s", s);
    outputstream << buffer << endl;
    b.printto(outputstream);
}

(B::printto аналогично) и определение

void A::print(FILE *f) {
    printto(ofstream(f));
}

string A::to_str() {
    ostringstream os;
    printto(os);
    return os.str();
}

Конечно, вы должны действительно использовать snprintf вместо sprintf, чтобы избежать переполнения буфера. Вы также можете выборочно изменить более рискованные sprintfs на «формат», чтобы сделать их более безопасными и при этом изменить как можно меньше.

1 голос
/ 16 сентября 2008

Вы можете использовать std :: string и iostreams с форматированием, таким как вызов setw () и другие в iomanip

0 голосов
/ 23 мая 2019

Библиотека {fmt} предоставляет функцию fmt::sprintf, которая выполняет printf -совместимое форматирование (включая позиционные аргументы согласно спецификации POSIX ) и возвращает результат как std::string :

std::string s = fmt::sprintf("The answer is %d.", 42);

Отказ от ответственности : я являюсь автором этой библиотеки.

0 голосов
/ 16 сентября 2008

Это о сериализации? Или собственно печать? Если первое, рассмотрите boost :: serialization также. Все дело в «рекурсивной» сериализации объектов и подобъектов.

...