`

转- 虚继承与虚基类的本质

    博客分类:
  • C++
阅读更多
虚继承和虚基类的定义是非常的简单的,同时也是非常容易判断一个继承是否是虚继承
的,虽然这两个概念的定义是非常的简单明确的,但是在C++语言中虚继承作为一个比较生
僻的但是又是绝对必要的组成部份而存在着,并且其行为和模型均表现出和一般的继承体系
之间的巨大的差异(包括访问性能上的差异),现在我们就来彻底的从语言、模型、性能和
应用等多个方面对虚继承和虚基类进行研究。
    首先还是先给出虚继承和虚基类的定义。
    虚继承:在继承定义中包含了virtual关键字的继承关系;
    虚基类:在虚继承体系中的通过virtual继承而来的基类,需要注意的是:
            struct CSubClass : public virtual CBase {}; 其中CBase称之为CSubClass
            的虚基类,而不是说CBase就是个虚基类,因为CBase还可以不不是虚继承体系
            中的基类。
    有了上面的定义后,就可以开始虚继承和虚基类的本质研究了,下面按照语法、语义、
模型、性能和应用五个方面进行全面的描述。

    1. 语法
       语法有语言的本身的定义所决定,总体上来说非常的简单,如下:
           struct CSubClass : public virtual CBaseClass {};
       其中可以采用public、protected、private三种不同的继承关键字进行修饰,只要
       确保包含virtual就可以了,这样一来就形成了虚继承体系,同时CBaseClass就成为
       了CSubClass的虚基类了。
       其实并没有那么的简单,如果出现虚继承体系的进一步继承会出现什么样的状况呢?
       如下所示:
            /*
             * 带有数据成员的基类
             */
            struct CBaseClass1
            {
                CBaseClass1( size_t i ) : m_val( i ) {}
            
                size_t m_val;
            };
            /*
             * 虚拟继承体系
             */
            struct CSubClassV1 : public virtual CBaseClass1
            {
                CSubClassV1( size_t i ) : CBaseClass1( i ) {}
            };           
            struct CSubClassV2 : public virtual CBaseClass1
            {
                CSubClassV2( size_t i ) : CBaseClass1( i ) {}
            };           
            struct CDiamondClass1 : public CSubClassV1, public CSubClassV2
            {
                CDiamondClass1( size_t i ) : CBaseClass1( i ), CSubClassV1( i ), CSubClassV2( i ) {}
            };           
            struct CDiamondSubClass1 : public CDiamondClass1
            {
                CDiamondSubClass1( size_t i ) : CBaseClass1( i ), CDiamondClass1( i ) {}
            };
       注意上面代码中的CDiamondClass1和CDiamondSubClass1两个类的构造函数初始化列
       表中的内容。可以发现其中均包含了虚基类CBaseClass1的初始化工作,如果没有这
       个初始化语句就会导致编译时错误,为什么会这样呢?一般情况下不是只要在
       CSubClassV1和CSubClassV2中包含初始化就可以了么?要解释该问题必须要明白虚
       继承的语义特征,所以参看下面语义部分的解释。
      
    2. 语义
       从语义上来讲什么是虚继承和虚基类呢?上面仅仅是从如何在C++语言中书写合法的
       虚继承类定义而已。首先来了解一下virtual这个关键字在C++中的公共含义,在C++
       语言中仅仅有两个地方可以使用virtual这个关键字,一个就是类成员虚函数和这里
       所讨论的虚继承。不要看这两种应用场合好像没什么关系,其实他们在背景语义上
       具有virtual这个词所代表的共同的含义,所以才会在这两种场合使用相同的关键字。
       那么virtual这个词的含义是什么呢?
       virtual在《美国传统词典[双解]》中是这样定义的:
           adj.(形容词)
           1. Existing or resulting in essence or effect though not in actual
              fact, form, or name:
              实质上的,实际上的:虽然没有实际的事实、形式或名义,但在实际上或效
              果上存在或产生的;
           2. Existing in the mind, especially as a product of the imagination.
              Used in literary criticism of text.
              虚的,内心的:在头脑中存在的,尤指意想的产物。用于文学批评中。
       我们采用第一个定义,也就是说被virtual所修饰的事物或现象在本质上是存在的,
       但是没有直观的形式表现,无法直接描述或定义,需要通过其他的间接方式或手段
       才能够体现出其实际上的效果。
       那么在C++中就是采用了这个词意,不可以在语言模型中直接调用或体现的,但是确
       实是存在可以被间接的方式进行调用或体现的。比如:虚函数必须要通过一种间接的
       运行时(而不是编译时)机制才能够激活(调用)的函数,而虚继承也是必须在运行
       时才能够进行定位访问的一种体制。存在,但间接。其中关键就在于存在、间接和共
       享这三种特征。
       对于虚函数而言,这三个特征是很好理解的,间接性表明了他必须在运行时根据实际
       的对象来完成函数寻址,共享性表象在基类会共享被子类重载后的虚函数,其实指向
       相同的函数入口。
       对于虚继承而言,这三个特征如何理解呢?存在即表示虚继承体系和虚基类确实存在,
       间接性表明了在访问虚基类的成员时同样也必须通过某种间接机制来完成(下面模型
       中会讲到),共享性表象在虚基类会在虚继承体系中被共享,而不会出现多份拷贝。
       那现在可以解释语法小节中留下来的那个问题了,“为什么一旦出现了虚基类,就必
       须在没有一个继承类中都必须包含虚基类的初始化语句”。由上面的分析可以知道,
       虚基类是被共享的,也就是在继承体系中无论被继承多少次,对象内存模型中均只会
       出现一个虚基类的子对象(这和多继承是完全不同的),这样一来既然是共享的那么
       每一个子类都不会独占,但是总还是必须要有一个类来完成基类的初始化过程(因为
       所有的对象都必须被初始化,哪怕是默认的),同时还不能够重复进行初始化,那到
       底谁应该负责完成初始化呢?C++标准中(也是很自然的)选择在每一次继承子类中
       都必须书写初始化语句(因为每一次继承子类可能都会用来定义对象),而在最下层
       继承子类中实际执行初始化过程。所以上面在每一个继承类中都要书写初始化语句,
       但是在创建对象时,而仅仅会在创建对象用的类构造函数中实际的执行初始化语句,
       其他的初始化语句都会被压制不调用。
      
    3. 模型
       为了实现上面所说的三种语义含义,在考虑对象的实现模型(也就是内存模型)时就
       很自然了。在C++中对象实际上就是一个连续的地址空间的语义代表,我们来分析虚
       继承下的内存模型。
       3.1. 存在
           也就是说在对象内存中必须要包含虚基类的完整子对象,以便能够完成通过地址
           完成对象的标识。那么至于虚基类的子对象会存放在对象的那个位置(头、中间、
           尾部)则由各个编译器选择,没有差别。(在VC8中无论虚基类被声明在什么位置,
           虚基类的子对象都会被放置在对象内存的尾部)
       3.2. 间接
           间接性表明了在直接虚基承子类中一定包含了某种指针(偏移或表格)来完成通
           过子类访问虚基类子对象(或成员)的间接手段(因为虚基类子对象是共享的,
           没有确定关系),至于采用何种手段由编译器选择。(在VC8中在子类中放置了
           一个虚基类指针vbc,该指针指向虚函数表中的一个slot,该slot中存放则虚基
           类子对象的偏移量的负值,实际上就是个以补码表示的int类型的值,在计算虚
           基类子对象首地址时,需要将该偏移量取绝对值相加,这个主要是为了和虚表
           中只能存放虚函数地址这一要求相区别,因为地址是原码表示的无符号int类型
           的值)
       3.3. 共享
           共享表明了在对象的内存空间中仅仅能够包含一份虚基类的子对象,并且通过
           某种间接的机制来完成共享的引用关系。在介绍完整个内容后会附上测试代码,
           体现这些内容。
    4. 性能
       由于有了间接性和共享性两个特征,所以决定了虚继承体系下的对象在访问时必然
       会在时间和空间上与一般情况有较大不同。
       4.1. 时间
           在通过继承类对象访问虚基类对象中的成员(包括数据成员和函数成员)时,都
           必须通过某种间接引用来完成,这样会增加引用寻址时间(就和虚函数一样),
           其实就是调整this指针以指向虚基类对象,只不过这个调整是运行时间接完成的。
           (在VC8中通过打开汇编输出,可以查看*.cod文件中的内容,在访问虚基类对象
           成员时会形成三条mov间接寻址语句,而在访问一般继承类对象时仅仅只有一条mov
           常量直接寻址语句)
       4.2. 空间
           由于共享所以不同在对象内存中保存多份虚基类子对象的拷贝,这样较之多继承
           节省空间。
    5. 应用
       谈了那么多语言特性和内容,那么在什么情况下需要使用虚继承,而一般应该如何使
       用呢?
       这个问题其实很难有答案,一般情况下如果你确性出现多继承没有必要,必须要共享
       基类子对象的时候可以考虑采用虚继承关系(C++标准ios体系就是这样的)。由于每
       一个继承类都必须包含初始化语句而又仅仅只在最底层子类中调用,这样可能就会使
       得某些上层子类得到的虚基类子对象的状态不是自己所期望的(因为自己的初始化语
       句被压制了),所以一般建议不要在虚基类中包含任何数据成员(不要有状态),只
       可以作为接口类来提供 

附录:测试代码
#include <ctime>
#include <iostream>

/*
 * 带有数据成员的基类
 */
struct CBaseClass1
{
    CBaseClass1( size_t i ) : m_val( i ) {}

    size_t m_val;
};
/*
 * 虚拟继承体系
 */
struct CSubClassV1 : public virtual CBaseClass1
{
    CSubClassV1( size_t i ) : CBaseClass1( i ) {}
};

struct CSubClassV2 : public virtual CBaseClass1
{
    CSubClassV2( size_t i ) : CBaseClass1( i ) {}
};

struct CDiamondClass1 : public CSubClassV1, public CSubClassV2
{
    CDiamondClass1( size_t i ) : CBaseClass1( i ), CSubClassV1( i ), CSubClassV2( i ) {}
};

struct CDiamondSubClass1 : public CDiamondClass1
{
    CDiamondSubClass1( size_t i ) : CBaseClass1( i ), CDiamondClass1( i ) {}
};
/*
 * 正常继承体系
 */
struct CSubClassN1 : public CBaseClass1
{
    CSubClassN1( size_t i ) : CBaseClass1( i ) {}
};
struct CSubClassN2 : public CBaseClass1
{
    CSubClassN2( size_t i ) : CBaseClass1( i ) {}
};
struct CMultiClass1 : public CSubClassN1, public CSubClassN2
{
    CMultiClass1( size_t i ) : CSubClassN1( i ), CSubClassN2( i ) {}
};
struct CMultiSubClass1 : public CMultiClass1
{
    CMultiSubClass1( size_t i ) : CMultiClass1( i ) {}
};
/*
 * 不带有数据成员的接口基类
 */
struct CBaseClass2
{
    virtual void func() {};
    virtual ~CBaseClass2() {}
};
/*
 * 虚拟继承体系
 */
// struct CBaseClassX { CBaseClassX() {i1 = i2 = 0xFFFFFFFF;} size_t i1, i2;};
struct CSubClassV3 : public virtual CBaseClass2
{
};
struct CSubClassV4 : public virtual CBaseClass2
{
};
struct CDiamondClass2 : public CSubClassV3, public CSubClassV4
{
};
struct CDiamondSubClass2 : public CDiamondClass2
{
};

/*
 * 正常继承体系
 */
struct CSubClassN3 : public CBaseClass2
{
};
struct CSubClassN4 : public CBaseClass2
{
};
struct CMultiClass2 : public CSubClassN3, public CSubClassN4
{
};
struct CMultiSubClass2 : public CMultiClass2
{
};

/*
 * 内存布局用类声明.
 */
struct CLayoutBase1
{
    CLayoutBase1() : m_val1( 0 ), m_val2( 1 ) {}

    size_t m_val1, m_val2;
};
struct CLayoutBase2
{
    CLayoutBase2() : m_val1( 3 ) {}

    size_t m_val1;
};
struct CLayoutSubClass1 : public virtual CBaseClass1, public CLayoutBase1, public CLayoutBase2
{
    CLayoutSubClass1() : CBaseClass1( 2 ) {}
};


#define MAX_TEST_COUNT 1000 * 1000 * 16
#define TIME_ELAPSE() ( std::clock() - start * 1.0 ) / CLOCKS_PER_SEC

int main( int argc, char *argv[] )
{
    /*
     * 类体系中的尺寸.
     */
    std::cout << "================================ sizeof ================================" << std::endl;
    std::cout << "    ----------------------------------------------------------------" << std::endl;
    std::cout << "sizeof( CBaseClass1 )       = " << sizeof( CBaseClass1 ) << std::endl;
    std::cout << std::endl;
    std::cout << "sizeof( CSubClassV1 )       = " << sizeof( CSubClassV1 ) << std::endl;
    std::cout << "sizeof( CSubClassV2 )       = " << sizeof( CSubClassV2 ) << std::endl;
    std::cout << "sizeof( CDiamondClass1 )    = " << sizeof( CDiamondClass1 ) << std::endl;
    std::cout << "sizeof( CDiamondSubClass1 ) = " << sizeof( CDiamondSubClass1 ) << std::endl;
    std::cout << std::endl;
    std::cout << "sizeof( CSubClassN1 )       = " << sizeof( CSubClassN1 ) << std::endl;
    std::cout << "sizeof( CSubClassN2 )       = " << sizeof( CSubClassN2 ) << std::endl;
    std::cout << "sizeof( CMultiClass1 )      = " << sizeof( CMultiClass1 ) << std::endl;
    std::cout << "sizeof( CMultiSubClass1 )   = " << sizeof( CMultiSubClass1 ) << std::endl;

    std::cout << "    ----------------------------------------------------------------" << std::endl;
    std::cout << "sizeof( CBaseClass2 )       = " << sizeof( CBaseClass2 ) << std::endl;
    std::cout << std::endl;
    std::cout << "sizeof( CSubClassV3 )       = " << sizeof( CSubClassV3 ) << std::endl;
    std::cout << "sizeof( CSubClassV4 )       = " << sizeof( CSubClassV4 ) << std::endl;
    std::cout << "sizeof( CDiamondClass2 )    = " << sizeof( CDiamondClass2 ) << std::endl;
    std::cout << "sizeof( CDiamondSubClass2 ) = " << sizeof( CDiamondSubClass2 ) << std::endl;
    std::cout << std::endl;
    std::cout << "sizeof( CSubClassN3 )       = " << sizeof( CSubClassN3 ) << std::endl;
    std::cout << "sizeof( CSubClassN4 )       = " << sizeof( CSubClassN4 ) << std::endl;
    std::cout << "sizeof( CMultiClass2 )      = " << sizeof( CMultiClass2 ) << std::endl;
    std::cout << "sizeof( CMultiSubClass2 )   = " << sizeof( CMultiSubClass2 ) << std::endl;
    /*
     * 对象内存布局
     */
    std::cout << "================================ layout ================================" << std::endl;
    std::cout << "    --------------------------------MI------------------------------" << std::endl;
    CLayoutSubClass1 *lsc = new CLayoutSubClass1;
    std::cout << "sizeof( CLayoutSubClass1 )   = " << sizeof( CLayoutSubClass1 ) << std::endl;
    std::cout << "CLayoutBase1 offset of CLayoutSubClass1 is " << (char*)(CLayoutBase1 *)lsc - (char*)lsc << std::endl;
    std::cout << "CBaseClass1  offset of CLayoutSubClass1 is " << (char*)(CBaseClass1  *)lsc - (char*)lsc << std::endl;
    std::cout << "CLayoutBase2 offset of CLayoutSubClass1 is " << (char*)(CLayoutBase2 *)lsc - (char*)lsc << std::endl;

    int *ptr = (int*)lsc;
    std::cout << "vbc in CLayoutSubClass1 is " << *(int*)ptr[3] << std::endl;

    delete lsc;

    std::cout << "    --------------------------------SI------------------------------" << std::endl;
    CSubClassV1 *scv1 = new CSubClassV1( 1 );
    std::cout << "sizeof( CSubClassV1 )   = " << sizeof( CSubClassV1 ) << std::endl;
    std::cout << "CBaseClass1 offset of CSubClassV1 is " << (char*)(CBaseClass1 *)scv1 - (char*)scv1 << std::endl;

    ptr = (int*)scv1;
    std::cout << "vbc in CSubClassV1 is " << *(int*)ptr[0] << std::endl;

    delete scv1;

    /*
     * 性能测试
     */
    std::cout << "================================ Performance ================================" << std::endl;
    double times[4];
    size_t idx = 0;

    CSubClassV1 *ptr1 = new CDiamondClass1( 1 );
    std::clock_t start = std::clock();
    {
        for ( size_t i = 0; i < MAX_TEST_COUNT; ++i )
            ptr1->m_val = i;
    }
    times[idx++] = TIME_ELAPSE();
    delete static_cast<CDiamondClass1*>( ptr1 );

    CSubClassN1 *ptr2 = new CMultiClass1( 0 );
    start = std::clock();
    {
        for ( size_t i = 0; i < MAX_TEST_COUNT; ++i )
            ptr2->m_val = i;
    }
    times[idx++] = TIME_ELAPSE();
    delete static_cast<CMultiClass1*>( ptr2 );

    std::cout << "CSubClassV1::ptr1->m_val " << times[0] << " s" << std::endl;
    std::cout << "CSubClassN1::ptr2->m_val " << times[1] << " s" << std::endl;

    return 0;
}

测试环境:
    软件环境:Visual Studio2005 Pro + SP1, boost1.34.0
    硬件环境:PentiumD 3.0GHz, 4G RAM
测试数据:
================================ sizeof ================================
    ----------------------------------------------------------------
sizeof( CBaseClass1 )       = 4

sizeof( CSubClassV1 )       = 8
sizeof( CSubClassV2 )       = 8
sizeof( CDiamondClass1 )    = 12
sizeof( CDiamondSubClass1 ) = 12

sizeof( CSubClassN1 )       = 4
sizeof( CSubClassN2 )       = 4
sizeof( CMultiClass1 )      = 8
sizeof( CMultiSubClass1 )   = 8
    ----------------------------------------------------------------
sizeof( CBaseClass2 )       = 4

sizeof( CSubClassV3 )       = 8
sizeof( CSubClassV4 )       = 8
sizeof( CDiamondClass2 )    = 12
sizeof( CDiamondSubClass2 ) = 12

sizeof( CSubClassN3 )       = 4
sizeof( CSubClassN4 )       = 4
sizeof( CMultiClass2 )      = 8
sizeof( CMultiSubClass2 )   = 8
================================ layout ================================
    --------------------------------MI------------------------------
sizeof( CLayoutSubClass1 )   = 20
CLayoutBase1 offset of CLayoutSubClass1 is 0
CBaseClass1  offset of CLayoutSubClass1 is 16
CLayoutBase2 offset of CLayoutSubClass1 is 8
vbc in CLayoutSubClass1 is -12
    --------------------------------SI------------------------------
sizeof( CSubClassV1 )   = 8
CBaseClass1 offset of CSubClassV1 is 4
vbc in CSubClassV1 is 0
================================ Performance ================================
CSubClassV1::ptr1->m_val 0.062 s
CSubClassN1::ptr2->m_val 0.016 s

结果分析:
    1. 由于虚继承引入的间接性指针所以导致了虚继承类的尺寸会增加4个字节;
    2. 由Layout输出可以看出,虚基类子对象被放在了对象的尾部(偏移为16),并且vbc
       指针必须紧紧的接在虚基类子对象的前面,所以vbc指针所指向的内容为“偏移 - 4”;
    3. 由于VC8将偏移放在了虚函数表中,所以为了区分函数地址和偏移,所以偏移是用补
       码int表示的负值;
    4. 间接性可以通过性能来看出,在虚继承体系同通过指针访问成员时的时间一般是一般
       类访问情况下的4倍左右,符合汇编语言输出文件中的汇编语句的安排。

分享到:
评论

相关推荐

    详解虚继承

    在虚继承中,“实质上”的含义体现在即使存在多个派生类,但虚基类中的成员只被继承一次。这样做的目的是为了避免多重继承时可能出现的二义性问题,特别是在基类中包含数据成员的情况下。例如: ```cpp struct ...

    C++继承与派生机制详解

    2. **覆盖基类成员**:通过在派生类中重新定义基类的虚函数,可以在派生类中提供不同的实现,这是实现多态性的基础。 #### 六、新增派生类特有的成员 除了继承自基类的成员之外,派生类还可以增加自己的特有成员,...

    4类的继承与派生继承与派生

    2. **派生类的扩展**:派生类不仅可以继承基类的功能,还可以添加新的属性或方法来满足特定需求。此外,派生类还可以重写(override)基类的方法以提供不同的实现,这称为覆盖。 ### 类成员的访问控制 在继承过程...

    C#面向对象编程习题.doc

    - d) 错误,抽象基类与接口有本质区别。 #### 四、设计模式的选择 第四题探讨了在现有设计基础上添加新类型的最佳实践: - a) 单独创建杂志类`Journal`,这是最简单的方式。 - b) 让`Journal`继承`TextBook`类,这...

    Inside the C++ Object Model

    - 继承使得派生类可以继承基类的属性和方法,并可以添加新的特性或覆盖基类的行为。 - 虚继承(virtual inheritance)解决了多重继承中公共基类的问题,确保派生类只继承一次公共基类的成员。 **5. 虚函数和虚继承*...

    c++继承初步学习

    #### 五、多重继承与虚基类 C++支持多重继承,即一个派生类可以继承多个基类。但是,这种继承可能会导致**钻石问题**,即两个或多个基类共享了一个更基础的基类。为了解决这个问题,C++引入了虚基类的概念。 **...

    构造函数不能声明为虚函数,析构函数可以声明为虚函数

    综上所述,构造函数不能声明为虚函数是因为它们与对象构造过程的本质相悖。在构造阶段,对象的类型和状态尚不确定,使得动态绑定变得不可行。 ### 析构函数可以声明为虚函数 与构造函数不同,析构函数通常用于释放...

    MoreEffectiveC++

    - **定义**:抛出异常与传递参数或调用虚函数在机制上有本质区别。 - **应用场景**: - 了解这些机制的工作原理对于编写健壮的代码至关重要。 - **注意事项**: - 异常处理机制可以跨越函数边界; - 异常处理比...

    虚方法的解释

    虚方法与非虚方法之间存在本质的区别,主要体现在以下几个方面: 1. **多态性**: - **虚方法**支持多态性,即可以通过基类类型的指针变量来调用派生类的方法实现,这使得代码更加灵活。 - **非虚方法**不支持多...

    为什么构造函数不能是虚函数

    - 在构造函数中调用虚函数时,实际上只会调用当前类的版本,而不是继承体系中其他类的版本。 - 这是因为构造函数调用的顺序是从基类到派生类,而在构造函数中调用虚函数时,虚拟表(VTable)中的指针还未被更新为...

    C#中CLR虚方法的多态调用

    - **非虚方法与虚方法的区别**:`Base`类中的`NoneVirtualFun1`是非虚方法。这意味着即使在派生类中,它也不能被重写。这也回答了最初的问题之一——父类中的非虚方法不会在子类中被复制。 - **子类继承机制**:当`...

    深度探索C++对象模型

    - **案例三:多重继承与虚基类**:展示多重继承中出现的问题,并通过使用虚基类解决数据成员重复问题。 #### 总结 通过对《深度探索C++对象模型》的学习,我们可以更深刻地理解C++中对象的本质及其内部机制。这...

    New与Override重写的区别例子.txt

    这意味着当派生类的方法签名与基类的虚方法完全相同时,可以使用 `override` 关键字来实现对基类方法的重写。这样做的结果是,无论通过哪种类型的引用调用该方法,实际执行的都是派生类中的方法。 **示例代码:** ...

    C++记忆要点.pdf

    多重继承是从多个基类派生出新类,而虚继承则是指基类通过虚函数的方式被继承,其具体的初始化则由最后的派生类进行。继承和派生过程中,如果派生类中声明了与基类成员函数同名的新函数,即便函数参数表不同,也会...

    CC++电话面试

    - **总结**:数组与指针虽然都涉及内存地址的概念,但它们在定义、使用方式以及功能上有着本质的区别。 #### 2. 预编译及其适用场景? - **预编译**: - 是编译过程的第一阶段,主要用于处理源代码中的预处理指令...

    C++相关高频经典面试题100问.pdf

    - **虚继承**:解决“钻石问题”,确保派生类只继承一个基类实例。 #### 10. 命名空间 - **定义**:用于组织代码,避免命名冲突。 - **使用**:通过 `namespace` 关键字声明。 #### 11. 模板与泛型编程 - **模板**...

    C++程序设计习题答案第八章.pdf

    在C++编程语言中,继承与派生是面向对象编程的重要特性,它们允许我们创建新的类(派生类)基于已存在的类(基类),从而实现代码的复用和类之间的层次结构。第八章主要讨论了这些概念以及相关的知识点。 首先,类...

    C++箴言:避免析构函数调用虚函数

    #### 析构函数与虚函数 在C++中,析构函数用于释放对象占用的资源。虚函数则允许在派生类中重写基类中的函数,从而实现了多态性。然而,在析构函数中调用虚函数可能会引发意想不到的问题,尤其是在类继承体系中。 ...

    华为笔试内部资料

    而内联函数则是为了减少函数调用的开销,它在编译时将函数体直接嵌入到调用处,无法与虚函数的动态性相兼容。 ### 析构函数的虚化与调用 2. **析构函数的虚化及调用机制** - 文件中提到:“父类的析构函数是非虚...

Global site tag (gtag.js) - Google Analytics