关于C++中的虚拟继承的一些总结

本文详细探讨了C++中虚继承的概念及其应用场景,对比了虚继承与直接继承在时间和空间上的差异,并通过具体实例展示了虚继承如何解决多重继承中的菱形问题。此外,还深入分析了重载、覆盖及隐藏的区别。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这部分内容仅仅是用来记录关于《Inside the C++ Object Model》 一书中的问题。

1.为什么要引入虚拟继承(转自:http://www.cnblogs.com/BeyondAnyTime/archive/2012/06/05/2537451.html

虚继承是多重继承里面特有的概念。虚继承为了解决菱形继承的问题,如:D继承B1、B2,B1和B2都继承自A,因此会在D中出现两次A的变量和函数,为了节省空间,可以将B1和B2继承定义为虚继承,这样A就变成了虚基类。代码如下:

class A{};
class B1:public virtual A{};
class B2:public virtual A{};
class D:public B1,public B2{};

虚继承主要是用在c++的多重继承里面的。一旦离开了多重继承,虚拟继承也就没有什么意思了,会降低效率和占用更多空间。

2.引入虚继承和直接继承会有什么区别呢

虚继承和直接继承在时间上和空间上存在以下不同:

时间:

在通过继承类对象访问虚基类对象中的成员(包括数据成员和函数成员)时,都必须通过某种间接引用来完成,这样会增加引用寻址时间(就和虚函数一样),其实就是调整this指针以指向虚基类对象,只不过这个调整是运行时间接完成的。

空间:

由于共享所以不必要在对象内存中保存多份虚基类子对象的拷贝,这样较之多继承节省空间。虚拟继承与普通继承不同的是,虚拟继承可以防止出现diamond继承时,一个派生类中同时出现了两个基类的子对象。也就是说,为了保证这一点,在虚拟继承情况下,基类子对象的布局是不同于普通继承的。因此,它需要多出一个指向基类子对象的指针。

3.笔试,面试中常考的C++虚拟继承的知识点

测试例子:

第一种情况:         第二种情况:          第三种情况            第四种情况:
class a           class a              class a              class a
{              {                {                 {
    virtual void func();      virtual void func();       virtual void func();        virtual void func();
};              };                  char x;              char x;
class b:public virtual a   class b :public a           };                };
{              {                class b:public virtual a      class b:public a
    virtual void foo();        virtual void foo();     {                 {
};              };                  virtual void foo();        virtual void foo();
                               };                };

如果对这四种情况分别求sizeof(a),  sizeof(b)。结果是什么样的呢?下面是输出结果:(在codeblock13,使用g++编译器,win64系统)

四种情况的实测输出结果分别为:

第一种:4 4

第二种:4 4

第三种:8 12

第四种:8 8

想想这是为什么呢?

因为每个存在虚函数的类都要有一个4字节的指针指向自己的虚函数表,所以每种情况的类a所占的字节数应该是没有什么问题的,那么类b的字节数怎么算呢?看“第一种”和“第三种”情况采用的是虚继承,那么这时候就要有这样的一个指针vptr_b_a,这个指针叫虚类指针,也是四个字节;还要包括类a的字节数,所以类b的字节数就求出来了。而“第二种”和“第四种”情况则不包括vptr_b_a这个指针,这回应该木有问题了吧。

上述输出结果与事实不符合,第一种和第二种其实是一样的,差别就是第三、四种里面有变量了,可能是由于编译器的优化原因。。这个后面再确认。。之所以用char也是8个字节,是由于存在对齐问题。

4 c++重载、覆盖、隐藏的区别和执行方式

既然说到了继承的问题,那么不妨讨论一下经常提到的重载,覆盖和隐藏
4.1成员函数被重载的特征
(1)相同的范围(在同一个类中); 
(2)函数名字相同; 
(3)参数不同; 
(4)virtual 关键字可有可无。 
4.2“覆盖”是指派生类函数覆盖基类函数,特征是:
(1)不同的范围(分别位于派生类与基类); 
(2)函数名字相同; 
(3)参数相同; 
(4)基类函数必须有virtual 关键字。 
4.3“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,特征是:

(1)如果派生类的函数与基类的函数同名,但是参数不同,此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。 
(2)如果派生类的函数与基类的函数同名,但是参数相同,但是基类函数没有virtual 关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。

小结:说白了就是如果派生类和基类的函数名和参数都相同,属于覆盖,这是可以理解的吧,完全一样当然要覆盖了;如果只是函数名相同,参数并不相同,则属于隐藏。


4.4 三种情况怎么执行:

4.4.1 重载:看参数。

4.4.2 隐藏:用什么就调用什么。(通过指针强制性转换,还可以进行调用的)

4.4.3 覆盖:调用派生类。(也就是说无论如何都没有办法再去调用基类的函数了)

测试实例:

#include <iostream>

using namespace std;

class A
{
public:

     void func()
    {
        cout <<"a func" << endl;
    }
    virtual void foo()
    {
        cout << "a foo "<<endl;
    }
    void f(char *c)
    {
        cout << "a c " <<  c <<endl;
    }
};

class B:public     A
{
public:
    virtual void func()
    {
        cout << "b func" <<endl;
    }
     void foo()
    {
        cout << "b foo "<<endl;
    }
    void f(int c)
    {
        cout << "b int c " <<  c <<endl;
    }
    void f(double c)
    {
        cout << "b double c" << c << endl;
    }
};




int main ()
{
    B b;
    A *a;
    b.f('m');
    b.f(12.3);
    b.foo();
    b.func();
   // b.f("asdsa"); // error
   a = &b;
   a->f("as");
   //a->f(12.4); // error
   a->func();
   a->foo();
}

可以看出来,函数f(char*c) 被影藏了,但是可以通过A*指针调用;同样的,函数func也是被隐藏了;函数foo则被覆盖了,不论通过什么方法调用,都是B的版本函数;B中的函数f存在相互的重载。





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值