О строковом форматировании в современном C++

Доброго времени суток! В этой статье я хотел бы рассказать о существующих возможностях строкового форматирования в современном C++, показать свои наработки, которые я уже несколько лет использую в реальных проектах, а также сравнить производительность различных подходов к строковому форматированию. Строковое форматирование — это операция, позволяющая получить результирующую строку из строки-шаблона и набора аргументов. Строка-шаблон содержит текст, в который включены местозаполнители (placeholders), вместо которых подставляются аргументы. Подробнее можно узнать перейдя по ссылке: https://github.com/fi1a/format.
Для наглядности небольшой пример:

int apples = 5;
int oranges = 7;
std::string str = format("I have %d apples and %d oranges, so I have %d fruits", apples, oranges, apples + oranges);
std::cout << str << std::endl;

Здесь:
Строка-шаблон: I have %d apples and %d oranges, so I have %d fruits
Местозаполнители: %d, %d, %d
Аргументы: apples, oranges, apples + oranges

При выполнении примера, получаем результирующую строку

I have 5 apples and 7 oranges, so I have 12 fruits

Теперь посмотрим, что же нам предоставляет C++ для строкового форматирования.

О строковом форматировании в современном C++

Наследие C

Строковое форматирование в C осуществляется с помощью семейства функций Xprintf. С тем же успехом, мы можем воспользоваться этими функциями и в C++:

char buf[100];
int res = snprintf(buf, sizeof(buf), "I have %d apples and %d oranges, so I have %d fruits", apples, oranges, apples + oranges);
std::string str = "error!";
if (res >= 0 && res < sizeof(buf))
    str = buf;
std::cout << str << std::endl;

Это довольно неплохой способ форматирования, несмотря на кажущуюся неуклюжесть:

  • это самый быстрый способ строкового форматирования
  • этот способ работает практически на всех версиях компиляторов, не требуя поддержки новых стандартов
->  Персональные прокси – зачем покупать?

Но, конечно, не обошлось и без недостатков:

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

Функция std::to_string()

Начиная с C++11 в стандартной библиотеке появилась функция std::to_string(), которая позволяет преобразовать передаваемое значение в строку. Функция работает не со всеми типами аргументов, а только со следующими:

  • int
  • long
  • long long
  • unsinged int
  • unsinged long
  • unsigned long long
  • float
  • double
  • long double

Пример использования:

std::string str = "I have " + std::to_string(apples) + " apples and " + std::to_string(oranges) + " oranges, so I have " + std::to_string(apples + oranges) + " fruits";
std::cout << str << std::endl;

Класс std::stringstream

Класс std::stringstream — это основной способ строкового форматирования, который нам предоставляет C++:

std::stringstream ss;
ss << "I have " << apples << " apples and " << oranges << " oranges, so I have " << apples + oranges << " fruits";
std::string str = ss.str();
std::cout << str << std::endl;

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

ss << "A[" << i1 << ", " << j1 << "] + A[" << i2 << ", " << j2 << "] = " << A[i1][j1] + A[i2][j2];

сравните с:

std::string str = format("A[%d, %d] + A[%d, %d] = %d", i1, j1, i2, j2, A[i1][j1] + A[i2][j2]);

Объект std::sringstream позволяет реализовать несколько интересных оберток, которые могут понадобится в дальнейшем.

->  Черный арбитраж: все, что вы хотели знать, но боялись спросить

Преобразование «чего угодно» в строку:

template<typename T> std::string to_string(const T &t)
{
    std::stringstream ss;
    ss << t;
    return ss.str();
}
std::string str = to_string("5");

Преобразование строки во «что угодно»:

template<typename T> T from_string(const std::string &str)
{   
    std::stringstream ss(str);
    T t;
    ss >> t;
    return t;
}

template<> std::string from_string(const std::string &str)
{
    return str;
}
int x = from_string<int>("5");

Преобразование строки во «что угодно» с проверкой:

template<typename T> T from_string(const std::string &str, bool &ok)
{   
    std::stringstream ss(str);
    T t;
    ss >> t;
    ok = !ss.fail();
    return t;
}

template<> std::string from_string(const std::string &str, bool &ok)
{
    ok = true;
    return str;
}
bool ok = false;
int x = from_string<int>("x5", ok);
if (!ok) ...
Понравилась статья? Поделиться с друзьями: