不考虑性能,当虚拟继承不能代替正常继承时?



如果我们不关心性能,似乎虚拟继承可以随时取代正常继承。是否存在我们只能使用正常继承的实际情况?在我看来,当我们使用继承时,我们总是希望派生类可以转换为基类。但是在常规继承中,我们仍然使派生类的数据由基类的数据组合而成。尽管这样的组合使得我们可以将派生类转换为基类,但这并不是必需的。

除了性能问题(通过virtual基类在每次virtual函数调用上都要付出代价),将所有内容都变成virtual基类有两个主要问题。

首先,每个派生类必须初始化它所使用的所有虚基类。所有,一直往上。这是一个巨大的麻烦;每次为派生类编写新的派生类或甚至只是为派生类编写新的构造函数时,现在可能都必须手动转发一堆参数。更糟糕的是,如果层次结构中间的类型需要一个新的基类,每个派生类都必须知道它,并可能调用它的特定构造函数。

相反,对于非virtual继承,层次结构中的每个类负责初始化其基类,而不必使派生类甚至知道这些基类的存在。这使得非virtual继承更容易使用,并且对组合更友好。和易于维护。

第二,如果你一直这样做,你可能会遇到菱形继承是而不是你想要的的情况。仅仅因为两个类碰巧继承了相同的基类,并不意味着从这两个类派生的类想要将该基类合并为单个实例。实际上,virtual继承不是默认的原因之一是这种情况实际上非常罕见。

我知道该怎么做了。想想一个经典的菱形继承的例子。

B继承自A。C遗传自A。D继承自B和c。

A由impA组成。B由impA和B的扩展组成,我称之为impB。C是由impA和C中的扩展组成的,我称之为impC。

D应由impA、impB、impC和impD组成。但是在正常继承中,D由两次impA、一次impB、一次impC和impD组成。impA出现了两次,这就是菱形问题。为了解决这个问题,我们可以使用虚拟继承。

对于虚拟继承是违反直觉的(就像这个问题中的人一样),有一个更好的方法来解决这个问题。我们可以写impA impB impC和impD。然后使A继承自impA。B继承自impA和impB。C继承自impA和impC。D继承自impA, impB, impC和impD。那么问题就解决了。

struct impA {
void a() {}
};
template <typename derived>
struct impB {
void b() {
auto&& d = static_cast<derived&>(*this);
d.a();
d.a();
}
};
template <typename derived>
struct impC {
void c() {
auto&& d = static_cast<derived&>(*this);
d.a();
d.a();
}
};

template <typename derived>
struct impD {
void d() {
auto&& d = static_cast<derived&>(*this);
d.a();
d.b();
d.c();
}
};
struct A : public impA {};
struct B : public impA, impB<B> {};
struct C : public impA, impC<C> {};
struct D : public impA, impB<D>, impC<D>, impD<D> {};
int main() {
A{}.a();
B{}.a();
B{}.b();
C{}.a();
C{}.c();
D{}.a();
D{}.b();
D{}.c();
D{}.d();
}

最新更新