原文地址:http://www.cppblog.com/fwxjj/archive/2007/01/25/17996.html
多态性 (polymorphism) 是面向对象编程的基本特征之一。而在C++ 中,多态性通过虚函数 (virtual function) 来实现。我们来看一段简单的代码:
#include <iostream>
using namespace std;
class Base
{
int a;
public:
virtual void fun1() {cout<<"Base::fun1()"<<endl;}
virtual void fun2() {cout<<"Base::fun2()"<<endl;}
virtual void fun3() {cout<<"Base::fun3()"<<endl;}
};
class A:public Base
{
int a;
public:
void fun1() {cout<<"A::fun1()"<<endl;}
void fun2() {cout<<"A::fun2()"<<endl;}
};
void foo (Base& obj)
{
obj.fun1();
obj.fun2();
obj.fun3();
}
int main()
{
Base b;
A a;
foo(b);
foo(a);
}
运行结果为:
Base::fun1()
Base::fun2()
Base::fun3()
A::fun1()
A::fun2()
Base::fun3()
仅通过基类的接口,程序调用了正确的函数,它就好像知道我们输入的对象的类型一样!
那么,编译器是如何知道正确代码的位置的呢?
其实,编译器在编译时并不知道要调用的函数体的正确位置,但它插入了一段能找到正确的函数体的代码。这称之为 晚捆绑(late binding) 或 运行时捆绑(runtime binding) 技术。
通过virtual 关键字创建虚函数能引发晚捆绑,编译器在幕后完成了实现晚捆绑的必要机制。它对每个包含虚函数的类创建一个表(称为VTABLE),用于放置虚函数的地址。在每个包含虚函数的类中,编译器秘密地放置了一个称之为vpointer(缩写为VPTR)的指针,指向这个对象的VTABLE。所以无论这个对象包含一个或是多少虚函数,编译器都只放置一个VPTR即可。VPTR由编译器在构造函数中秘密地插入的代码来完成初始化,指向相应的VTABLE,这样对象就“知道”自己是什么类型了。 VPTR都在对象的相同位置,常常是对象的开头。这样,编译器可以容易地找到对象的VTABLE并获取函数体的地址。
如果我们用sizeof查看前面Base类的长度,我们就会发现,它的长度不仅仅是一个int的长度,而是增加了刚好是一个void指针的长度(在我的机器里面,一个int占4个字节,一个void指针占4个字节,这样正好类Base的长度为8个字节)。
每当创建一个包含虚函数的类或从包含虚函数的类派生一个类时,编译器就为这个类创建一个唯一的VTABLE。在VTABLE中,放置了这个类中或是它的基类中所有虚函数的地址,这些虚函数的顺序都是一样的,所以通过偏移量可以容易地找到所需的函数体的地址。假如在派生类中没有对在基类中的某个虚函数进行重写(overriding),那么还使用基类的这个虚函数的地址(正如上面的程序结果所示)。
至今为止,一切顺利。下面,我们的试验开始了。
就目前得知的,我们可以试探着通过自己的代码来调用虚函数,也就是说我们要找寻一下编译器秘密地插入的那段能找到正确函数体的代码的足迹。
如果我们有一个Base指针作为接口,它一定指向一个Base或由Base派生的对象,或者是A,或者是其它什么。这无关紧要,因为VPTR的位置都一样,一般都在对象的开头。如果是这样的话,那么包含有虚函数的对象的指针,例如Base指针,指向的位置恰恰是另一个指针——VPTR。VPTR指向的 VTABLE其实就是一个函数指针的数组,现在,VPTR正指向它的第一个元素,那是一个函数指针。如果VPTR向后偏移一个Void指针长度的话,那么它应该指向了VTABLE中的第二个函数指针了。
这看来就像是一个指针连成的链,我们得从当前指针获取它指向的下一个指针,这样我们才能“顺藤摸瓜”。那么,我来介绍一个函数:
void *getp (void* p)
{
return (void*)*(unsigned long*)p;
}
我们不考虑它漂亮与否,我们只是试验。getp() 可以从当前指针获取它指向的下一个指针。如果我们能找到函数体的地址,用什么来存储它呢?我想应该用一个函数指针:
typedef void (*fun)();
它与Base中的三个虚函数相似,为了简单我们不要任何输入和返回,我们只要知道它实际上被执行了即可。
然后,我们负责“摸瓜”的函数登场了:
fun getfun (Base* obj, unsigned long off)
{
void *vptr = getp(obj);
unsigned char *p = (unsigned char *)vptr;
p += sizeof(void*) * off;
return (fun)getp(p);
}
第一个参数是Base指针,我们可以输入Base或是Base派生对象的指针。第二个参数是VTABLE偏移量,偏移量如果是0那么对应fun1(),如果是1对应fun2()。getfun() 返回的是fun类型函数指针,我们上面定义的那个。可以看到,函数首先就对Base指针调用了一次getp(),这样得到了vptr这个指针,然后用一个 unsigned char指针运算偏移量,得到的结果再次输入getp(),这次得到的就应该是正确的函数体的位置了。
那么它到底能不能正确工作呢?我们修改main() 来测试一下:
int main()
{
Base *p = new A;
fun f = getfun(p, 0);
(*f)();
f = getfun(p, 1);
(*f)();
f = getfun(p, 2);
(*f)();
delete p;
}
激动人心的时刻到来了,让我们运行它!
运行结果为:
A::fun1()
A::fun2()
Base::fun3()
至此,我们真的成功了。通过我们的方法,我们获取了对象的VPTR,在它的体外执行了它的虚函数。
分享到:
相关推荐
多态性是面向对象编程的一个核心特性,它允许子类对象可以被当作其父类对象来使用,从而提高了代码的复用性和灵活性。在C++中,多态性的实现主要依赖于虚函数机制。本文将详细介绍C++中多态性的实现原理,特别是通过...
### C++中的多态性和虚函数 在C++编程语言中,多态性是一种重要的特性,它允许子类对象可以被当作其父类对象来处理,从而极大地提高了代码的灵活性和可扩展性。多态性的实现主要依赖于虚函数机制。下面我们将详细...
这样,通过vptr和vtable,即使基类的指针也可以调用到派生类中的相应函数,从而实现运行时的多态性。 在MFC(Microsoft Foundation Classes)库中,虚函数的使用尤为关键,因为它涉及到消息响应和成员函数的重载。...
重载多态和强制多态性称为特殊多态性,用来刻画语义上无关连的类型间关系。 1.1 多态的分类 在C++中,多态性可以通过四种方式来实现: * 重载多态:通过函数和运算符重载来实现多态性。 * 强制重载:通过类型强制...
这种设计使得C++能够支持动态绑定和多态性,但同时也增加了运行时的开销。 理解虚函数的实现机制对于编写高效、健壮的C++代码至关重要。例如,当遇到库接口与头文件不匹配导致的错误时,分析对象的vptr和vtable可以...
在C++中,虚函数是实现多态性的重要机制,特别是在面向对象编程中。当一个类含有虚函数时,编译器会为该类创建一个虚拟表(VTABLE),其中包含了类中所有虚函数的地址。这个VTABLE使得在运行时能够根据对象的实际...
OOP的核心概念包括封装、继承和多态性。其中,**多态性**允许程序根据不同的数据类型调用适当的方法或函数,这极大地提高了代码的灵活性和复用性。 #### 二、多态性的实现机制:动态联编 多态性的一个重要实现方式...
总之,C++的虚函数通过VTABLE和VPTR实现了多态性,使得我们可以根据对象的实际类型调用适当的函数,而无需在编译时就确定。这种机制在处理复杂继承关系和动态绑定时非常有用,是C++面向对象编程的核心特性之一。理解...
总之,C++中的虚函数是实现多态性的重要工具,它允许我们通过基类指针调用派生类的函数,提高了代码的灵活性和可扩展性。通过VTABLE和vptr,编译器实现了动态联编,确保在运行时能够根据对象的实际类型调用正确的...
在C++编程语言中,虚函数是实现多态性的重要机制。本文主要探讨了如何通过对象、引用和指针调用虚函数,并深入解析了虚函数的实现原理和相关知识点。 首先,虚函数的主要作用是允许基类指针或引用调用派生类中的...
C++中的虚函数通过使用`vptr`和`vtable`机制,在运行时实现了动态绑定,使得程序可以根据对象的实际类型调用不同的虚函数,从而实现了多态性。理解虚函数的实现机制对于编写高质量、可扩展的C++代码至关重要。
而在C++的面向对象编程中,多态性主要通过虚函数和虚函数表(VTABLE)来实现。 虚函数是多态性实现的关键,它允许子类覆盖父类的方法。在类声明中,使用`virtual`关键字声明虚函数,这会在编译器创建一个虚函数表,...
接着,我们要讨论C++的多态性,这是通过虚函数实现的。虚函数表(vtable)是C++实现动态绑定的关键机制,它允许子类重写基类的方法并保持向上兼容。理解vptr和vtable的工作原理对于调试和优化代码至关重要。 模板是...
vptr(虚拟指针)和vtable(虚函数表)是C++多态性的实现基础。在含有虚函数的类中,每个对象都会有一个vptr,它指向对应的vtable。vtable存储了虚函数的地址,使得动态绑定(运行时多态)成为可能。 6. **...
在C++中,虚函数是实现多态性的重要机制,...了解这些原理后,程序员可以更好地理解和利用C++的多态性,编写出更加灵活和可扩展的代码。同时,这也解释了为什么含有虚函数的类会比没有虚函数的类占用更多的内存空间。
这就是多态性的体现——即使是在编译时期未知的对象类型,也可以正确地调用其对应的函数版本。 ##### 2. 多态的实现原理 多态性是通过动态绑定(dynamic binding)实现的,这意味着函数调用的解析是在运行时完成的,...
总之,虚函数的实现基于vtable和vptr,通过这种方式,C++在运行时能够根据对象的实际类型来决定调用哪个版本的函数,实现了多态性的核心功能。理解这个机制对于深入理解C++的面向对象特性至关重要。
### C++中重载与重写函数的区别及虚函数详解...- **虚函数**:用于支持重写和多态性的关键。 通过以上分析可以看出,虚函数、重载和重写在C++中的应用各有侧重,合理利用这些特性可以极大地提高程序的灵活性和扩展性。