我正在尝试完成一些学校作业,我刚刚注意到在乘法派生类中重用基类中的函数可能会导致问题。
假设我有这些类:
class A
class B: public A
class C: public A
class D: public B, public C
每个类都有此方法:
virtual void read(ifstream& in);
在class B
和class C
中,read()
函数也调用class A::read()
:
void B::read(ifstream& in)
{
A::read(in);
/* read method of B*/
}
void C::read(ifstream& in)
{
A::read(in);
/* read method of C*/
}
现在,问题是,当我想为 class D
创建一个 read()
函数时,我实际上调用了A::read()
两次:
void D::read(ifstream& in)
{
B::read(in);
C::read(in);
/* read method of D*/
}
我知道我只能在一个类(B
或C
)中使用A::read()
的选项,但假设我需要在两个类中使用它。
这是为什么不鼓励多重继承(尤其是来自共同祖先的多重继承)的一个例子。不是因为它总是不好 - 尽管它经常是!- 但更多的是因为它通常很困难。如果你能找到替代方案,那通常是可取的。不一定。我相信你会考虑这个问题,并决定它是否是最好的设计。但就目前而言,我们在这里寻找避免重复A::read()
和其他陷阱的方法。
我首先与著名的可怕的末日钻石进行类比——并不像传说中所说的那么可怕,但值得牢记。这说明,当解决由这种菱形继承层次结构创建的"重复基成员"问题时,通过使用虚拟继承 - 派生类现在完全负责调用其所有虚拟基的所有构造函数。构造函数调用不像正常情况那样向上链接,数据成员初始化很奇怪。谷歌它!
注:注:如果菱形层次结构的顶级类具有任何数据成员,则应使用虚拟继承,以避免重复/引入歧义。这就是它的用途。但是回到主要主题,我使用它来类比函数(并不严格要求它)。
这个想法是从虚拟继承要求最终类手动调用虚拟基的构造函数中汲取灵感,方法是以相同的方式处理派生类的read()
行为:通过使每个派生类面向公众的read()
方法完全负责调用所有基类来避免重复调用。这也使您可以很好地控制调用哪些碱基的方法 - 还可以控制它们的顺序。
如何?我们可以分解每个派生read()
的实际工作,以在每个类中受保护的"impl
ementation"函数,并在每个最终类中提供公共覆盖的"包装函数"。然后,包装器函数将负责调用其各自类的impl
和任何所需基的,以您想要的任何顺序:
class A {
protected:
void read_impl(ifstream &in) { /* A-specific stuff */ }
public:
virtual void read(ifstream &in)
{
read_impl(in);
}
};
class B: public A { // N.B. virtual public if A has data members!
protected:
void read_impl(ifstream &in) { /* B-specific stuff */ }
public:
virtual void read(ifstream &in)
{
A::read_impl(in);
read_impl(in); // B
}
};
class C: public A {
protected:
void read_impl(ifstream &in) { /* C-specific stuff */ }
public:
virtual void read(ifstream &in)
{
A::read_impl(in);
read_impl(in); // CMy
}
};
class D: public B, public C {
protected:
void read_impl(ifstream &in) { /* D-specific stuff */ }
public:
virtual void read(ifstream &in)
{
A::read_impl(in);
B::read_impl(in); // avoids calling A again from B
C::read_impl(in); // ditto from C
read_impl(in); // D
}
};
有了这个,您可以完全控制每个最终课程完成哪些基本内容,以及何时完成,而无需不必要的重复调用。就 DRY 而言,impl
函数意味着中间类不会重复行为代码:每个派生read()
中的编写都是关于它们如何编排基础行为的有用声明性信息。您还可以在两者之间添加额外的内容等。
解决此问题的一种非常非通用的方法是使用布尔值,只需为每个类(B &&C)切换一个开关,以说明我是否应该调用超类的函数。解决它的另一种方法是不要在 B 或 C 中调用 A 的函数。