Skip to content

2023/5/17


研究虚函数时, 发现其中的内存大有学问, 试总结如下

  • 首先需要明确, 在编译器中可以选择内存对齐的模式, 本文使用的是8字节对齐(windows64)
  • 由于是8字节对齐, 每一个int是4字节, 就会出现每两个int会让size增加8的现象
  • 纯虚函数要用"=0"来声明, 此时该类为抽象类, 不能被实例化
  • 每一个含有虚函数的类都会有一个虚指针(vptr)和虚表(vtbl)
  • 构造函数不能为虚函数, 因为虚指针和虚表的建立在构造函数完成之后
  • 析构函数可以是虚函数
  • 构造时是先构造父类(小), 再构造子类(大), 析构时先析构子类(大), 再析构父类(小)
  • 关于强转: 只能将大的强转为小的, 即子类转为父类, 因为我们允许数据的丢失而不允许数据的缺失

  • 一个没有任何数据的类size是1, 应该是编译器为其分配了一个字符, 用于区分不同的对象(obj)

  • 指针的字节是按照系统来的, 64位系统的指针是8字节, 32位系统的指针是4字节

#include <iostream>
using namespace std;
class Base
{
private:
    int a;

public:
    Base() {}
    virtual ~Base() { cout << "~Base()" << endl; }
};

class Dervied1 : virtual public Base
{
private:
    int dervied1;

public:
    Dervied1() {}
    ~Dervied1() { cout << "~Derived1()" << endl; }
};
class Dervied2 : virtual public Base
{
private:
    int dervied2;

public:
    Dervied2() {}
    virtual ~Dervied2() {}
};
class Final : public Dervied1, public Dervied2
{
private:
    int final;

public:
    Final() {}
    virtual ~Final() {}
};

int main()
{

    cout << sizeof(Base) << endl;
    cout << sizeof(Dervied1) << endl;
    cout << sizeof(Dervied2) << endl;
    cout << sizeof(Final) << endl;
    return 0;
}

重头戏--虚继承

  • 虚继承和普通继承是有很大区别的
  • 普通继承时会发生内存合并, 考虑到内存对齐的问题, 如果父类有一个int子类有一个int, 此时普通继承会将他们放在同一块内存, size仍然是8, 如果是虚继承, 子类会为父类在内存中单独存放(所以菱形继承时只会有一个爷类, 如果内存对齐将他们都合并就无法确保只有一个爷类了), 何谓单独存放呢? 在这个例子里, 子类为了存放这两个int的size为16, 再加上虚基类表指针8(就是基类的虚指针), 他自己的虚指针8(注意这里无论子类是否有虚函数都需要有这个虚指针, 因为父类的虚函数需要通过子类的虚指针来调用, 它和虚基类表指针的区别是虚基类表指针是可以用来维护孙类的继承的)
  • 补充: 普通继承是没有虚基类表指针的, 基类的虚函数全都转移到了子类的虚表当中去了
  • 此时孙类的继承由于不是虚继承, 父类由于内存对齐而浪费的内存可以利用起来, 而最开始的base的数据和虚基类表指针都原封不动的继承下来, 无法发生内存间的合并, 就像一个黑盒!