深度探索C++对象模型——Function语意学

前言:

最近在读《深度探索C++对象模型》,收获不小,整理一些笔记,一来总结加体悟,二来希望以后在某些知识点的遗忘时能快速拾起也希望对读者有一定的帮助。

C++支持三种类型的成员函数

static
nonstatic
virtual

static成员函数

  • 静态成员函数不能直接存取非静态成员(包括静态成员数据和静态成员函数):因为静态成员函数是属于类的,不属于任何一个对象,在类对象创建以前就已经存在了。而非静态成员属于类,在类对象创建时才生成,如果在一个静态成员函数中直接存取非静态成员相当于在操作一个不存在的东西。
  • 静态成员函数不能是const成员函数:const成员函数其实是指其隐含的this指针指向的对象是const的,因为this指针永远指向当前对象,所以指针本身是const,那const成员函数的隐含参数就是一个指向const对象的const指针。既const成员函数本身是属于创建它的对象其this指针指向的对象只能是常量对象,而static成员函数属于类,它并不关心调用它的对象是谁,没有隐含的this指针,不存在关心指针指向的对象是不是常量的问题。同样的道理,静态成员函数也不支持virtual。
  • 网上看到篇分析静态成员函数的文儿,觉得挺好,贴个链接 http://blog.csdn.net/morewindows/article/details/6721430

Nonstatic Member Functions(非静态成员函数)

注意,以下,如果成员函数或者成员数据未强调静态,则默认为非静态的。
C++的设计准则之一,就是非静态成员函数至少必须和一般的nonmember function(非成员函数)有相同的效率。因为编译器内部会将“成员函数实体”转换成对等的“非成员函数实体”。
假如现在有一个3d图形类:Point3d定义如下:

class Point3d
{
public:
    Point3d()
    {
        x_ = 0;
        y_ = 0; 
        z_ = 0;
    }
    Point3d(float x, float y, float z){
        //
    }
    float Sum()
    {
        return (x_*x_ + y_*y_ + z_*z_);
    }
private:
    float x_;
    float y_;
    float z_;
};

以及,一个非成员函数Sum:

float NonSum(const Point3d* s)
{
    return (s->x_*s->x_ + s->y_*s->y_ + s->z_*s->z_);
}

成员函数float Point3d::Sum经过三个步骤被编译器内化为非成员函数NonSum函数的形式,如下:

  1. 改写成员函数的原型,安插一个额外的参数,即this指针到成员函数中,用以提供一个存取管道,使class object得以调用该函数,变成:
/*non-const nonstatic member*/
float Point3d::Sum(Point3d* const this)

/*如果是常量成员函数则*/
float Point3d::NonSum(const Point3d* const this)

2.对每一个nonstatic data member 的存取操作改为经由this指针来存取

{
    return (this->x_*this->x_ + this->y_*this->y_ + this->z_*this->z_);
}

3.将member function重新写成一个外部函数,对函数进行”mangling”处理,使它成为程序中独一无二的语汇。

如此,这个函数就算是转换好了,每一个调用操作也必须转换,于是

obj.Sun();
//变成:
mangling.._Sum(&obj);

ptr->Sum();
//变成:
mangling.._Sum(ptr);

这里Sum函数的前缀代表转换后的语汇,具体是什么视编译器而定。

名称的特殊处理(Name Mangling)

编译器在实现命名机制时,为了区分两个类的同名成员给每个成员的命名再加上其类型,为了区分两个重载函数,用(函数名+参数类型+参数数目)的方式区分,但是没有规定根据函数返回类型来区分,这就是为什么我们在定义两个重载函数的时候当两个函数只有返回值类型不一样时编译器会报重复定义的错误。注意,这里的函数参数类型,static int和int的类型是一样的都是int,切不可认为它们是不同的类型。

Virtual Member Functions(虚拟成员函数)

注:以下将Virtual Member Functions简称为virtual函数或者虚函数

先简单说一下我对虚函数(virtual函数)的一点理解,在OO(Object Oriented)设计中,通过虚函数来实现多态。具体实现的方法就是,基类定义一个虚函数,各个子类继承基类的虚函数并改写(也可以不改写),在调用操作的时候通过把子类的指针/引用赋给基类指针/引用,然后通过该被赋值的指针调用虚函数,调用的就是赋值符号右边的指针/引用类型对象的虚函数。其中,因为多态性会引入一些额外的执行期信息,所以必须把需要支持多态的类和不需要的类区分开来。而识别一个class是否支持多态的唯一适当的方法是看它是否有任何的virtual函数,只要class拥有一个virtual函数,它就需要这份额外的执行期信息。
我们可以简单概括一下虚函数的实现原理:

  • 每个含有virtual函数的类都有一个虚函数表(虚表),这个虚表里面放着索引,每个索引关联着一个virtual函数地址,编译器按照virtual函数声明顺序将其索引放置在虚表中,可根据该索引找到虚函数。
  • 每个含有(继承或者自身定义)虚函数的类对象都会在构造函数执行的时候创建一个指向该类虚函数表的指针,类对象可根据该指针找到其所属类的虚函数表,虚表里面对应的虚函数们。
  • 子类继承父类时,先将父类虚函数放进虚表,然后用新的函数指针代替子类改写后的虚函数地址,再将新定义的虚函数放在虚表后面。
    可以参考:http://www.cnblogs.com/malecrab/p/5572730.html

好了,紧接我们上面提到的Name Mangling,我们来看一下,编译器是如何对虚函数实行内部转化的:

如果normalize()是一个Virtual Member Function,那么以下的调用
ptr->normalize()
将会被内部转化为:

(*ptr->vptr[1])(ptr);
  • vptr表示编译器产生的指针,即每个对象都有的指向虚表的指针。事实上,vptr名称也会被mangled,因为在一个复杂的class派生体系中,可能存在多个vptr。
  • 1是虚函数表的索引值,关联到normalize()函数
  • 函数参数位置的ptr表示this指针

接着补充一下,如果Point3d::normalize()是一个静态成员函数的情景:
一下两个操作:

obj.normalize();
ptr->normalize();

将会被转换为一般的的nonmember函数调用,这也就是为什么当你定义两个函数像这样:

static void print(){}
void print(){}

会在编译时报重复定义错误的原因。

虽然静态成员函数大部分时候都被class object调用,但是它真的不是必须经过类对象才能调用,它是属于类的!还是上面说的原因,static member functions缺乏this指针所以它差不多等于nonmember函数,如果取其地址,获得的是其在内存中的位置,并不是一个指向class member function 的指针而是一个指向nonmember函数指针。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值