什么时候虚拟继承是一个好的设计?

发布于 2024-10-10 04:27:21 字数 532 浏览 0 评论 0原文

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

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

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

我看到人们提到了诸如 IUnknown 或 ISerialized 之类的接口,而且 iostream 设计是基于虚拟继承的。这些是很好地使用虚拟继承的好例子吗?这只是因为没有更好的选择,还是因为虚拟继承在这种情况下是正确的设计?谢谢。

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

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

EDIT3: Please be sure to clearly understand what I am asking before answering (there are EDIT2 and lots of comments around). There are (or were) many answers which clearly show misunderstanding of the question (I know that's also my fault, sorry for that)

Hi, I've looked over the questions on virtual inheritance (class B: public virtual A {...}) in C++, but did not find an answer to my question.

I know that there are some issues with virtual inheritance, but what I'd like to know is in which cases virtual inheritance would be considered a good design.

I saw people mentioning interfaces like IUnknown or ISerializable, and also that iostream design is based on virtual inheritance. Would those be good examples of a good use of virtual inheritance, is that just because there is no better alternative, or because virtual inheritance is the proper design in this case? Thanks.

EDIT: To clarify, I'm asking about real-life examples, please don't give abstract ones. I know what virtual inheritance is and which inheritance pattern requires it, what I want to know is when it is the good way to do things and not just a consequence of complex inheritance.

EDIT2: In other words, I want to know when the diamond hierarchy (which is the reason for virtual inheritance) is a good design

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

汐鸠 2024-10-17 04:27:21

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

例如,

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 类不可实例化,而是具有更高级别的继承,不提供额外的功能(除了构造函数)。

If you have an interface hierarchy and a corresponding implementation hierarchy, making the interface base classes virtual bases is necessary.

E.g.

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();
};

Usually this only makes sense if you have a number of interfaces that extend the basic interface and more than one implementation strategy required in different situations. This way you have a clear interface hierarchy and your implementation hierarchies can use inheritance to avoid the duplication of common implementations. If you're using Visual Studio you get a lot of warning C4250, though.

To prevent accidental slicing it is usually best if the CBasicImpl and CExtendedImpl classes aren't instantiable but instead have a further level of inheritance providing no extra functionality save a constructor.

罪歌 2024-10-17 04:27:21

当类 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 成员函数)。

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

Virtual inheritance is a good design choice for the case when a class A extends another class B, but B has no virtual member functions other than possibly the destructor. You can think of classes like B as mixins, where a type hierarchy needs only one base class of the mixin type in order to benefit from it.

One good example is the virtual inheritance that is used with some of the iostream templates in the libstdc++ implementation of the STL. For example, libstdc++ declares template basic_istream with:

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

It uses virtual inheritance to extend basic_ios<_CharT, _Traits> because istreams should only have one input streambuf, and many operations of an istream should always have the same functionality (notably the rdbuf member function to get the one and only input streambuf).

Now imagine that you write a class (baz_reader) that extends std::istream with a member function to read in objects of type baz, and another class (bat_reader) that extends std::istream with a member function to read in objects of type bat. You can have a class that extends both baz_reader and bat_reader. If virtual inheritance were not used, then the baz_reader and bat_reader bases would each have their own input streambuf—probably not the intent. You would probably want the baz_reader and bat_reader bases to both read from the same streambuf. Without virtual inheritance in std::istream to extend std::basic_ios<char>, you could accomplish that by setting the member readbufs of the baz_reader and bat_reader bases to the same streambuf object, but then you would have two copies of the pointer to the streambuf when one would suffice.

┈┾☆殇 2024-10-17 04:27:21

Grrr .. 虚拟继承必须用于抽象子类型。如果要遵守面向对象的设计原则,就完全别无选择。如果不这样做,就会阻止其他程序员派生其他子类型。

首先是一个抽象示例:您有一些基本抽象 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 提供了一种在只有名义类型的语言中模拟结构类型的方法。

Grrr .. Virtual inheritance MUST be used for abstraction subtyping. There is utterly no choice if you are to obey the design principles of OO. Failing to do so prevents other programmers deriving other subtypes.

An abstract example first: you have some base abstraction A. You want to make a subtype B. Please note subtype necessarily means another abstraction. If it isn't abstract, it is an implementation not a type.

