C++中虚函数的动态绑定和多态性
目录
-
- 静态类型 vs 动态类型、静态绑定 vs 动态绑定
- 虚函数动态绑定实现机制、虚析构函数
- 多态性
一.静态 vs 动态
静态类型 VS 动态类型。静态类型指的是对象声明的类型,在编译器确定的。动态类型指的是对象的所指向的类型,动态类型是可以更改的,静态类型无法更改。继承关系会导致对象的指针和引用具有静态类型和动态类型,因为继承关系的存在中可能会存在类型之间的向上向下类型转换。静态绑定 VS 动态绑定。某特性(函数)依赖与对象的静态类型,在编译期确定,某特性(函数)依赖于对象的动态类型,在运行期确定。只有通过基类的引用或者指针调用虚函数时,才能发生动态绑定,如果使用对象来操作虚函数的话,仍然会采用静态绑定的方式。因为引用或者指针既可以指向基类对象,也可以指向派生类对象的基类部分,用引用或者指针调用的虚函数。例如下面的例子中,pd的静态类型是D*,pb的静态类型是B*,是在编译器确定的;pd和pb的动态类型都是D*。DoSomething()普通的成员函数是在编译器静态绑定的,调用各自的静态类型中的方法。vfun()是虚成员函数,虚函数是动态绑定的,因此调用动态类型的方法,也就是D类型中vfun。
1 #include <iostream> 2 using namespace std; 3 4 class B 5 { 6 public: 7 void DoSomething(){ 8 std::cout << "B::DoSomething()" << endl; 9 } 10 virtual void vfun(){ 11 std::cout << "B::vfun()" << endl; 12 } 13 }; 14 15 class D : public B 16 { 17 public: 18 void DoSomething(){ 19 std::cout << "D::DoSomething()" << endl; 20 } 21 virtual void vfun(){ 22 std::cout << "D::vfun()" << endl; 23 24 } 25 }; 26 27 int main() 28 { 29 D* pd = new D(); //指针存在注意静态类型和动态类型 30 B* pb = pd; //指正注意静态类型和动态类型 31 pd->DoSomething(); //普通成员函数,静态绑定静态类型中的方法 32 pb->DoSomething(); //普通成员函数,静态绑定静态类型中的方法 33 34 pd->vfun(); //虚成员函数,动态绑定到动态类型中的方法 35 pb->vfun(); //虚成员函数,动态绑定到动态类型中的方法 36 return 0; 37 }
二.虚函数动态绑定的实现机制
对象的指针或者引用调用虚函数的行为是动态绑定。虚函数是通过虚函数表来实现的。对于每一个含有虚函数的类,编译器会为其创建一个虚函数表Vtable,虚函数表中记录JMP指令的地址。类的虚函数表被该类的所有对象共享,编译器会为每个虚函数的实例分配指向该类的虚函数表的指针。当一个含有虚成员函数的类被继承后,器派生类中的同名函数会自动的成为虚函数,因此子类在重写这个虚函数时,可以添加,也可以不加virtual关键字,习惯上会加上virtual使得程序更加的清晰。父类指针调用虚成员函数是多态在代码层面的体现,这在运行过程中,父类指针将动态绑定(关联)到具体的某个虚函数。指的是在运行期根据父类指针的动态类型的虚函数表来绑定到具体的函数。含有虚函数的类记住使用虚析构函数,否则容易导致派生类的资源无法正常释放。例如下面情况。如果想进一步了解虚函数表需要了解,C++的对象模型,建议查看《C++中对象模型》。
1 #include <iostream> 2 using namespace std; 3 4 class B 5 { 6 public: 7 void DoSomething(){ 8 std::cout << "B::DoSomething()" << endl; 9 } 10 virtual void vfun(){ 11 std::cout << "B::vfun()" << endl; 12 } 13 14 ~B(){ 15 std::cout << "~B()" << std::endl; 16 } 17 }; 18 19 class D : public B 20 { 21 public: 22 void DoSomething(){ 23 std::cout << "D::DoSomething()" << endl; 24 } 25 virtual void vfun(){ 26 std::cout << "D::vfun()" << endl; 27 28 } 29 ~D(){ 30 std::cout << "~D()" << std::endl; 31 } 32 }; 33 34 int main() 35 { 36 B* pb = new D(); 37 pb->vfun(); 38 delete pb; // ~B(); 39 return 0; 40 }
三 C++多态性体现
C++多态性代码层面体现的是父类的指针或引用调用虚成员函数。多态产生有三个条件:产生的条件是继承关系、基类中含有虚成员函数、派生类重写基类的虚成员函数。当用父类指针或引用调用虚成员函数,这个调用关系在运行器可能绑定(关联)到不同的子类,产生不同的行为。
1 #include <iostream> 2 using namespace std; 3 class Person 4 { 5 public: 6 virtual void print(){ 7 std::cout << "I'm a person" << endl; 8 } 9 virtual void foo(){} 11 }; 12 13 class Chinese : public Person 14 { 15 public: 16 virtual void print(){ 17 std::cout << "I'm as Chinese" << endl; 18 } 19 virtual void foo2(){} 20 }; 21 class American : public Person 22 { 23 public: 24 virtual void print(){ 25 std::cout << "I'm as American" << endl; 26 } 27 }; 28 //reference 29 void printPerson(Person& person){ 30 person.print(); 31 } 32 //pointer 33 void printPerson(Person* p){ 34 p->print(); 35 } 36 int main() 37 { 38 Person p; 39 Chinese c; 40 American a; 41 printPerson(&p); 42 printPerson(&c); 43 printPerson(&a); 44 return 0; 45 }