深度搜索C++对象模型 - 函数语义学



深度搜索C++对象模型:  Function Semantics

C++支持三种类型的 member function: static nonstatic virtual; 后文分析他们的差异

//*********************************************************************************************//
---> "nonstatic member function: ( 非静态成员函数 )":
C++设计准则之一就是: 非静态成员函数至少要和非成员函数有相同的效率;
那么
Func( const A *this );
A.Func();
他们至少要具有相同的效率, 也就是说 选择非静态成员函数不该带来额外的负担;
事实上 编译器在内部已经"将 member function 转换为 nonmember function";
改写规则:
-> 改写函数的函数原型, 安插一个额外的函数参数, this 指针.. 提供一个存取管道
-> 将每一个对非静态数据成员的操作改为"经由 this 指针来存取";
-> 将成员函数重新改写为一个外部函数,并且经过"函数改名" ( 支持多态 进行独一无二的改名 );

//-----------------------------------------------------------------------------------------------------------------------//
---> "virtual member function: ( 虚拟成员函数 )":
class A
{
 public:
  virtual void Func1();
};

A *ptr;
ptr->Func1();
这个调用在内部将被转化为: ( * ptr->vptr[0] ) ( ptr );
-> vptr表示由编译器产生的指针, 指向 vtbl, 他被安插到每一个"声明有 virtual functions"的 class object 中;
-> 0 是 virtual table slot 的索引值, 关联到 Func1(); 函数
-> 第二个 ptr 是 this 指针

//-----------------------------------------------------------------------------------------------------------------------//
---> "static member function: ( 静态成员函数 )":
如果一个class里存在 static member function, 那么他将会被转化为一般的 nonmember function 调用;
-> 一个 static member function 不能直接存取其 class 中的 nonstatic member;
-> 他不能被声明为 const volatile virtual;
-> 他不需要经由 class object 才被调用;

//*********************************************************************************************//

---> "virtual member function: ( 虚拟成员函数 )":
为了支持 virtual function 机制, 首先必须能够对于多态对象有某种形式的"执行期类型判断法",
在C++中 多态表示"以一个 public base class 的指针, 寻址出一个 derived class object"的意思;
Point *ptr;
ptr = new Point2d;
ptr的多态机能主要扮演一个 "输送机制" 的角色, 经由他,我们可以在程序的任何地方采用一组 public derived 类型, 这种多态形式"是消极的";
当被指出的对象真正被使用的时候, 多态就变成"积极"的了, 如:
ptr->Func();

欲判断哪些 class 展现出多态特性 我们需要一些额外的执行期信息 ( 光凭是 class 和 struct 并不能很好的判断 )
因此识别一个 class 是否支持多态, "唯一适当"的方法就是看他是否有任何 virtual function, 那么他需要这一份额外的信息;

什么样的额外信息能让我们在执行期调用正确的 Func(); ? 我们需要知道:
-> ptr所指向的真实类型, 这可以使我们选择正确的 Func() 实例
-> Func() 实例的位置, 以便我们能够调用他;

实际上就是 vptr 和 vtbl,
为了找到 vtbl , class object 被安插了一个 vptr 指向 vtbl;
为了找到函数地址, vtbl 里每一个表格地址里都是一个函数地址;

一个 class 只会有一个 vtbl, 每一个 vtbl 中内含与之对应的 class object 中的所有 active virtual function 地址 包括
-> 这一 class override 的所有函数实例;
-> 继承自 base class  的 virtual 函数实例;
-> 虚函数实例;

class A
{
 public:
 virtual void Func1() { };
};

class B : public A
{
 public:
 virtual void Func1() { };
 virtual void Func2() { };
};

当 class B 继承自 class A 时 发生了什么:
-> 他可以继承来自 base class 的 virtual function 实例 并且拷贝到 vtbl 相对应的 slot 中内
-> 他要把自己的函数实例 override 到 vtbl 中, 替代 base class 的 slot
-> 他可以加入新的 virtual function, 此时 vtbl 的尺寸会变大

