标签:UNC mic 继承体系 完全 函数 tab code protect ima
static member function不能:1.直接存取nonstatic数据;2.它不能被声明为const
1.1Nonstatic member function(非静态成员函数)的调用方式
编译器会将member 函数实例转换为对等的”nonmember函数实例。转换步骤如下
1. 改写函数的signature(意指:函数原型)以安插一个额外的参数到member function中,用以提供一个存取管道,使class object得以将此函数调用。该额外参数被称为this指针
float Point3d::magnitude3d() const{...} //non-const nonstatic member的扩张过程 Point3d Point3d::magnitude(Point3d *const this) 如果是member function 是const,则变成: //const nonstatic member的扩张过程: Point3d Point3d::magnitued(const Point3d *const this)
2. 将每一个”对nonstatic data member的存取操作”改为经由this指针来存取
3.将member function重新写成一个外部函数。将函数名称经过“mangling”处理,使它在程序中成为独一无二的词汇
名字的特殊处理(name mangling)
1.2Virtual Member Functions(虚拟成员函数)
如果normalize()是一个virtual member function,那么以下的调用:
ptr->normalize(); //将会被内部转化为: (*ptr->vptr[1])(ptr);
如果使用类对象调用虚拟函数,其解析方式和非静态成员函数一样。对于上一节中的normalize()函数。
1.3Static member Function(静态成员函数)
static member function会被提出于class声明之外,并给予一个经过mangled的适当的名称,以对象、引用或指针调用static member function将被转换为一般的nonmember函数调用。
取一个静态成员函数的地址,获得的将是其在内存中的位置,也就是其地址。由于static member function没有this指针,所以其地址类型并不是一个“指向class member function的指针”,而是一个“nonmember函数指针“
在执行期间,调用操作需要执行期间获得某些相关的信息,如果把这些信息放在ptr上,那么一个指针或一个引用持有两项信息
这些信息放在指针上会增加额外的空间负担和降低与C语言程序的连接兼容性,所以把这额外的信息应放在对象本身。
为了支持virtual function机制,必须首先能够对多态对象有某种形式的“执行器类型判断法”,在C++中,多态表示“以一个public base class 指针(或reference)寻址出一个derived class object”的意思。
看一个class是否支持多态就看他有没有virtual function,因此它就需要这份额外的执行期信息
ptr->z();
在实现上,可以在每一个多态的class object身上添加两个members
表格中的virutual functions地址在编译时期就可以获得virtual function的地址,此外,这一组地址是固定不变的,执行期不可能新增或替换之,由于程序执行时,表格的大小和内容都不会发生改变,所以其构建和存取都可以由编译器完全掌握,不需要执行器的任何接入。
执行期要做的,只是在特定的记录着virutual function的地址激活virutal function
一个class只会有一个virtual table,每一个table内涵其对应的class object中所有的active virtual functions函数实体的地址,这些active virtual function包括:
每一个virtual function都派有一个固定的索引值,这个索引值在整个继承体系中与特定的virtual function相关联。
上图为单一继承情况
如果这样一个调用
ptr->z();
那么我怎么拥有足够多的只是在编译时期设定virtual function调用呢?
(*ptr->vptr[4])(ptr);
2.1多继承下的虚函数
class Base1 { public: Base1(); virtual ~Base1(); virtual void speakClearly(); virtual Base1 *clone() const; protected: float data_Base1; } class Base2 { public: Base2(); virtual ~Base1(); 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; }
在多重继承中支持virtual function,复杂度在于第二个及后继的base classes上以及必须在执行期调整this指针。如上derived支持virtual function难度落在base2上。
Base2 *pbase2=new Derived; //新对象的地址必须调整用以指向Base2 subobject,编译期会产生以下代码: Derived *temp=new Derived; Base2 *pbase2=temp?temp+sizeof(Base1):0; //如果没有这样调整指针指向“非多态运用”会失败 //当删除pbase2时,先调用正确的virtual destructor函数,然后实行delete运算符,pbase2需要调整以指出完整的对象起点 delete pbase2;
this指针的调整操作必须在执行期完成,即offset的大小及把offset加到this指针上头必须让编译器在某个地方插入。
Base1 *pbase1=new Derived; Base2 *pbase2=new Derived; //以下操作执行相同的Derived destructor delete pbase1; delete pbase2;
Thunk技术:以适当的offset值调整this指针;跳转到虚函数。
比如代码delete pbase2;
thunk看起来如下:
this += sizeof(base1); Derived::~Derived(this);
而对于多重继承的派生类,一个derived class内含n-1个额外的虚表,其中n表示上一层继承的base class的个数,因此对于该继承体系中的Derived,会产生2个虚表,分别对应于Base1和Base2。
针对每一个虚表,Derived对象中由对应的vptr,vptrs将在构造函数中设定初值。这一点可以说明构造函数一般不能是虚函数。
Derived关联的两个虚表可能有这样的名称:
当将一个derived对象地址给指定的Base1指针或derived指针,被处理virtual table是主要表格,当derived对象地址给base2指针,被处理的virtual table是次要表格,sun编译器将多个virtual table连锁为一个,指向次要表格的指针,可由主要表格的名称加一个offset获得。每个class只有个具名的virtual table。
2.2虚继承的情况
class Point2d { public: Point2d(float x = 0.0, float y = 0.0); virtual ~Point2d(); virtual void mumble(); virtual float z(); protected: float _x, _y; }; class Point3d : public virtual Point2d { public: Point3d(float x = 0.0, float y = 0.0, float z = 0.0); ~Point3d(); float z(); protected: float _z; }
当point3有唯一一个base class时,继承对象模型也不像非虚拟单一继承那么简单。
不要在virtual base class中声明nonstatic data member
对于nonmember、static member、nonstatic member函数都是转换为一样的形式,所以三者的效率完全一样。inline函数不仅能节省一般函数调用所带来的额外负担,也提供了程序优化的额外机会。
double (Point::*pmf)();//声明pmf函数指针指向Point的member function //指定其值 pmf = &Point::y;//y是Point的成员函数 //或者直接声明是赋值 double (Point::*pmf)() = &Point::y; //假设一个Point对象origin以及一个Point指针ptr,那么可以这样调用: (origin.*pmf)(); //或者 (ptr->*pmf)(); //编译器的转化为 (pmf)(&origin); //或者 (pmf)(ptr);
指向member function的指针声明语法以及指向“member selection运算符”的指针,其作用是作为this指针的空间保留
使用一个“member function指针”如果并不用于virtual function、多重继承、virtual base class 等情况,并不会比使用一个“nonmember function指针”的成本更高。
4.1指向Virtual Member Functions的指针
float (Point::*pmf)()=&Point::z; Point *ptr=new Point; //直接调用 ptr->z(); //pmf间接调用 (ptr->*pmf)();
内部转换为:(*ptr->vptr[(int)pmf])(ptr);
对于一个virtual member function,取地址,得到的是一个索引值。
假设有如下Point声明
class Point { public: virtual ~Point(); float x(); float y(); virtua float z(); }
取析构函数的地址:&Point::~Point();
得到索引1;取&Point::x()
得到函数在内存中的地址;取&Point::z()
得到索引2
但是“指向member function的指针”评估求值,会议内该值有两种意义而复杂化,对于nonvirtual该值是一个地址,对于virtual该值是索引值,但是如何辨别这个数值是内存地址还是索引值呢?
(((int)pmf) & ~127) ? (*pmf)(ptr) : (*ptr->vptr[(int)pmf]());
对于x&~127=0 当x<=127
,这种实现技巧必须假设继承体系中的virtual functions的个数小于128
4.2重继承下,指向Virtual Member Functions的指针
delta表示this指针的offset值,v_offset字段放置的是一个virtual(或多重继承中的第二个或后继的)base class的vptr位置,如果vptr放在对象的起始头处,该字段就没必要。
内联函数只是建议请求编译器实行,真正决定内联还是看编译器本身。如果请求被接受,编译器必须认为它可以用一个表达式合理地将这个函数扩展开来。通常编译器会计算assignments, function calls, virtual function calls等操作的次数的总和来决定是否内联。
一般处理inline function的两个阶段
形式参数(Formal Arguments)
如果实参是一个常量表达式,可以在替换前先完成求值操作,后继的inline替换,就可以把常量直接“绑”上去。如果既不是常量表达式,也不是带有副作用的表达式,那么就直接替换。
在内联函数的扩展期间,形式参数有什么变化?看如下例子:
inline int min(int i, int j) { return i < j ? i : j; } inline int bar() { int minval; int val1 = 1024; int val2 = 2048; /*(1)*/minval = min(val1, val2); /*(2)*/minval = min(1024, 2048); /*(3)*/minval = min(foo(), bar() + 1); return minval; }
标识(1)中直接参数替换:minval = val1 < val2 ? val1 : val2;
标识(2)中直接拥抱常量:minval = 1024;
标识(3)中引发参数副作用,需要导入两个临时对象:int t1,t2;minval = (t1 = foo()), (t2 = bar() + 1), t1 < t2 ? t1 : t2;
局部变量(Local Variables)
如果在inline定义中加入局部变量(局部变量都必须被放在函数调用的一个封闭区段中,拥有一个独一无二的名称)
inline int min(int i, int j){ int minval = i < j ? i : j; return minval; }
假设有操作:int minval = min(val1, val2);
为了维护局部变量可能变成
int __min_lv_minval; int minval = (__min_lv_minval = val1 < val2 ? val1 : val2), __min_lv_minval);
inline函数的每一个局部变量都必须放在函数调用的一个封闭的区段中,拥有独一无二的名称。
另外,如果扩展多次,可能会产生很多临时变量
int minval = min(val1, val2) + min(foo(), foo() + 1);
可能扩展为
//为局部变量产生的临时对象 int __min_lv_minval_00, __min_lv_minval_01; //为放置副作用产生的临时变量 int t1, t2; int minval = (__min_lv_minval_00 = val1 < val2 ? val1 : val2), __min_lv_minval_00) + ((__min_lv_minval_01 = (t1 = foo()), (t2 = bar() + 1), t1 < t2 ? t1 : t2), __min_lv_minval_00);
因此如果一个inline函数参数带有副作用或者以一个单一表达式进行多重调用或者函数内部有多个局部变量,这样都会产生临时对象,产生大量的扩展码,使得程序大小暴增。
标签:UNC mic 继承体系 完全 函数 tab code protect ima
原文地址:https://www.cnblogs.com/tianzeng/p/12153391.html