README.md

Keep It Simple (Будь Проще)!

Введение

Библиотека KIS призвана упростить жизнь мне, в первую очередь.

Буду рад, если она сумеет упростить жизнь кому-то ещё.

В известном слогане KISS (Keep It Simple, Stupid!) намерено опущено последнее слово, так как оно без необходимости усложняет полезную концепцию, т.е. противоречит первым трём словам.

Я убеждён, что простота (любого) интерфейса экспоненциально влияет на его полезность. Чем проще интерфейс инструмента (будь то API библиотеки или UI программы), тем проще сходу начать использовать его, тем быстрее привыкание к нему, тем больше полезного кода можно написать, не отвлекаясь и не морща мозг каждый раз, когда необходимо что-нибудь сделать с помощью этого инструмента. За свою долгую программистскую жизнь я нащупал несколько удобных концепций (подходов? шаблонов?), которые приходится тащить за собой по жизни как бомж тащит тележку из супермаркета со скарбом, из проекта в проект, иногда видоизменяясь и расширяясь. Эта библиотека - попытка привести тележку в цивильный вид, чтобы мне было легче дальше её тащить.

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

Концепции

Когда-то в каждом языке программирования был свой собственный подход к написанию программ. Собственно, новые языки программирования зачастую придумывались именно с целью внедрения какого-то нового подхода к написанию программ. Фортран придумали как более удобный макроассемблер; Си придумали для практической реализации концепций языка Unix; Smalltalk придумали для проверки концепции программы, состоящей из обменивающихся сообщениями объектов; C++ придумали для привнесения объектно-ориентированной концепции в язык Си… Этот тренд продолжается и сегодня: BrainFuck, Go, Rust, … каждый из авторов подобных языков что-то хотел показать и доказать, что именно таким образом определённый класс задач решается проще и надёжнее.

Сегодня всё стало сложнее и проще одновременно. Помимо базовой библиотеки стандартных функций в языках программирования появились мощнейшие библиотеки (frameworks, рус. слэнг фреймворки), иногда общего плана, иногда специализированных под определённый класс задач, которые меняют даже подход к написанию программ. Например, в фреймворке Qt широко используется концепция сигналов, это часто заставляет программиста даже думать иначе. В стандартной библиотеке C++ очень широко используются шаблоны, надстройки над указателями (шаблоны xxx_ptr), запутанные манипуляции с типами данных (type_traits) и т.д. Также нужно отметить фреймворк Node.js и им подобные для JavaScript, которые кардинально меняют способ мышления программиста - из линейно выполняющейся последовательности вызова процедур программы превращаются в раздробленный на кучу мелких, независимо действующих осколков, конгломерат.

То есть, сегодня крупная библиотека (фреймворк) - не просто сборник подпрограм, но и определённый стиль мышления программиста. Если этого не увидеть, использование такой библиотеки становится неэффективным.

Библиотека KIS также использует определённые подходы к написанию программ. Следующие разделы описывают наиболее значимые концепции, используемые в KIS.

RefObj: Объекты с имманентным счётчиком ссылок

В отличие от “навесных” счётчиков ссылок, наподобие std::shared_ptr, RefObj - это базовый класс со встроенным счётчиком ссылок. Все классы, которые наследуют RefObj, получают возможность считать, сколько ссылок на этот объект разбросаны по всей программе. Это позволяет избежать ситуаций, когда объект кем-то уничтожается, хотя в других местах программы всё ещё сохраняются рабочие ссылки на этот объект.

Встраивание счётчика ссылок в сам объект позволяет избежать ситуаций создания нескольких разобщённых семейств “навесных” счётчиков ссылок, а также упрощает передачу объектов в функции и т.п. - любой указатель на объект всегда имеет доступ к мастер-копии счётчика ссылок.

@ref refobj.h

Singleton: Глобальные переменные с прогнозируемым поведением

Для многих классов заранее известно, что они будут использоваться в единственном экземпляре. Как правило, единственный экземпляр таких классов создаётся как глобальная переменная.

Минус такого подхода в том, что во время инициализации программы (до вызова main()) неизвестно, проинициализировались ли подобные глобальные переменные, или ещё нет - C++ не гарантирует какого-либо порядка инициализации глобальных переменных (кроме порядка инициализации глобальных переменных в пределах одного модуля трансляции).

Шаблон Singleton позволяет создать классы, которые:

  • Не нужно декларировать глобальными переменными - доступ к единственному экземпляру объекта этого класса можно получить в любой момент через статические функции Class &Class::inst() и Class *Class::instp().

  • При этом объекты, возвращаемые этими методами, гарантировано прошли инициализацию.

  • При необходимости, экземпляр объекта можно уничтожить - если в дальнейшем будет опять запрошен доступ к нему, объект будет создан ещё раз. Это позволяет чётко локализовать место уничтожения одиночки.

  • Одиночки можно принудительно создавать во время инициализации программы при помощи вспомогательного шаблона CreateSingleton.

@ref singleton.h

AutoChain: Автоматические связанные списки

Часто так бывает, что необходимо собрать воедино объекты, разбросанные по разным модулям. Концепция автоматических списков позволяет автоматизировать эту задачу.

Имеются шаблонные классы списков из объектов определённого класса, и шаблонный класс элемента списка, от которого наследуются классы объектов, которые автоматически попадают в этот список. Класс списка является глобальным объектом-одиночкой (Singleton), элементы списка сортируются по приоритетам (числовой приоритет передаётся в конструктор элемента списка).

@ref autochain.h

