28

EDIT3:请务必在回答之前清楚地理解我的要求(有EDIT2和很多评论)。有(或曾经)很多答案清楚地表明了对问题的误解(我知道这也是我的错,对此感到抱歉)

嗨,我已经查看了有关class B: public virtual A {...}C++ 中虚拟继承 ( ) 的问题,但没有找到我的问题的答案。

我知道虚拟继承存在一些问题,但我想知道的是在哪些情况下虚拟继承会被认为是一个好的设计。

我看到人们提到类似IUnknownor的接口ISerializable,而且这种iostream设计是基于虚拟继承的。这些是否是很好地使用虚拟继承的好例子,仅仅是因为没有更好的选择,还是因为在这种情况下虚拟继承正确的设计?谢谢。

编辑:为了澄清,我问的是现实生活中的例子,请不要给出抽象的例子。我知道什么是虚拟继承以及哪种继承模式需要它,我想知道什么时候它是做事的好方法,而不仅仅是复杂继承的结果。

EDIT2:换句话说,我想知道菱形层次结构(这是虚拟继承的原因)何时是一个好的设计

4

7 回答 7

26

如果您有接口层次结构和相应的实现层次结构,则有必要使接口基类成为虚拟基类。

例如

struct IBasicInterface
{
    virtual ~IBasicInterface() {}
    virtual void f() = 0;
};

struct IExtendedInterface : virtual IBasicInterface
{
    virtual ~IExtendedInterface() {}
    virtual void g() = 0;
};

// One possible implementation strategy
struct CBasicImpl : virtual IBasicInterface
{
    virtual ~CBasicImpl() {}
    virtual void f();
};

struct CExtendedImpl : virtual IExtendedInterface, CBasicImpl
{
    virtual ~CExtendedImpl() {}
    virtual void g();
};

通常,只有当您有许多扩展基本接口的接口以及在不同情况下所需的多个实现策略时,这才有意义。这样你就有了一个清晰的接口层次结构,你的实现层次结构可以使用继承来避免常见实现的重复。但是,如果您使用的是 Visual Studio,则会收到很多警告 C4250。

为了防止意外切片,通常最好是CBasicImplCExtendedImpl类不是可实例化的,而是具有更高级别的继承,不提供额外的功能,只保存一个构造函数。

于 2011-01-05T16:03:32.587 回答
3

当一个类 A 扩展另一个类 B 但 B 除了可能的析构函数之外没有虚拟成员函数时,虚拟继承是一个很好的设计选择。您可以将像 B 这样的类视为mixins,其中类型层次结构只需要一个 mixin 类型的基类即可从中受益。

一个很好的例子是在 STL 的 libstdc++ 实现中与一些 iostream 模板一起使用的虚拟继承。例如,libstdc++ 声明模板basic_istream

template<typename _CharT, typename _Traits>
class basic_istream : virtual public basic_ios<_CharT, _Traits>

它使用虚拟继承进行扩展basic_ios<_CharT, _Traits>,因为 istream 应该只有一个输入 streambuf,并且 istream 的许多操作应该始终具有相同的功能(特别是rdbuf获取唯一输入 streambuf 的成员函数)。

现在假设您编写了一个类 ( baz_reader),该类 ()std::istream使用成员函数扩展以读取类型的对象baz,另一个类 ( bat_reader)std::istream使用成员函数扩展以读取类型的对象bat。您可以拥有一个同时扩展baz_reader和的类bat_reader。如果不使用虚拟继承,那么baz_readerbat_reader基将每个都有自己的输入流缓冲区——可能不是本意。您可能希望baz_readerbat_reader基都从同一个 streambuf 中读取。std::istream如果没有要扩展的虚拟继承std::basic_ios<char>,您可以通过设置 and 的成员 readbufs 来实现baz_readerbat_reader基于相同的 streambuf 对象,但是当一个就足够时,您将拥有指向 streambuf 的指针的两个副本。

于 2011-01-05T15:51:03.393 回答
1

Grrr .. 虚拟继承必须用于抽象子类型化。如果要遵循 OO 的设计原则,就没有选择的余地。不这样做会阻止其他程序员派生其他子类型。

