C++中的虚拟继承

Vij*_*jay 12 c++ inheritance multiple-inheritance virtual-inheritance object-layout

我在阅读c ++中的虚拟继承时在网站上发现了这个

使用多重继承时,有时需要使用虚拟继承.一个很好的例子是标准的iostream类层次结构:

//Note: this is a simplified description of iostream classes

class  ostream: virtual public ios { /*..*/ }
class  istream: virtual public ios { /*..*/ }

class iostream : public istream, public ostream { /*..*/ } 
//a single ios inherited
Run Code Online (Sandbox Code Playgroud)

C++如何确保只存在虚拟成员的单个实例,而不管从中派生的类的数量是多少?C++使用额外的间接级别来访问虚拟类,通常是通过指针.换句话说,iostream层次结构中的每个对象都有一个指向ios对象的共享实例的指针.额外的间接级别有轻微的性能开销,但这是一个很小的代价.

我对声明感到困惑:

C++使用额外的间接级别来访问虚拟类,通常是通过指针

任何人都能解释一下吗?

Dav*_*eas 9

要解决的基本问题是,如果将指向最派生类型的指针强制转换为指向其中一个基类的指针,则指针必须引用内存中的地址,通过该代码可以找到该类型的每个成员.知道派生类型.对于非虚拟继承,这通常通过具有精确布局来实现,而这通过包含基类子对象然后添加派生类型的额外位来实现:

struct base { int x; };
struct derived : base { int y };
Run Code Online (Sandbox Code Playgroud)

派生的布局:

--------- <- base & derived start here
    x
---------
    y
---------
Run Code Online (Sandbox Code Playgroud)

如果添加第二个派生类型和最多派生类型(再次,没有虚拟继承),您将获得如下内容:

struct derived2 : base { int z; };
struct most_derived : derived, derived 2 {};
Run Code Online (Sandbox Code Playgroud)

有了这个布局:

--------- <- derived::base, derived and most_derived start here
    x
---------
    y
--------- <- derived2::base & derived2 start here
    x
---------
    z
---------
Run Code Online (Sandbox Code Playgroud)

如果你有一个most_derived对象并绑定了一个类型的指针/引用,derived2它将指向标有的行derived2::base.现在,如果从base继承是虚拟的,那么应该有一个实例base.为了便于讨论,假设我们天真地删除了第二个base:

--------- <- derived::base, derived and most_derived start here
    x
---------
    y
--------- <- derived2 start here??
    z
---------
Run Code Online (Sandbox Code Playgroud)

现在的问题是,如果我们获得指向derived它的指针具有与原始布局相同的布局,但是如果我们试图获得指向derived2布局的指针会有所不同,并且代码derived2将无法找到该x成员.我们需要做一些更聪明的事情,这就是指针发挥作用的地方.通过添加指向虚拟继承的每个对象的指针,我们得到了这个布局:

---------  <- derived starts here
base::ptr  --\
    y        |  pointer to where the base object resides
---------  <-/
    x
---------
Run Code Online (Sandbox Code Playgroud)

同样的derived2.现在,以额外间接为代价,我们可以x通过指针找到子对象.当我们可以most_derived使用单个基础创建布局时,它可能如下所示:

---------          <- derived starts here
base::ptr  -----\
    y           |
---------       |  <- derived2
base::ptr  --\  |
    z         | |
---------  <--+-/  <- base
    x
---------
Run Code Online (Sandbox Code Playgroud)

现在编写代码derived并注意derived2如何访问基础子对象(只需取消引用base::ptr成员对象),同时您只有一个实例base.如果中间类访问中的代码x可以通过执行this->[hidden base pointer]->x,并且将在运行时解析到正确的位置.

这里重要的一点是在derived/ derived2layer 编译的代码可以与该类型的对象或任何派生对象一起使用.如果我们编写了第二个most_derived2对象,其中继承的顺序被颠倒了,那么它们的布局yz可以交换,并且从指向子对象derivedderived2子对象的指针的偏移量base将是不同的,但访问的代码x仍然是相同的:取消引用您自己的隐藏基指针,保证如果一个方法derived是最终的覆盖,并且该访问base::x然后无论最终布局如何都会找到它.


Fré*_*idi 8

基本上,如果不使用虚拟继承,则基本成员实际上是派生类实例的一部分.基本成员的内存在每个实例中分配,访问它们不需要进一步的间接寻址:

class Base {
public:
    int base_member;
};

class Derived: public Base {
public:
    int derived_member;
};


Derived *d = new Derived();
int foo = d->derived_member;  // Only one indirection necessary.
int bar = d->base_member;     // Same here.
delete d;
Run Code Online (Sandbox Code Playgroud)

但是,当虚拟继承发挥作用时,虚拟基本成员由其继承树中的所有类共享,而不是在多次继承基类时创建多个副本.在你的榜样,iostream只包含一个共享副本ios的成员,尽管它继承了他们两次从两个istreamostream.

class Base {
public:
    // Shared by Derived from Intermediate1 and Intermediate2.
    int base_member;  
};

class Intermediate1 : virtual public Base {
};

class Intermediate2 : virtual public Base {
};

class Derived: public Intermediate1, public Intermediate2 {
public:
    int derived_member;
};
Run Code Online (Sandbox Code Playgroud)

这意味着需要额外的间接步骤才能访问虚拟基本成员:

Derived *d = new Derived();
int foo = d->derived_member;  // Only one indirection necessary.
int bar = d->base_member;     // Roughly equivalent to
                              // d->shared_Base->base_member.
delete d;
Run Code Online (Sandbox Code Playgroud)