【问题标题】:tag dispatching for inherited classes继承类的标签调度
【发布时间】:2017-05-08 17:20:27
【问题描述】:

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

【问题讨论】:

  • AFAIK 标记调度涉及许多带有虚拟类型参数(即标记)的重载,我不确定这将如何减少代码,并且由于它是编译时选择,我不确定这对你有什么帮助。无论如何,您仍然必须列出从名称到功能(重载)的关联。我是不是误会了?

标签: c++ inheritance optimization tags dispatch


【解决方案1】:

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

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

此解决方案将在程序启动时自动注册所有类,这更像您以前的。


更新。

要使用它,您只需将所有 if-else 代码替换为以下行:

add_module(Factory::spawn(name);

或者如果你不处理 nullptr:

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

感谢D Drmmr,这段代码是安全的。

【讨论】:

  • 如前所述,这受到static initialization order fiasco 的影响。您需要为您的工厂(或其回调函数映射)使用单例。
  • @DDrmmr 确实如此。我需要修复它
  • @teivaz 我还是有点困惑,我会传递给 add_modules 函数什么?
【解决方案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&lt;P2_26&gt;) 并跳过冗长的 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&lt;P4_30&gt;) 并且它可以正常工作。

【讨论】:

  • 如果你有硬编码的"P2_26" 为什么不直接打电话给add_module(new P2_26());
  • 这段代码的哪一部分需要添加到生成的派生类中?还是这一切都在他们之外? (类型名称在运行时确定)
  • @DDrmmr 这正是我正在做的事情,但是有很多模块,因此有巨大的 if/else 块。派生类名称直到运行时才给出。
  • @Prgrm.celeritas 您要询问哪些解决方案?无需将任何内容添加到生成的派生类中。设置解决方案确实需要为每个派生类添加FACTORY(name,foo), 行。我把它放在一个地方;您可以做一些花哨的事情来分发注册(并将其放在派生类中)。
  • @Yakk 我在问是否需要对派生类进行任何更改,因为这需要更多的工作。但您认为它们不需要修改。
猜你喜欢
  • 1970-01-01
  • 2012-10-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-21
  • 1970-01-01
相关资源
最近更新 更多