第4章 Function语义学

C++的设计准则之一:nonstatic member function至少和一般的nonmember function有相同的效率。

4.1 Member的各种调用方式

  • 非静态成员函数:内化为nonmember function。1.安插this指针 2.重写为一个外部函数,进行mangling处理
  • 虚拟成员函数:基类的指针调用派生类的虚函数——通过虚表寻址调用;非指针对象直接调用虚函数——同nonstatic member function一样; 函数内部调用虚函数——同nonstatic member function一样(压制虚拟机制)。
  • 静态成员函数:没有this指针。主要特性:1.不能直接访问nonstatic members; 2.不能被声明为const、volatile、virtual; 3.作为callback函数,提供封装性(否则只能作为全局函数才能实现回调)。

4.2 Virtual Member Functions

实现模型:每一个class 只会有一个virtual table,内含该class之中有作用的virtual function的地址,每一个object有一个vptr(多继承可能有多个),指向virtual table的所在。

虚函数的地址以及存放虚函数地址的虚表在编译时期就确定下来了,不需要执行期的介入。执行期要做的是,激活虚函数。

(一)单一继承

一个类只会有一个virtual table,表内含有所有active virtual functions函数实体的地址。active virtual functions包括:

  1. 这个类所定义的函数实体,它会改写一个可能存在的base virtual functions函数实体地址。
  2. 继承自基类,但派生类决定不该写的虚函数。
  3. 纯虚函数地址。

单一继承的原理

  • 继承基类的虚函数实体:基类所有虚函数实体的地址会被拷贝到derived class的virtual table相对应的slot之中。
  • 重写基类的虚函数实体:将自己的函数实体地址放到虚表对应的slot中。
  • 新加入的virtual function:virtual table的尺寸增大一个slot,新的函数实体地址放进该slot之中。

多态调用的原理

基类指针ptr指向派生类的对象,派生类的对象在构建时,会生成一个指向派生类虚表的虚指针,并设定初值。经由ptr可以存取到virtual table。基类的虚函数在派生类虚表中的位置保持不变,最终调用的是,派生类重写的虚函数。执行期所知道的是,所调用的函数的真实实体。

(二)多重继承

 有多少个基类,派生类就会有多少个虚表以及指向虚表的虚指针。

基类的不同,被处理的虚表指针不同。(书中说,根据虚表的独一无二的名字,不同的基类,处理不同的表格。难道不是通过虚指针访问虚表的嘛?)。以下三种情况需要调整指针:

  • 派生类指针指向派生类对象,使用虚表1,访问虚表2的函数时。
  • base2基类的指针指向派生类对象,调用析构函数。
  • 派生类对象虚函数执行的返回值是派生类指针,赋给base2基类指针。

不同编译器对该处的处理方法不同,例如,Sun编译器将virtual tables连锁为一个,指向次要虚表的指针,由指向主要虚表的指针加上一个offset。

(三)虚继承下的 Virtual Functions

用单一虚继承来分析

class Point2d
{
    public:
        Point2d();
        virtual ~Point2d();
        virtual void mumble();
        virtual void z();
    private:
        int x,y;
}

class Point3d:virtual public Point2d
{
    public:
        Point3d();
        ~Point3d();
        void z();
    private:
        int z;
}

// test虚基类指针
code: cout << sizeof(Point2d) << "\t" << sizeof(Point3d) << endl;
outPut: 12    20

// test派生类新增虚函数
class Point3d:virtual public Point2d
{
    public:
        Point3d();
        ~Point3d();
        void z();
        virtual void fun();
    private:
        int z;
}
code: cout << sizeof(Point3d) << endl;
output: 24

在上一小节分析:单一继承,一个类只有一个虚表。

单一虚继承,在原来的虚函数表的基础上会增加一个虚基类表(暂且这么叫)和指向虚基类表的指针,包含虚基类的offset。

??? 问题:

当派生类新增了虚函数,派生类的大小会增加一个指针的大小。

4.4 指向Member Functions的指针

取一个nonstatic data member的地址,得到的时该member在class布局的offset+1;取一个nonstatic member function的地址,如果是nonvirtual,则得到它在内存中的真正地址;对一个virtual member function取其地址,所获得的是一个索引值。

double (Point2d::*ptr_fun)() = &Point2d::mumble;
// 绑定object调用
(origin.*ptr_fun)();
或
(ptr->*ptr_fun)();

// 一、mumble是nonvirtual function
内化为:(*ptr_fun)(ptr);
// 二、mumble是virtual function,非指针对象调用虚函数时,会压制虚机制。
内化为:(*ptr->vptr[(int)ptr_fun])(ptr);

指向member function的指针声明语法,以及指向"member selection运算符"的指针,其作用就是作为this指针的空间保留者。这也就是为什么static member functions(没有this指针)的类型是“函数指针”,而不是指向member function指针的原因。

(一)支持指向"Virtual member Functions"之指针

对一个virtual member function取其地址,所获得的是一个索引值。上述调用会被内化为一个编译期的式子:

(*ptr->vptr[(int)ptr_fun])(ptr);

编译器必须能够区分:ptr_fun代表内存地址还是Virtual table中的索引值。

 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值