静态绑定 & 动态绑定

  • 静态绑定(statically bound),又名前期绑定(early binding);
  • 动态绑定(dynamically bound),又名延期绑定(late binding)。

ps 英文名称摘自《Effective C++》 条款37。此条款中有关于“静态类型、动态类型”的描述。

在 C 语言中并没有“静态绑定”、“动态绑定”的概念(至少我没有查到)。



我理解的 C++ 类的内存模型其实就是 C 语言中的 struct 结构体。但成员函数又是归属于类所有的,所以就存在函数到类的绑定。

  • 静态绑定指的就是这种绑定关系(映射关系)是在编译期间确定的;
  • 动态绑定指的就是这种绑定关系(映射关系)在编译期间确定不了,得等到程序运行、执行期间才能最终确定

我们朴素地分析一下静态绑定、动态绑定。函数都有其地址,函数调用翻译成汇编代码其实就是直接用地址,很明显在汇编代码这个层次(甚至不用这么底层,C语言层次就行)不同的函数实现有不同的地址(使用C 语言的话,就有不同的函数名),但在 C++、Java 高级语言这个层次,不同的实现可能有相同的函数名(有很多种情况:重载、不同类里面相同名称、模板、继承体系中的重写),在 C++ 代码中出现一个函数调用(函数名),怎么正确地找到对应的实现(地址)呢?

  1. 重载:因为参数类型或者个数不同其实是有区分的,直接在高级语言下一个层次(比如C语言层面)使用不同的命名重新包装就可以了。调用时根据传参的情况,再映射就可以了。
  2. 不同的 class 里面相同名称:编译器实现这个完全可以和重载情形使用同样的方案。维护一个映射表就可以。
  3. 模板:暂时不了解
  4. 继承体系的重写:

virtual

继承体系中允许 base 指针是可以指向 derived 对象,但编译器依旧是根据声明指针时的类型去映射具体的函数实现的,所以会出现一些变态的现象:

  • class Base 无 void func(),class Derived 有 void func(),我们执行 

Base *p=new Derived(); p->func();

  • class Base 的 void func() 打印 base,class Derived 的 void func() 打印 derived,我们执行 

Base *p=new Derived(); p->func();

  • class myclass 有函数 simple(),函数实现中没有对 this 解引用的操作(不管是显式的还是隐在的),我们执行 

myclass *p=NULL; p->simple();

so,正如我们看到的,这就是静态绑定。

p->func()virtual,用来表明碰到这个类的指针(或引用)调用此函数时不要根据静态类型(声明指针的类型)映射具体实现,你们要根据这个指针指向的对象的实际类型来映射具体实现(即动态绑定)。编译器说,纳尼,我靠,我哪知道啊?我只解析 declaration,只分析了变量声明的类型,内存的初始化、赋值在运行期才发生呢……好吧,编译器感觉为难做不了这个事情,只能把这个找到(绑定)函数具体实现的步骤放到运行期间了,可是效率会低一些呢。如果你要多态性,只能接受了。virtualvirtual

// Base 类有虚函数 func()
// Derived 继承自 Base,且 override 了 func()
   Base *p=new Derived(); 
   p->func();



我们觉得一目了然的事情,比如编译器汇编时直接把 

p->func()

 调用换成  derived::func(p)

 不就好了吗,实现起来很难吗?事实上编译器是卡在它只知道 p 是  base *

 类型,它并不知晓 p 被初始化(赋值)了什么,它只生成“得到一个地址,把这个地址赋给 base 指针 p;根据 p 指明的地址调用 func()” 的指令,至于前一条“分配内存,初始化 derived 对象”的指令,现在是前后相邻紧挨着,其他场景可能这两条指令相差十万八千里呢。事实上我们只会在测试时写  Base *p=new Derived(); p->func();

 这样的例子,在真实的业务场景中为了效率至少应该写成  Derived derived; derived.func();

,在能够确定类型的时候使用静态绑定效率更高。实际上真实的业务场景多是