C++对象模型:第1章-C++对象模型
2015-06-08 10:40
363 查看
与此文相关的文章:
多重继承和虚继承的内存布局
C++对象模型
C++
虚函数表解析
C++在布局以及存取时间上主要的额外负担是由virtual引起的,包括:
(1)虚函数机制;(2)虚基类
C++类对象
这篇文章不错,本文几个图来自这里:http://www.cnblogs.com/skynet/p/3343726.html
在c语言中,“数据”和“处理数据的操作(函数)”是分开来声明的,也就是说,语言本身并没有支持“数据和函数”之间的关联性。在c++中,通过抽象数据类型(abstract
data type,ADT),在类中定义数据和函数,来实现数据和函数直接的绑定。
概括来说,在C++类中有两种成员数据:static、nonstatic;三种成员函数:static、nonstatic、virtual。
nonstatic data成员被配置于每一个类对象之内;static data成员被存放在个别类对象之外。
static 和 nonstatic 函数成员被放在个别类对象之外;
virtual函数则按照以下两个步骤支持:
a. 每个类产生一些(数量等于该类中的虚函数个数)虚函数的指针(指针指向的是虚函数的地址),放在虚函数表中;
b. 每个类对象会产生一个vptr指针,该指针指向虚函数表。vptr的设定和重置都是由每个类的构造函数、析构函数和拷贝赋值运算符自动完成。每个类含关联一个type_info对象用以支持运行时类型识别(runtime type identification,RTT1),它是编译器生成的特殊类型信息,包括对象继承关系、对象本身的描述,RTT1是为多态而生成的信息,所以只有具有虚函数的对象才会生成。
例如下面要说的:(1)《深度探索C++对象模型》该书侯捷所译的版本第9页提到,虚函数表的第一项是存放type_info参数的,但我实际测试的结果并不是这样:虚函数表的第一项是第一个声明的虚函数地址不是type_info的地址。(2)这篇博文C++对象模型的验证说明其系统的编译器中:虚函数表的地址依次增大(例如第一个虚函数表的地址小于第二个虚函数表的地址),但是我的验证中虚函数表的地址却是依次递减的。(3)同样是上一条提到的博文中,其编译器下,虚函数表中每个表项(一个虚函数的地址)占四个字节,而我的g++(gcc
4.4.3)下是占四个字节,但是VS2010却不是这样。
我验证的编译器下,VS2010和gcc 4.4.3下与CSDN博主陈皓的文章C++ 虚函数表解析结论一致。
VS2010:
g++:
可以看出,虚函数表中表项地址一次递减。
结论:虚函数按照其声明顺序放于表中。
1)虚函数按照其声明顺序放于表中。
2)父类的虚函数在子类的虚函数前面。
2)没有被覆盖的函数依旧。
2) 子类的成员函数被放到了第一个父类的表中。(所谓的第一个父类是按照声明顺序来判断的)
2) 子类的成员函数被放到了第一个基类的表中。
3) 内存布局中,其父类布局依次按声明顺序排列。
4) 如果子类重写了基类的虚函数,例如重写了基类的虚函数print(),那么,每个基类的虚表中的print()函数都被覆盖成了子类的print()。这样做就是为了解决不同的基类类型的指针指向同一个子类实例,而能够调用到实际的函数。
多重继承和虚继承的内存布局
C++对象模型
C++
虚函数表解析
C++在布局以及存取时间上主要的额外负担是由virtual引起的,包括:
(1)虚函数机制;(2)虚基类
C++类对象
这篇文章不错,本文几个图来自这里:http://www.cnblogs.com/skynet/p/3343726.html在c语言中,“数据”和“处理数据的操作(函数)”是分开来声明的,也就是说,语言本身并没有支持“数据和函数”之间的关联性。在c++中,通过抽象数据类型(abstract
data type,ADT),在类中定义数据和函数,来实现数据和函数直接的绑定。
概括来说,在C++类中有两种成员数据:static、nonstatic;三种成员函数:static、nonstatic、virtual。
nonstatic data成员被配置于每一个类对象之内;static data成员被存放在个别类对象之外。
static 和 nonstatic 函数成员被放在个别类对象之外;
virtual函数则按照以下两个步骤支持:
a. 每个类产生一些(数量等于该类中的虚函数个数)虚函数的指针(指针指向的是虚函数的地址),放在虚函数表中;
b. 每个类对象会产生一个vptr指针,该指针指向虚函数表。vptr的设定和重置都是由每个类的构造函数、析构函数和拷贝赋值运算符自动完成。每个类含关联一个type_info对象用以支持运行时类型识别(runtime type identification,RTT1),它是编译器生成的特殊类型信息,包括对象继承关系、对象本身的描述,RTT1是为多态而生成的信息,所以只有具有虚函数的对象才会生成。
C++对象模型
关于C++对象模型主要的参考书就是Stanley B. Lippman所著的《深度探索C++对象模型》一书。然而就虚函数表的布局问题,这是一个与编译器相关的问题,因此,该书所提到的C++对象模型的布局问题仅可作为参考,而实际不同的编译器以及同一编译器的不同版本都可能有不同的实现方式。例如下面要说的:(1)《深度探索C++对象模型》该书侯捷所译的版本第9页提到,虚函数表的第一项是存放type_info参数的,但我实际测试的结果并不是这样:虚函数表的第一项是第一个声明的虚函数地址不是type_info的地址。(2)这篇博文C++对象模型的验证说明其系统的编译器中:虚函数表的地址依次增大(例如第一个虚函数表的地址小于第二个虚函数表的地址),但是我的验证中虚函数表的地址却是依次递减的。(3)同样是上一条提到的博文中,其编译器下,虚函数表中每个表项(一个虚函数的地址)占四个字节,而我的g++(gcc
4.4.3)下是占四个字节,但是VS2010却不是这样。
我验证的编译器下,VS2010和gcc 4.4.3下与CSDN博主陈皓的文章C++ 虚函数表解析结论一致。
没有派生类时的虚函数表
#include <iostream> #include <string> #include <typeinfo> using namespace std; #include<iostream> using namespace std; class Base { public: virtual void test() { cout << "\t\t Base: virtual void test()" << endl; }; int getIBase() const; static int instanceCount(); virtual void print() const { cout << "\t\t Base: virtual void print() const" << endl; }; protected: int iBase; static int count; }; int main() { Base base; const type_info &a = typeid(base); cout << &a << "\t type_info base对象的地址" << endl; /*const type_info &b = typeid(base); cout << &b << "\t type_info base对象的地址"<< endl;*/ cout << (int *)(&base) << "\t 虚函数表地址" << endl; typedef void (*Fun)(); Fun fun = (Fun)*((int *)*(int *)(&base)); cout << &fun << "\t virtual void test()函数地址" <<endl; fun(); Fun fun2 = (Fun)*(((int*)*(int*)(&base)) + 1); cout << &fun2 << "\t virtual void print() const函数地址" << endl; fun2(); }结果:
VS2010:
g++:
可以看出,虚函数表中表项地址一次递减。
结论:虚函数按照其声明顺序放于表中。
单继承,没有虚函数覆盖
图参考文章:http://www.cnblogs.com/skynet/p/3343726.html#include <iostream> #include <string> #include <typeinfo> using namespace std; #include<iostream> using namespace std; class Base { public: virtual void test() { cout << "\t\t Base: virtual void test()" << endl; }; int getIBase() const; static int instanceCount(); virtual void print() const { cout << "\t\t Base: virtual void print() const" << endl; }; protected: int iBase; static int count; }; class Drived: public Base { virtual void test_drived( ) { cout << "\t\t Derived:in drived" << endl; } }; int main() { Drived d; const type_info &a = typeid(d); cout << &a << "\t type_info base对象的地址" << endl; /*const type_info &b = typeid(base); cout << &b << "\t type_info base对象的地址"<< endl;*/ cout << (int *)(&d) << "\t 虚函数表地址" << endl; typedef void (*Fun)(); Fun fun = (Fun)*((int *)*(int *)(&d)); cout << &fun << "\t virtual void test()函数地址" <<endl; fun(); Fun fun2 = (Fun)*(((int*)*(int*)(&d)) + 1); cout << &fun2 << "\t virtual void print() const函数地址" << endl; fun2(); Fun fun3 = (Fun)*(((int*)*(int*)(&d)) + 2); cout << &fun3 << "\t virtual void print() const函数地址" << endl; fun3(); }结论:
1)虚函数按照其声明顺序放于表中。
2)父类的虚函数在子类的虚函数前面。
单继承:有函数覆盖
1)覆盖的f()函数被放到了虚表中原来父类虚函数的位置。2)没有被覆盖的函数依旧。
多继承:无函数覆盖
1) 每个父类都有自己的虚表。2) 子类的成员函数被放到了第一个父类的表中。(所谓的第一个父类是按照声明顺序来判断的)
多继承:有函数覆盖
1) 每个基类都有自己的虚表。2) 子类的成员函数被放到了第一个基类的表中。
3) 内存布局中,其父类布局依次按声明顺序排列。
4) 如果子类重写了基类的虚函数,例如重写了基类的虚函数print(),那么,每个基类的虚表中的print()函数都被覆盖成了子类的print()。这样做就是为了解决不同的基类类型的指针指向同一个子类实例,而能够调用到实际的函数。
相关文章推荐
- C++异常处理
- C++9.4 vector容器的自增长(size、capacity、reserve)
- C++对象模型之详述C++对象的内存布局
- 《C/C++工程师综合练习卷》之小试牛刀
- 《C/C++工程师综合练习卷》之小试牛刀
- PAT Broken Keyboard (20)
- 设计模式C++实现——代理模式
- c++对象内存布局
- c++对象内存布局
- C++调用java记录
- 对string对象或者vector对象执行sizeof运算
- 不可或缺 Windows Native (16) - C++: 函数重载, 缺省参数, 内联函数, 函数模板
- 给字符数组赋值的方法
- C++9.3.8 赋值与swap
- C++之静态成员变量和静态成员函数
- C++ 堆 和 栈 浅析
- 如何用VS2012编写c语言?
- 智能指针的标准之争:Boost vs. Loki
- C++继承、虚继承、虚函数类的大小问题
- C/C++类型转换