A a;
B b;
对于实例a 他有一个 vtbl 内含了 Func1(); ( 此Func1() 为 A::Func1() );
对于实例b 他有一个 vtbl 内含了 Func1(); ( 此Func1() 为 B::Func1() ); Func2();

//*********************************************************************************************//

我们怎么使用我们上文加入的额外信息来判断我们需要哪个 virtual function 的调用?
--> "单继承":
A *ptr;
ptr = new B;
ptr->Func1();
-> 一般来说 每次调用 Func1(); 时,  我们并不知道 ptr 所指向的 class object 的真实类型, 但是我们知道, 经由 ptr 可以存取到这个对象的 vtbl;
-> 我们也不知道哪一个 Func1(); 实例被调用, 但是我们知道每一个 Func1(); 函数地址都被放在了 slot 1 中;

此时: ptr = new B;  ptr 指向了 class B object,  通过 A 内存模型解释 class B object 时;
我们找到了 vptr, 找到了 vtbl, 在找到 slot 1, 这样就可以调用正确的 Func1(); 了, 我们并不需要知道 slot 1 里面到底是什么内容
( 很明显vptr指向了 class B vtbl, 自然调用的是 class B 的 virtual Func1() )

--> "多重继承":
class A
{
 public:
 virtual void Func1() { };
};

class B
{
 public:
 virtual void Func2() { };
};

class C : public A, public B
{
    public:
    virtual void Func1() { };
    virtual void Func2() { };
    virtual void Func3() { };
};

A *a = new C;  // 不会调整 因为 class A 是第一 base class;
B *b = new C;  // 需要经过调整 因为 class B 不是第一 base class; ( 加上了 sizeof( A ); )
此时 class C object 之中 会有两根 vptr,
--> "base A vptr" 其中第一根按照我们上文单继承所说 他内含了 base class A 的所有 virtual function, 并且 override 了所有已经重写的 function,
而且 他还在这里"加入了新写的的 virtual function", ( 即这张表里有 "C::Func1(); C::Func3();" )
--> "base B vptr" 第二根只会进行上面的前两个步骤, 就是 内含 base class B 的所有 virtual function, 并且 override 了所有已经重写的 function,
( 这张表里有 "C::Func2();" )
C c;
A *a_1 = new C;         // 不需要调整 直接按照 A 的内存模型解释C, 但是只可以解释到 C::Func1()
A a_2 = c;                  // 经过拷贝构造函数, vptr 指向了 A 的 vtbl,  解释到 A::Func1();
B *b_1 = new C;         // 需要调整, 调整之后按照 B 的内存模型解释, 只可以解释到 C::Func2()
B b_2 = c;                  // 经过拷贝构造函数, 而且调整了 vptr, 指向了 B 的 vtbl, 解释到 B::Func2();

--> "虚拟继承":
class A
{
 public:
    virtual void Func1() { };
};

class B : virtual public A
{
 public:
    virtual void Func1() { };
    virtual void Func2() { };
};

对于 class B object 来说 他有一根由 "virtual function 机制带来的 vptr_1", 还有一根由 "virtual base class 机制带来的 vptr_2";
而虚拟继承时, vptr_1 指向的 vtbl 内部只含有自己的虚函数, 也就是 B::Func2(); 而另一个 override 的虚函数 在 vptr_2 指向的 vtbl 里;
所以虚拟继承时, 两者之间的转换必须调整指针, 而且非常复杂...   所以"不要在一个 virtual base class 中声明 非静态变量... 否则可能会越来越复杂";

//*********************************************************************************************//

---> "函数的效能":
对于函数 一般有几种形式:
-> inline member;
-> nonmember friend;
-> static member;
-> virtual member ( 单继承 )
-> virtual member ( 多继承 )
-> virtual member ( 虚拟继承 )
经过实验发现: 前三种具有完全相同的效率 ( 实际上他们被编译器改编成了完全相同的形式 ), 越往后 效率越低;

//*********************************************************************************************//

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值