首先是一个抽象示例:您有一些基本抽象 A。您想创建一个子类型 B。请注意子类型必然意味着另一个抽象。如果它不是抽象的,那么它是一个实现而不是一个类型。

现在另一个程序员来了,想要制作一个 A.Cool 的子类型 C。

最后,又一个程序员出现了,想要一个既是 B 又是 C 的东西。当然也是 A。在这些场景中,虚拟继承是强制性的。

这是一个真实世界的示例:来自编译器,对数据类型进行建模:

struct function { ..
struct int_to_float_type : virtual function { ..

struct cloneable : virtual function { .. 

struct cloneable_int_to_float_type : 
  virtual function, 
  virtual int_to_float_type 
  virtual cloneable 
{ ..

struct function_f : cloneable_int_to_float_type { 

在这里,function表示函数,int_to_float_type表示由从 int 到 float 的函数组成的子类型。Cloneable是函数可以被克隆的特殊属性。function_f是一个具体的(非抽象的)函数。

请注意,如果我最初没有创建function虚拟基础,则int_to_float_type无法混入cloneable(反之亦然)。

一般来说,如果你遵循“严格”的 OOP 风格,你总是定义一个抽象网格,然后为它们派生实现。您严格区分仅适用于抽象和实现的子类型

在 Java 中,这是强制执行的(接口不是类)。在 C++ 中,它不是强制执行的,您也不必遵循模式,但您应该意识到这一点,并且您正在与之合作的团队或您正在从事的项目越大,理由就越充分你需要离开它。

Mixin 类型需要在 C++ 中进行大量的内务处理。在 Ocaml 中,类和类类型是独立的,并通过结构(是否拥有方法)匹配,因此继承总是很方便。这实际上比名义打字更容易使用。Mixins 提供了一种方法来模拟仅具有名义类型的语言中的结构类型。

于 2011-01-05T16:28:50.843 回答
0

虚拟继承不是一件好事或坏事——它是一个实现细节,就像其他任何东西一样,它的存在是为了实现发生相同抽象的代码。当代码必须是超级运行时,这通常是正确的做法,例如,在 COM 中,一些 COM 对象必须在进程之间共享,更不用说编译器等,需要使用 IUnknown,而普通 C++ 库只会使用shared_ptr. 因此,在我看来,普通的 C++ 代码应该依赖于模板和类似的,不应该需要虚拟继承,但在某些特殊情况下是完全必要的。

于 2011-01-05T16:40:32.020 回答
0

由于您要求提供具体示例,因此我建议使用侵入式引用计数。在这种情况下,并不是说虚拟继承是好的设计,而是虚拟继承是使这项工作正常工作的正确工具。

在 90 年代初期,我使用了一个类库,该类库具有一个ReferenceCounted其他类将从中派生的类,为它提供引用计数和一些管理引用计数的方法。继承必须是虚拟的,否则如果您有多个基础,每个基础都非虚拟派生自ReferenceCounted,您最终会得到多个引用计数。虚拟继承确保您的对象只有一个引用计数。

如今,非侵入式引用计数shared_ptr和其他方法似乎更受欢迎,但是当类传递this给其他方法时,侵入式引用计数仍然有用。在这种情况下,外部引用计数会丢失。我也喜欢侵入式引用计数说明一个类如何管理该类对象的生命周期。

[我认为我在为侵入式引用计数辩护,因为这些天我很少看到它,但我喜欢它。]

于 2011-01-06T05:16:14.137 回答
-3

当您被迫使用多重继承时,需要虚拟继承。有一些问题不能通过避免多重继承来干净/容易地解决。在这些情况下(很少见),您将需要查看虚拟继承。95% 的情况下,您可以(并且应该)避免多重继承,以免您(以及那些在您之后查看您的代码的人)感到头疼。

附带说明一下,COM 不会强制您使用多重继承。可以(并且很常见)创建从 IUnknown(直接或间接)派生​​的具有线性继承树的 COM 对象。

于 2011-01-05T15:25:22.330 回答
-3

这些常见问题解答回答了与虚拟继承相关的所有可能问题。甚至是您问题的答案(如果我正确识别了您的问题;)):FAQ item 25.5

于 2011-01-06T06:47:23.480 回答