C++的virtual函数(从编译器的角度看)

来源:互联网 发布:淘宝的销售数据 编辑:程序博客网 时间:2024/06/06 08:20

编了几个小程序测试了下,梳理了一下思路,总结如下:

(零)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 A13 {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 A14 {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