标记为继承的类调度

时间:2017-05-08 17:20:27

标签: c++ inheritance optimization tags dispatch

我有一些代码,我有一个基类(让我们称之为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块。在我看来,这可以通过使用标签调度来简化代码,但我在网上找到的每个例子都使用内置函数,比如已经定义了标签的迭代器,我无法插入到我的用例中。无论如何都要精简这段代码吗?

2 个答案:

答案 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>),它只是有效。