编了几个小程序测试了下,梳理了一下思路,总结如下:
(零)VTABLE机制
当一个类中有virtual函数时,编译器会为这个类建立且仅建立一个VTABLE
这个VTABLE大概是个数组的感觉,其元素是“函数指针”。
所以说,下边这段代码:
1 class A 2 { 3 public: 4 virtual void f0() {cout<<"a0"<<endl}; 5 6 virtual void f1() {cout<<"a1"<<endl}; 7 8 virtual void f2() {cout<<"a2"<<endl}; 9 10 }; 11 12 class B: public A 13 { 14 public: 15 virtual void f0() {cout<<"b0"<<endl}; 16 17 virtual void f1() {cout<<"b1"<<endl}; 18 19 virtual void f2() {cout<<"b2"<<endl}; 20 21 };
编译器会给类A和类B各自建一个VTABLE:
VTABLE_A的0,1,2个元素分别放的是指向A::f0() , A::f1() , A::f2()的指针
VTABLE_B的0,1,2个元素分别放的是指向B::f0() , B::f1() , B::f2()的指针
而对每个实际的类对象,编译器会增加一个vptr字段
(注:这也是很多笔经面经里说的sizeof(A)和sizeof(B)里多出4的问题的原因,多出来这4个Byte就是给vptr指针分配的空间,由此引发的sizeof对齐问题经常被问到)
而下边这段代码:
A *a = new B; a->f1(); //这里,编译器实际上做的是a->vptr[1]();这样,实际上运行的是VTABLE_B[1]指向的函数
这样看来,以前很多死记硬背的细节,哪里是override,哪里是overload,调用的到底是哪个函数,用vptr和VTABLE的概念就很好理解了
(一)多层次类中的virtrual
看下边的代码:
class A { public: virtual void f(){cout<<"a"<<endl;} void g(){cout<<"ag"<<endl;} }; class B: public A { public: void f(){cout<<"b"<<endl;} // B::f()没有virtual关键字! }; class C: public B // C继承的是B,而B中没有显式的virtual { public: void f(){cout<<"c"<<endl;} // C中的f也没有virtual关键字! void g(){cout<<"cg"<<endl;} }; void main() { A *a = new C(); a->f(); // 这里调用了C::f(),实际上是a->vptr[0](); a->g(); // 这里调用的是A::g(),因为g在A中不是virtual的 delete a; a = new B(); a->f(); // (这行语句加得有点蛋疼,只是为了测试得更完整)这里调用了B::f(),a->vptr[0](); delete a; }
A::f()是virtual的,但B::f()和C::f()都没有显式声明virtual
而实际上,C::f()是多态了A::f()的。
说穿了很简单,编译器给A建立了VTABLE,也就会给A的每个派生类都建立VTABLE(哪怕是孙子辈的C)
而A,B,C类中VTABLE中元素的顺序都是一样的(这个例子只有1个f(),如果A还有virtual的f2,f3,那么B和C的VTABLE中的相应位置中也会存放B,C版本的f2,f3)
(二)virtual析构函数
基本上,C++的多态,都要“虚”一下析构函数(这1年来C#用得多,人都变傻了,前几天电面的时候,面试官和我聊多态,“虚析构”这地方还被鄙视了下=。=)
1 class A 2 { 3 public: 4 A() { cout<<"A()"<<endl;ptra_ = new char[10];} 5 virtual ~A() { cout<<"~A()"<<endl; delete[] ptra_;} // 注意:这里如果不“虚”一下,main函数里调用的顺序是A(),B(),~A() 6 7 // “虚”了之后,main里调用顺序是A(),B(),~B(),~A() 8 9 private: 10 char * ptra_; 11 }; 12 13 class B: public A 14 { 15 public: 16 B() { cout<<"B()"<<endl;ptrb_ = new char[20];} 17 ~B() { cout<<"~B()"<<endl; delete[] ptrb_;} 18 private: 19 char * ptrb_; 20 }; 21 22 void main() 23 { 24 A * a = new B; 25 delete a; 26 }
由此可见,C++里用多态,除非你的程序很土,从来没new过堆空间,否则“虚析构”是必用的。
另:构造函数不能虚!
(三)private虚函数
class A { public: void foo() { bar();} // 实际上这里是调用了this->vptr[0],具体调用了谁,要看“this”指向的谁 private: virtual void bar() {cout<<"a"<<endl;} }; class B: public A { private: virtual void bar() { cout<<"b"<<endl;} }; void main() { A *a = new B(); a->foo(); //a指向的是B,所以a->vptr[0]调用的是VTABLE_B[0]指向的函数,即B::bar() }
(四)构造函数和析构函数中调用virtual函数
class A { public: A() { cout<<"A()"<<endl;foo();} // 在这里,无论如何都是A::foo()被调用! ~A() { cout<<"~A()"<<endl;foo();} // 同上 virtual void foo(){cout<<"a"<<endl;} }; class B: public A { public: virtual void foo(){cout<<"b"<<endl;} }; class C: public B { public: virtual void foo(){cout<<"c"<<endl;} }; void main() { A *a = new B; delete a; B *b = new C; delete b; cout<<sizeof(A)<<endl; cout<<sizeof(B)<<endl; // 2个构造、2个析构都调用的a::foo() // 无责任猜想,应该是构造函数被调用时,B的vptr还没做成;而析构函数被调用时,vptr已被撤销,所以不能通过B::vptr饮用B的VTABLE }
转自:http://www.cnblogs.com/fte99923/archive/2011/04/24/2026517.html