C++ 杂谈(12)

有了上面所说的 extractor framework 之后,我们会碰到几个有意思的问题:

  • 如果有几个同类型的 extractor,我想把他们都执行了怎么办
  • 如果有一个 A->B 的 extractor,后面又有一个 B->C 的 extractor,我们是不是可以直接获得一个 A->C 的 extractor?
  • 如果有一个 A->C 的 extractor,B 是 A 的子类,我们能否将 A->C 的 extractor 当做 B->C 的 extractor 来用?这个问题简化的版本是如果一个人有 DEFINE_EXTRACTOR(Foo, A, B),另一个人写的是 DEFINE_EXTRACTOR(Bar, A, B),根据前面的设计这两个类虽然有公共的祖先,但是其 reflection lib 提供的 factory 却是两个不同的版本,因此两个 extractor 不能共用

可以看到的是,这些东西跟具体哪个 extractor<In, Out> 没有关系,它们处理的是“结构”,我们能否为之前的 framework 添加一些小 utility 呢?这其实跟 functional programming 里面一些思维比较类似了。好在我们发现 ExtractorConfig 本身是 type agnostic 的,这为我们创造了一种可能。

为了解决第一个问题,我们可以引入如下的 config

message CompositeConfig {
  repeated ExtractorConfig extractor = 1;
  extend ExtractorConfig {
    optional CompositeConfig config = 18;
  }
}

我们实现这个功能巨简单

template <typename Child>
class composite {
public:
  DEFINE_EXTRACTOR_CTOR(composite, CompositeConfig::config)
  composite(const CompositeConfig& config) {
    for (const auto& ec : config.extractor()) {
      extractors.emplace_back(Child::create(ec));
    }
  }

  bool extract(const typename Child::InType& in,
               typename Child::OutType& out) const override {
    flag = false;
    for (auto& extractor : extractors)
      flag |= extractor->extract(in, out);
    return flag;
  }
private:
  std::vector<std::unique_ptr<Child> > extractors;
};

通过一个简单的宏,我们可以在 DEFINE_EXTRACTOR 的时候将 Composite 直接注册,这样这个功能就变成所有 extractor 都有的一个结构了。第二个问题我们可以在 config 里面放两个 ExtractorConfig,但是很显然这个只能作为一个给定第三个类(中间)之后的一个 wrapper,为此我们可以在实现类似的 Chain<Child, C1, C2> 之后,要求使用一个 macro 来注册这个 wrapper。最后一个类似。为了保证 type safety,我们可以在这些实现里面直接 static_assert 对应的类是否复合一些显而易见的约束条件。

一个比较奇怪的要求是为某些特定的类提供“counter”,比如某些 extraction 可能需要为某些细节提供更加细粒度的统计信息,而不仅仅是这个 extraction 成功与否(return value),那么如何改动现有的 API?另一个问题是如果 extraction 还有副产品,又怎么能把副产品输出?很显然这些都是实现类的问题,但是在我们提供的 API 上却不能很容易的解决。

如果不考虑问题二和三导致的结构,我们可以为 extractor<In, Out> 提供一个如下的 interface:

template <typename In, typename Out, typename Child>
void extractor<In, Out, Child>::apply(const function<void (Child&)> func) {
  func(*this);
}

对于 composite 来说我们只需要简单的把这个 func 应用到每个子 extractor 即可。这样我们可以

extractor.apply([](XYZExtractor& extractor) {
  SomeInterface* some = dynamic_cast<SomeInterface*>(&extractor);
  if (some) {
    some->api_of_some();
  }
});

但是比如如果有 chaining 的行为,子 extractor 的类型不能匹配 func 的要求,无法自动的传递下去。有什么更好地策略能够让我们能够为这些需求打开合理的天窗呢?我们继续研究研究…

——————
And Timna was concubine to Eliphaz Esau’s son; and she bare to Eliphaz Amalek:these were the sons of Adah Esau’s wife

C++ 杂谈(12)

留下评论