C++继承模型

在C++继承模型中,一个派生类对象表现出来的东西,是其自己的成员加上其基类成员的总和。但这些成员如何摆放,标准并未强制规定。一般而言,低地址放基类子对象,高地址放派生类对象。

下面从四个部分讨论C++继承模型:
  • 单一继承不含虚函数
  • 单一继承并含虚函数
  • 多重继承
  • 虚拟继承
1、单一继承不含虚函数

这种继承关系很简单,基类子对象包含在了派生类对象中,在内存中连续存放。但有一点需要注意,把类分解成多层可能会造成空间的膨胀。例如:
#include <iostream>
#include <vector>
 
using namespace std;
 
class Foo {
public:
    int val;
    char bit1, bit2, bit3;
};
 
class A {
public:
    int val;
    char bit1;
};
 
class B : public A {
public:
    char bit2;
};
 
class C : public B {
public:
    char bit3;
};
 
int main()
{
    cout << "size Foo = " << sizeof(Foo) << endl;
    cout << "size C   = " << sizeof(C) << endl;
    system("pause");
    return 0;
}


运行结果:


两个类中包含同样的成员,空间却差了一倍,这是由于基类需要边界对齐的缘故。 C++语言保证,出现在派生类中的基类子对象有其完整原样性,这是关键所在。为什么要使用这样牺牲空间的布局?原因是在对象之间拷贝时,只对子对象进行成员拷贝而不影响派生类中的成员。

2、单一继承并含虚函数
基类中有虚函数,那么编译器会给基类生成一个virtual function table和一个vptr, 派生类会继承此vptr,但不会指向相同的virtual function table,而是指向自己的virtual function table。毕竟派生类一般都会重写从基类继承的虚函数。关于vptr的摆放位置,要视编译器而定,要么放在对象开头,要么放在对象结尾。我手头的VS2013就把vptr放在了对象的开头处。


可以看到,派生类Point3d中的vptr属于Point2d子对象。所以当一个基类指针指向派生类时,能够顺利取得这个vptr然后调用所需的虚函数以表现多态性。

下面做个实验:
#include <iostream>
#include <vector>
 
using namespace std;
 
class Foo {
public:
    int x;
};
 
class Bar : public Foo {
public:
    int y;
    virtual void func()
    {}
};
 
int main()
{
    Bar bar;
    cout << &bar << endl;
    cout << &bar.x << endl;
    cout << &bar.y << endl;
    system("pause");
    return 0;
}


运行结果:


Foo类没有虚函数,也就没有vptr。而派生类Bar有虚函数,编译器把它的vptr插在了类的开头处,先于基类成员摆放。

3、多重继承
对一个多重派生对象,将其地址指定给派生列表“最左端”的基类指针,情况将和单一继承时相同,因为二者都指向相同的起始地址。对后继的基类指针的赋值,需要由编译器负责加上一个偏移地址。

例如有如下继承结构:


它的数据分布有可能如下:


假设有如下定义:
Vertex3d v3d;
Vertex *pv;
Point2d *p2d;
Point3d *p3d;


操作如下:
pv = &v3d;
p2d = &v3d;
p3d = &v3d;


对第一种赋值操作,编译器会产生类似下面的伪代码:
pv = (Vertex *)(((char *)&v3d) + sizeof(Point3d));
因为从布局图可以看出,指向Vertex的指针需要跳过开头的Point3d部分才能指向数据自己的子对象。然后,对第二和第三种赋值操作,只需要简单地拷贝其地址就好,因为Point2d指针和Point3d指针都指向对象v3d的起始地址。

4、虚拟继承
由于虚拟基类是共享的,所以在各个派生类中必须要由编译器添加某种信息,用来保存共享的虚拟基类的地址。关于如何添加,各个编译器厂家的实现都有所不同,而且在未来也会有更新,这里就不具体说明了。除了通过对象来存取虚基类中的成员之外,通过指针和引用都会引起运行成本上的额外开销。一般而言,虚基类最有效的一种运用形式就是:一个抽象的虚基类,没有任何数据成员。

参考:
《深度探索C++对象模型》 P99-P123.
 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值