Сегодня столкнулся с интересной задачкой.
Есть сторонняя библиотека, регламентирующая вид своих плагинов. Есть набор плагинов как "вшитых" в код намертво, так и отдельных (часть из них - сторонние).
Требуется прицепить к некоторым классам из плагинов некоторые одинаковые по составу наборы данных, в соответствии один класс к многим. Причем интерфейс классов этого проводить не позволяет и даже больше - некоторые конечные классы в плагинах обьявлены только в cpp-шниках, а значит в основном приложении недоступны. Чтобы стало совсем хорошо - укажем ограничение, что в некоторых из них набор определяется параметром из режима исполнения. Клиентский код работает с объектами исключительно через интерфейсы.
Единственное, что есть у классов - аналог оператора typeid, который позволяет получить строку с именем класса.
Менять интерфейс - плохо, ибо будут проблемы с обновлением, т.к. менять надо чуть ли не в корневых классах иерархии. Строить же в использующем доп. данные коде какие-то адопостроения с различием по классам тоже не хочется, ибо нагромождение и никто не может сказать, сколько кодеков будет дальше. Было решено сделать примерно следующее:
Каждый набор представляем структурой с некоторым интерфейсом, которую будем определять по строковому ключу. Ключ будет представлять собой строчку с именем класса.
Единственное, что представляет проблему - ассоциирование класса и нескольких наборов в зависимости от некоторого условия. Ввиду того, что такая ситуация была обнаружена только во "втроенных" в код плагинах, так что в таких случаях добавляем некий метод, по которому будем различать значения. Ограничение - должна быть возможна конвертация в строку. Я в своем подходе возвращал прямо строки.
Пример для самых упоротых. // класс-родитель для всех плагинов
class Plugin_interface;
// интерфейс для набора данных
struct Abstract_description {
virtual int get_param (Plugin_interface * ptr = 0) = 0;
};
// карта описаний
class Plugin_additional_info_map : public std::map <std::string, Abstract_description *> {
public:
void add_description (std::string key, Abstract_description * description) {
insert ( std::pair <std::string, Abstract_description *> ( key, description) );
}
Abstract_description * get_description (std::string & key) {
iterator it = find (key);
return (it != end()) ? it->second : 0;
}
};
static info_map;
// макрос, объявляющий описание
#define DECLARE_PLUGIN_DESCRIPTION(class_name, param) \
static struct class_name##__generic_description { \
virtual int get_param (Plugin_interface * ptr = 0) { return param; } \
class_name##__generic_description () { info_map.add_description (#class_name, this); } \
} class_name##__generic_descriptor
// служебный макрос, ибо запись #macro1##macro2 не работает =^_^=
#define MACRO_TO_STR(macro) #macro
// макрос условного определения. Нужен, если ассоциируемый параметр зависит от некоторого значения
#define DECLARE_PLUGIN_DESCRIPTION_CONDITION(class_name, value, param, value_getter) \
DECLARE_PLUGIN_DESCRIPTION(class_name##_##value, param); \
static struct class_name##_##value##__condition_description { \
virtual int get_param (Plugin_interface * ptr) { \
class_name * cls_ptr = dynamic_cast <class_name *> (ptr); \
std::string value_str = (cls_ptr != 0) ? cls_ptr->value_getter : ""; \
std::string class_name_str (#class_name); \
class_name_str.append ("_"); \
class_name_str.append (value_str); \
Plugin_additional_info_map::iterator it = info_map.find (class_name_str); \
return (it != info_map.end()) ? it->second : 0; \
} \
class_name##_##value##__condition_description () { info_map.add_description (MACRO_TO_STR(class_name##_##value), this); } \
} class_name##_##value##__condition_descriptor
// макрос для получения параметра. Чисто для красоты
#define GET_DESCRIPTION(class_name) info_map.get_description (#class_name)
Как оно работает - опишу позже.