小探虚函数、虚继承、对象的初始化顺序、内存对齐与类大小

598 阅读4分钟

虚继承

虚基类的成员是由派生类的构造函数通过调用虚基类的构造函数进行初始化的。

在整个继承结构中,直接或间接继承虚基类的所有派生类,都必须在构造函数的成员初始化表中给出对虚基类的构造函数的调用。如果未列出,则表示调用该虚基类的缺省构造函数。

在建立对象时,只有最派生类的构造函数调用虚基类的构造函数,该派生类的其他基类对虚基类的构造函数的调用被忽略。

对象的初始化过程

类对象的初始化顺序:基类构造函数–>派生类成员变量的构造函数–>自身构造函数

基类构造函数的调用顺序:
首先按继承层次(祖先在前),同层次的按冒号后的继承声明顺序(不是初始化列表);

成员变量的初始化顺序:
取决于类内成员变量声明顺序(和初始化列表仍然无关);

析构函数的调用顺序:
析构顺序和构造顺序完全相反,且按照继承列表、成员变量声明的反方向; 先析构成员变量,再析构次级祖先,再析构更高级的祖先,最后析构自己。

虚函数

0.虚函数的重写,返回值,参数列表必须完全一致。如果参数列表不同,无论父类是否将其声明为virtual,都会造成函数被子类隐藏而非重写(覆盖)。

1.虚函数表是一个指针数组,存储的是各个虚函数的地址;虚表指针是一个二级指针,存储着虚表的地址。
2.虚函数表的指针存在于对象实例中最前面的位置。对象实例只保存指针,而虚表本身是属于每个特定类的;同类不同对象的虚表指针地址相同。 所以父类指针指向子类对象时,能够通过子类实例中特有的虚表指针找到子类的虚表,遍历虚表找到实际调用的那个虚函数的地址,实现了多态。
3.多继承时,子类会具有多个虚表指针,分别对应每个父类的虚表。
当子类新增了虚函数,会将其添加至第一张虚表的末尾。
当子类重写了虚函数,虚表中该虚函数所在位置的内容将被替换为新的子类的虚函数所在地址。
虚函数的地址按声明顺序置于虚表中。


类大小

1.使用vs 2019命令行工具查看类布局;
2.仅有纯虚函数的父类一样会提供虚函数指针(4字节);类中有空类的成员对象,每个对象同样占1字节;
3.static 静态变量、非虚成员函数均不占类对象内存,它们属于类。

内存对齐

1)内置数据类型的对齐值:等于sizeof(基本数据类型)。

2)结构体或者类的对齐值:其成员中size最大的那个值。

3)指定对齐值:#pragma pack (value) 时的指定对齐值value。

4)基本数据成员在对齐时,以min(自身长度,指定value)为对齐值。
整个类末尾补全时,以min(max(各成员对齐值),指定value)为对齐值。
类中的类成员对象计算对齐值,应取类中类的最大成员对齐值。

实例

一.普通函数,菱形多继承(virtual public)

1.如果class a b c不使用虚继承,t对象调用basefunc()将有二义性,编译不通过;可通过添加类名限定如t.a::f()或t.base::f()来解决。

2.class a b c均使用虚继承。为最远的派生类d提供唯一的基类base的成员,而不重复产生多次拷贝。

二.虚函数,菱形多继承(virtual public)

测试代码:

#include <queue>
using namespace std;
class base
{
public:
    base(int x) : val(x)
    {
        cout << "base struct function" << endl;
    }
    virtual void basefunc()
    {
        cout << "base:basefunc()" << endl;
    }
    ~base()
    {
        cout << "base destruct function" << endl;
    }

private:
    int val;
};
class a : virtual public base
{
public:
    a(int x) : base(x)
    {
        cout << "a struct function" << endl;
    }
    ~a()
    {
        cout << "a destruct function" << endl;
    }
    void basefunc()
    {
        cout << "override:  a::basefunc()" << endl;
    }
private:
    int aa;
};
class b : virtual public base
{
public:
    b(int x) : base(x)
    {
        cout << "b struct function" << endl;
    }
    ~b()
    {
        cout << "b destruct function" << endl;
    }
private:
    int bb;
};
class c : virtual public base
{
public:
    c(int x) : base(x)
    {
        cout << "c struct function" << endl;
    }
    ~c()
    {
        cout << "c destruct function" << endl;
    }
private:
    int cc;
};
//a,b,c都继承于base
class e
{
public:
    e(int x)
    {
        cout << "e struct function" << endl;
    }
    ~e()
    {
        cout << "e destruct function" << endl;
    }
private:
    short xx;
    long yy;
};

class g
{
public:
    g(int x)
    {
        cout << "g struct function" << endl;
    }
    ~g()
    {
        cout << "g destruct function" << endl;
    }
private:
    char xx;
};
//两个成员变量测试:e和g
class test : public b, public a, public c
{
public:
    test(int x) : a(x), b(x), base(x), c(x), memberG(7), memberE(6)
    {
        cout << "test struct function" << endl;
    }
    ~test()
    {
        cout << "test destruct function" << endl;
    }

private:
    e memberE;
    g memberG;
};

int main()
{
    test t(5);
    t.basefunc();
}

注意:basefunc的结果是a中重写后的结果。
菱形继承+虚继承+虚函数得到的test类对象的内存模型:

image.png

参考文章:www.cnblogs.com/liangxiaofe…

leehao.blog.csdn.net/article/det…

blog.csdn.net/lq87002128/…

blog.csdn.net/hexieXXX/ar…