在前面已经探讨过了虚继承对类的大小的影响,这次来加上虚函数和虚继承对类的大小的影响。
先来回顾一下之前例子的代码:
#include <iostream> using namespace std; class BB { public: int bb_; }; class B1 : virtual public BB { public: int b1_; }; class B2 : virtual public BB { public: int b2_; }; class DD : public B1, public B2 { public: int dd_; }; int main(void) { cout<<sizeof(BB)<<endl; cout<<sizeof(B1)<<endl; cout<<sizeof(DD)<<endl; B1 b1; long** p; cout<<&b1<<endl;//类的首地址 cout<<&b1.bb_<<endl; cout<<&b1.b1_<<endl; p = (long**)&b1;//这表示是vbptr cout<<p[0][0]<<endl;//取出vbptr指向的vbtl的第一个数据项 cout<<p[0][1]<<endl;//取出vbptr指向的vbtl的第二个数据项 DD dd; cout<<&dd<<endl;//类的首地址 cout<<&dd.bb_<<endl; cout<<&dd.b1_<<endl; cout<<&dd.b2_<<endl; cout<<&dd.dd_<<endl; p = (long**)ⅆ cout<<p[0][0]<<endl; cout<<p[0][1]<<endl; cout<<p[2][0]<<endl; cout<<p[2][1]<<endl; dd.bb_ = 10; BB* pp; pp = ⅆ pp->bb_;//这是通过间接访问,需要运行时的支持 return 0; }
编译运行:
而数据模型为:
关于对虚继承的详细分析可以参考博文:http://www.cnblogs.com/webor2006/p/5621825.html
下面在这个例子上加上虚函数来进一步讨论数据模型:
#include <iostream> using namespace std; class BB { public: virtual void vfbb() { cout<<"BB::vfbb()"<<endl; } virtual void vfbb2() { cout<<"BB::vfbb2()"<<endl; } int bb_; }; class B1 : virtual public BB { public: virtual void vfb1() { cout<<"B1::vfb1()"<<endl; } int b1_; }; class B2 : virtual public BB { public: virtual void vfb2() { cout<<"B2::vfb2()"<<endl; } int b2_; }; class DD : public B1, public B2 { public: virtual void vfdd() { cout<<"DD::vfdd()"<<endl; } int dd_; }; int main(void) { cout<<sizeof(BB)<<endl; cout<<sizeof(B1)<<endl; cout<<sizeof(DD)<<endl; //B1 b1; //long** p; //cout<<&b1<<endl;//类的首地址 //cout<<&b1.bb_<<endl; //cout<<&b1.b1_<<endl; //p = (long**)&b1;//这表示是vbptr //cout<<p[0][0]<<endl;//取出vbptr指向的vbtl的第一个数据项 //cout<<p[0][1]<<endl;//取出vbptr指向的vbtl的第二个数据项 //DD dd; //cout<<&dd<<endl;//类的首地址 //cout<<&dd.bb_<<endl; //cout<<&dd.b1_<<endl; //cout<<&dd.b2_<<endl; //cout<<&dd.dd_<<endl; //p = (long**)ⅆ //cout<<p[0][0]<<endl; //cout<<p[0][1]<<endl; //cout<<p[2][0]<<endl; //cout<<p[2][1]<<endl; return 0; }
编译运行:
下面还是先画一下它的内存模型,然后再用代码来验证:
先来看下BB类:
编译运行:
下面来分析一下B1类:
下面来验证:
typedef void (*FUNC)(); int main(void) { cout<<sizeof(BB)<<endl; cout<<sizeof(B1)<<endl; cout<<sizeof(DD)<<endl; BB bb; long** p = (long**)&bb; FUNC fun; fun = (FUNC)p[0][0]; fun(); fun = (FUNC)p[0][1]; fun(); cout<<"----------------------------"<<endl; B1 b1; p = (long**)&b1; fun = (FUNC)p[0][0]; fun(); cout<<p[1][0]<<endl; cout<<p[1][1]<<endl; fun = (FUNC)p[3][0]; fun(); fun = (FUNC)p[3][1]; fun(); //DD dd; //cout<<&dd<<endl;//类的首地址 //cout<<&dd.bb_<<endl; //cout<<&dd.b1_<<endl; //cout<<&dd.b2_<<endl; //cout<<&dd.dd_<<endl; //p = (long**)ⅆ //cout<<p[0][0]<<endl; //cout<<p[0][1]<<endl; //cout<<p[2][0]<<endl; //cout<<p[2][1]<<endl; return 0; }
编译运行:
下面再来分析一下DD类:
同样用代码来验证:
typedef void (*FUNC)(); int main(void) { cout<<sizeof(BB)<<endl; cout<<sizeof(B1)<<endl; cout<<sizeof(DD)<<endl; BB bb; long** p = (long**)&bb; FUNC fun; fun = (FUNC)p[0][0]; fun(); fun = (FUNC)p[0][1]; fun(); cout<<"----------------------------"<<endl; B1 b1; p = (long**)&b1; fun = (FUNC)p[0][0]; fun(); cout<<p[1][0]<<endl; cout<<p[1][1]<<endl; fun = (FUNC)p[3][0]; fun(); fun = (FUNC)p[3][1]; fun(); cout<<"----------------------------"<<endl; DD dd; p = (long**)ⅆ fun = (FUNC)p[0][0]; fun(); cout<<p[1][0]<<endl; cout<<p[1][1]<<endl; fun = (FUNC)p[3][0]; fun(); cout<<p[4][0]<<endl; cout<<p[4][1]<<endl; fun = (FUNC)p[7][0]; fun(); fun = (FUNC)p[7][1]; fun(); return 0; }
编译运行:
有了虚继承和虚函数的类的内存模型是比较复杂的,需细细体会。下面来讨论一个新的东东:
对于C++的数据模型,实际上它还包括另外一些信息,也就是RTTI,以便在运行时进行类型识别,C++的运行时类型识别主要是由dynamic_cast运算符、typeid运算符、type_info来支持下,下面具体来学习下:
#include <iostream> using namespace std; class Shape { public: virtual void draw() = 0; virtual ~Shape() { } }; class Circle : public Shape {//圆形 public: void draw() { cout<<"Circle::draw ..."<<endl; } }; class Square : public Shape {//正方形 public: void draw() { cout<<"Square::draw ..."<<endl; } }; int main(void) { Shape* p; Circle c; p = &c; p->draw(); return 0; }
编译运行:
以上输出毫无疑问,这时可以用dynamic_cast运算符来进行类型识别:
#include <iostream> using namespace std; class Shape { public: virtual void draw() = 0; virtual ~Shape() { } }; class Circle : public Shape {//圆形 public: void draw() { cout<<"Circle::draw ..."<<endl; } }; class Square : public Shape {//正方形 public: void draw() { cout<<"Square::draw ..."<<endl; } }; int main(void) { Shape* p; Circle c; p = &c; p->draw(); if(dynamic_cast<Circle*>(p)) { cout<<"p is point to a Circle Object"<<endl; } else if(dynamic_cast<Square*>(p)) { cout<<"p is point to a Square Object"<<endl; } else { cout<<"p is point to a Other Object"<<endl; } return 0; }
编译运行:
这时就可以做安全的向下转型:
这里要提醒一下:在VS C++中要想支持这个类型识别,需要进行一个设置才行,否则是不支持的:
如果选择“否(/GR-)”,再次运行则会给出警告了:
如果运行的话会直接报错的:
所以还是需要将其打开,将配置还原才行。
现在已经学到几个转换相关的函数了,下面来总结一下:
static_cast:用在编译器认可的转型。
reinterpret_cast:用在编译器不认可的转型。
const_cast:去除常量性。
以上三个都是静态转型,不需要运行时支持。
而这里用的的dynamic_cast是安全向下转型,是动态转型,需要运行时的支持。
对于这个运算符它返回的是type_info对象,它的结构如下:
class type_info { public: virtual ~type_info(); bool operator==(const type_info& rhs) const; bool operator!=(const type_info& rhs) const; int before(const type_info& rhs) const; const char* name() const;//它就代表类型的实际名,可以用它来判断类型 const char* raw_name() const; private: void *_m_data; char _m_d_name[1]; type_info(const type_info& rhs); type_info& operator=(const type_info& rhs); static const char _Name_base(const type_info *,__type_info_node* __ptype_info_node); };
下面用它来打印一下:
编译运行:
所以就可以这样来写判断语句:
int main(void) { Shape* p; Circle c; p = &c; p->draw(); if(dynamic_cast<Circle*>(p)) { cout<<"p is point to a Circle Object"<<endl; Circle* cp = dynamic_cast<Circle*>(p); //安全向下转型 cp->draw(); } else if(dynamic_cast<Square*>(p)) { cout<<"p is point to a Square Object"<<endl; } else { cout<<"p is point to a Other Object"<<endl; } //typeid运算符 cout<<typeid(*p).name()<<endl; cout<<typeid(Circle).name()<<endl; if(typeid(*p).name() == typeid(Circle).name()) { cout<<"p is point to a Circle Object"<<endl; ((Circle*)p)->draw(); }else if(typeid(*p).name() == typeid(Square).name()) { cout<<"p is point to a Square Object"<<endl; ((Square*)p)->draw(); } else { cout<<"p is point to a Other Object"<<endl; } return 0; }
编译运行:
【注意】:
①、由于type_info的构造函数和=号运算符是私有的:
所以不能这样写:
所以代码中是“typeid(Circle).name()”直接来用。
②、这样的类型转换都没有通过多态来访问效率来得高:
③、reinterpret_cast和C风格的强制转让换换还是有区别的: