我有一些代码,我有一个基类(让我们称之为foo
),它有一个由生成脚本创建的可变数量的派生类(10-500之间)。目前,我们有一个函数,它将通过将其名称作为字符串传递,然后使用巨大的if / else语句来查找正确的基类来创建新的基类。
例如
if (name == "P2_26") {add_module(new P2_26());}
else if (name == "P4_30") {add_module(new P4_30());}
...
这导致了一个巨大的if else块。在我看来,这可以通过使用标签调度来简化代码,但我在网上找到的每个例子都使用内置函数,比如已经定义了标签的迭代器,我无法插入到我的用例中。无论如何都要精简这段代码吗?
答案 0 :(得分:3)
调度的标签基于类型信息作为输入。从您的代码判断,您有一个字符串作为输入,不能在运行时使用 您的案例看起来更像是一个抽象工厂:
// 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这段代码是安全的。
答案 1 :(得分:2)
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>)
,它只是有效。