[英]Virtual inheritance in C++
我在阅读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 ++使用额外的间接级别来访问虚拟类,通常是通过指针
任何人都能解释一下吗?
要解决的基本问题是,如果将指向最派生类型的指针强制转换为指向其中一个基类的指针,则指针必须引用内存中的地址,通过该代码可以找到该类型的每个成员。知道派生类型。 对于非虚拟继承,这通常通过具有精确布局来实现,而这通过包含基类子对象然后添加派生类型的额外位来实现:
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
布局会有所不同,在代码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
代码如何访问基础子对象(只是取消引用base::ptr
成员对象),同时你有一个base
实例。 如果中间类中的代码访问x
则可以通过执行this->[hidden base pointer]->x
,并且这将在运行时解析到正确的位置。
这里重要的一点是,在derived
/ derived2
层编译的代码可以与该类型的对象或任何派生对象一起使用。 如果我们编写了第二个most_derived2
对象,其中继承的顺序被颠倒了,那么它们的y
和z
布局可以被交换,并且从指向derived
或derived
derived2
对象到base
子对象的指针的偏移将是不同的,但代码是访问x
仍然是相同的:取消引用您自己的隐藏基指针,保证如果derived
的方法是最终的覆盖,并且访问base::x
然后它将找到它而不管最终的布局。
基本上,如果不使用虚拟继承,则基本成员实际上是派生类实例的一部分。 基本成员的内存在每个实例中分配,访问它们不需要进一步的间接寻址:
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
成员的一个共享副本,即使它从istream
和ostream
继承了两次 。
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;
在C ++中,类以固定顺序布置在内存中。 基类在字面上存在于分配给派生类的内存中,处于固定偏移量,类似于较大框内的较小框。
如果你没有虚拟继承,你会说iostream
包含一个istream
和一个ostream
每个都包含一个ios
。 因此, iostream
包含两个ios
es。
使用虚拟继承时,虚拟基类不存在于固定偏移处。 它类似于挂在盒子的外面,用一点绳子连接起来。
因此, iostream
包含一个istream
和一个ostream
每个都通过字符串链接到ios
。 因此, iostream
有一个ios
,由两个独立的字符串链接。
在实践中,字符串的位是一个整数,表示实际ios
相对于派生类的地址开始的位置。 即istream
有一个隐藏的成员,例如__virtual_base_offset_ios
。 当istream
方法想要访问ios
基础时,它们会使用自己的this
指针,添加__ios_base_offset
,这就是ios
基类指针。
-
换句话说,在非虚拟派生类中,派生类知道基类的偏移是什么,因为它是固定的,并且在物理上位于派生类中。 在虚拟派生类中,必须共享基类,因此它不能总是存在于派生类中。
为了消除歧义,使用了虚拟继承。
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();
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.