C++:как получить результаты fprintf в виде std::string без sprintf
Вопрос
Я работаю с инструментом 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(), каждый из которых имеет свои преимущества и недостатки:
Измените вызовы функции fprintf() на sprintf()
- Мне не пришлось бы переписывать какие-либо строки формата
- функция print() может быть переопределена следующим образом:fprint(f, this.to_str());
- Но мне нужно было бы вручную выделить char []s, объединить множество строк c и, наконец, преобразовать массив символов в std::string
Попробуйте перехватить результаты функции.print() в строковом потоке
- Мне пришлось бы преобразовать все строки формата в << выходной формат.Существуют сотни функций fprintf() для преобразования:-{
- print() пришлось бы переписать, потому что я не знаю стандартного способа создания выходного потока из дескриптора файла UNIX (хотя этот парень говорит, что это возможно).
Используйте строку Boost библиотека форматов
- Больше внешних зависимостей.Фу.
- Синтаксис Format достаточно отличается от printf(), чтобы раздражать:
printf(format_str, аргументы) -> cout << boost::формат (format_str) % arg1 % arg2 % и т.д.
Используйте Qt QString::asprintf()
- Другая внешняя зависимость.
Итак, исчерпал ли я все возможные варианты?Если да, то какой, по-вашему, для меня лучший выбор?Если нет, то что я упустил из виду?
Спасибо.
Решение
Я использую # 3:библиотека формата boost string - но я должен признать, что у меня никогда не было никаких проблем с различиями в спецификациях формата.
Для меня это работает как шарм - и внешние зависимости могли бы быть хуже (очень стабильная библиотека).
Отредактированный:добавление примера использования 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 приложениях (запись форматированных строк в файлы или пользовательский вывод в файлы журналов) и никогда не испытывал проблем с различиями в формате.Могут быть некоторые (более или менее неясные) спецификаторы формата, которые отличаются друг от друга, но у меня никогда не возникало проблем.
Напротив, у меня были некоторые спецификации формата, которые я действительно не мог использовать с потоками (насколько я помню).
Другие советы
Вот идиома, которая мне нравится для создания функциональности, идентичной '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];
}
}
Вы можете использовать std::string и iostreams с форматированием, например вызов setw() и другие в iomanip
Альтернативным решением может быть следующее:
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 на << форматируйте, чтобы быть безопаснее и в то же время изменять как можно меньше.
Вам следует попробовать заголовочный файл SafeFormat библиотеки Loki (http://loki-lib.sourceforge.net/index.php?n=Idioms .Printf).Он похож на библиотеку строковых форматов boost, но сохраняет синтаксис функций printf(...).
Я надеюсь, что это поможет!
Речь идет о сериализации?Или правильная печать?Если первое, рассмотрите также boost::сериализацию.Все дело в "рекурсивной" сериализации объектов и подобъектов.
Библиотека {fmt} обеспечивает fmt::sprintf
функция, которая выполняет printf
-совместимое форматирование (включая позиционные аргументы в соответствии с Спецификация POSIX) и возвращает результат в виде std::string
:
std::string s = fmt::sprintf("The answer is %d.", 42);
Отказ от ответственности:Я автор этой библиотеки.