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

5

我有一个基类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"函数,但在其他函数方面则可以实现多态性。

因此,我想知道这是否是良好的实践。如果您有一个抽象的基类,应该使其完全抽象还是采用这种混合方法可以接受?


2
这对我来说很正常。我每天都使用它们。 - Bryan Chen
你可以做到这一点。我感觉这里没有糟糕的编程。这就是为什么有纯虚函数存在的原因。 - Abhineet
2个回答

6
这是一种常见的做法。实际上,一些著名的设计模式依赖于此,例如 模板方法模式。简而言之,这使您能够通过类层次结构指定行为的某些方面作为不变量,同时让该行为的其他方面因特定类型的实例而异。
它是否好取决于您的精确用例:在所有基类中共享浮点成员数据存储的实现是否有意义?由于派生类没有以任何方式依赖于 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); }  
  ...
}

2
能够使某些函数非虚函数有一定的好处,这些好处之间有很强的相关性:
- 您可以修改它们,知道通过 Base*/Base& 调用将使用您修改后的代码,而不管实际派生类型是什么。 - 例如,您可以收集所有 Base*/& 的性能测量数据,而不管它们的派生关系。 - 非虚接口(NVI)方法旨在实现“两全其美”——非虚函数调用非公共虚函数,为您提供了一个拦截通过 Base*/&Base 中调用的单一位置以及可定制性。
- 对非虚函数的调用可能会更快——如果内联,对于像获取/设置几个字节字段这样的微不足道的函数,速度可以提高一个数量级。
- 您可以确保从 Base 派生的所有对象的不变量,选择性地封装一些私有数据和影响它的函数(C++11 中引入的 final 关键字让您可以在层次结构的较低级别上进行此操作)。
- 在 Base 类中“最终确定”的数据/功能有助于理解和推断类行为,而这种分解使整体代码更加简洁,但必然会损失灵活性和意外重用——需要根据个人口味进行调整。

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