Now another programmer comes along and wants to make a subtype C of A. Cool.

Finally, yet another programmer comes along and wants something which is both a B and a C. It's also an A of course. In these scenarios virtual inheritance is mandatory.

Here's a real world example: from a compiler, modelling data types:

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 { 

Here, function represents functions, int_to_float_type represents a subtype
consisting of functions from int to float. Cloneable is a special property
that the function can be cloned. function_f is a concrete (non-abstract)
function.

Note that if I did not originally make function a virtual base of int_to_float_type I could not mixin cloneable (and vice versa).

In general, if you follow "strict" OOP style, you always define a lattice of abstractions, and then implementations are derived for them. You separate strictly subtyping which only applies to abstractions, and implementation.

In Java, this is enforced (interfaces are not classes). In C++ it isn't enforced, and you don't have to follow the pattern, but you should be aware of it, and the larger the team you're working with, or project you're working on, the stronger the reason you will need to depart from it.

Mixin typing requires a lot of housekeeping in C++. In Ocaml, classes and class types are independent and matched by structure (possession of methods or not) so inheritance is always a convenience. This is actually much easier to use than nominal typing. Mixins provide a way to simulate structural typing in a language that only has nominal typing.

我为君王 2024-10-17 04:27:21

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

Virtual inheritance is not a good or bad thing- it is an implementation detail, just like any other, and it exists to implement code where the same abstractions occur. This is typically the correct thing to do when code must be super-runtime, for example, in COM where some COM objects have to be shared between processes, let alone compilers and suchlike, necessitating the use of IUnknown where normal C++ libraries would simply use shared_ptr. As such, in my opinion, normal C++ code should depend on templates and similar and should not require virtual inheritance, but it is totally necessary in some special cases.

霞映澄塘 2024-10-17 04:27:21

既然您要求提供具体示例,我建议使用侵入式引用计数。在这种情况下,虚拟继承并不是一个好的设计,但虚拟继承是使该工作正常工作的正确工具。

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

使用 shared_ptr 和其他方法的非侵入式引用计数现在似乎更流行,但是当类将 this 传递给其他方法时,侵入式引用计数仍然有用。在这种情况下,外部引用计数会丢失。我还喜欢侵入式引用计数,它讲述了一个类如何管理该类的对象的生命周期。

[我想我是在捍卫侵入式引用计数,因为我现在很少看到它,但我喜欢它。]

Since you're asking for specific examples, I'll suggest intrusive reference counting. It's not that virtual inheritance is good design in this case, but virtual inheritance is the right tool for the job to make it work correctly.

In the early '90s I used a class library that had a ReferenceCounted class that other classes would derive from to give it a reference count and a couple of methods for managing the reference count. The inheritance had to be virtual, otherwise if you had multiple bases that each derived non-virtually from ReferenceCounted, you'd end up with multiple reference counts. The virtual inheritance ensured you'd have a single reference count for your objects.

Non-intrusive reference counting with shared_ptr and others seems to be more popular these days, but intrusive reference counting is still useful when a class passes this to other methods. External reference counts are lost in this case. I also like that intrusive reference counting says about a class how the lifecycle of objects of that class are managed.

[I think I'm defending intrusive reference counting because I see it so rarely these days, but I like it.]

棒棒糖 2024-10-17 04:27:21

当您被迫使用多重继承时,就需要虚拟继承。有些问题无法通过避免多重继承来干净/轻松地解决。在这些情况下(这种情况很少见),您将需要考虑虚拟继承。 95% 的情况下,您可以(并且应该)避免多重继承,以避免为您自己(以及在您之后查看代码的人)带来许多麻烦。

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

Virtual Inheritance is needed when you are forced to use multiple inheritance. There are some problems that cannot be cleanly/easily solved by avoiding multiple inheritance. In those cases (which are rare), you will need to look at virtual inheritance. 95% of the time, you can (and should) avoid multiple inheritance to save yourself (and those looking at your code after you) many headaches.

As a side note, COM does not force you to use multiple inheritance. It is possible (and quite common) to create a COM object derived from IUnknown (directly or indirectly) that has a linear inheritance tree.

夜唯美灬不弃 2024-10-17 04:27:21

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

These FAQs answered all possible questions related to virtual inheritance. Even the answer to your question (if I recognized your questions correctly ;) ) : FAQ item 25.5

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文