逆变类型和扩展性

Contravariant types and extensibility

本文关键字:扩展性 类型      更新时间:2023-10-16

我正在编写一个用于优化的C++库,我遇到了一个奇怪的反变量类型的问题。

因此,我根据它们可以计算的信息来定义"函数"的层次结构。

class Function {
 public:
    double value()=0;
}
class DifferentiableFunction : public Function {
 public:
  const double* gradient()=0;
}
class TwiceDifferentiableFunction : public DifferentiableFunction {
 public:
    const double* hessian()=0;
}

这一切都很好,但现在我想为优化器定义接口。 例如,一些优化器需要梯度信息或 hessian 信息才能进行优化,而有些则不需要。 因此,优化器的类型与函数的类型是逆变的。

class HessianOptimizer {
 public:
    set_function(TwiceDifferentiableFunction* f)=0;
}
class GradientOptimizer : public HessianOptimizer {
 public:
    set_function(DifferentiableFunction* f)=0;
}
class Optimizer: public GradientOptimizer {
 public:
    set_function(TwiceDifferentiableFunction* f)=0;
}

我认为从类型论的角度来看这是有道理的,但奇怪的是,通常当人们想要扩展代码时,他们会继承已经存在的类。 例如,如果其他人正在使用这个库,并且他们想要创建一种需要比 hessian 更多信息的新型优化器,他们可能会创建一个类似

class ThriceDifferentiableFunction: public TwiceDifferentiableFunction }
 public:
    const double* thirdderivative()=0;
}

但是为了创建相应的优化器类,我们必须使HessianOptimizer扩展ThirdOrderOptimizer。 但是库用户必须修改库才能这样做! 因此,虽然我们可以添加 ThriceDifferentiableFunction 而无需修改库,但似乎逆变类型会丢失此属性。 这似乎只是类声明其父类型而不是其子类型这一事实的产物。

但是你应该如何处理这个问题呢?有什么办法可以做得好吗?

由于它们只是接口,因此您不必担心与它们的多重继承。为什么不让优化器类型成为兄弟姐妹而不是后代呢?

class OptimizerBase
{
  // Common stuff goes here
};
class HessianOptimizer : virtual public OptimizerBase {
 public:
    virtual set_function(TwiceDifferentiableFunction* f)=0;
}
class GradientOptimizer : virtual public OptimizerBase {
 public:
    virtual set_function(DifferentiableFunction* f)=0;
}
class Optimizer : virtual public OptimizerBase {
 public:
    virtual set_function(TwiceDifferentiableFunction* f)=0;
}

// impl
class MyGradientOptimizer : virtual public GradientOptimizer, virtual public HessianOptimizer
{
  // ...
};