将具有默认值的参数添加到虚拟方法

时间:2014-10-14 17:07:09

标签: c++ c++11 c++14

在多层开发的环境中,在完全不相关的开发团队(许多客户端开发团队)中控制核心库和客户端代码,通过添加方法扩展Base类接口的最有效方法是什么?带有默认值的新参数?

从概念上讲,我需要替换(在核心库中)这个旧代码:

struct Base
{
    virtual void foo() {}
    vitrual ~Base() {}
}

使用这个新代码:

struct Base
{
    virtual void foo(bool b = true) {}
    vitrual ~Base() {}
}

问题是,这将默默地破坏客户端代码,例如:

struct Derived: public Base
{
    void foo() {}
}

int main()
{
    Derived d;
    Base &b = d;
    b.foo();
}

一种解决方案是使用两种方法,例如:

struct Base
{
    virtual void foo(bool b) {}
    virtual void foo() {foo(true);}
    vitrual ~Base() {}
}

这增加了一种不必要的方法,这不是一种可持续的图书馆维护方法(界面膨胀,维护成本,测试,文档等)。 当然,旧方法可能会被弃用,但这意味着新的客户端代码总是需要指定布尔参数。

另一种解决方案可能是提供新版本的Base类:

struct BaseV2: public Base
{
    virtual void foo(bool b = true) {/* delegate impl. */ }
}

这会增加一个不必要的类,但至少可以为客户端方便地处理弃用。

还有哪些其他选择?可以做些什么来简化核心库中这种简单的界面变化的引入?

2 个答案:

答案 0 :(得分:3)

这里可能会有一些帮助:

1)只要方法现在是阴影,而不是覆盖基类方法,使用override关键字将给出编译器警告。 e.g:

struct Derived: public Base
{
    void foo() override {} // Warns when Base::foo changes
};

可悲的是,这是你必须依赖用户做的事情,而不是你可以强制执行的事情。如果您的用户经历了足够的痛苦,他们可能会为此而努力。

2)将您的班级界面与其实施分开 - 在这种情况下,实施是虚拟的,理想情况下是私人的。 e.g。

struct Base {
    void foo() { fooImpl(); }
private:
    virtual void fooImpl() = 0; // Or provide a default implementation
};

struct Derived : public Base {
private:
    void fooImpl() override { ... } 
};

这样做的好处是,您可以将默认参数添加到foo()而不会破坏任何内容,然后决定如何处理代码库的其他用户。

如果您决定绝对需要将参数传递给fooImpl()的客户端实现者而不保留已弃用的版本,则可以更改其签名。使用纯虚拟,然后编译器将阻止任何人实例化现在不再发生覆盖的类,并且您不会得到静默破坏的编译。优点:没有糟糕的构建,缺点:为一些用户工作,即使他们不关心新功能。

或者,如果你决定你的类的行为需要作为参数的结果委托给不同的函数,例如fooImpl2(...)然后在Base::foo中,您可以测试变量是否为默认值,并根据需要调用fooImplfooImpl2fooImpl2当然不需要获取bool参数的冗余副本;您的委托代码可以使用完全不同的参数调用它,只要您的foo实现可以解决旧方法签名和新参数的操作。

沿着fooImpl2路线走,您可以选择提供默认实施(专业:每个人的代码编译并且不费力地工作; con:您必须提供合理的默认实现)或者制作一个纯虚拟(专业:对你来说更容易; con:其他人的代码中断,即使他们不想实现你的新界面)。

这种方法的另一个好处是,现在知道您的界面的所有用户都是通过控制的方法进入的,因此身份验证/日志记录/常见行为/前后授权完整性检查可以一切都在一个地方完成,而不是让每个人都自己烘烤一半。

3)或许考虑mixins,具体取决于您的新默认参数要实现的目标。在专业方面,这种方法为您和您的用户提供了最大的灵活性,可以组合方法,创建新方法,而无需在没有任何更改时编写新代码。另一方面,错误消息将是不可理解的,如果组织中的人员对模板编程不太熟悉,那么事情就会变得糟糕。

答案 1 :(得分:1)

  

这增加了一种不必要的方法,这不是一种可持续的图书馆维护方法(界面膨胀,维护成本,测试,文档等)。

这就是为什么具有相关客户端代码的软件实际上无法在进行更改时进行清理的原因往往会经历一些小错误,然后是一个打破向后兼容性的清理/新版本。

当这完全不可接受时,会使用一些可怕的替代方案 - 就像采用容器的函数一样,以后可以运行任意运行时可解码的选项....如果你是那种绝望,那就睡觉吧。