C++虚继承与虚函数

虚继承

在菱形继承中出现的数据二义性问题,使得数据访问时变得复杂,并且导致了数据冗存。虚继承则解决了从不同途径继承来的同名的数据成员在内存中有不同的拷贝造成数据不一致问题。

关键字:virtual

用法:将共同基类声明设置为虚基类,这时从不同路径继承过来的同名数据成员在内存中只有一份,同一个函数名也只有一个映射。

语法:
class 派生类: virtual 基类1,virtual 基类2,…,virtual 基类n
{
…//派生类成员声明
};

构造函数与析构函数的执行次序:
先执行虚基类的构造函数,多个虚基类按照被继承的次序顺序构造。
执行基类的构造函数,如果有多个基类则按照被声明次序依次构造。
执行成员对象的构造函数,多个成员对象按照声明次序构造。
执行派生类自己的构造函数。
析构函数与构造次序相反。
注意:
只有用于建立对象的最派生类的构造函数调用虚基类的构造函数,而该派生类的所有基类中列出的对虚基类的构造函数的调用在执行中被忽略,从而保证对虚基类子对象只初始化一次。
在一个成员初始化列表中同时出现对虚基类和非虚基类构造函数的调用时,虚基类的构造函数先于非虚基类的构造函数执行。

class A
{
public:
    A()
        :_a(1)
    {}
private:
    int _a;
};
class B :virtual public A
{
public:
    B()
        :A()
        ,_b1(2)
        ,_b2(3)
    {}
private:
    int _b1;
    int _b2;

};
void test() {
    B date;
}

打开监视窗口可以发现:
这里写图片描述
对象模型变为:
这里写图片描述
可以发现虚拟继承比普通继承多了4个字节。
而这4个字节保存的是一个地址,再调用一个内存监视跳转。
这里写图片描述
这个表格被称为偏移量表格,记载虚基类成员相对于函数内存起始地址的偏移量。(因为虚继承中对象模型改变,虚基类的成员调到最底部,所以需要记录虚基类成员相对于对象)
这时虚继承与普通继承之间的一处区别。

菱形虚拟继承

class A
{
public:
    A()
        :_a(0)
    {}
    int _a;
};
class B1 :virtual public A   //虚继承
{
public:
    B1()
        :_b1(1)
    {}
    int _b1;
};
class B2 : virtual public A   //虚继承
{
public:
    B2()
        :_b2(2)
    {}
    int _b2;
};
class C :public B1,public B2
{
public:
    C()
        :_c(3)
    {}
    int _c;
};
void test()
{
    C c1;
    c._a;
}

运行后调出监视窗口:
这里写图片描述
这时数据模型变为:
这里写图片描述
可以看到在对象C中只有一份 _a 数据,并且在直接访问时候,不再需要声明作用域。

虚函数

C++中,虚函数用来实现多态。
关键字:virtual
如果一个类包含了虚函数,那么在创建对象时会额外增加一张表,表中的每一项都是虚函数的入口地址。这张表就是虚函数表,也称为 vtable。 可以认为虚函数表是一个数组。 为了把对象和虚函数表关联起来,编译器会在对象中安插一个指针,指向虚函数表的起始位置。
虚表:

class A
{
public:
    virtual void fun1() {}
    virtual void fun2() {}
};
void test() {
    A a1;
}

打开监视窗口:
这里写图片描述
可以发现,虚表指针被放在了对象的首部。
各种情况下的虚表:

class A {
protected:
    int a1;
    int a2;
public:
    virtual void display() {
        cout << "A::display()" << endl;
    }
    virtual void clone() {
        cout << "A::clone()" << endl;
    }
};
class B : public A {
protected:
    int b;
public:
    virtual void display() {
        cout << "B::display()" << endl;
    }
    virtual void init() {
        cout << "B::init()" << endl;
    }
};
class C : public B {
protected:
    int c;
public:
    virtual void display() {
        cout << "C::display()" << endl;
    }
    virtual void execute() {
        cout << "C::execute()" << endl;
    }
};
各个类的内存分布如下:

这里写图片描述
调用:
对于虚函数display(),它在虚表中的索引为0,发生调用时,
p->display()编译器内部发生转换,转换为:( *( p->vptr )[0] ) (p),完成函数调用。
对于不同的虚函数,只需要改变索引值即可。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值