Немного рефлексии для С++. Часть третья: документационная

c048e1c9295c4659a3c2cf9d3f6f4426.bmp

Данная статья является третьей и заключительной в цикле о разработке библиотеки cpp runtime, которая добавляет возможность добавлять немного метаинформации о классах С++ и дальше использовать эту информацию во время исполнения программы.

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

Cсылки на все статьи цикла

Ссылка на репозиторий библиотеки
То, что излагается в данной статье, верно для коммита e9c34bb.

Структура проекта
— Документация ---
readme.md
Ридми-файл. Задумывался как файл, c которого начинается ознакомление с проектом.

README.cmake
Файл, который рассказывает, как собрать с помощью CMake библиотеку и/или поставляемые вместе с ней дополнительные цели сборки.

/docs
Папка с документацией… Увы, единственное, что в ней сейчас есть — это ссылка на данный цикл статей. Я ошибся, отложил документацию напоследок, и мне не хватило духу заняться ею после трёхнедельной работы без выходных над данным циклом статей.

— Файлы библиотеки cpprt ---
/include
Программный интерфейс для доступа к API библиотеки cpprt.

/src
Папка с исходным кодом библиотеки.

— Сборки для популярных систем ---
/build
Готовые проекты для некоторых популярных тулчейнов и IDE.

/lib
Готовые сборки библиотеки для некоторых популярных компиляторов.

— Дополнительные проекты ---
/examples
Папка с исходным кодом для примеров… В данный момент, для одного простого примера, демонстрирующего принципы использования библиотеки.

/tools
Папка с исходным кодом для тулзов, идущих в поставке с библиотекой.

— Система сборки ---
/build
Пустая папка, в которой рекомендуется выполнять сборку библиотеки и/или дополнительных целей сборки.

CMakeLists.txt
Файл с описанием конфигурации CMake.

— Прочие файлы ---
LICENCE.txt
Файл лицензии, под которой распространяется проект (MIT-лицензия).

copying.txt
Файл с шаблоном шапки для файлов исходного кода, в которой описывается информация о лицензии.

Сборка библиотеки и сопроводительных материалов

О том, как создавалась система сборки для проекта, можно почитать во второй статье цикла (ссылка).

Рекомендуемый способ сборки самой библиотеки и сопроводительных материалов подразумевает использование CMake. Если у вас не установлен CMake, можете скачать его отсюда.

Для сборки через командную строку нужно выполнить следующие действия:

1. Создать где-нибудь, где вам удобно, папку, в рамках которой вы будете осуществлять сборку. Перейти в эту папку:
mkdir {your-build-folder}
cd {your-build-folder}
{your-build-folder} — папка, в которой вы будете выполнять сборку.

2. Вызывать команду для генерации конфигурации:
cmake -G »{generator-name}» {options} {cpprt-folder-path}
{generator-name} — имя генератора для вашего тучлейна.
{options} — перечень опций для указания того, что именно вы желаете собирать.

Подробнее про опции
Независимо от выбранных опций, в сгенерированных конфигах (файлах IDE) всегда будет по крайней мере одна цель сборки — для статической сборки самой библиотеки cpprt. Для того, чтобы сгенерировать конфиги для сборки дополнительно сопроводительных проектов, вы можете использовать следующие опции (первой указывается значение опции по-умолчанию):

-DBUILD_ALL={OFF/ON}
Если эта опция выставлена в значение ON, то будут сгенерированны конфигурации для всех поставляемых вместе библиотекой сопроводительных проектов. При этом если опции для сборки конкретных целей сборки.

-DBUILD_TOOLS={OFF/ON}
Если данная опция выставлена в значение ON, то будут сгенерированны конфигурации для сборки инструментов, поставляемых вместе с библиотекой. В данный момент есть один инструмент — console (о ней ещё будет дальше).

-DBUILD_EXAMPLES={OFF/ON}
Если данная опция выставлена в значение ON, то будут сгенерированны конфигурации для сборки примеров, поставляемых вместе с библиотекой. В данный момент есть один пример (simple_example) в котором описывается классический для ООП пример иерархии классов животных.

Дополнительные опции:
-DCONSOLE_WITH_EXAMPLE={ON/OFF}
Если данная опция выставлена в ON (по умолчанию выставлена), то вместе с исходным кодом инструмента console будут собраны файлы с описанием тестовых иерархий классов.

