C ++中的虚拟继承

时间:2011-03-03 11:02:57

标签: 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

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

我对声明感到困惑:

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

任何人都可以解释一下吗?

4 个答案:

答案 0 :(得分:9)

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

struct base { int x; };
struct derived : base { int y };

派生的布局:

--------- <- base & derived start here
    x
---------
    y
---------

如果你添加第二个派生类型和最多派生类型(再次,没有虚拟继承),你会得到类似的东西:

struct derived2 : base { int z; };
struct most_derived : derived, derived 2 {};

采用这种布局:

--------- <- derived::base, derived and most_derived start here
    x
---------
    y
--------- <- derived2::base & derived2 start here
    x
---------
    z
---------

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

--------- <- derived::base, derived and most_derived start here
    x
---------
    y
--------- <- derived2 start here??
    z
---------

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

x

同样适用于--------- <- derived starts here base::ptr --\ y | pointer to where the base object resides --------- <-/ x --------- 。现在,以额外间接为代价,我们可以通过指针找到derived2子对象。当我们可以使用单个基础创建x布局时,它可能如下所示:

most_derived

现在--------- <- derived starts here base::ptr -----\ y | --------- | <- derived2 base::ptr --\ | z | | --------- <--+-/ <- base x --------- derived中的代码如何访问基础子对象(只是取消引用derived2成员对象),同时你有一个{{1}实例1}}。如果中间类中的代码访问base::ptr,则可以通过执行base来执行此操作,并且这将在运行时解析到正确的位置。

这里重要的一点是,在x / this->[hidden base pointer]->x层编译的代码可以与该类型的对象或任何派生对象一起使用。如果我们编写了第二个derived对象,其中继承的顺序被颠倒了,那么derived2most_derived2的布局可以被交换,并且指向{{1}的指针的偏移量。 }或y子对象到z子对象会有所不同,但访问derived的代码仍然是相同的:取消引用您自己的隐藏基指针,保证如果{ {1}}是最终的覆盖者,访问derived2然后无论最终布局如何都会找到它。

答案 1 :(得分: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;

但是,当虚拟继承发挥作用时,虚拟基本成员将由其继承树中的所有类共享,而不是在多次继承基类时创建多个副本。在您的示例中,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;
};

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

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;

答案 2 :(得分:3)

在C ++中,一个类按固定顺序排列在内存中。基类在字面上存在于分配给派生类的内存中,固定偏移量,类似于较大框内的较小框。

如果您没有虚拟继承,则说iostream包含istreamostream,其中每个ios都包含iostream。因此,ios包含两个iostream es。

使用虚拟继承,虚拟基类不存在于固定偏移处。它类似于挂在盒子的外面,用一点绳子连接起来。

然后,istream包含ostreamios,每个iostream都通过字符串链接到ios。因此ios有一个istream,由两个独立的字符串链接。

实际上,string的位是一个整数,表示实际__virtual_base_offset_ios相对于派生类地址的起始位置。即istream有一个隐藏的成员,例如,ios。当this方法想要访问__ios_base_offset基础时,它们会使用自己的ios指针,添加{{1}},这就是{{1}}基类指针。< / p>

-

换句话说,在非虚拟派生类中,派生类知道基类的偏移是什么,因为它是固定的,并且在物理上位于派生类中。在虚拟派生类中,必须共享基类,因此它不能总是存在于派生类中。

答案 3 :(得分:-2)

要消除歧义,使用虚拟继承。

class base {
    public:
        int a;
};

class new1 :virtual public base
{
    public:
        int b;
};
class new2 :virtual public base
{
    public:
        int c;
};

class drive : public new1,public new2
{
    public:
        void getvalue()
        {
            cout<<"input a b c "<<endl;
            cin>>a>>b>>c;
        }
        void printf()
        {

            cout<<a<<b<<c;
        }
};

int main()
{
    drive ob;
    ob.getvalue();
    ob.printf();
}