逆变类型和可扩展性

6
我正在编写一份关于优化的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;
}

这都很好,但现在我希望针对优化器定义接口。例如,某些优化器需要梯度信息或海森矩阵信息才能进行优化,而有些则不需要。因此,优化器的类型与函数的类型是逆变的。

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;
}

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

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

但是,为了创建相应的优化器类,我们必须使HessianOptimizer扩展ThirdOrderOptimizer。但是,库用户必须修改库才能这样做!因此,虽然我们可以添加ThriceDifferentiableFunction而不必修改库,但逆变类型似乎失去了这个属性。这似乎只是一个因素,类声明其父类型而不是子类型而导致的。那么,你应该如何处理呢?有没有什么好的方法呢?

1
为什么继承层次结构中需要优化器? - Angew is no longer proud of SO
它将允许我在梯度优化器的位置透明地使用优化器等。 - Jeremy Salwen
1个回答

2

由于它们只是接口,所以你不必担心它们的多重继承。为什么不将优化器类型设置为兄弟而不是子类呢?

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
{
  // ...
};

2
+1。OP关于让TwiceDifferentiableFunction继承自DifferentiableFunction是正确的,因为从语义上讲这是有意义的。然而,让GradiantOptimizer继承自HessianOptimizer就没有意义了。从语义上讲,这是不正确的。 - Félix Cantournet
当然,根据Liskov替换原则,GradientOptimizer是HessianOptimizer的子类型。虽然一种方法是在C++类型中忽略这种关系,但如果没有解释为什么C++无法模拟这种关系,那就似乎忽略了问题。 - Jeremy Salwen
@JeremySalwen 当然,C++可以模拟这种关系。你在问题中已经做到了。只是通过进一步的派生扩展,它需要改变整个优化器层次结构,因为它需要添加一个基类。但这并不与LSP相冲突。 - Angew is no longer proud of SO

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接