在C++的设计原则中,其中有一条就是成员函数的效率最差也不能比非成员函数的效率差。在C++中也确实是这样,这是怎么做到的?
Nonstatic Member Functions简单来说,C++会将成员函数转化为一个普通的非成员函数。这种转换包括三步:1、在参数列表中插入一个该类的指针,即this指针。如果是const成员函数,则该指针的类型也是const的。2、将成员函数内的数据成员访问修改为通过入参的指针访问。3、修改成员函数的名称,使其在整个程序中独一无二。这样,一个成员函数即被转化为一个非成员函数,其效率自然与非成员函数的效率一样。当然自己写一个非成员函数,只不过C++提供了封装的特性,成为了一种面向对象的设计。
Static Member Function
成员函数的调用需要借助一个已经存在的类对象,而有些类函数的操作与该类的数据并无太大关系,也就是说与具体的类对象无关。那么如果仍然要通过一个类对象才能调用这样的函数就显得有点多余了和不精确了,尤其是当你没有一个类对象的时候就没办法这样调用了。那么编译器是怎么做的呢?
C++仍然会将一个static成员函数转化为一个普通的非成员函数,并且该函数的参数列表中并没有this指针。(当然,也可以有一种思路去解决这个问题,static成员函数的参数列表中仍然保有this指针,然后在static调用的地方,加入一个实参(Point3d *)(0))。static成员函数由于没有this指针,所以其会一些“限制”。1、不能访问非static成员。2、调用不需要类对象。3、不能被声明为const, virtual和volatile。
Virtual Member Function 单继承中的虚函数
通过前面的学习中我们已经大致知道多态的实现方式是通过虚函数表来实现的,但具体是怎么通过虚函数表进行运行期绑定的又没有介绍很多,这一节就是介绍了虚函数的动态绑定。
C++中多态的唯一标记就是虚函数,只有一个类中有虚函数时才会有虚函数表及动态绑定。虚函数表中存放有虚函数的地址,且该表在编译期产生,在程序运行期是只读的,虚函数表中存放有虚函数的地址。虚函数表中的虚函数有三种:
基类的虚函数地址(派生类没有重写)。派生类重写了基类虚函数的地址。纯虚函数的地址。(纯虚函数如果没定义,则会存放一个库函数pure_virtual_called()的地址,如果运行期调用这个纯虚函数,则会发生异常,使程序退出。)具体的,虚函数的地址是怎么在虚函数表中存放的呢?以Point, Point2d这个继承体系为例。
class Point{public: virtual float getX() {return x;} virtual ~Point(){} virtual void move() = 0;public: float x;};
对于类Point,其虚函数表中的函数地址如下:
如果Point2d类继承了Point,其声明如下:
class Point2d : public Point{public: virtual float getX() {return x;} virtual float getY() {return y;} virtual ~Point2d(){}public: float x;};
则其虚函数表中,虚函数地址的存放如下所示:
可见,当派生类重写了基类的虚函数时,派生类虚函数的地址在虚函数表中的位移是一样的,如果派生类新增了虚函数,则会将该新增的虚函数地址存放在虚函数表的尾部。
有了上述的信息,动态绑定是怎么实现的呢?假如有下列调用:
Point* p_ptr = PointFactory();p_ptr->getX();
则该调用会被转化为:
Point* p_ptr = PointFactory();p_ptr->virtual_table[1](p_ptr);
至于virtual_table的地址以及virtual_table[1]中存放的是哪个虚函数的地址,这些都是在运行期获得的。