ImString: Неизменные строки (immutable strings)

В отличие от большинства других реализаций “умных строк” для C++, данная реализация представляет собой неизменные строки. Такой подход позволяет избежать сложно-отслеживаемых ошибок, которые часто возникают при работе с традиционными строками типа std::string. Речь о том, что при определённых модификациях строк все существующие итераторы должны быть аннулированы. Заранее невозможно предсказать, какая операция приведёт к разрушению итераторв, а какая - не приведёт (часто операции модификации не разрушают итераторы, что приводит программиста к ложной уверенности в отлаженности программы), а частое аннулирование итераторов приводит к усложнению программы и падению производительности.

Вторая особенность строк класса ImString - они не завершаются традиционным нулевым символом. Объект класса хранит в себя указатель на символы и длину в байтах. Это позволяет легко создавать подстроки из существующих строк лёгкой дешёвой операцией, без копирования данных. Такой подход даёт огромный выигрыш и по памяти, и по производительности, в таких задачах, как синтаксический анализ текста (интерпретаторы и компиляторы, да и просто обработка текста). Часто память выделяется только на буффер под считанный файл, а все объекты ImString в процессе обработки текста лишь ссылаются на те или иные участки этого буффера.

Для значительно более редких задач, которые предполагают итеративное построение новой текстовой строки, реализован класс MString (Mutable String) на базе шаблонного класса Vector, который предоставляет практически тот же функционал, что и ImString, но менее производительный (такие строки всегда копируются). По окончании построения строки она легко (и дёшево) преобразуется в ImString.

@ref imstring.h @ref mstring.h

StrIterator: Эффективная обработка текстовых данных

Строковые итераторы позволяют эффективно обрабатывать тексты, по производительности не уступая работе с текстом на “классическом” C при помощи указателей. Итераторы предоставляют методы для пропуска символов, поиска символов, сравнения, а также для работы с символами в кодировке UTF-8.

@ref striterator.h

Vector: Структура данных на (почти) все случаи жизни

Vector - это шаблонный класс, реализующий динамические массивы из элементов любого типа. В некотором роде, аналог std::vector.

Vector предоставляет произвольный доступ к любому элементу массива по его индексу, а также итераторы, с помощью которого можно в цикле перебрать все элементы в любом направлении.

Функционал класса содержит операции добавления новых элементов (по одиночке и скопом), вставки и удаления элементов, сортировки элементов (с помощью алгоритма QuickSort) и поиск элементов (линейный поиск по неупорядоченному динамическому массиву, и двоичный поиск по сортированному массиву).

Производный шаблонный класс Map реализует классический словарь, хранящий пары { ключ, значение }. Словарь всегда отсортирован по ключу, поэтому время поиска по нему пропорционально двоичному логарифму от количества элементов, O(log2 N).

@ref vector.h @ref map.h @ref mstring.h

Format: Гибкое форматирование текста по шаблону-строке

Реализация собственного варианта строки привело к необходимости реализации собственной функции форматирования строк. К тому же, меня давно перестали устраивать возможности традиционных функций форматирования текста.

Реализованный механизм форматирования одновременно прост в использовании, и имеет мощные возможности, при этом легко позволяет расширять его возможностями форматирования новых типов данных.

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

Сама строка-шаблон имеет простой формат: маркеры в формате %1, %2 и т.д. заменяются текстом, который выдаёт первый, второй и т.д. форматировщик.

Библиотека KIS предоставляет форматировщики для следующих типов данных:

  • CFormat: char, wchar_t, char16_t, char32_t - форматирование отдельного символа (в т.ч. Unicode символов).

  • FFormat: float, double, long double - форматирование чисел с плавающей точкой, с богатым выбором опций форматирования.

  • NFormat: un/signed char, un/signed short, un/signed int, un/signed long, un/signed long long - форматирование целочисленных значений с богатым набором опций форматирования.

  • SFormat: ImString, ImStringVal, MString - форматирование текстовых строк, с выравниванием в поле определённой ширины и заполнением заданным символом.

  • TimeFormat: Time - форматирование показаний времени и даты, по возможностям не уступает функции strftime().

@see format.h @see formatter.h @see cformat.h @see fformat.h @see nformat.h @see sformat.h @see timeformat.h

log: Интегрированное протоколирование действий программы

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

  • log::DeviceConsole - выводит журнал в консоль, с возможностью использования ANSI цветов для более удобного восприятия журнала, а также возможностью настройки формата вывода.

  • log::DeviceTextFile - выводит журнал в текстовый файл, с возможностью настройки формата вывода.

@see log.h @see log-node.h @see log-sink.h @see log-channel.h @see log-device.h

И просто полезные функции

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

ArrayArg: Шаблон для передачи списка однотипных аргументов

Atomic: Переменные с атомарным доступом

clock: Работа с временем

Delegate: Универсальные указатели на функции (аналог std::function)

type: Динамические типы данных

GetOpt: Развитые способности обработки командной строки

LazyObj: “Лениво” инициализируемые объекты

Locale: Доступ к данным локали

MemCursor: Работа с памятью по принципу машины Тьюринга

Range: Работа с любыми числовыми диапазонами

Service: Внутрипрограммные независимые сервисы

Variant: Хранение значений любых типов, используя систему динамических типов

WordSplitter/WordStitcher: Разбивка строк на слова и сшивка слов в строки

Описание

Библиотека KIS (Keep It Simple/Будь проще): универсальная основа для программирования на C++

Конвейеры
0 успешных
0 с ошибкой