Создать строку C++, используя функции printf-стиль форматирования


Это часто удобно использовать C-стиля printf формат строки при написании c++. Я часто нахожу модификаторы гораздо проще в использовании, чем c++ ввода-вывода манипуляторы, и если я подпорки из существующего кода на языке C, это помогает быть в состоянии повторно использовать существующие строки формата.

Вот мое мнение о создании нового std::string учитывая формат и соответствующие аргументы. Я дал его аннотации, так что GCC может проверить типы аргументов согласен, когда формат является буквальным, но я не знаю как помочь другими компиляторами.

Я предоставил более эффективно использует память версии для C++17, который обеспечивает чтение/запись доступ к основному массиву строк. Моя копия ЦПП ссылка по-прежнему говорит, что "изменение характера выбора доступны через data() имеет неопределенное поведение", но веб-версия была отредактирована (май 2017), чтобы указать, что это только const версия, которая имеет ограничения.

Для более ранних стандартов (мне требуется минимум в C++11), мы должны выделить временный массив, а мы не можем написать данные строки. К сожалению, это требует дополнительного выделения и копирования.

#include <string>

std::string format(const char *fmt, ...)
#ifdef __GNUC__
    __attribute__ ((format (printf, 1, 2)))
#endif
    ;
// Implementation

#include <cstdio>
#include <cstdarg>

#if __cplusplus < 201703L
#include <memory>
#endif

std::string format(const char *fmt, ...)
{
    char buf[256];

    va_list args;
    va_start(args, fmt);
    const auto r = std::vsnprintf(buf, sizeof buf, fmt, args);
    va_end(args);

    if (r < 0)
        // conversion failed
        return {};

    const size_t len = r;
    if (len < sizeof buf)
        // we fit in the buffer
        return { buf, len };

#if __cplusplus >= 201703L
    // C++17: Create a string and write to its underlying array
    std::string s(len, '\0');
    va_start(args, fmt);
    std::vsnprintf(s.data(), len+1, fmt, args);
    va_end(args);

    return s;
#else
    // C++11 or C++14: We need to allocate scratch memory
    auto vbuf = std::unique_ptr<char[]>(new char[len+1]);
    va_start(args, fmt);
    std::vsnprintf(vbuf.get(), len+1, fmt, args);
    va_end(args);

    return { vbuf.get(), len };
#endif
}
// Test program

#include <iostream>
int main()
{
    std::clog << "'" << format("a")
              << "'" << std::endl;
    std::clog << "'" << format("%#x", 1337)
              << "'" << std::endl;
    std::clog << "'" << format("--%c--", 0) // an embedded NUL
              << "'" << std::endl;
    std::clog << "'" << format("%300s++%6.2f", "**", 0.0).substr(300)
              << "'" << std::endl;
}

void provoke_warnings()
{
    // warning: zero-length gnu_printf format string
    // [-Wformat-zero-length]
    std::clog << "'" << format("") << "'" << std::endl;

    // warning: format ‘%c’ expects argument of type ‘int’, but
    // argument 2 has type ‘const char*’ [-Wformat=]
    std::clog << "'" << format("%c", "bar") << "'" << std::endl;
}

Я скомпилировал код с C++17 и C++11 компиляторы, и проверил их обоих под Valgrind с помощью этой тестовой программы.

Я бы приветствуем любые комментарии на сам код или на моем тестировании.



12221
18
задан 9 февраля 2018 в 01:02 Источник Поделиться
Комментарии
3 ответа

Дизайн звука.

Несмотря на то, что скептики могут заявить, есть несколько подавляющего преимущества решения, основанные на маститый printf и C-в аргументы за ostream и C++ в


  • производительность: ostream ужасный форматирование производительность, дизайн,

  • след: любой с переменным числом аргументов шаблона решения должны быть тщательно разработаны, чтобы избежать вздутия в результате создания одного варианта для каждой комбинации аргументов; достижения нулевой стоимости возможно, только если функция может быть полностью встроен без увеличения назвать-сайте след (возможно, путем делегирования не ядра шаблона).

Ваша конструкция обходит эти два нюанса, и это здорово.

Кроме того, ваше использование format атрибут обеспечивает время компиляции проверить формат против аргументов. В отличие от представленных претендентами, это будет диагностировать во время компиляции, что число аргументов совпадает (по их видам), избегая необходимости для ошибок во время выполнения.


Придираться

Я действительно призываю Вас поставить брекеты систематически вокруг если-блоков. Нет особых причин не делать этого, и это не допустить случайной ошибки.


Недостатки

Есть две слабости к дизайну:


  • не вариант, позволяющий пользователю указать буфер,

  • очень ограниченный набор общепринятых типов.

Первая проблема, по составу и повторного использования.


  • Композиция: если я хочу создать большую строку путем вызова в суб-функции, она будет создать несколько промежуточных буферов, которые могут свести на нет преимущество представления решения в части сырья и форматирования,

  • Повторного использования: пользователь может уже иметь достаточно большой буфер доступен.

