C++ 动态绑定与静态绑定

今天做题的时候遇到这样一个问题。

以下程序片段输出内容为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Demo {
public:
Demo():count(0) {}
~Demo() {}

void say(const std::string&msg) {
fprintf(stderr,"%s\n", msg.c_str());
}
private:
int count;
};

int main(int argc, char **argv) {
Demo* v = NULL;
v->say("hello world");
}
答案为 "hello, world"。

这个问题涉及到了 C++ 中动态绑定和静态绑定的知识。静态绑定又名前期绑定, early binding,是在编译期就确定的,通过对象调用。而动态绑定又名后期绑定, late binding,是在运行时确定的,通过地址实现。

只有采用 “指针 -> 函数 ()”“引用变量。函数 ()” 的方式调用 * C++ 类中的虚函数才会执行动态绑定。对于 C++* 中的非虚函数,因为其不具备动态绑定的特征,所以不管采用什么样的方式调用,都不会执行动态绑定。(本段及下面表格来自阿波 123 的博客,感谢。)

        

代码形式

对于虚函数

对于非虚函数

作用

绑定方式

作用

绑定方式

类名:: 函数 ()

调用指定类的指定函数

静态绑定

调用指定类的指定函数

静态绑定

对象名. 函数 ()

调用指定对象的指定函数

静态绑定

调用指定对象的指定函数

静态绑定

引用变量. 函数 ()

调用引用对象所属类的指定函数

动态绑定

调用引用变量所属类的指定函数

静态绑定

指针 -> 函数 ()

调用引用对象所属类的指定函数

动态绑定

调用指针变量所属类的指定函数

静态绑定

本题中, say() 方法为非虚函数,故进行静态绑定,即使 v 是空指针,也可以根据 v 的类型调用该函数。

这令我想起了之前阅 《Effective C++ 》 时的一些收获,一起写在这里。

  1. 条款 20: 宁以 pass-by-reference-to-const 替换 pass-by-value
    使用引用可以解决切割 (slicing) 问题。
  2. 条款 36: 绝不重新定义继承而来的 non-virtual 函数
    因为如果重新定义,根据上述静态绑定和动态绑定的不同,会绑定到不同的函数中,从而效果不一样。
  3. 条款 37: 绝不重新定义继承而来的缺少参数值
    由条款 36 重新定义 non-virtual 函数永远是错误的。对于 virtual 函数,其为动态绑定,而缺少参数值却是静态绑定!