接口更改:为抽象c++方法添加新参数

interface changes: add new arguments to abstract c++ method

本文关键字:新参数 添加 参数 方法 c++ 抽象 接口      更新时间:2023-10-16

我们有一个大型软件框架,在代码的繁重部分,我们有一个抽象基类,说

class Base {
public:
  virtual double eval() const = 0;
};

在很大程度上充当了许多其他类的公共接口类,这些类分布在不同的子包中,驻留在不同的存储库中等等。

class Derived1 {
public:
  virtual double eval() const override { return 1; }
}

然而,在我们的代码中实际上只有一个地方调用了这个函数,那就是在核心应用程序的主循环中。

 int main(int argc, char* argv[]){
    Base* x = createInstance(argv[1]); // some factory function
    while(true){
      std::cout << x->eval() << std::endl;
    }
 }

我们最近发现扩展这样的接口是非常有用的:

virtual double eval(int idx) const = 0;

在核心应用程序中修复对该方法的一个调用不是问题,但是修复分布在我们代码中的数十个现有派生类实现来接受和(在大多数情况下)忽略这个新参数将是一场噩梦。

我考虑过提供一个像这样的遗留包装器:

double Base::eval(int idx) const { return this->eval(); } 

但这意味着所有派生类都需要实现该函数的无参数版本才能存在,即使它应该被弃用。或者,我们可以实现非参数版本作为Base的一部分,但这样我们就会牺牲Base的抽象性质,因为这样所有组件都已经在那里实现了。

是否有任何"整洁"的方法来解决这个问题,或者唯一相同的事情是实际联系所有的子包开发人员,让他们改变他们的实现来坚持新的接口?

可以用double eval(int idx)添加一个新的抽象基类New,而弃用旧的。

当您让一个继承另一个时,您可以编写接受New的新api,并且它们与Old保持兼容。

struct New
{
    virtual ~New() {}
    virtual double eval(int idx) = 0;
};
struct Old : New
{
    virtual double eval() = 0;
    virtual double eval(int idx) { eval(); } // backwards-compatible
};

实现New的人被强制覆盖一元函数,而实现Old的人被强制覆盖旧的,非一元函数。

或者,我们可以将非实参版本实现为Base的一部分,但这样我们就会牺牲Base的抽象性质,因为这样所有组件都已经在那里实现了。

您可以将一个接口用作转换接口(两个接口都有),或者您可以创建两个接口,本质上是将人员转换到第二个接口。这使主循环变得复杂,但使类实际上保持抽象。

你可以这样做

class Base {
public:
  virtual double eval() const{}
  virtual double eval(int idx) const{
    this->eval();
  }
  virtual ~Base() = 0;
};

Base::~Base(){}

并在派生类中实现eval()eval(int idx)

实现的纯虚析构函数确保Base类不能被实例化。(见https://stackoverflow.com/a/14631710/2186392)。