首页 > 解决方案 > 在 C++ 层次结构中放置 util 方法的位置

问题描述

我有一个基类作为接口和多个派生类。我有 2 个 util 函数,每个派生类方法都需要使用它们。所以我不能将 util 函数放在派生类中,因为它会重复代码。所以一种选择是我可以将它们放入单独的 util 命名空间中,但我不希望它暴露给客户端。所以这些方法需要隐藏在某个地方。我的设计应该是什么,或者我应该如何使用这些功能?

标签: c++oop

解决方案


有些人希望接口基类是空的纯虚拟抽象类,这对他们来说可能感觉像是“适当的设计”,但是这种抽象级别会导致实现本身的效率更低。但是,如果您真的想保持您的接口类完全抽象,只需在其之上添加另一层,然后所有实现都从该层派生:

Abstract pure virtual interface base class
        ^
        |
        |
Implementation base class: common things, including your utility functions
  ^            ^             ^
  |            |             |
  |            |             |
Impl1         Impl2         Impl3

-------- ...in code: --------

class IThing
{
public:
   virtual void runAround();
   virtual void makeAMess();
};

class ThingBase : public IThing
{
public:
   // runAround() is not implemented here; implement it in derived classes
   virtual void makeAMess()
   {
      // ...default implementation of makeAMess(), which a derived class
      // can override if necessary...
   }
private:
   int utilFunc1(int a) { return ...whatever...; } // not virtual! (or, it could be, if necessary)
   int utilFunc2(int a) { return ...whatever...; } // not virtual! (or, it could be, if necessary)
}

class Thing1 : public ThingBase
{
public:
   virtual void runAround() { /* ...special code for Thing1... */ }
   // Thing1 uses the default implementation of makeAMess()
}

class Thing2 : public ThingBase
{
public:
   virtual void runAround() { /* ...special code for Thing2... */ }
   // Thing2 uses the default implementation of makeAMess()
}

class Thing3 : public ThingBase
{
public:
   virtual void runAround() { /* ...special code for Thing3... */ }
   virtual void makeAMess() { /* ...special code for Thing3... */ }
}

如果所有实现之间有任何共同的数据或功能,请务必将其移至上面的实现基类。将其保留在每个派生实现之外。但是,如果该功能(方法)是接口方法的实现,那么为了调用它,您将不必要地为所有用户增加多态方法调用的(小)开销:

每个派生的 vtable 都会有一个用于该公共函数的额外函数指针,当从接口指针/引用调用时必须以多态方式调用该函数,即使该方法只有一个实现。每次这样的调用都需要额外的两三个指令,以及额外的内存提取,因为它是多态的。额外的开销通常不会被认为太多,但它是额外的开销,在某些情况下它可能很重要(例如operator[](),当在紧密循环中使用数千次时,“Array”类的多态:如果所有派生将有一个private指向其数据分配位置的指针,然后您可以将该指针放在基类中并实现单个非多态版本operator[]()基于该指针访问适当的元素)。

如果由于您的类型的性质,某些功能(及其相关数据)在它的所有派生中总是通用的,那么开销较低的事情是放弃使您的接口类完全抽象的理想,并移动它将通用功能(及其相关数据)放入接口类(现在不是完全抽象的),并且不要将方法virtual. 我知道这会刺激一些人的神经,但实际上,如果不需要,为什么要支付多态性的成本呢?如果事实证明某些派生确实需要不同的功能,您可以随时对其进行重构。

Not-fully-abstract "interface" base class
  ^            ^             ^
  |            |             |
  |            |             |
Impl1         Impl2         Impl3

请注意,如果您这样做,您仍然可以在那些非多态方法中具有特定于派生的行为,因为非多态方法可以在必要时调用其他多态类方法


推荐阅读