К сожалению, в стандартной библиотеки C++ не позволяет передать существующий буфер в строку (вздох) и, как правило, очень не хватает в RAW буфер (вздох), так что вам придется свернуть свой собственный.

Я хотел сделать это в два шага: абстрактный базовый класс Write который предоставляет возможность писать байт в ломтики и готовые реализации на основе std::unique_ptr<char, free> + размер + емкость (не vector, потому что он обнуляет память при изменении размера...).

Вторая проблема для расширения и производительности. Для того, чтобы отформатировать их собственные типы, пользователям предлагается "докризисном формате" свои собственные типы в строки, что приведет к излишне временных распределений.

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

4
ответ дан 10 февраля 2018 в 12:02 Источник Поделиться

Я согласен с вами, что 'форматирование в стиле printf' был по многим параметрам лучше, чем c++'ы манипуляторов: более лаконичным, более разнообразны, и т. д. Что сказал, Я чувствую, что это шаг назад, если мы порт в C++ без обновления с лежащими в ее основе. На мой взгляд, ваш код не удастся сделать это по двум причинам: отсутствие автоматического управления памятью, и отсутствие строгой типизации.

О безопасности, я считаю, мы сейчас должны использовать шаблоны с переменным числом аргументов при общении с неизвестным количеством аргументов. Вариативная макросы имеют слишком много недостатков для нас, чтобы продолжить их использование. Шаблоны с переменным числом аргументов, мы можем проверить, если формат директив и аргументы совпадать по количеству и типам в более надежным способом (хотя я согласен, что, когда вы можете положиться на цепочке формате риск является управляемым даже в C-подобных языках макросы).

Про управление памятью: я считаю, что мы должны избежать явного управления памятью, когда мы можем. std::stringstream Это хороший способ, чтобы сделать это, когда один хочет построить строку последовательно с гетерогенными аргументами.

Так вот зародыш того, что я хотел бы рассмотреть более современный c++:

#include <string>
#include <sstream>
#include <iostream>
#include <type_traits>
#include <exception>

// base case of recursion, no more arguments
void format_impl(std::stringstream& ss, const char* format) {
while (*format) {
if (*format == '%' && *++format != '%') // %% == % (not a format directive)
throw std::invalid_argument("not enough arguments !\n");
ss << *format++;
}
}

template <typename Arg, typename... Args>
void format_impl(std::stringstream& ss, const char* format, Arg arg, Args... args) {
while (*format) {
if (*format == '%' && *++format != '%') {
auto current_format_qualifier = *format;
switch(current_format_qualifier) {
case 'd' : if (!std::is_integral<Arg>()) throw std::invalid_argument("%d introduces integral argument");
// etc.
}
// it's true you'd have to handle many more format qualifiers, but on a safer basis
ss << arg; // arg type is deduced
return format_impl(ss, ++format, args...); // one arg less
}
ss << *format++;
} // the format string is exhausted and we still have args : throw
throw std::invalid_argument("Too many arguments\n");
}

template <typename... Args>
std::string format(const char* fmt, Args... args) {
std::stringstream ss;
format_impl(ss, fmt, args...);
return ss.str();
}

int main() {
auto display = format("My name is %s and I'm %d year old", "John", 22);
std::cout << display;
}

11
ответ дан 9 февраля 2018 в 03:02 Источник Поделиться

Я согласен, что старый c способы указания форматирование строк был намного лучше, чем текущий c++ путь.

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

Почему вы используете старые с переменной разбора аргументов.

std::string format(const char *fmt, ...)
{
char buf[256];

va_list args;
va_start(args, fmt);

Почему бы не использовать C++ переменные параметры шаблона аргумент.

template<typename... Args>
std::string format(const char *fmt, Args const&...)
{
char buf[256];

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

Также мне не нравится, что вы строите строку и вернуть, что в результате. Это означает, что мы делаем лишнюю работу. Почему бы не использовать строку, чтобы просто применить правильный выход форматирования данных в поток.

class Format
{
public:
template<typename... Args>
Format(char const& fmt, Args const&... args)
: /* Magic */
{}

friend std::ostream& operator<<(std::ostream& s, Format const& formatData) {
// More Magic
return s;
}
};

int main()
{
std::cout << Format("My stuff %5.3f\n", 12.233) << " Still more\n";
}

Это приведет в эквиваленте:

    std::cout << std::string_view("My stuff %5.3f\n" + 0,  9)  // the string upto %
<< std::setw(5) << std::setprecision(3) << 12.233
<< std::string_view("My stuff %5.3f\n" + 14, 1) // the string after the formatter
<< " Still more\n";

Теперь я знаю, что все это пишу код, чтобы сделать это на самом деле очень непростой (и крупный проект, за то, что вы хотели сделать). Тем более, что вы можете переместить все сложно получается std::vsnprintf().

Но я уверен, существует также широкий набор модульных тестов, которые существуют в текущей реализации компилятора C, которые мы могли бы украсть. Тогда мы могли бы установить его и заставить людей осуществить частей и не спеша сделать все модульные тесты для работы.

6
ответ дан 9 февраля 2018 в 06:02 Источник Поделиться