虚函数、虚继承

1、虚函数、虚继承对sizeof的影响

https://blog.csdn.net/acb0y/article/details/8822983

  • 空类大小: class {}; = 1
    空类大小为1,编译器安插一个char给空类,用来标记它的每一个对象。

  • 包含数据成员的类的大小:class{int, char}; = 8 or 5
    类(或结构)的大小需为类中最大数据类型的整数倍,CPU访问对齐数据效率最高的,因此通常编译时浪费一些空间来使得数据是对齐的。我们也可以编译指示对齐的大小。在上面的代码中我们只需要在程序开头的地方指定语句#pragma pack(1),指示编译器以1对齐。我们可以预期到A size = 5。

  • 包含数据成员和普通函数的类的大小: class{int, char, f(); }; = 8
    普通成员函数的添加并不会对类的大小产生影响,故在类A中填加AA这个成员函数是不会影响类A的大小(填加n个普通成员函数也是一样)

  • 包含虚函数的类的大小: class{int, char, f(); virtual f2();}; = 8+4 = 12
    当添加了一个虚函数,类的大小增加了4,这个是因为当类中友虚函数时,类会多一个虚函数表指针(virtual table pointer),而这个指针占4个字节。

  • 虚继承对类大小的影响
    class A{int, char, f(); virtual f2();}; = 8+4 = 12
    class B: public virtual A {int}; = 12 + 4 + 4 = 20 虚继承
    class C{int, char, f(); virtual f2();}; = 8+4 = 12
    class D: public A{int}; = 12 + 4 = 16 非虚继承
    class E : public virtual A, public virtual C {}; = 28
    从输出结果我们可以看出,虚继承的类较普通继承的类大小大了4,这额外的4个字节是从那里来呢?事实上这4个字节是为子类添加虚表指针而增加的,虚继承要求子类不和父类共享虚表指针,非虚继承,子类和父类共享同一个虚表指针。

2、C++析构函数为什么要为虚函数

https://www.cnblogs.com/lixiaohui-ambition/archive/2012/07/13/2589716.html
实现多态时,当用基类操作派生类,在析构时防止只析构基类而不析构派生类的状况发生。 内存泄漏。

虚函数:基类操作派生类时,如果基类的函数是虚函数,会同时操作派生类的函数。因此,在用基类操作派生类时,如果此时进行销毁,可以同时析构基类和派生类。对于普通函数亦然。本质静态联编。

补充:

  • 基类的析构函数不为虚的话,其子类中所有的成员变量的类中分配的内存也将可能泄漏。
  • 将基类的析构函数设为virtual型,则所有的基类的派生类对象的析构函数都会自动设置为virtual型,这保证了任何情况下,不会出现由于析构函数没有被调用而导致的内存泄漏。 这是MFC将基类的析构函数设置为虚函数的真正原因。

案例:

  1. 派生类操作派生类:基类的析构函数不是虚函数,在main函数中用继承类的指针去操作继承类的成员,释放指针P的过程是:先释放继承类的资源,再释放基类资源.
  2. 基类操作派生类:基类的析构函数同样不是虚函数,不同的是在main函数中用基类的指针去操作继承类的成员,释放指针P的过程是:只是释放了基类的资源,而没有调用继承类的析构函数.
  3. 基类操作派生类:基类的析构函数被定义为虚函数,在main函数中用基类的指针去操作继承类的成员,释放指针P的过程是:先释放继承类的资源,再释放基类资源.

如果不需要基类对派生类及对象进行操作,则不能定义虚函数,因为这样会增加内存开销.当类里面有定义虚函数的时候,编译器会给类添加一个虚函数表,里面来存放虚函数指针,这样就会增加类的存储空间.所以,只有当一个类被用来作为基类的时候,才把析构函数写成虚函数.

为什么 构造函数不能为虚函数。原文地址:http://blog.sina.com.cn/s/blog_620882f401016ri2.html

3、何为动态联编和静态联编?

