C++对象模型的那些事儿之六:成员函数调用方式对象模型的那些事儿之六:成员函数调用方式
前言
C++的成员函数分为静态函数、非静态函数和虚函数三种,在本系列文章中,多处提到static和non-static不影响对象占
用的内存,而虚函数需要引入虚指针,所以需要调整对象的内存布局。既然已经解决了数据,函数等在内存中的布局
问题,下一个需要考虑的就是如何调用,上述提到的三种函数的调用机制都不一样,其间的差异正是本篇博客需要讨
论的。
非静态成员函数
C++的设计准则之一就是:非静态成员函数至少必须和一般的非成员函数有相同的效率。要达到这一点,成员函数的
成员属性不会给其带来额外的负担。考虑以下两种函数调用:
int getAge(Animal *_this);//非成员函数
int Animal::getAge();//成员函数
//getNum函数定义如下:
int getAge(){
return age;
}
前者需要传入一个类指针,属于非成员函数调用,后者直接指明Animal类的函数调用。本质上,这两个函数是一样
的,因为编译器会将后者转换为前者,其转换步骤如下:
1.改写函数的原型,使得其接受一个额外的参数,这个额外的参数就是函数的this指针:
int Animal::getNum(Animal *this);//在函数内安插一个this指针
2.将每一个对非静态成员变量的存取操作改为经由this指针来存取:
{
return this.age;
}
3.将成员函数重写成一个外部函数,函数名称经过“mangling”处理,使它在程序中称为独一无二的语汇,如上述函数可
能被处理为:getNum_AnimalFv(p),这里需要保证名字不会有冲突!
这里引申一下,extern “C”操作会抑制函数名称的“mangling”效果,用于在C++中调用C函数。
所以,将一个成员函数改写成一个非成员函数的关键在于两点:一是能够提供给函数一个读写成员变量的通道,二是
解决好有可能带来的名字冲突。前者通过传递一个this指针可以很好的解决,后者则通过一定的名字转换规则来确保名
字的独一无二性。
虚拟成员函数
我们来回想一下如果一个类中存在虚函数,编译器会做以下三件事:
1.为该类分配一个虚函数表,它存有虚函数在执行器的地址
2. 在该类中安插一个虚指针,指向该类的虚表
3.将每一个虚函数的入口地址存放在虚函数表中相应的slot
所以,要想正确调用虚函数,只需找到该虚函数在虚函数表的相应位置即可,于是,考虑到以下示例。
class Animal{
public:
char name[10];//动物名字
int weight;//体重
virtual void eat(){}
};
Animal *animal;
animal->eat();
当调用虚函数eat的时候,编译器会自动转换成以下代码:
//vptr为指向虚函数表的指针,eat存放在虚函数的第一位,
//由于是成员函数,所以函数还必须传入一个this指针参数
(* animal->vptr[0])(animal);