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;
}
这样。
本文深入探讨C++中的多态实现原理,包括VTABLE机制、多层次类中的virtual使用、virtual析构函数的重要性、private虚函数及构造函数和析构函数中调用virtual函数的行为。

1773

被折叠的 条评论
为什么被折叠?



