【问题标题】:How to use the strategy pattern with STL containers?如何在 STL 容器中使用策略模式?
【发布时间】:2014-04-11 08:38:34
【问题描述】:

假设我有一个名为BinaryClassifier 的策略接口,它可以接受Sample 并返回一个double,表示Sample 对象属于正类的概率:

struct BinaryClassifier {
  virtual ~BinaryClassifier(){}
  virtual double classify(std::shared_ptr<Sample> sample) const = 0;
};

我们可能有多个BinaryClassifier 的实现,例如LogisticRegressionBinaryClassifier

Sample 又是一个只公开两个方法的接口:

struct Sample {
  virtual ~Sample() {}
  InputFeatures const& get_input_features() const = 0;
  double get_label() const = 0;
};

除了这两种方法,Sample 的具体实现暴露了完全不同的接口(即它们是不相关的),它们唯一的共同点是它们可以被二进制分类器分类。

到目前为止一切顺利。

当我们决定引入BinaryClassifier::train 方法时,问题就出现了:

struct BinaryClassifier {
  virtual ~BinaryClassifier(){}
  virtual double classify(std::shared_ptr<Sample> sample) const = 0;
  virtual void train(std::vector<std::shared_ptr<Sample>> samples) = 0;
};

此时,以下内容将不起作用:

std::vector<std::shared_ptr<ConcreteSample>> concreteSamples = ...;
concreteBinaryClassifier.train(concreteSamples);

那是因为std::vector&lt;std::shared_ptr&lt;ConcreteSample&gt;&gt;std::vector&lt;std::shared_ptr&lt;Sample&gt;&gt; 是两个不相关的类型。

C++-ish 解决方案是依赖模板:

      template<class SampleType>
      virtual void train(std::vector<std::shared_ptr<SampleType>> samples) = 0; // non-working code, template method cannot be virtual

但是模板方法不能是virtual。尽管如此,我希望BinaryClassifier 成为一个策略接口,因为可能存在尽可能多的BinaryClassifier 实现。在这一点上,即使设计看起来很合理,我也陷入了死胡同。

编辑:此外,给定的BinaryClassifier 对象可能会使用ConcreteSampleA 的向量进行训练,同时对ConcreteSampleB 类型的对象进行分类

哪种是最适合 C++ 的建模方式?

【问题讨论】:

    标签: c++ templates polymorphism containers covariance


    【解决方案1】:

    你可以让你的 BinaryClassifier 成为一个模板类

    template<SampleType> class BinaryClassifier
    {
        virtual void train(std::vector<std::shared_ptr<SampleType>> samples) = 0;
    }
    

    【讨论】:

    • 感谢您的回答,这是真的,但在这一点上,首先拥有Sample 接口的原因是什么?此外,给定的BinaryClassifier 对象可能会使用ConcreteSampleA 的向量进行训练,同时对ConcreteSampleB 类型的对象进行分类。
    【解决方案2】:

    您不能在ConcreteSampleA 上训练BinaryClassifier,然后用它来分类任何随机的ConcreteSampleB。因此,样本类型是BinaryClassifier 的固有部分。 Nullref 的回答是合理的:将样本类型设为模板参数。

    正如您所发现的,这意味着不再需要Sample 接口。好的。 std::vector&lt;int&gt; 也不要求 int 派生自某个 Element 接口。

    当你摆脱东西时,InputFeatures 看起来也很可疑。我只想说get_input_features 必须返回一些std::tuple,其成员类型都定义了std::less。由于它不再是虚拟的,因此您不必关心不同的样本类型返回不同的元组。我绝对不会硬编码get_label 必须返回double。无论如何,这是一种奇怪的标签类型。

    现在您说使用样本类型 A 进行训练然后对样本类型 B 进行分类可能是有意义的。这就是您需要改进的地方:看起来实际的兼容性要求是它们返回相同的元组。因此,Nullref 更好的解决方案是在get_input_sample 返回的元组类型上模板化BinaryClassifier

    [编辑] 此外,classify 不需要共同拥有。通过Sample const&amp;train() 真的应该只使用一对迭代器。 C++ 约定是将一组对象作为范围传递。

    【讨论】:

    • 感谢您的回答。容器container&lt;T&gt; 不要求T 由任何特定的基类派生是有意义的。但是,在这种情况下,我试图强制执行特定要求:BinaryClassifier 只知道如何处理SampleInputFeatures 只是typedefEigen::RowVectorXd,所以它与您使用std::tuple 的建议非常相似。我将标签建模为double,因为它也可以是软标签。分类器不能仅仅通过要求输入特征是同一类型来训练,它仍然需要访问标签。
    • @burton0:当你说“BinaryClassifier 只能处理样本”时,你不是说“它只能处理同时实现了get_input_featuresget_label 的类”吗?就像 vector 只能处理 Movable 和 Assignable 类一样? IE。需要方法,而不是基类。
    • 关于所有权,你是完全正确的,我在 SO 上即时编写了代码,但实际代码使用引用。 BinaryClassifier 只能处理实现get_input_featuresget_label 的类。您可以决定通过接口强制执行此操作,或通过模板将其作为要求。模板的问题在于您正在修复要使用的具体示例。相反,我想trainclassify 任何样本,不管它的具体实现。最后,shared_ptr&lt;ConcreteSample&gt; 的迭代器仍然不是shared_ptr&lt;Sample&gt; 的迭代器。
    猜你喜欢
    • 2023-03-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多