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

http://www.cnblogs.com/fte99923/archive/2011/04/24/2026517.html

晚自习和秦多铎讨论了C++里多态的问题,发现自己在这方面存在两个严重问题:

1.以前只是知其然,但不知所以然,只知道怎么实现多态,但不明白编译器内部大概是怎么实现的,也就导致了编程时很多细节处需要翻书。

2.C#后遗症很严重,用VC6写小程序验证自己想法的时候好几次编译不通过。

多铎在网上找了一篇技术贴(http://user.qzone.qq.com/624576542/blog/1303304079

帖子内容非常赞,但是重点不够突出(什么是多态、怎么用多态这样的基础问题这帖子也不厌其烦地讲),也有说得不太明白、模棱两可的地方。

按照里边的知识点,编了几个小程序测试了下,梳理了一下思路,总结如下:

(零)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 }

(五)C#后遗症两条

1.多态的实现是用指针,而不是对象

 

    
    
Base base = new Derived;     // 这是多态不了的,因为实际上是进行了Derived到Base的强制类型转换,转完之后实际上还是Base类的VTABLE Base * base = new Derived;     // 这里base指针指向了Derived对象,base->f()调用的是“被指向的Derived对象”的vptr字段,即Derived类的VTABLE中的元素

2.C#的base关键字和Java的super关键字,C++里木有

想实现类似的功能,直接用父类的类名和“::”运算符,即:

    
    
Class B : public A {      virtual void f() { A::f(); cout << " f() in B " << endl; }

这样。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值