在基类中混合使用虚函数和非虚函数是不好的编程实践吗?

Is it bad programming practice to mix virtual and non-virtual functions in a base class?

本文关键字:函数 编程 混合 基类      更新时间:2023-10-16

我有一个基类base,我声明了几个多态子类。基类的一些函数是纯虚函数,而其他函数则由子类直接使用。

(这都是c++)

例如:

class Base
{
protected:
     float my_float;
public:
    virtual void Function() = 0;
    void SetFloat(float value){ my_float = value}

}

class subclass : public Base
{
  void Function(){ std::cout<<"Hello, world!"<<std::endl; }
}
class subclass2 : public Base
{
  void Function(){ std::cout<<"Hello, mars!"<<std::endl; }
}

因此,正如您所看到的,子类将依赖于设置"my_float"的函数的基类,但对于另一个函数来说将是多态的。

所以我想知道这是否是一个好的实践。如果你有一个抽象基类,你是应该让它完全抽象,还是可以采用这种混合方法?

这是一种常见的做法。事实上,一些著名的设计模式依赖于此,例如模板方法模式。简而言之,这允许您将通过类层次结构描述的行为的某些方面指定为不变的,而让该行为的其他方面根据您在给定点引用的特定实例类型而变化。

是否它是一个好或不取决于你的精确用例:它是有意义的,你在所有的基类中共享浮点成员数据存储的实现?这是一个有点难回答的例子,你张贴作为派生类不依赖于my_float以任何方式,但有吨的情况下,这是有意义的,是一个很好的方式来分割你的类层次结构的责任。

即使在跨类共享细节实现确实有意义的情况下,您也有几个其他选择,例如使用组合来共享功能。与通过组合共享功能相比,通过基类共享功能通常可以减少冗余,因为它允许您共享实现和接口。为了说明,您的解决方案比使用组合的替代方案具有更少的重复代码:

class DataStorage {
private:
  float data_;
public:
  DataStorage()
  : data_(0.f) {
  }
  void setFloat(float data) {
    data_ = data;
  }
};
class NotASubclass1 {
private:
  DataStorage data_;
public:
  void SetFloat(float value){ data_.setFloat(value); }  
  ...
}
class NotASubclass2 {
private:
  DataStorage data_;
public:
  void SetFloat(float value){ data_.setFloat(value); }  
  ...
}

能够使某些函数非虚有一定的好处,其中许多是密切相关的:

  • 您可以修改它们,知道通过Base*/Base&调用将使用您修改的代码,而不管Base*指向什么实际派生类型

    • 例如,您可以收集所有Base*/& s的性能测量值,而不管它们的派生

    • 非虚拟接口(NVI)方法的目标是"两全其美"——非虚拟函数调用非公共虚拟函数,给你一个单一的地方通过Base中的Base*/&拦截调用,以及可定制性

  • 对非虚函数的调用可能会更快——如果内联的话,对于像get/set这样的小函数来说,速度可能会快一个数量级

  • 您可以确保从Base派生的所有对象的不变性,有选择地封装一些私有数据和影响它的函数(在c++ 11中引入的final关键字允许您在层次结构中进一步这样做)

  • Base类中"最终确定"数据/功能有助于理解和推理类行为,并且分解使代码总体上更简洁,但必然以令人失望的灵活性和不可预见的重用为代价调到品味