{cpprt-folder-path} — папка, которая является корнем клона репозитория cpprt.

После вызова в папке {your-build-folder} образуются конфигурационные файлы для вашего тулчейна (либо для вашей IDE). Дальше ничего настраивать не нужно. Достаточно просто запустить сборку нужных целей сборки (проектов для IDE).

Возможности библиотеки

С помощью API, предоставляемого библиотекой cpprt, класс может быть зарегистрирован. Если класс зарегистрирован, то для него открываются следующие возможности:
1. Создание объекта класса во время исполнения программы на основании имени класса в строковом представлении.
2. Доступ к информации о зарегистрированных наследниках и предках класса (в меру переданной при регистрации информации).
3. Возможность получать информацию об абстрактности классов.
4. Возможность получать строковое имя класса через методы его объектов.

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

Интерфейс библиотеки cpprt
// Класс, реализующий общее управление метаданными cpprt.
// Объект-синглтон данного класса используется для большей
// части действий с метаданными классов.
class CPPRTRuntime {
public:

        //----------------------------------------------------------------
        // ТИПЫ

        // Класс, объекты которого хранят метаинформацию
        // для всех регистрируемых классов. Практически не
        // имеет самостоятельного поведения, только аксессоры
        // для доступа к данным. Инициализацией и большей
        // частью действий по регистрации объектов данного
        // класса занимается CPPRTRuntime.
        class ClassData {
        public:

                // Геттеры для получения имени класса.
                // Полное строковое имя класса соответствует
                // записи полного имени в рамках исходного кода
                // С++. Неймспейсы разделяются двумя двоеточиями
                // (ns - сокращение от namespace). Можно условно
                // описать следующим образом отношение между
                // имением и полным именем классов.
                //  = ::::...::
                const char *fullName() const;
                const char *name() const;

                // Геттер, через который можно узнать, является ли
                // класс абстрактным и, соответственно, возможно
                // ли создавать объекты этого класса.
                bool isInterface();

                // Создание объекта класса, которому соответствует
                // данный объект ClassData.
                ICPPRTManagedClass *createObject();
        };

        // Нумератор, с помощью которого конфигурируются
        // особенности получения информации о наследниках класса
        // через метод CPPRTRuntime::observeChildren(…). Флаги задают
        // следующие правила поиска метаданных о наследниках:
        enum ObservingFlag {

                // Если выставлен данный флаг, то в перечень получаемых
                // метаданных не передаются метеданные для абстрактных
                // классов.
                ObservingFlagWithoutInterface = 1 << 0,

                // Данный флаг требует выполнять поиск метаданных
                // для всех наследников рекурсивно (перебор наследников
                // наследников, и далее по древу наследования).
                ObservingFlagRecursive = 1 << 1,

                // Если выставлен данный флаг, то в перечень получаемых
                // метаданных не передаются метаданные класса, для которого
                // выполнялся поиск наследников (аргумент inBaseRegistry
                // метода CPPRTRuntime::observeChildren(…)
                ObservingFlagIgnoreBase = 1 << 2
        };

        // Переменная, через которую передаются флаги при запросе
        // информации о наследовании классов. Флаги передают так,
        // через побитное ИЛИ (общепринятый приём, примеры
        // дальше покажут, как конкретно это работает)
        typedef char ObservingFlags;

        //----------------------------------------------------------------
        // МЕТОДЫ

        // Публичный метод, через который можно получить информацию
        // о наследниках класса inBaseRegistry с учётом правил, которые
        // передаются через флаги inFlags (см. выше). Все найденные метаданные,
        // подходящие по критериям, которые задают флаги, добавляются
        // в массив outRegistries.
        // Если в качестве inBaseRegistry передать NULL, то поиск будет
        // выполняться, условно говоря, начиная с некоего условного
        // класса, от которого наследуются любые регистрированные
        // пользователем классы, не имеющие предков.
        void observeChildren(ClassData *inBaseRegistry,
                        std::vector &outRegistries,
                        ObservingFlags inInheritFlags = 0);

        // Публичный метод для получения метаинформации о классе
        // через его полное строковое имя.
        ClassData *getClassData(const char *inClassName);

        // Публичный метод, позволяющий создать объект по строковому
        // имени его класса.
        ICPPRTManagedClass *createObject(const char *inClassName);
};

// Функция для доступа к глобальному менеджеру метаданных класса.
CPPRTRuntime &cppRuntime();

// Задание синонима типа для хранения метаданных. Пользователю библиотеки
// рекомендуется использовать именно этот синоним в случае, если нужно
// описывать тип данного класса.
typedef CPPRTRuntime::ClassData CPPRTClassData;


Примеры использования API для взаимодействия с метаданными:

1.Создание объекта по строковому имени его класса:

ICPPRTManagedClass *theTestClassObject = cppRuntime().createObject("TestClass”);

2. Распечатка информации об абстрактности класса по указателю на объект базового класса:

void printObjectClassInformation(ICPPRTManagedClass *inObject) {
        const CPPRTRuntime::ClassData &theClassData = *inObject->getClassDataRT();
        std::cout << "Class ” << theClassData.fullName() << " is ”
                        << theClassData.isInterface() ? "abstract” : "concrete” << std::endl;
}

3. Распечатка имён всех классов-наследников класса TestClass. При этом выбираются и наследники наследников рекурсивно, фильтруются абстрактные классы и в получаемый перечень не добавляется метаинформация для самого класса TestClass:

std::vector theChildData;
cppRuntime().observeChildren(TestClass::gClassData, theChildData,
                ObservingFlagWithoutInterface |
                ObservingFlagRecursive |
                ObservingFlagIgnoreBase);

for (size_t theIndex = 0, theSize = theChildData.size(); theIndex < theSize; ++theIndex) {
        std::cout << theChildData[theIndex]->fullName() << std::endl;
}

4. Распечатка имён всех классов, не имеющих предков:

std::vector theBasesData;
cppRuntime().observeChildren(NULL, theBasesData);

for (size_t theIndex = 0, theSize = theBasesData.size(); theIndex < theSize; ++theIndex) {
        std::cout << theBasesData [theIndex]->fullName() << std::endl;
}

Регистрация классов

Теперь разберёмся с тем, что необходимо для регистрации классов:

1. Регистрируемый класс должен иметь в предках класс ICPPRTManagedClass; достаточно чтобы базовый класс пользовательской иерархии наследовал ICPPRTManagedClass. Пример:

// UserClasses.h

// Базовый класс иерархии объектов пользователя.
// Наследует ICPPRTManagedClass.
class UserBaseClass : public ICPPRTManagedClass {
. . . // <- Декларация класса, о ней дальше
};

// Так как UserBaseClass наследует ICPPRTManagedClass,
// UserInheritedClass не должен наследовать ICPPRTManagedClass.
class UserInheritedClass : public UserBaseClass { 
. . . // <- Декларация класса, о ней дальше
};

2. Регистрированный класс должен обладать методами и полями, которые используются библиотекой cpprt для работы. Чтобы не писать их вручную, лучше воспользоваться макросами для регистрации. Есть две группы макросов: для декларации необходимых полей и методов, и для реализации.

Декларация. CPPRT_DECLARATION — макрос для декларации. Принимает один аргумент — имя класса, который регистрируется (не строка с именем, а само имя). Код, в который раскрывается макрос, заканчивается спецификатором доступа protected. Это сделано для более простого включения макроса в существующий код, ведь если использовать макрос в начале декларации класса, то доступ для всех последующих декларируемых членов класса останется таким же, как без использования макроса (так как в С++ до объявления другого спецификатора доступа декларируемые члены класса имеют protected доступ). Пример:

// UserClasses.h

class UserBaseClass : public ICPPRTManagedClass {
        // Что с макросом, что без, все следующие
        // декларируемые члены класса имеют
        // спецификатором доступа protected.
        CPPRT_DECLARATION(UserBaseClass)

. . . // <- прочие декларации членов класса
};

class UserInheritedClass : public UserBaseClass {
        // В декларации для наследника не нужно
        // указывать базовый класс – только имя
        // данного класса
        CPPRT_DECLARATION(UserInheritedClass)

. . . // <- прочие декларации членов класса
};

Реализация. Основная часть метаинформации о классе передаётся в рамках макроса реализации. Эти макросы делятся на группы в зависимости от количества базовых классов.

CPPRT_CLASS_IMPLEMENTATION_BASE_{N}(C, B1, …, BN)
CPPRT_INTERFACE_IMPLEMENTATION_BASE_{N}(C, B1, …, BN)
N — количество базовых классов.
C — полное имя класса, для которого описывается реализация.
B1BN — полные имена базовых классов. Эти классы должны быть зарегистрированы в рамках cpprt.

Макрос с CLASS используется для регистрации классов, объекты которых можно создавать, а с INTERFACE — для регистрации абстрактных классов. Пример:

// UserClasses.cpp

CPPRT_CLASS_IMPLEMENTATION_BASE_0(UserBaseClass);
CPPRT_CLASS_IMPLEMENTATION_BASE_1(UserInheritedClass, UserBaseClass);

Собственно, всё. Описанных вещей достаточно для регистрации класса в рамках cpprt. Живой пример вы можете посмотреть в рамках репозитория (цель сборки simple_example, опция -DBUILD_EXAMPLES={OFF/ON} для генерации проекта через CMake).

Здесь тоже приведу пример для регистрации более сложной иерархии классов, иерархия наследования классов для какого-нибудь типичного шутера:

WeaponClasses.h
// Abstract classes
// Bases
class IWeapon : public ICPPRTManagedClass {
        CPPRT_DECLARATION(IWeapon)
. . .
};

class IAntiAir {
        CPPRT_DECLARATION(IAntiAir)
. . .
};

// Classification by weapon type
class IMachineGunWeapon : public IWeapon {
        CPPRT_DECLARATION(IMachineGunWeapon)
. . .
};

class IRocketWeapon : public IWeapon {
        CPPRT_DECLARATION(IRocketWeapon)
. . .
};

// Concrete classes
// Machine guns
class M16 : public IMachineGunWeapon {
        CPPRT_DECLARATION(M16)
. . .
};

class Abakan : public IMachineGunWeapon {
.       CPPRT_DECLARATION(Abakan)
. . .
};

class Uzi : public IMachineGunWeapon {
        CPPRT_DECLARATION(Uzi)
. . .
};

// Rocket weapons
class Stinger : public IRocketWeapon, public IAntiAir {
        CPPRT_DECLARATION(Stinger)
. . .
};

namespace IraqEdition {
        class Bazuka : public IRocketWeapon {
                CPPRT_DECLARATION(Bazuka)
        . . .
        };
}

// Tank inner classes
class Tank {
public:
        class MachineGunTurret : public Abakan, public IAntiAir {
                CPPRT_DECLARATION(MachineGunTurret)
. . .
        };

        class RocketTurret : public IRocketWeapon {
                CPPRT_DECLARATION(RocketTurret)
. . .
        };
};


WeaponClasses.cpp
// Abstract classes
// Bases
CPPRT_INTERFACE_IMPLEMENTATION_BASE_0(IWeapon)
CPPRT_INTERFACE_IMPLEMENTATION_BASE_0(IAntiAir)

// Classification by weapon type
CPPRT_INTERFACE_IMPLEMENTATION_BASE_1(IMachineGunWeapon, IWeapon)
CPPRT_INTERFACE_IMPLEMENTATION_BASE_1(IRocketWeapon, IWeapon)

// Concrete classes
// Machine guns
CPPRT_CLASS_IMPLEMENTATION_BASE_1(M16, IMachineGunWeapon)
CPPRT_CLASS_IMPLEMENTATION_BASE_1(Abakan, IMachineGunWeapon)
CPPRT_CLASS_IMPLEMENTATION_BASE_1(Uzi, IMachineGunWeapon)

// Rocket weapons
CPPRT_CLASS_IMPLEMENTATION_BASE_2(Stinger, IRocketWeapon, IAntiAir)
CPPRT_CLASS_IMPLEMENTATION_BASE_1(IraqEdition::Bazuka, IRocketWeapon)

// Tank inner classes
CPPRT_CLASS_IMPLEMENTATION_BASE_2(Tank::MachineGunTurret, IRocketWeapon, IAntiAir)

CPPRT_CLASS_IMPLEMENTATION_BASE_1(Tank::RocketTurret, IRocketWeapon)


Player.h
class Player {
private:
        IWeapon *_weapon; // Set with NULL value in constructor
. . .

public:
        // It’s possible to set player’s weapon by its class name.
        void setWeapon(const char *inName) {
                if (NULL != _weapon) delete _weapon;
                _weapon = cppRuntime().createObject(inName);
        }
. . .
};


Testdrive.cpp
int main() {
        Player thePlayer;
        thePlayer1.setWeapon("Abakan");
        return 0;
}


Прочие возможности

В рамках cpprt не обязательно регистрировать все классы и можно не указывать всех наследников для классов. Это может быть полезно, например, для сокрытия части служебного интерфейса в наследовании классов. Например:

// Dog.h

class Dog : public Mammal, public ISerializable {
        CPPRT_DECLARATION(Dog)
. . .
};

// Dog.cpp

// ISerializable не указывается среди базовых классов для Dog
// во время регистрации. Это может быть служебный интерфейс,
// о котором нет смысла знать при оперировании с метаданными
// классов. В таком случае ISerializable вообще не имеет смысла
// регистрировать.
CPPRT_CLASS_IMPLEMENTATION_BASE_1(Dog, Mammal)

Console tool

Вместе с библиотекой, помимо примера, в поставке идёт инструмент консоль (console). Он предоставляет несколько команд для создания объектов по строковому имени их классов (объекты хранятся в тестовом массиве), а также для просмотра иерархий наследования для регистрированных классов. Короткое описание команд, поддерживаемых консолью:

help
Команда, при вызове которой распечатывается короткая информация о всех доступных для консоли командах.

print (-c) ({full-class-name})
Если данная команда вызывается без аргументов, то она распечатывает перечень всех созданных в данный момент объектов с указанием их классов.
Если передан флаг -c (необязательный флаг), но при этом не передано имя класса (необязательный параметр {full-class-name}), то данный вызов распечатает деревья зарегистрированных классов-наследников для всех зарегистрированных классов, не имеющих базовых классов (корневых классов).
Если помимо флага -c передан параметр {full-class-name}, задающий имя класса, то данная команда распечатает дерево зарегистрированных классов-наследников для класса с переданным именем.

create {class-name} {object-name}
Команда, создающая объект зарегистрированного класса {class-name} с именем {object-name} в тестовом массиве объектов.

delete {object-name}
Команда, удаляющая объект из тестового массива объектов по его имени.

exit
Команда для выхода из консоли. Завершает исполнение программы консоли.

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

Планы на будущее

1. Реализовать возможность обхода дерева наследования не только от предков к наследникам, но и наоборот.

Прототип
std::vector theParentsData;
cppRuntime().observeParents(TestClass::gClassData, theParentsData,
                ObservingFlagWithoutInterface |
                ObservingFlagRecursive);


Данная фича реализуема элементарно в рамках текущей реализации — нужно сделать internal-методы для обхода наследников шаблонными, с булевым аргументом ObserveChildren, в зависимости от значения true/false которого выбирать метаданные наследников/базовых классов для переданных метаданных.

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

3. Сделать итераторы для возможности обходить дерево метаинформации для классов. Это позволит экономить использование данных при рекурсивном обходе дерева наследников и даст пользователю самому выполнять обход дерева наследования классов без нарушения инкапсуляции.

Прототип
CPPRTRuntime::iterator theIterator = cppRuntime().iteratorForClassData(TestClass::gClassData);

// Обход метаданных классов-наследников класса TestClass до тех пор,
// пока не будет найден наследник с именем ClassToFind.
CPPRTRuntime::ClassData *theData = NULL;
while((theData = theIterator.nextChild()) != NULL) {
        if (0 == strcmp(theData->name(), "ClassToFind")) {
                // Если метаданные для класса-наследника с именем найдены – перевести итератор на него
                theIterator.moveToChild();
        }
}


В качестве альтернативы итератору можно использовать шаблон проектирования visitor (например, как это сделано в clang, вот большой пример использования). Этот подход вообще часто используется для обхода древовидных структур данных.

4. Сделать возможность добавления пользовательской информации к классам — чтобы можно было фильтровать классы по этим данным при запросе.

Прототип
// Classes.cpp

CPPRT_CLASS_IMPLEMENTATION_BASE_1(Car, IVehicle)
CPPRT_CLASS_IMPLEMENTATION_BASE_1(Bus, IVehicle)

CPPRT_CLASS_IMPLEMENTATION_BASE_1(BigBus, IVehicle).
        // Добавление метки "bold_in_editor” для метаданных
        // класса BigBus.
        addTag("bold_in_editor”);

// Using.cpp

// Получение и распечатка метаданных классов, у которых
// выставлена метка "bold_in_editor”
std::vector theChildData;
cppRuntime().observeChildren(TestClass::gClassData, theChildData,
                ObservingFlagWithoutInterface |
                ObservingFlagRecursive |
                ObservingFlagIgnoreBase, "bold_in_editor”);

for (size_t theIndex = 0, theSize = theChildData.size(); theIndex < theSize; ++theIndex) {
        std::cout << theChildData[theIndex]->fullName() << std::endl;
}


4. Сделать поддержку шаблонов. Вот это весьма нетривиальная задача. Нужно хорошо думать по поводу того, действительно ли нужно (скорее всего, нужно) и как вообще можно описать API для использования шаблонозависимых классов.

5. Подумать о возможности работы с метаданными классов в runtime. Например, регистрация класса в runtime, либо наоборот — удаление классов из списка регистрированных.

Прототип
cppRuntime().createClassData("MyRTClass”, new CPPRTRuntime::Fabric() );
. . .
// Use registered class
. . .
cppRuntime().removeClassData("MyRTClass”);


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

6. Место для ваших предложений. Предлагайте, что ещё может быть полезным. Обсудим.

Заключение статьи и заключение цикла

На этом, пожалуй, можно поставить жирную точку. Я рассказал всё, что хотел, о библиотеке cpprt. Надеюсь, дорогой читатель, ты извлёк что-нибудь полезное из данного цикла статей. Я был бы рад ещё больше, если бы ты прямо сейчас взял какую-нибудь пылящуюся без дела на домашнем репозитории библиотеку, оформил её как следует и опубликовал бы её. Расскажи о ней людям в какой-нибудь своей статье!

Ну и будет неплохо также, если кто-нибудь, заинтересовавшись, внесёт свой вклад в развитие библиотеки cpprt.

Про таинственный основной проект
Значит, интересно, что за «основной проект»?… Если вы это читаете — значит план удался.

Основной проект — это библиотека, позволяющая использовать данные о состоянии объекта, передаваемые для сериализации, с целью генерации метаинформации о данном объекте и дальнейшего использования этой метаинформации.
Замечу, данный подход не нов — его часто используют для игровых проектов, да и некоторые из упомянутых в первой статье аналогов библиотеки cpprt позволяют пользователю задавать подобную информацию для полей классов в момент регистрации классов. Перечислю здесь реализованные и запланированные особенности проекта, из-за которых, на мой взгляд, стоит им заниматься:
1. В библиотеки выполняется не только сбор метаданных о состояниях объектов, но собранные данные также используются для генерации GUI, через которое можно влиять на состояния объектов. За счёт этого пользователю библиотеки достаточно описать методы save и load для объекта, чтобы получить автоматически генерируемый для него GUI object inspector. В данный момент GUI сделан на Qt, но код проектировался с прицелом на поддержку разных GUI (в том числе GUI пользователя, которое можно будет подключить, реализовав интерфейс). В планах до донышка попробовать возможности, открывающиеся за счёт такого взгляда на генерирование GUI.
2. Я всегда фанател от концепции middleware. Меня впечатляла возможность найти общее в ряде инструментов, которые имеют одно назначение, вынести это нечто общее в интерфейс и дальше подсовывать разные инструменты под этот интерфейс, имея возможность выбора реализации интерфейса… Так вот, задумано как следует реализовать концепцию middleware. За счёт этого хочется добиться максимальной простоты интеграции пользователем библиотеки в свой проект, вплоть до передаче системы типов пользователя в шаблонные классы библиотеки.

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

Я надеялся сам пилить эту библиотеку, но в какой-то момент понял, что зашиваюсь… Присоединяйтесь, станете сооснователями проекта. Если вас в какой-нибудь степени заинтересовало всё, что здесь написано — пишите в личку, расскажу подробнее.

Приведём вместе библиотеку в подобающий вид, опубликуем, сделаем цикл статей для пиара… Мало ли — вдруг взлетит, и когда-нибудь мы будем вспоминать, как закладывали вместе начало новой вехи для создания GUI в рамках языка С++?

Спасибо что дочитали всё это! Надеюсь, мне удалось рассказать хоть что-нибудь полезное!

Автор заранее благодарит читателей за указания на ошибки в статье, конструктивные советы и пожелания.

Титры

Редактирование статьи: Сергей Семенякин

© Habrahabr.ru