2008-09-16 2 views
17

Я работаю с открытым исходным кодом UNIX, который реализован на C++, и мне нужно изменить код, чтобы заставить его делать то, что я хотеть. Я хотел бы внести минимальные изменения в надежде на то, что мой патч будет принят вверх по течению. Решения, которые реализуются в стандартном C++ и не создают больше внешних зависимостей, являются предпочтительными.C++: как получить результаты fprintf как std :: string w/o sprintf

Вот моя проблема. У меня есть класс 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 является достаточно стабильным, но а и в (и остальные иждивенцев страны а) находятся в состоянии потока, тем меньше изменений кода необходимо, тем лучше. Также необходимо сохранить текущий интерфейс печати (FILE * F). Я рассмотрел несколько подходов к реализации A :: to_str(), каждый из которых имеет свои преимущества и недостатки:

  1. Изменить Призывы fprintf() в Sprintf()

    • Я не должен был бы переписать любые строки формата
    • print() может быть переопределен как: fprint (f, this.to_str());
    • Но мне нужно будет вручную выделить полукокс [] S, объединить много строк гр, и, наконец, преобразовать массив символов в станде :: строку
  2. попытаться поймать результаты a.print() в виде строки потока

    • я бы преобразовать все строки формата в < < выходного формата. Есть сотни файлов fprintf() s для преобразования: - {
    • print() пришлось бы переписать, потому что нет стандартного способа создания потока вывода из дескриптора файла UNIX (хотя this guy says it may be possible).
  3. Использование строки Boost в format library

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

    Printf (format_str, арг) -> соиЬ < < подталкивание :: формат (format_str)% arg1% арг2% и т.д.

  4. Использование Qt's QString::asprintf()

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

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

Спасибо.

+0

Хотя я уже ответил на этот вопрос, я также хотел бы указать на этот проект: https://github.com/c42f/tinyformat, который прекрасно решает проблему и действительно отлично справляется с воспроизведением нотации форматирования printf. В наши дни я использую этот пакет напрямую, а не метод vsprintf, который я подробно описал несколько лет назад в своем другом ответе. – 2014-07-15 19:09:51

ответ

12

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

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

Edited: добавление пример того, как использовать повышение :: формат вместо Printf:

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

бы что-то подобное с библиотекой подталкивания :: формат:

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

Надеется, что это помогает прояснить использование наддува :: формат

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

В отличие от меня были некоторые спецификации формата я не мог сделать с потоками (насколько я помню)

+0

Спасибо за разъяснение в использовании boost :: format. Это заманчиво, учитывая, что этот проект уже зависит от другой библиотеки boost, но я не думаю, что что-то бьет printf, который просто работает с std :: string, как кажется Loki. – underspecified 2008-09-16 08:39:22

+0

Я попробовал SafeFormat от Loki, но оказалось, что это просто замена 5s для() s. С положительной точки зрения, мой код действительно работал, как только я принял boost :: format :-) – underspecified 2008-09-18 07:29:42

+0

Приятно слышать, что форсирование: формат работал для вас - никогда не пробовал метод Loki. – bernhardrusch 2008-09-18 07:45:39

0

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

+0

Речь идет о собственно печатании. C.foo - это одна часть данных, которая в конечном итоге отображается пользователю (в основном) как есть. Если бы это был мой код, я бы потерял бессмысленность печати (FILE *), которая слишком ограничительна. – underspecified 2008-09-16 08:34:16

1

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

+0

Спасибо, что сообщили мне о iomanip. Это, конечно, выглядит полезно, но я хочу как можно больше попытаться сохранить многочисленные существующие строки формата. – underspecified 2008-09-16 08:35:10

35

Вот идиома, которую мне нравится, чтобы сделать функциональность идентичной «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]); 
    } 
} 

EDIT: когда я писал этот код, я понятия не имел, что это требует 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]; 
    } 
} 
1

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

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

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

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

Смежные вопросы