联编是一个计算机程序彼此相关连的过程。按照联编所进行的阶段不同,可分为两种不同的联编方法:静态联编和动态联编。

  • 将源代码中的函数调用解释为执行特定的函数代码被称为函数名联编(binding).
  • 静态联编(static binding),又称早期联编(early binding):在编译过程中进行联编。它对函数的选择是根据基于对象的 指针或者引用来确定的。(即指针或者引用 指向哪个对象就调用哪个对象的相应的函数)
  • 动态联编(dynamic binding), 又称为晚期联编(late binding):编译器必须生成能够在程序运行时选择正确的虚方法的代码.

优劣:

  • 动态联编让您能够重新定义类方法,而静态联编在这方面很差
  • 动态联编为使程序能够在运行阶段进行决策,必须采取一些方法来跟踪实际运行中基类指针或引用指向的对象类型,这增加了额外的开销. (效率)
    • 如果类不会用作基类,则不需要动态联编
    • 如果派生类不重新定义基类的任何方法,也不需要使用动态联编
  • 由于静态联编的效率更高,因此被设置为C++的默认选择

4、虚函数的工作原理

通常,编译器处理虚函数的方法是:给每一个对象添加一个隐藏成员.隐藏成员中保存了一个指向函数地址数组的指针.这种数组称为虚函数表(virtual function table, vtbl).虚函数表中存储了为类对象进行声明的虚函数的地址.

例如,基类对象包含一个指针,访指针指向基类中所有虚函数地址表.派生类对象将包含一个指向独立地址表的指针.如果派生类提供了虚岁函数的新定义,访虚函数表将保存新函数的地址;如果派生类没有重新定义虚函数.该vtbl将保存函数原始版本的地址.如果派生类定义了新的虚函数,则该函数的地址也将被添加到vtbl中.注意,无论类中包含的虚函数是1还是10 个,都只需要在对象中添加1个地址成员,只是表的大小不同而已.

调用虚函数时,程序将查看存储在对象中的vtbl地址,然后转向相应的函数地址表.如果使用类声明中定义的第一个虚函数,则程序将使用数组中的第一个函数地址,并执行具有该地址的函数.如果使用类声明中的第三个虚函数,程序将使用地址为数组中第三个元素的函数.

  • 每个对象都将增大,增大量为存储地址的空间.
  • 对每个类,编译器都创建一个虚岁函数地址表.
  • 每个函数调用都有需要执行一步额外的操作,即到表中查找地址.

5、哪些函数不能为虚函数
https://blog.csdn.net/songchuwang1868/article/details/94575050
常见的不能声明为虚函数的有:普通函数(非成员函数);静态成员函数;内联成员函数;构造函数;友元函数。

1.为什么C++不支持普通函数为虚函数?
普通函数(非成员函数)只能被overload,不能被override,声明为虚函数也没有什么意思,因此编译器会在编译时邦定函数。

2.为什么C++不支持构造函数为虚函数?
这个原因很简单,主要是从语义上考虑,所以不支持。因为构造函数本来就是为了明确初始化对象成员才产生的,然而virtual function主要是为了再不完全了解细节的情况下也能正确处理对象。另外,virtual函数是在不同类型的对象产生不同的动作,现在对象还没有产生,如何使用virtual函数来完成你想完成的动作。(这不就是典型的悖论)

3.为什么C++不支持内联成员函数为虚函数?
其实很简单,那内联函数就是为了在代码中直接展开,减少函数调用花费的代价,虚函数是为了在继承后对象能够准确的执行自己的动作,这是不可能统一的。(再说了,inline函数在编译时被展开,虚函数在运行时才能动态的邦定函数)

4.为什么C++不支持静态成员函数为虚函数?
这也很简单,静态成员函数对于每个类来说只有一份代码,所有的对象都共享这一份代码,他不归某个具体对象所有,所以他也没有要动态邦定的必要性。

5.为什么C++不支持友元函数为虚函数?
因为C++不支持友元函数的继承,对于没有继承特性的函数没有虚函数的说法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值