类中函数的深度探索
类中包含的函数主要有三种:static成员函数、nostatic成员函数、virtual成员函数。
C++类中数据成员和成员函数的命名机制:
数据成员的命名:在每个数据成员命名的时候编译器将该成员所属的类名也添加上,用来标志这个成员的来源范围。这样,继承类就可以与子类用相同的名字命名其成员,这样就不会产生二义性和冲突,但是对外界而言,通过继承类对象访问该名字,只会获取继承类的数据成员,因为根据命名查找机制,两个名字属于不同的作用域,继承类中的成员覆盖了子类中的成员,想要调用必须显示调用子类成员或在继承类作用域中使用using。(函数名相同)
成员函数的命名:在同一个类中允许函数重载,重载的机制就是对函数名+参数名编码获得唯一的编码识别。
(1)static成员函数
首先,说明static成员函数的几条限制:1)static成员函数只能使用static数据成员;2)static成员函数不能设为const、virtual、inline函数,只能保持一份实例;3)static成员函数不使用this指针,因此不必需要通过对象来访问,虽然很多时候是这样使用的。
static成员函数在内存空间中只有一份实例,如果取函数的地址,得到的是真实的内存地址,其函数指针是与普通函数相同的函数指针,而不是指向成员函数的指针,例如
static int foo(); int (*p)()=&A::foo; 使用方法与普通函数指针相同。
因此,对于static成员函数的调用与调用非成员函数的效率是相同的。
(2)nostatic成员函数
虽然nostatic成员函数也是存放在对象外的内存空间中,且取其函数地址也是真实的内存地址,但是调用nostatic成员函数也需要通过对象,也就是说需要this指针,因为nostatic成员函数中可以直接使用类数据成员。
编译器将this指针作为参数传进nostatic成员函数中去,然后通过this指针访问数据成员。例如:
int foo();A a;a.foo();编译器将转化为foo(&a);
编译器将成员函数转化为了非成员函数,是的对成员函数的调用不会有异于非成员函数。
成员函数的指针的使用也必须通过对象调用,例如
int (A::p)()=&A::foo;(a.p)();相当于p(&a)调用,也可以由其继承类对象调用。
(3)virtual函数
虚拟成员函数地址存放在虚表中,而获得到虚表的访问权仍然需要通过对象的地址。
指向虚拟成员函数的指针获取到的虚拟成员函数的地址实际上是虚拟函数在虚表中slot编号,通过该编号可以获取到虚拟成员函数的地址,也正是因为获取得到的是编号而非真正的内存地址,才能实现多态机制。
1)单一继承
单一继承情况下,子类会继承父类的vptr指针和虚表,不会产生新的vptr,这时,子类中若存在不同与父类的虚拟函数,将会添加到虚表的尾部,子类若修改了父类中虚拟函数的定义,将会在虚表中相应的位置进行覆盖指向新的函数地址,也就是说,子类的虚表是在父类虚表的基础上扩展修改得到的。
2)多重继承
多重继承中牵扯到this指针调整的问题,也就是第二个及以后的父类地址的问题。C++中将继承类的虚函数写到第一个父类的虚表中,而第一个父类的虚表也就成为主虚表,其他的则成为次虚表。
在继承类中修改的虚函数将会覆盖所有的虚表中原虚函数,<strong>凡是继承类中存在的虚函数都会在主虚表中出现,</strong>对于没有修改的虚函数,其实际地址存放位置仍然在原父类的虚表中,调用的时候需要进行this指针调整。
如图,Derived a;a.mumble();的调用将会在主虚表中查到4号函数,然后将this指针调整到Base2位置,同多Base2 子对象调用mumble()函数。
再例如,Base2 *p=new Derived; delete p;也会产生this指针的调整,必须调用正确的析构函数,this指针必须回到真个Derived对象的地址首部。
因此,对于多重继承主要是要考虑this指针的调整问题。
3)虚拟继承
虚拟继承的子类将会产生一个vptr和一个虚表来存储子类中虚拟函数和其子类对象的地址。
虚拟继承也需要进行this指针的调整,如同多重继承一样,但是this指针的调整更加复杂,建议不要在虚拟基类中定义nostatic成员,这样会使虚拟子类对象的offset值的确定变得过于复杂。(在不同子类中,虚拟子对象的offset是不相同的)。
原文地址:http://www.cnblogs.com/tracylee/archive/2012/12/20/2825562.html