【C++】浅析C++中的对象模型

来源:互联网 发布:德力西 知乎 编辑:程序博客网 时间:2024/04/26 17:23

以下代码运行环境:windows8.1 32位 VS2015

(一)不含有虚函数的单一继承模型:
测试代码:
//单一继承,无虚函数class A{public:       A(int a = 0, char c = 0)              :_a(a)              , _c(c)       {}       int GetA()       {              return _a;       }       static int staticFun()       {              return _val;       }       static int _val;private:       int _a;       char _c;};class B :public A{public:       B(char b = 0)              :_b(b)       {}       char GetB()       {              return _b;       }private:       char _b;};int A::_val = 100;void test1(){       A a(10,'a');       B b('b');}

现象以及分析:
关于类的静态成员,我们需要知道以下几点:
(1)类的静态成员是属于类而不属于对象,所以他不是类的单个对象所有。
(2)静态成员只存在一个,不像普通的成员,每创建一个对象,就会创建一组普通的成员。
(3)静态成员的初始化不能在类中,肯定是不能在构造函数中,只能在类外并且在main函数之前,按照这样的格式进行初始化:int A::_val = 100。并且是先初始化再使用。
(4)在静态成员函数中不可以使用非静态成员。因为非静态成员是属于对象,而类的静态成员函数是属于类的,在类的对象实例化之前就已经完成了初始化。如果在静态成员函数用引用非静态成员,就好比是使用一个并没有完成初始化的变量。
(5)类的非静态成员函数中可以引用类的静态成员。反之不可以。
(6)静态成员函数没有this指针。

了解了静态成员的这些特性(当然本文主要是来分析对象模型的),我们在分析对象模型的时候,可以顺便来看看静态成员到底存储在哪里?
首先看一下上边代码的内存分布:

在程序调试中,是这样表现出来的:
这里,我们只是看到的A类的普通数据成员继承给子类(上述图中,子类自称自父类的成员默认是0,是因为我们的父类的默认构造函数给出的默认值是0),下边我们来看一下A类的静态成员是存储在哪里?是否会继承给子类?

从这里我们可以看出,父类的静态成员继承给子类,并且两者是存储在一个地址上的。这里就验证了这样的一句话:父类中定义了静态成员,则整个继承体系中只有一个这样的成员,无论派生出多少个子类。静态成员是存储在全局区的。

(二)含有虚函数的单一继承模型:
测试代码:
class A{public:       virtual void foo()       {              cout << "A::foo()" << endl;       }       virtual void funA()       {              cout << "A::funA()" << endl;       }private:       int _a;       char _c;};class B :public A{public:       virtual void foo()       {              cout << "B::foo()" << endl;       }       virtual void funB()       {              cout << "B::funB()" << endl;       }private:       char _b;};void test2(){       A a;       B b;}
内存分布:

结合程序的调试进行分析:

结合程序的调试信息进行分析:

下边我们来写一个函数来打印出我们的虚表,看看与我们分析的是否一样~~
void PrintTable(int* vTable){       typedef void(*pFun)();       cout << "虚表地址为:" << vTable << endl;       for (int i = 0; vTable[i] != NULL; ++i)       {              printf("第%d个虚表地址为:%p->", i, vTable[i]);              pFun f = (pFun)vTable[i];              f();       }       cout << endl;}void test2(){       A a;       B b;       int* vTable1 = (int*)(*(int*)&a);       int* vTable2 = (int*)(*(int*)&b);       PrintTable(vTable1);       PrintTable(vTable2);}
运行结果:

【总结】:
(1)一个类只要有一个虚函数,它就会被编译器分配一个虚表指针,也就是__vfptr,用来存储虚函数的地址;
(2)子类的虚函数表是在父类的虚函数表上进行修改的,就像上边的对象模型所示,B类的虚函数就是在A类的虚函数之后;
(3)父类中的虚函数被子类改写,也就是说,子类中含有与父类的虚函数 函数名相同,参数列表相同,返回值相同的函数(协变除外),这样就构成了重写。下边再次区分几个容易混淆的概念--重载、重写(覆盖)、重定义(隐藏)。

重载--在同一个作用域内,函数名相同,参数列表不同,返回值可以相同可以不同的两个函数可以构成重载,需要声明的是,c++语言中支持函数的重载,而c语言中不支持函数的重载。原因是c语言和c++对函数的处理是不同的。具体可以点击文末的链接。
重写(覆盖)--在不同的作用域中(分别在父类和子类中),函数名相同,参数列表,返回值都相同(协变除外),并且父类的函数是虚函数,访问限定符可同可不同的两个函数就构成了重写。
重定义(隐藏)--在不同的作用域中(分别在父类和子类),函数名相同,只要不是构成重写就是重定义。
     协变:协变也是一种重写,只是父类和子类中的函数的返回值不同,父类的函数返回父类的指针或者引用,子类函数返回子类的指针或者引用。

(4)只有类的成员函数才可以被定义为虚函数,静态成员函数不可以被定义为虚函数。

(三)多继承的对象模型
测试代码:
class A{public:       virtual void foo()       {              cout << "A::foo()" << endl;       }       virtual void funA()       {              cout << "A::funA()" << endl;       }private:       int _a;};class B{public:       virtual void foo()       {              cout << "B::foo()" << endl;       }       virtual void funB()       {              cout << "B::funB()" << endl;       }private:       int _b;};class C :public A, public B{public:       virtual void foo()       {              cout << "C::foo()" << endl;       }       virtual void funC()       {              cout << "C::funC()" << endl;       }private:       int _c;};void test3(){       C c;}
下边我们先通过调试信息,看看对象的内存分布:

通过这个图,我们就可以画出多继承的对象的内存分布:

下边我们仍然编写一个函数打印出虚函数表:
void PrintTable(int* vTable){       typedef void(*pFun)();       cout << "虚表地址为:" << vTable << endl;       for (int i = 0; vTable[i] != NULL; ++i)       {              printf("第%d个虚函数地址为:0x%p->", i, vTable[i]);              pFun f = (pFun)vTable[i];              f();       }       cout << endl;}void test3(){       C c;       int* vTable = (int*)(*(int*)&c);       PrintTable(vTable);       vTable = (int *)(*((int*)&c + sizeof(A) / 4));       PrintTable(vTable);}
程序运行结果:

【总结】
在多重继承体系下,有n个含有虚函数的父类,派生类中就有n个虚函数表,最终子类的虚函数是在第一个父类的虚函数表中;

(四)含有虚继承的多重继承模型(含有虚函数)
测试代码:
class A{public:A():_a(1){}virtual void foo(){cout << "A::foo()" << endl;}virtual void funA(){cout << "A::funA()" << endl;}private:int _a;};class B : public virtual A{public:B():_b(2){}virtual void foo(){cout << "B::foo()" << endl;}virtual void funB(){cout << "B::funB()" << endl;}private:int _b;};class C : public virtual A{public:C():_c(3){}virtual void foo(){cout << "C::foo()" << endl;}virtual void funC(){cout << "C::funC()" << endl;}private:int _c;};class D :public B, public C{public:D():_d(4){}virtual void foo(){cout << "D::foo()" << endl;}virtual void FunD(){cout << "D::funD()" << endl;}private:int _d;};


通过调试信息看对象模型:
B类对象的调试信息:

C类对象的调试信息:

D类对象的调试信息:



下边,根据调试信息画出内存分布:



文中涉及到的内容链接:
内存对齐
重载、重写、重定义
详解重载
参考文档:
对象模型(一)
对象模型(二)

3 0
原创粉丝点击