Относно форматирането на низове в съвременния C

Добър ден! В тази статия бих искал да говоря за съществуващите възможности за форматиране на низове в съвременния C ++, да покажа моите най-добри практики, които използвам в реални проекти от няколко години, а също така да сравня ефективността на различни подходи за форматиране на низове.

Форматирането на низ е операция, която ви позволява да получите резултатния низ от шаблонен низ и набор от аргументи. Шаблонният низ съдържа текст, който включва контейнери, които се заменят с аргументи.

За по-голяма яснота, малък пример:

Тук: Шаблонен низ: имам %d ябълки и %d портокала, така че имам %d плода Заместители: %d, %d, %d Аргументи: ябълки, портокали, ябълки + портокали

При изпълнение на примера получаваме резултантния низ

Сега нека видим какво ни предоставя C++ за форматиране на низове.

Наследство C

Форматирането на низове в C се извършва с помощта на фамилията функции Xprintf. Със същия успех можем да използваме тези функции в C ++:

Това е доста добър начин за форматиране, въпреки очевидната тромавост:

  • това е най-бързият начин за форматиране на низ
  • този метод работи на почти всички версии на компилатора, без да изисква поддръжка за нови стандарти

Но, разбира се, не беше без недостатъците:

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

функция std::to_string().

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

  • вътр
  • дълго
  • дълго дълго
  • неизпят вътр
  • неизпят дълго
  • неподписан дълъг дълъг
  • плавам
  • двойно
  • дълъг двоен

std::stringstream клас

Класът std::stringstream е основното форматиране на низ, което C++ ни предоставя:

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

Обектът std::sringstream ви позволява да имплементирате няколко интересни обвивки, които може да ви потрябват по-късно.

Преобразуване на "всичко" в низ:

Преобразувайте низ в "каквото и да е":

Преобразувайте низ в "каквото и да е" с проверка:

Също така можете да напишете няколко обвивки за удобно използване на std::stringstream в един ред.

Използване на обект std::stringstream за всеки аргумент:

Използване на един std::stringstream обект за целия низ:

Гледайки напред, се оказва, че производителността на std::to_string е 3-4 пъти по-висока от тази на to_string, реализирана със std::stringstream. Следователно би било логично да използвате std::to_string за подходящи типове, а за всички останали използвайте шаблона to_string:

библиотека boost::format

Комплектът библиотека за усилване е мощен, отличенв допълнение към езика C++ и стандартната библиотека. Форматирането на низове се осигурява от библиотеката boost::format.

Посочването като общи заместители се поддържа:

и редни:

Единственият недостатък на boost::format е, че е бавен, това е най-бавният начин за форматиране на низове. Освен това този метод не е приложим, ако библиотеки на трети страни не могат да се използват в проекта.

И така, оказва се, че C++ и стандартната библиотека не ни предоставят удобни инструменти за форматиране на низове, така че ще напишем нещо свое.

Обвивка над vsnprintf

Нека се опитаме да напишем обвивка върху функцията Xprintf, заделяйки достатъчно памет и предавайки произволен брой параметри.

За да разпределим памет, ще използваме следната стратегия:

  1. първо разпределете толкова памет, колкото е достатъчно в повечето случаи
  2. опитайте да извикате функцията за форматиране
  3. ако повикването е неуспешно, разпределете повече памет и повторете предишната стъпка

За да предадем параметри, ще използваме механизма stdarg и функцията vsnprintf.

Тук си струва да изясните няколко нюанса. Върнатата стойност на функциите Xprintf зависи от платформата, на някои платформи връща -1, ако не успее, в който случай удвояваме буфера. На други платформи се връща дължината на резултантния низ (с изключение на нулевия знак), в който случай можем незабавно да разпределим толкова памет, колкото е необходимо. Можете да прочетете повече за поведението на функциите на Xprintf на различни платформи тук. Освен това на някои платформи vsnprintf() "разваля" списъка с аргументи, така че го копираме, преди да го извикаме.

Започнах да използвам тази функция преди да се появи C++11С малки промени продължавам да го използвам и до днес. Основното неудобство при използването му е липсата на поддръжка за std::string като аргументи, така че трябва да запомните да добавите .c_str() към всички аргументи на низ:

Различен шаблон

В C++, започвайки с C++11, стана възможно да се използват шаблони с променлив брой аргументи (Variadic Templates).

Такива шаблони могат да се използват при предаване на аргументи към функцията за форматиране. Освен това вече не е необходимо да се интересуваме от типовете на аргументите, тъй като можем да използваме шаблона to_string, който беше внедрен по-рано. Затова ще използваме редни заместители.

За да получим всички аргументи, отделяме първия аргумент, преобразуваме го в низ, съхраняваме го и повтаряме тази операция рекурсивно. Ако няма аргументи или когато те свършат (крайната точка на рекурсията), анализираме низа на шаблона, заместваме аргументите и получаваме резултантния низ.

По този начин имаме всичко, за да приложим напълно функцията за форматиране: анализиране на шаблонния низ, събиране и преобразуване на всички параметри в низ, заместване на параметри в шаблонния низ и получаване на резултантния низ:

Алгоритъмът се оказа доста ефективен, работи с едно преминаване по форматния низ. Ако аргумент не може да бъде вмъкнат вместо контейнер, той остава непроменен и не се хвърлят изключения.

Сравнение на производителността

to_string срещу std::to_string сравнение на производителността, милисекунди на милион повиквания

int, ms long long, ms double, ms
към_низ6817041109
std::to_string130201291

низове

Сравнение на производителността на функцията за формат, милисекунди на милион повиквания

Госпожица
fstr1308
sstr1243
формат788
boost::format2554
vtformat2022 г

низове

Благодаря за вниманието. Коментари и допълнения са добре дошли.

Hardcore conf в C++. Каним само професионалисти.