Аннотация:
Цель данной статьи — показать, что метаданные это мощный механизм, требующий выделения в отдельный компонент, который хотелось бы видеть в стандартной библиотеке С++.
01.09.05 19:00: Перенесено модератором из 'C/C++. Прикладные вопросы' — Павел Кузнецов
Re: Использование метаданных в программах на языке C++
Сейчас прочитал статью два раза. Первый раз -- бегло, по диагонали. Ничего не понял. Второй раз внимательнее, начал что-то понимать, но к средине статьи опять понимание куда-то исчезло. Посему решил высказать свое первое впечатление.
Сама статья, имхо, очень тяжело читается. В основном, это сплошной навороченный шаблонный код, местами разбавленный односложными фразами, слабо проясняющими назначение кода и его смысл. Имхо, это больше похоже на результат прохода doxygen-а по самописной библиотеке, чем на попытку объяснить полезность метаинформации на шаблонах C++.
ВЮ>Аннотация: ВЮ>Цель данной статьи — показать, что метаданные это мощный механизм, требующий выделения в отдельный компонент, который хотелось бы видеть в стандартной библиотеке С++.
Имхо, удалось показать, что метаданные на шаблонах это слишком мощный (я бы даже сказал overkill) механизм. Что-то на нем очень круто как-то делается. Только вот что делается, почему это делается именно так, и стоило ли это делать именно так -- для меня остается загадкой. Вот серьезно, я не понял, почему на примере сериализации структур делается вывод о полезности метаинформации. Лично у меня приведенный код не вызывает желания им пользоваться, даже в виде вот такого макросного описания:
Поэтому, если задачей было показать необходимость подобного механизма в стандартной библиотеке C++, то, имхо, эта задача достигнута не была. Чесно говоря я вообще не понял, что за компонент должен быть в стандарной библиотеке и что этот компонент должен делать.
... << RSDN@Home 1.1.4 stable rev. 510>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Re[2]: Использование метаданных в программах на языке C++
E>...начал что-то понимать, но к средине статьи опять понимание куда-то исчезло...
Согласен. Мне уже по этому поводу люди высказали...
E>Имхо, удалось показать, что метаданные на шаблонах это слишком мощный (я бы даже сказал overkill) механизм. Что-то на нем очень круто как-то делается. Только вот что делается, почему это делается именно так, и стоило ли это делать именно так -- для меня остается загадкой. Вот серьезно, я не понял, почему на примере сериализации структур делается вывод о полезности метаинформации.
Сериализация — это классический пример использования метаданных.
Загляни в дотнет — там активно они используются (например, XmlSerializer). Существенное отличие в том, что в дотнете используются кодогенерация и компиляция на лету.
Задача была показать — что аналогичные возможности доступны и в С++.
Есть наметки как решить существующие проблемы (статья + пример).
Что бы сделать полноценную библиотеку нужно время. Если бы кто-нибудь за это взялся — я бы с удовольствием попользовался результатами
E>Поэтому, если задачей было показать необходимость подобного механизма в стандартной библиотеке C++, то, имхо, эта задача достигнута не была. Чесно говоря я вообще не понял, что за компонент должен быть в стандарной библиотеке и что этот компонент должен делать.
Стандартный способ описания и использования метаданных.
Если есть вопросы — я на них с удовольствием отвечу.
Влад
Использование метаданных в программах на языке C++
Здравствуй, Влад
E>>Имхо, удалось показать, что метаданные на шаблонах это слишком мощный (я бы даже сказал overkill) механизм. Что-то на нем очень круто как-то делается. Только вот что делается, почему это делается именно так, и стоило ли это делать именно так -- для меня остается загадкой. Вот серьезно, я не понял, почему на примере сериализации структур делается вывод о полезности метаинформации.
А>Сериализация — это классический пример использования метаданных. А>Загляни в дотнет — там активно они используются (например, XmlSerializer). Существенное отличие в том, что в дотнете используются кодогенерация и компиляция на лету.
А>Задача была показать — что аналогичные возможности доступны и в С++.
, то могу сказать, что сериализация -- это классический пример весьма сложной задачи, решать которую можно разными способами. Один способ -- это создание метаданных вручную в коде программы. По этому пути пошли авторы boost::serialization, s11n.net и ты в своей статье. Если поднять обсуждения проблем сериализации и рефлекшена здесь и в форуме "C/C++ Прикладные вопросы", то такая точка зрения многим симпатична.
Еще один способ -- создание метаданных автоматически на основе парсинга C++ кода. Активным ее проповедником здесь является adontz (см., например, темы Проект сериализации [просьба высказаться]
Ну и еще один способ -- это наличие внешнего описания на Data Definition Language (DDL) из которого генерируется код сериализации/десериализации данных. Сторонником этого подхода являюсь я сам (в моем проекте ObjESSty так и происходит). По такому принципу работают ASN1 компиляторы (только там еще и описания C++ классов генерируются). Что-то подобное с IDL применяется в CORBA и Ice.
Лично мне последний способ представляется наиболее удобным, т.к. тогда мы получаем возможность делать более простые и, одновременно, более удобные описания. Например, на моем ObjESSty DDL описание структуры SPoint выглядело бы:
{type SPoint
{attr x {of oess_1::int_t}}
{attr y {of oess_1::int_t}}
}
(правда, ObjESSty пока не умеет делать XML-сериализацию, зато есть другие фишки). А из такого описания строится довольно-таки сложный вспомогательный код (пример которого можно посмотреть здесь
Наличие отдельного, декларативного описания схемы сериализуемых данных позволяет получать дополнительные преимущества:
— простота описания (особенно в случае, когда сериализуемые C++ классы полностью генерируются по DDL описанию);
— возможность по одному описанию строить разные схемы сериализации/десериализации. Например, в твоем подходе для XML-сериализации сразу указываются имена XML элементов и атрибутов. И жестко фиксируются в метаданных. А что делать, если SPoint может быть сериализован в разных XML схемах и названия элементов и атрибутов должны быть разными? В DDL можно сделать что-то подобное:
И при генерации вспомогательного кода указывать, код для какой XML схемы нужно генерировать.
Или можно даже разнести описания самой структуры SPoint и способов ее отображения на XML:
{type SPoint
{attr x {of oess_1::int_t}}
{attr y {of oess_1::int_t}}
}
{xml-reflection {scheme "http://www.superpuper.com/xml/schemas/scheme1"}
{type-reflection SPoint
{element "point"}
{attr x {as {attribute "x"}}}
{attr y {as {attribute "y"}}}
}
}
{xml-reflection {scheme "http://www.megapupersuper.com/schames/another"}
{type-reflection SPoint
{element "PT"}
{attr x {as {element "x"}}}
{attr y {as {element "y"}}}
}
}
При этом дополнительные описания {xml-reflection} можно дополнять уже после того, как основной вспомогательный код для SPoint будет сгенерирован, скомпилирован и влинкован. Более того, можно даже сделать так, чтобы описания {xml-reflection} подгружались динамически и сериализация в новую XML схему выполнялась путем интерпретации описания {xml-reflection};
— возможность по DDL описанию генерировать код для поддержки этого типа в другом языке. Например, когда сериализованные C++ кодом данные должны быть прочитаны из Python или Ruby;
— возможность делать автоматические средства для трансформирования сериализованных данных при эволюции схемы данных.
Достижения аналогичных целей путем описания метаданных непосредственно в C++ коде, имхо, гораздо более трудоемко. Причем эта трудоемкость будет заключатся в том, что четырех-пятиэтажные шаблонные конструкции в C++ коде придется оборачивать сложными кружевами макросов. В одном своем проекте я подобную глупость допустил. Теперь нужно приложить массу усилий чтобы избавится от ее последствий.
Так что пример с применением предложенной тобой схемы сериализации лично для меня выглядит не очень удачным. Но это из-за того, что я сам сериализацией занимался и у меня другой взгляд на то, как это должно быть.
А>Есть наметки как решить существующие проблемы (статья + пример). А>Что бы сделать полноценную библиотеку нужно время. Если бы кто-нибудь за это взялся — я бы с удовольствием попользовался результатами
Ну дык быблиотек для С++ сериализации не так уж и мало. Неужели ни одна не подходит?
E>>Поэтому, если задачей было показать необходимость подобного механизма в стандартной библиотеке C++, то, имхо, эта задача достигнута не была. Чесно говоря я вообще не понял, что за компонент должен быть в стандарной библиотеке и что этот компонент должен делать. А>Стандартный способ описания и использования метаданных.
Имхо, нет такого понятия, как стандартный способ описания и использования метаданных. Поскольку метаданные привязаны к конкретной задаче. Для сериализации в XML -- нужен один тип метаданных, для сериализации в ASN1 -- совсем другой, для сериализации в YAML -- третий. А для создания более продвинутого аналога boost::signal (с возможностью выбора разных нитей для разных обработчиков сигналов) нужны будут совсем другие метаданные.
Один подход для их использования (который, имхо, сейчас очень популярен) -- это эксплуатация на полную катушку возможностей метапрограммирования в C++ с шаблонами. Лично мне этот подход не нравится. Потому, что получается очень трудновоспринимаемый код. Очень уж write-only код выходит. В boost когда-то предлагали включить библиотеку FSM (для описания конечных автоматов) -- это было нечто
А вот второй подход, основанный на использовании DSL (Domain Specific Language) мне гораздо более симпатичным. Создается простой язык для конкретной предметной области. Создается его транслятор и генератор вспомогательного кода. И создается необходимый набор библиотек для поддержки сгенерированного кода. Каждый компонентик оказывается относительно простым как в написании, так и в использовании. Но, что более важно, -- в сопровождении. Для сопровождения транслятора, созданного на основе, например, bison/flex, совсем не требуется гуру в области C++ шаблонов.
А еще интереснее то, что для DSL не обязательно создавать отдельные специализированные языки. Вполне возможно использовать некоторые существующие языки, которые способствуют метапрограммированию. Например, Lisp (см. Metaprogramming et al
При этом нам не нужно писать ни лексический анализатор, ни синтаксический анализатор. Можно сразу создавать прикладные классы для хранения описания в Ruby коде и генерацию по этим описаниям C++ кода.
А>Если есть вопросы — я на них с удовольствием отвечу.
Да нет, вопросов как раз нет. Есть просто впечатление о статье, которое я и высказал. Надеюсь, оно окажется полезным.
... << RSDN@Home 1.1.4 stable rev. 510>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Re: Использование метаданных в программах на языке C++
Здравствуйте, eao197, Вы писали:
E>А вот второй подход, основанный на использовании DSL (Domain Specific Language) мне гораздо более симпатичным. Создается простой язык для конкретной предметной области. Создается его транслятор и генератор вспомогательного кода. И создается необходимый набор библиотек для поддержки сгенерированного кода. Каждый компонентик оказывается относительно простым как в написании, так и в использовании. Но, что более важно, -- в сопровождении. Для сопровождения транслятора, созданного на основе, например, bison/flex, совсем не требуется гуру в области C++ шаблонов.
E>А еще интереснее то, что для DSL не обязательно создавать отдельные специализированные языки. Вполне возможно использовать некоторые существующие языки, которые способствуют метапрограммированию. Например, Lisp (см. Metaprogramming et al
) я сегодня решился применить кодогенерацию с помощь простенького DSL. Нужно мне было создать C++ класс, который бы хранил несколько полей (с перспективой увеличения их числа в ближайшем будущем). Проблема была в том, что для каждого поля нужно было иметь четыре метода (например, для поля с именем source_addr_ton):
Все еще усугублялось тем, что признак наличия актуального значения у поля должен быть выставлен в специальной битовой маске. И если вызывается метод getter, а бит в этой маске не выставлен, то getter должен порождать исключение. Соответственно, метод setter должен выставлять соответствующий бит в битовой маске. Т.е. что-то типа:
Можно было бы применить обычный copy-paste. А затем править названия полей и методов, ожидая, что где-то что-то окажется забытым.
Можно было бы написать пару макросов и использовать их:
т.е. файлы submit_deliver.detail.hpp и submit_deliver.detail.cpp перестраиваются при изменении submit_deliver.rb или submit_deliver_gen.rb. Перестройка выполняется с помощью Ruby и скрипта submit_deliver.rb.
Автоматически генерируемые файлы submit_deliver.detail.* используются очень просто:
// Файл submit_deliver.cpp
/*!
\file
\brief Класс для хранения опциональных SMPP полей для
submit_sm/deliver_sm/data_sm PDU.
*/#include <mbsms_2/smpp/h/submit_deliver.hpp>
#include <oess_1/stdsn/h/inout_templ.hpp>
namespace mbsms_2
{
namespace smpp
{
#include"submit_deliver.detail.cpp"#include"submit_deliver.ddl.cpp"
} /* namespace smpp */
} /* namespace mbsms_2 */
В результате получается вот такое описание класса (фрагмент):
/*!
Класс для хранения опциональных значений SMPP-полей для
submit_sm/deliver_sm/data_sm PDU.
Поле считается имеющим актуальное значение, если соответствующий
метод is_*_present возвращает true. В противном случае поле считается
не заданным и обращение к нему будет приводить к возникновению
исключения std::exception.
*/class MBSMS_2_TYPE submit_deliver_t
: public oess_1::stdsn::serializable_t
{
OESS_SERIALIZER_EX( submit_deliver_t, MBSMS_2_TYPE )
public :
/*!
Конструктор по умолчанию.
Все поля считаются не имеющими актуального значения.
*/
submit_deliver_t();
virtual ~submit_deliver_t();
/*!
\return true, если значение для поля source_addr_ton задано.
*/bool
is_source_addr_ton_present() const;
/*!
\return значение поля source_addr_ton.
\throw std::exception при попытке получить значение отсутствующего
поля.
*/
oess_1::uchar_t
source_addr_ton() const;
/*!
Установить значение поля source_addr_ton.
Одновременно выставляется признак того, что поле задано.
*/void
set_source_addr_ton( oess_1::uchar_t v );
/*!
Сбросить признак того, что поле source_addr_ton задано.
*/void
drop_source_addr_ton();
/*!
\return true, если значение для поля source_addr_npi задано.
*/bool
is_source_addr_npi_present() const;
/*!
\return значение поля source_addr_npi.
\throw std::exception при попытке получить значение отсутствующего
поля.
*/
oess_1::uchar_t
source_addr_npi() const;
...
Самое прикольное, что doxygen по этому делу генерирует нормальную документацию
В итоге получилось, что я написал ~230 строк генерирующего Ruby скрипта submit_deliver_gen.rb, плюс 42 строки DSL-я submit_deliver.rb. И получил ~430 строк сгенерированного C++ текста. Правда, потратил я на написание этого скрипта порядка 1.5 часов. Но, имхо, за это получил инструментик, который легко поможет мне как добавлять новые поля, так и менять реализацию работы с полями в генерируемом классе.
... << RSDN@Home 1.1.4 stable rev. 510>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Re[2]: Использование метаданных в программах на языке C++
E>Самое прикольное, что doxygen по этому делу генерирует нормальную документацию
не это не самое прикольное
E>В итоге получилось, что я написал ~230 строк генерирующего Ruby скрипта submit_deliver_gen.rb, плюс 42 строки DSL-я submit_deliver.rb. И получил ~430 строк сгенерированного C++ текста. Правда, потратил я на написание этого скрипта порядка 1.5 часов. Но, имхо, за это получил инструментик, который легко поможет мне как добавлять новые поля, так и менять реализацию работы с полями в генерируемом классе.
Самое прикольное будет через пару лет, когда ты уволишься а новый человек возьмётся всё это сопровождать. Думаю он подумает о тебе много хорощего:
Кодировать нужно так, как будто человек который будет сопровождать этот код — маньяк, и он знает где вы живёте! (c) McConnell
... << RSDN@Home 1.1.4 beta 4 rev. 303>>
Народная мудрось
всем все никому ничего(с).
Re[3]: Использование метаданных в программах на языке C++
Здравствуйте, Tom, Вы писали:
E>>В итоге получилось, что я написал ~230 строк генерирующего Ruby скрипта submit_deliver_gen.rb, плюс 42 строки DSL-я submit_deliver.rb. И получил ~430 строк сгенерированного C++ текста. Правда, потратил я на написание этого скрипта порядка 1.5 часов. Но, имхо, за это получил инструментик, который легко поможет мне как добавлять новые поля, так и менять реализацию работы с полями в генерируемом классе. Tom>Самое прикольное будет через пару лет, когда ты уволишься а новый человек возьмётся всё это сопровождать. Думаю он подумает о тебе много хорощего:
Tom>
Tom>Кодировать нужно так, как будто человек который будет сопровождать этот код — маньяк, и он знает где вы живёте! (c) McConnell
Не вижу в этом случае никаких проблем с дальнейшим сопровождением.
Если бы я сделал пару-тройку многоярусных макросов и оставил кусок кода, который эти макросы использует, вот тогда, имхо, было бы больше шансов получить в спину пару ласковых слов. А так -- нормальный C++-ный код, с комментариями . Что хочешь, то и делай. Хочешь -- просто возьми результаты генерации, выброси Ruby DSL и правь дальше однаждый сгенерированный C++ный код. Хочешь -- переделай все под макросы, хочешь -- напиши другой генератор на Python-е и XML -- полная свобода.
Кроме того, имхо, разобраться в небольшом Ruby-скрипте гораздо проще, чем в сотне-другой copy-paste кода. Или каких нибудь шаблонных наворотах.
... << RSDN@Home 1.1.4 stable rev. 510>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Re[3]: Использование метаданных в программах на языке C++