диспетчеризация тегов для унаследованных классов

У меня есть код, где у меня есть базовый класс (давайте назовем его foo) с переменным числом производных классов (между 10-500), созданных сценарием генерации. В настоящее время у нас есть функция, которая будет создавать новый базовый класс, передавая его имя в виде строки, а затем используя гигантский оператор if / else, чтобы найти правильный.
например

if      (name == "P2_26") {add_module(new P2_26());}
else if (name == "P4_30") {add_module(new P4_30());}
...

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

2

Решение

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

// Factory.h
class Base;

struct Factory {
using spawn_t = std::function<Base*()>;
using container_t = std::unordered_map<std::string, spawn_t>;

static container_t& producers() {
// This way it will be initialized before first use
static container_t producers;
return producers;
}

static Base* spawn(const std::string& name) {
auto it = producers().find(name);
if (it == producers().end()) return nullptr;
return it->second();
}
};

// Base.h
#define STR(x) #x
#define DEFINE_REGISTRATOR(_class_) \
DerivedRegistrator<_class_> _class_::_sRegistrator_(STR(_class_))

#define DECLARE_REGISTRATOR(_class_) \
static DerivedRegistrator<_class_> _sRegistrator_

template<typename T>
struct DerivedRegistrator{
DerivedRegistrator(const std::string& name) {
Factory::producers()[name] = [](){ return new T(); };
}
};

class Base {
// ...
};

И тогда сгенерированные файлы должны включать:

// Derived1.h
class Derived1 : public Base {
DECLARE_REGISTRATOR(Derived1);
// ...
};

// Derived1.cpp
DEFINE_REGISTRATOR(Derived1); // Will register automatically

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


UPD.

Чтобы использовать его, вы можете просто заменить весь свой код if-else на следующую строку:

add_module(Factory::spawn(name);

Или, если вы не обрабатываете nullptr:

Base* ptr = Factory::spawn(name);
if (ptr) {
add_module(ptr);
}

И благодаря D Drmmr этот код безопасен

3

Другие решения

template<class T>
struct named_factory {
const char* name;
std::function<std::unique_ptr<T>()> factory;
};
struct find_factory {
using is_transparent=std::true_type;
struct named {
const char* str;
template<class T>
named(named_factory<T> const& f):str(f.name) {}
named(const char* name):str(name) {}
};
bool operator()(named lhs, named rhs) {
return strcmp(lhs.str, rhs.str)<0;
}
};
#define MAKE_STR2(X) #X
#define MAKE_STR(X) MAKE_STR2(X)
#define FACTORY(X,...) \
named_factory<__VA_ARGS__>{\
MAKE_STR(X),\
[]{\
return std::make_unique<X>()\
}\
}

Теперь мы можем:

std::set<named_factory<foo>, find_factory> factories = {
FACTORY(P2_26, foo),
FACTORY(P4_30, foo),
// ...
};

и в коде вы делаете:

bool add_module_by_name( const char* name ) {
auto it = factories.find(name);
if (it == factories.end()) return false;
auto module = it->factory();
if (!module) return false;
add_module( module.release() );
return true;
}

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


тем не мение, если ваши имена типов определены во время компиляции, ты можешь лучше. (Т. Е. Если у вас есть жесткий код "P2_26" на сайте вызова).

template<class T>
struct tag_t { using type=T; constexpr tag_t(){} };
template<class T>
constexpr tag_t<T> tag{};

template<class T>
void add_module( tag_t<T> ) {
// ...
add_module( new T() );
}

Теперь вы можете add_module(tag<P2_26>) и пропустите длинную инструкцию if / else.

Мы можем даже скрыть реализацию внешнего add_module через это:

// in cpp file:
void add_module_impl( std::function< std::unique_ptr<foo>() > maker ) {
// ...
add_module( maker().release() );
}
// in h file:
void add_module_impl( std::function< std::unique_ptr<foo>() > maker );
template<class T>
void add_module( tag_t<T> t ) {
add_module_impl([]{ return std::make_unique<T>(); });
}

и снова мы можем add_module(tag<P4_30>) и это просто работает.

2

По вопросам рекламы ammmcru@yandex.ru
Adblock
detector