c++ 继承类的标记调度

62lalag4  于 2022-12-15  发布在  其他
关注(0)|答案(2)|浏览(170)

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

omqzjyyz

omqzjyyz1#

标记调度是基于类型信息作为输入的。从您的代码判断,您有一个字符串作为输入,它不能在运行时使用。
您的案例看起来更像一个抽象工厂:

// 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));

或者,如果无法在add_module中处理nullptr

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

感谢D Drmmr使这段代码变得更好。

wxclj1h5

wxclj1h52#

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>),它就可以正常工作。

相关问题