静态绑定 & 动态绑定
- 静态绑定(statically bound),又名前期绑定(early binding);
- 动态绑定(dynamically bound),又名延期绑定(late binding)。
ps 英文名称摘自《Effective C++》 条款37。此条款中有关于“静态类型、动态类型”的描述。
在 C 语言中并没有“静态绑定”、“动态绑定”的概念(至少我没有查到)。
我理解的 C++ 类的内存模型其实就是 C 语言中的 struct 结构体。但成员函数又是归属于类所有的,所以就存在函数到类的绑定。
- 静态绑定指的就是这种绑定关系(映射关系)是在编译期间确定的;
- 动态绑定指的就是这种绑定关系(映射关系)在编译期间确定不了,得等到程序运行、执行期间才能最终确定
我们朴素地分析一下静态绑定、动态绑定。函数都有其地址,函数调用翻译成汇编代码其实就是直接用地址,很明显在汇编代码这个层次(甚至不用这么底层,C语言层次就行)不同的函数实现有不同的地址(使用C 语言的话,就有不同的函数名),但在 C++、Java 高级语言这个层次,不同的实现可能有相同的函数名(有很多种情况:重载、不同类里面相同名称、模板、继承体系中的重写),在 C++ 代码中出现一个函数调用(函数名),怎么正确地找到对应的实现(地址)呢?
- 重载:因为参数类型或者个数不同其实是有区分的,直接在高级语言下一个层次(比如C语言层面)使用不同的命名重新包装就可以了。调用时根据传参的情况,再映射就可以了。
- 不同的 class 里面相同名称:编译器实现这个完全可以和重载情形使用同样的方案。维护一个映射表就可以。
- 模板:暂时不了解
- 继承体系的重写:
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,只分析了变量声明的类型,内存的初始化、赋值在运行期才发生呢……好吧,编译器感觉为难做不了这个事情,只能把这个找到(绑定)函数具体实现的步骤放到运行期间了,可是效率会低一些呢。如果你要多态性,只能接受了。virtual
virtual
// 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();
,在能够确定类型的时候使用静态绑定效率更高。实际上真实的业务场景多是