我有一个基类作为接口和多个派生类。我有2个util函数,每个派生类方法都需要使用。所以我不能将util函数放在派生类中,因为它将是代码的重复。因此,一种选择是我可以将它们放入单独的util名称空间,但是我不希望它公开给客户端。因此,这些方法需要隐藏在某个地方。我应该设计什么,或者应该如何使用这些功能?
答案 0 :(得分:2)
有些人希望接口基类是空的纯虚拟抽象类,这对他们来说可能感觉像是“正确的设计”,但是抽象级别导致实现本身的效率更低。但是,如果您真的想让您的接口类完全抽象,只需在所有实现的基础上再添加一层,即可得出:
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[]()
在一个数组中使用了数千次)紧密循环:如果所有派生对象均具有指向数据分配位置的private
指针,则只需将该指针放在基类中并实现operator[]()
的单个非多态版本即可访问基于该指针的适当元素)。
如果由于类型的性质,某些功能(及其关联的数据)将始终在其所有派生类之间是通用的,那么降低开销的事情是放弃使接口类完全抽象的理想,并将该通用功能(及其关联的数据)移到接口类(现在还不是完全抽象的)中,而不要使用方法virtual
。我知道这激怒了一些人的神经,但实际上,如果不需要的话,为什么要付出多态性的代价?如果事实证明某些派生实际上确实需要其他功能,则可以随时对其进行重构。
Not-fully-abstract "interface" base class
^ ^ ^
| | |
| | |
Impl1 Impl2 Impl3
请注意,如果这样做,在那些非多态方法中仍然可以具有特定于派生的行为,因为非多态方法可以在必要时调用其他属于 的类方法。