标签:
ptr->z();如此一来才能够找到并调用z()的适当实体.
struct date { int m, d, y; };严格地说,这符合上述规范,然而事实上它并不需要那些信息,加上那些信息将使C struct 膨胀并且打破链接兼容性,却没有带来任何明显的补偿利益.
class date { public: int m, d, y; };但实际上它并不需要那份信息,下面的 class 声明虽然不符合新规范,却需要那份信息:
struct geom { public: virtual ~geom(); };因此,需要一个更好的规范,一个"以class的使用为基础,而不在乎关键词是class或struct(1.2节)"的规范.如果 class 真正需要那份信息,它就会存在;如果不需要,它就不存在.那么,到底何时才需要这份信息?很明显是在必须支持某种形式的"执行期多态(runtime polymorphism)"的时候.
Point *ptr;可以指定ptr以寻址出一个Point2d对象:
ptr = new Point2d;或是一个Point3d对象:
ptr = new Point3d;ptr的多态性能主要扮演一个输送机制(transport mechanism)的角色,经由它,可以在程序的任何地方采用一组 public derived类型.这种多态形式被称为是消极的(passive),可以在编译时期完成——virtual base class 的情况除外.
// "积极多态(active polymorphism)"的常见例子 ptr->z();在runtime type identification(RTTI)性质于1993年被引入C++语言之前,C++对"积极多态(active polymorphism)"的唯一支持,就是对于 virtual function call的决议(resolution)操作.有了RTTI,就能够在执行期间查询一个多态的pointer或多态的reference.
// "积极多态(active polymorphism)"的第二个例子 if (Point3d *p3d = dynamic_cast<Point3d *>(ptr)) return p3d->_z;所以,问题已经被区分出来,那就是:欲鉴定哪些 class 展现多态特性,需要额外的执行期信息.关键词 class 和 struct 并不能够帮助这点.由于没有导入如polymorphism之类的新关键词,因此识别一个 class 是否支持多态,唯一适当的方法就是看看它是否有任何 virtual function.只要 class 拥有一个 virtual function,它就需要这份额外的执行期信息.
ptr->z();其中z()是一个 virtual function,那么什么信息才能使得在执行期调用正确的z()实体?需要知道:
class Point { public: virtual ~Point(); virtual Point &mult(float) = 0; float x() const { return _x; } virtual float y() const { return 0; } virtual float z() const { return 0; } protected: Point(float x = 0.0); float _x; };virtual destructor被赋值slot 1,而mult()被赋值slot 2,此例并没有mult()的函数定义(因为它是一个pure virtual function),所以pure_virtual_called()的函数地址会被放在slot 2中.如果该函数意外地被调用,通常的操作是结束掉这个程序,y()被赋值slot 3而z()被赋值slot 4,x()的slot是多少?答案是没有,因为x()并非 virtual function.
class Point2d : public Point { public: Point2d(float x = 0.0, float y = 0.0) : Point(x), _y(y) {} ~Point2d(); // 改写base class virtual functions Point2d& mult(float); float y() const { return _y; } protected: float _y; };一共有三种可能性:
class Point3d : public Point2d { public: Point3d(float x = 0.0, float y = 0.0, float z = 0.0) : Point2d(x, y), _z(z) {} ~Point3d(); // 改写的base class virtual functions Point3d& mult(float); float z() const { return _z; } protected: float _z; };其中 virtual table中的slot 1放置Point3d的destructor,slot 2放置Point3d::mult()函数地址,slot 3放置继承自Point2的y()函数地址,slot 4放置它自己的z()函数地址.
ptr->z();那么,如何有足够的知识在编译时期设定 virtual function的调用呢?
(*ptr->vptr[4])(ptr);在这个转化中,vptr表示编译器所插入的指针,指向 virtual table;4表示z()被赋值的slot编号(关联到Point体系的 virtual tabl).唯一一个在执行期才能直到的东西是:slot 4所指的到底是哪一个z()函数实体?
// class体系,用来描述多重继承(MI)情况下支持 virtual function时间的复杂度 class Base1 { public: Base1(); virtual ~Base1(); virtual void speakClearly(); virtual Base1 *clone() const; protected: float data_Base1; }; class Base2 { public: Base2(); virtual ~Base2(); virtual void mumble(); virtual Base2 *clone() const; protected: float data_Base2; }; class Derived : public Base1, public Base2 { public: Derived(); virtual ~Derived(); virtual Derived *clone() const; protected: float data_Derived; };"Derived 支持virtual functions"的困难度,统统落在Base2 subobject上,有三个问题需要解决,以此例而言分别是:
Base2 *pbase2 = new Derived;新的Derived对象的地址必须调整,以指向其Base2 subobject,编译时期会产生以下的代码:
// 转移以支持第二个base class Derived *temp = new Derived; Base2 *pbase = temp ? temp + sizeof(Base1) : 0;如果没有这样的调整,指针的任何"非多态运用"都将失败:
// 即使pbase2被指定一个Derived对象,这也应该没有问题 pbase2->data_Base2;当程序员要删除pbase2所指的对象时:
// 必须首先调用正确的virtual destructor函数实体 // 然后施行delete运算符 // pbase2可能需要调整,以指出完整对象的起始点 delete pbase2;指针必须被再一次调整,以求再一次指向Derived对象的起始处(推测它还指出Derived对象).然而上述的offset加法却不能够在编译时期直接设定,因为pbase2所指向的真正对象只有在执行期才能确定.
Base2 *pbase2 = new Derived; // ... delete pbase2;该调用操作所连带的"必要的this指针调整"操作,必须在执行期完成.也就是说,offset的大小,以及把offset加到 this 指针上头的那一小段代码,必须由编译器在某个地方插入.问题是,在哪个地方?
(*pbase2->vptr[1])(pbase2);改变为:
(*pbase2->vptr[1].faddr)(pbase2 + pbase2->vptr[1].offset);其中faddr内含 virtual function地址,offset内含 this 指针调整值.
Base1 *pbase1 = new Derived; Base2 *pbase2 = new Derived; delete pbase1; delete pbase2;虽然两个 delete 操作导致相同的Derived destructor,但它们需要两个不同的 virtual table slots.
vtbl_Derived; // 主要表格 vtbl_Base2_Derived // 次要表格于是将一个Derived对象地址指定给一个Base1指针或Derived指针时,被处理的 virtual table是主要表格vtbl_Derived,而当将一个Derived对象地址指定给一个Base2指针时,被处理的 virtual table是次要表格vtbl_Base2_Derived.
Base2 *ptr = new Derived; // 调用Derived::~Derived // ptr必须被向后调整sizeof(Base1)个bytes delete ptr;这个调用操作的重点:ptr指向Derived对象中的Base2 subobject:为了能够正确执行,ptr必须调整指向Derived对象的起始处.
Derived *pder = new Derived; // 调用Base2::mumble() // pder必须被向前调整sizeof(Base1)个bytes pder->mumble();第三种情况发生于一个语言扩充性质下:允许一个 virtual function的返回类型有所变化,可能是base type,也可能是publicly derived type,这一点可以通过Derived clone()函数实体来说明.clone()函数的Derived版本传回一个Derived class 指针,默默地改写了它的两个base class 函数实体.当通过"指向第二个base class"的指针来调用clone()时,this 指针的offset问题于是诞生:
Base2 *pb1 = new Derived; // 调用Derived *Derived::clone() // 返回值必须被调整,以指向Base2 subobject Base2 *pb2 = pb1->clone();当进行pb1->clone()时,pb1会被调整指向Derived对象的起始地址,于是clone()的Derived版会被调用,它会传回一个指针,指向一个新的Derived对象;该对象的地址在被指定给pb2之前,必须先经过调整,以指向Base2 subobject.
class Point2d { public: Point2d(float = 0.0, float = 0.0); virtual ~Point2d(); virtual void mumble(); virtual float z(); protected: float _x, _y; }; class Point3d : public virtual Point2d { public: Point3d(float = 0.0, float = 0.0, float = 0.0); ~Point3d(); float z(); protected: float _z; };虽然Point3d有唯一一个(同时也是最左边)的base class,也就是Point2d,但Point3d和Point2d的起始部分并不像"非虚拟的单一继承"情况那样一致.这种情况显示于图4.3(如下图所示).由于Point2d和Point3d的对象不再相符,两者之间的转换也需要调整 this 指针.至于在虚拟继承的情况下要清除thunks,一般而言已经被证明是一项高难度技术.
版权声明:本文为博主原创文章,未经博主允许不得转载。
C++对象模型——Virtual Member Functions (虚拟成员函数)(第四章)
标签:
原文地址:http://blog.csdn.net/yiranant/article/details/47379127