Использование шаблонов и RTTI для управления конфигурацией Григорий Цветков Software Architect Softeq Flash Solutions 18 Декабря, 2014
Jul 10, 2015
Использование шаблонов и RTTI для управления конфигурацией
Григорий Цветков
Software Architect
Softeq Flash Solutions
18 Декабря, 2014
1
Коротко о себе
Стаж в IT: 20 лет
Стаж С++: 18 лет
IT-тренер: 10 лет
Области, где работает мой С++ код:
Симуляторы систем
• ФЛЭШ накопители
• 3D координатно-измерительные машины
САПР (CAD/CAM/CAE/CAx/PLM)
• Авиастроение
• Машиностроение
• Энергетика
• Метрология (высокоточные измерения)
2
ООО «Софтек Флеш Солюшнс» зарегистрировано 24 апреля
2014 года.
Софтек Флеш Солюшнс является частью компании SK hynix Inc.
SK hynix занимает следующие позиции на мировом рынке:
Информация о Компании
No. 5 Market Share in Semiconductor Industry
2014
RankCompany
1 Intel
2 Samsung Electronics
3 Qualcomm
4 Micron Technology
5 SK hynix
6 Toshiba
7 Texas Instruments
No. 2 DRAM Market Share (2Q14)
No. 5 NAND Market Share (2Q14)
Source: iSuppli, Semiconductor Market Shares (Feb. 2014)
Source: http://www.dramexchange.com/WeeklyResearch/Post/2/3822.html
Samsung Micron
Samsung Toshiba SanDisk Micron
3
ООО «Софтек Флеш Солюшнс»Центр Разработки SK hynix
OOO «Софек Флеш Солюшнс» – один из шести центров
разработки (Корея, США, Япония, Италия, Беларусь, Тайвань).
US
* Global network as of Mar.31, 2014
Taiwan
Japan
Italy
Производство
Центр Продаж
Центр Разработки
Belarus
4
Разработка Встроенного Программного Обеспечения (Firmware)
I/F SoC(ARM, ARC)
I/F NANDHost
System
System-on-Chip / Система-на-кристалле Firmware / Встроенное ПО
• Host I/F Controller / Хост-контроллер
• CPU / ЦП
• RAM / ОЗУ
• NAND I/F Controller / NAND-контроллер
• Host I/F / Хост-интерфейс
• FTL (Flash Translation Layer) /
Преобразование адресов и оптимизация износа
• NAND I/F / NAND-интерфейс
Mobile Enterprise SSD Client SSD NAND Flash
eMMC/UFS SATA/SAS/PCIe SATA/PCIe SLC/MLC/TLC16nm/3D
5
Преамбула
«C makes it easy to shoot yourself in the foot. C++
makes it harder, but when you do, it blows away
your whole leg.»Bjarne Stroustrup
6
Конфигурация программного продукта
Конфигурация программного продукта – это набор
параметров используемых приложением во время
исполнения.
Изменение конфигурации не должно приводить к
необходимости пересобрать приложение.
Применение конфигурации осуществляется путём
указания приложению хранилища параметров.
Для удобства использования параметры могут
группироваться по различным признакам, группы
могут иметь любую степень вложенности.
Приложение должно иметь простой и прозрачный
механизм для доступа к значениям параметров.
7
Требования к конфигурации симулятора флэш накопителя
1. Простой, прозрачный и удобный доступ к любому из более чем 2000 параметров.
2. Сложность доступа O(1).
3. Верификация:
a. Жёсткая типизация для проверки на этапе сборки.
b. Проверка по значению во время исполнения.
4. Максимально быстрая загрузка на старте.
5. Прозрачная процедура добавления/удаления параметров.
6. Возможность использовать наборы значений (массивы).
7. Возможность ссылаться на другие параметры/группы для переключения во время исполнения.
8. Совместимость со средствами подсветки синтаксиса.
9. Независимость от сторонних программных продуктов
10. Поддержка разных форматов хранилища.
8
Обзор известных подходов к конфигурации
Парсеры
– Плоский текст
• INI
• CSV
– Структурированные языки с разметкой
• XML
• JSON
• YAML
Генераторы кода (Middleware/Binder)
– Базы данных
– XML
• Доступ к параметрам
по ключу (литералу)
• Доступ за O(1) не
гарантирован
• Добавление/удаление
параметра не видно в
исходном коде
• Требуют
использования
стороннего ПО
9
Выбор решения
Первым трём требованиям (кроме 3b), a также 5-9 удовлетворяет глобальный экземпляр-синглтон С++ структуры, где для группировки параметров используются вложенные структуры:
…auto x = config.a.p1;auto y = config.b.p4;auto z = config.a.p1 + config.b.p3;…
struct config_t{
struct a_t{
int p1;bool p2;
} a;struct b_t{
int p3;bool p4;
} b;} config;
10
Выбор решения
Для проверки параметров во время исполнения (требование 3b)
«концевые» элементы дерева параметров можно реализовать в виде
шаблонных классов обёрток, реализующих механизм проверки.
Аналогичный подход можно использовать для наборов (массивов).
Для ссылок можно реализовать обёртку наподобие смарт указателя.
template<typename V, typename C = std::function<bool(V)> >class param_t{
V value_;C check_;
public:param_t(V value, C check) throw(invalid_parameter_value)
: value_(value), check_(check){
if (!check_(value_))throw(invalid_parameter_value);
}T const & get() const { return value_; }
};
11
Выбор решения
Требования 4 и 10 могут быть удовлетворены
путём добавления компонента сериализатора,
позволяющего работать как с бинарным дампом
структуры, так и с общепризнанными форматами
хранения структурированных данных с
поддержкой ссылок (XML, YAML).
12
Реализация
Задачи:1. Реализовать механизм самоописания С++ структуры в
виде дерева для того, чтобы иметь возможность обойти всю С++ структуру не привязываясь её определению. Фактически такое дерево есть ничто иное как DOM (Document Object Model) известная многим, кто сталкивался с XML.
2. Реализовать компонент-каталог, который во время инстанциации С++ структуры описывающей конфигурацию построит соответствующую DOM.
3. Реализовать компонент-сериализатор, позволяющий на основании DOM сохранять/загружать конфигурации в/из файлы/ов требуемых форматов.
4. Реализовать утилиту-приложение для генерации эталонной (дефолтной) конфигурации и конвертации её в различные форматы
13
Реализация
Задача №1: «Самоописывающаяся» C++ структура
• C++ RTTI
<!> Оператор typeid позволяет получить ссылку на структуру std::type_info, которая имеет функцию name() результатом вызова которой будет имя типа в виде char const *.
<?> Связкаtypeid – name выдаст имя типа элемента структуры, в то время как нам нужно имя элемента (члена данных) структуры.
<!> Нужно имя элемента однозначно связать с типом, используя соглашение о именовании: для простоты добавим суффикс «_t» к имени типа, а имя элемента будем объявлять без суффикса.
<!> Все элементы в дереве параметров унаследовать либо напрямую либо через адаптер от общего шаблонного класса, который и будет вычислять ключ и прочие мета-данные для элемента основе RTTI. В качестве параметра шаблона будем передавать тип декларируемого элемента.
<???> Много букв ? Давайте смотреть код
14
Реализация
Демонстрационный код можно взять тут:
https://yadi.sk/d/UtFR6gXedTai3
15
Реализация
Задача №1: «Самоописывающаяся» C++ структура
/** Интерфейс элемента дерева параметров. */struct i_node{
virtual size_t size() const = 0;virtual node_id const & key() const = 0;
};
namespace details{
/** Предварительное объявление преобразователя ключа */node_id compute_id(char const * type_name);/** Оператор вывода*/template<typename TOStream>TOStream & operator << (TOStream & ostream, node_id const & id);
}
16
Реализация
Задача №1: «Само-описывающаяся» C++ структура
/** Базовая структура вычисляющая ключ на основе RTTI */template<typename TNodeImpl>struct node : i_node{
node_id const & key() const override{
if (id_.empty())id_ = std::move(details::compute_id(typeid(TNodeImpl).name()));
return id_;}
private:mutable node_id id_;
};
/** Базовая структура для группы параметров */template<typename TNodeImpl>struct group : node<TNodeImpl>{
size_t size() const override { return 0; }};
17
Реализация
Задача №1: «Само-описывающаяся» C++ структура
namespace cfg = config_framework;
struct config_t : cfg::group<config_t>{
struct a_t : cfg::group<a_t>{
struct b_t : cfg::group<b_t>{} b;
} a;} config;
int _tmain(int argc, _TCHAR* argv[]){
using namespace std;cout << config.key() << endl;cout << config.a.key() << endl;cout << config.a.b.key() << endl;return 0;
}
18
Реализация
Задача №1: «Самоописывающаяся» C++ структура
– Вывод теста:
configconfig.aconfig.a.b
Бинго ?!
… я тоже думаю, что босс не тот человек, которому стоит показывать«полработы»
19
Реализация
Задача №1: «Самоописывающаяся» C++ структура
• Реализация обёртки для параметра конфигурации:/** Обёртка для значения параметра */template<typename TParamValue>struct value_wrapper{
explicit value_wrapper(TParamValue value) : value_(value) {}operator TParamValue const & () const { return value_; }TParamValue const & get() const { return value_; }
protected:size_t size() const { return sizeof value_; }
protected:TParamValue value_;
};
/** Базовая структура простого параметра */template<typename TParamValue, typename TParamImpl>struct simple_param : value_wrapper<TParamValue>, node<TParamImpl>{
explicit simple_param(TParamValue value): value_wrapper<TParamValue>(value) {}
size_t size() const override { return value_wrapper<TParamValue>::size(); }};
20
Реализация
Задача №1: «Самоописывающаяся» C++ структура
• Пробуем использовать простой параметр:
namespace cfg = config_framework;
struct config_t : cfg::group<config_t>{
struct a_t : cfg::group<a_t>{
struct b_t : cfg::group<b_t>{
struct int_p_t : cfg::simple_param<int, int_p_t>{
int_p_t() : cfg::simple_param<int, int_p_t>(0xDEADBEEF) {}} int_p;
} b;} a;
} config;
А не многовато ли кода ради объявления одного «простого» параметра ?..
21
Реализация
Задача №1: «Самоописывающаяся» C++ структура
• Пробуем использовать простой параметр:
int _tmain(int argc, _TCHAR* argv[]){
using namespace std;cout << config.key() << endl;cout << config.a.key() << endl;cout << config.a.b.key() << endl;cout << config.a.b.int_p.key()
<< " has size " << config.a.b.int_p.size()<< " and value in hex is [" << hex << config.a.b.int_p << "]"<< endl;
return 0;}
22
Реализация
Задача №1: «Самоописывающаяся» C++ структура
• Пробуем использовать простой параметр:
configconfig.aconfig.a.bconfig.a.b.int_p has size 4 and value in hex is [deadbeef]
И с точки зрения разработчика моделей, который активно использует значения параметров в коде ,всё выглядит так, как и было задумано
Придется извлечь старый магический инструмент
Вывод теста говорит о том, что мы на верном пути
23
Реализация
Задача №1: «Самоописывающаяся» C++ структура
• Упрощаем жизнь разработчикам:
/** Базовая структура простого параметра */template<typename TParamValue, typename TParamImpl>struct simple_param : value_wrapper<TParamValue>, node<TParamImpl>{
/** Алиас для имени типа */typedef simple_param<TParamValue, TParamImpl> base_impl_type;explicit simple_param(TParamValue value)
: value_wrapper<TParamValue>(value) {}size_t size() const override { return value_wrapper<TParamValue>::size(); }
};
24
Реализация
Задача №1: «Самоописывающаяся» C++ структура
• Упрощаем жизнь разработчикам
/** Макрос получения типа по имени типа элемента дерева. */#define guess_type(name) name##_t
/** Макрос для декларации простого параметра. */#define declare_simple_param(param_name,value_type,param_value) \
struct guess_type(param_name) \: config_framework::simple_param \
< value_type \, guess_type(param_name) > \
{ \guess_type(param_name)() : base_impl_type(param_value) {} \
} param_name \// end of macro: declare_simple_param()
25
Реализация
Задача №1: «Самоописывающаяся» C++ структура
• Упрощаем жизнь разработчикам:
namespace cfg = config_framework;
struct config_t : cfg::group<config_t>{
struct a_t : cfg::group<a_t>{
struct b_t : cfg::group<b_t>{
declare_simple_param(int_p, int, 0xDEADBEEF);declare_simple_param(double_p, double, 1.1E-11);
} b;} a;struct c_t : cfg::group<c_t>{
declare_simple_param(bool_p, bool, true);} c;
} config;
26
Реализация
Задача №1: «Самоописывающаяся» C++ структура
• Упрощаем жизнь разработчикам:
int _tmain(int argc, _TCHAR* argv[]){
using namespace std;cout << config.key() << endl;cout << config.a.key() << endl;cout << config.a.b.key() << endl;cout << config.a.b.int_p.key()
<< " has size " << config.a.b.int_p.size()<< " and value in hex is [" << hex << config.a.b.int_p << "]"<< endl;
cout << config.a.b.double_p.key()<< " has size " << config.a.b.double_p.size()<< " and value is [" << config.a.b.double_p << "]"<< endl;
cout << config.c.bool_p.key()<< " has size " << config.c.bool_p.size()<< " and value is [" << boolalpha << config.c.bool_p << "]"<< endl;
return 0;}
27
Реализация
Задача №2: Каталогизатор элементов дерева параметров• Служебный компонент, реализующий каталог связей
элементов дерева параметров с идентификаторами, которые используются во внешнем хранилище.
namespace catalog{
/** Регистрация элемента дерева параметров. */bool register_node(i_node const & node);/** Действие, применяемое обходе дерева параметров. */typedef std::function<bool(i_node & node)> node_visitor;/** Результат обхода ветви дерева параметров. */typedef std::pair<size_t, bool> visit_result;/** Обхода дерева параметров. */visit_result visit_children
( i_node const & node, node_visitor visitor );
}
28
Реализация
Задача №2: Каталогизатор элементов дерева параметров
• Скрытая реализация DOM:
namespace impl{
typedef std::reference_wrapper<i_node const> node_ref;struct node_ref_less
: std::binary_function<node_ref, node_ref, bool>{
bool operator()( node_ref left
, node_ref right ){
return ( left.get().key() < right.get().key() );}
};typedef std::set<node_ref, node_ref_less> dom_t;dom_t & dom(){
static dom_t dom_;return dom_;
}}
29
Реализация
Задача №2: Каталогизатор элементов дерева параметров
• Реализация сервисов каталога:
bool register_node(i_node const & node){
auto ins_res = impl::dom().insert(std::ref(node));return ins_res.second;
}
30
Реализация
visit_result visit_children( i_node const & node
, node_visitor visitor){
visit_result res = { 0, true };auto const & key = node.key();auto const idx = key.size() - 1;auto it_cat = impl::dom().lower_bound(std::ref(node));auto const & end = impl::dom().end();auto check_visit_condition = [&]()->bool{
if (end == it_cat)return false;
auto const & current_key = it_cat->get().key();if (idx >= current_key.size())
return false;return (key[idx] == current_key[idx]);
};for (; check_visit_condition(); ++it_cat, ++res.first)
if (!visitor(const_cast<i_node&>(it_cat->get()))){
res.second = false;break;
}return res;
}
31
Реализация
Задача №2: Каталогизатор элементов дерева параметров
• Модификации для работы с каталогом:
/** Базовая структура вычисляющая ключ на основе RTTI */template<typename TNodeImpl>struct node : i_node{
node_id const & key() const override{
return id_;}node() : id_( std::move(details::compute_id(typeid(TNodeImpl).name())) ){
catalog::register_node(*this);}
private:mutable node_id id_;
};
32
Реализация
Задача №2: Каталогизатор элементов дерева параметров
• Тестируем каталогизатор:
int _tmain(int argc, _TCHAR* argv[]){
using namespace std;cout << "Visit all from \"" << config.key() << "\"" << endl;auto vr1 = cfg::catalog::visit_children
( config, [](cfg::i_node & node)->bool{
cout << node.key() << endl;return true;
} );cout << "Visit all from \"" << config.a.b.key() << "\"" << endl;auto vr2 = cfg::catalog::visit_children
( config.a.b, [](cfg::i_node & node)->bool{
cout << node.key() << endl;return true;
} );return 0;
}
33
Реализация
Задача №2: Каталогизатор элементов дерева параметров
• Тестируем каталогизатор:
Test catalogVisit all from "config"configconfig.aconfig.a.bconfig.a.b.double_pconfig.a.b.int_pconfig.cconfig.c.bool_pVisit all from "config.a.b"config.a.bconfig.a.b.double_pconfig.a.b.int_pPress any key to continue . . .
34
Реализация
Задача №3: (Сериализатор)
• По известной DOM (Document Object Model) c
доступом к каждому элементу дерева реализация
сериализатора не составит большого труда.
• Текущая реализация компонента-сериализатора
построена на XML и YAML движках с открытым
исходным кодом.
• В наших условиях не требовался перенос бинарных
форматов между различными платформами,
поэтому текущая реализация бинарной
сериализации – это просто запись\чтение в\из
файл\а потока байт, соответствующих длине
внутренних данных концевых элементов дерева.
35
Реализация
Задача №4: (Утилита генератор/конвертор)
• Текущая реализация – это самостоятельное
приложение, использующее С++ код описания
конфигурации и сериализатор, которое в
зависимости от параметров командной строки:
Генерирует дефолтную конфигурацию в нужном
формате
Конвертирует указанную конфигурацию в требуемый
формат
36
Вопросы?
37
Спасибо за внимание!