以下是来自http://www.cplusplus.com/doc/tutorial/polymorphism.html的多态性示例(为了便于阅读而编辑):
// abstract base class
#include <iostream>
using namespace std;
class Polygon {
protected:
int width;
int height;
public:
void set_values(int a, int b) { width = a; height = b; }
virtual int area(void) =0;
};
class Rectangle: public Polygon {
public:
int area(void) { return width * height; }
};
class Triangle: public Polygon {
public:
int area(void) { return width * height / 2; }
};
int main () {
Rectangle rect;
Triangle trgl;
Polygon * ppoly1 = ▭
Polygon * ppoly2 = &trgl;
ppoly1->set_values (4,5);
ppoly2->set_values (4,5);
cout << ppoly1->area() << endl; // outputs 20
cout << ppoly2->area() << endl; // outputs 10
return 0;
}
我的问题是编译器如何知道ppoly1是一个Rectangle并且ppoly2是一个三角形,以便它可以调用正确的area()函数?它可以通过查看“Polygon * ppoly1 =▭”行并知道rect是一个Rectangle来找到它,但是在所有情况下都无法工作,不是吗?如果你这样做会怎么样?
cout << ((Polygon *)0x12345678)->area() << endl;
假设您被允许访问该随机内存区域。
我会测试一下,但我现在无法在计算机上测试。
(我希望我不会错过任何明显的东西......)
答案 0 :(得分:24)
每个对象(属于具有至少一个虚函数的类)都有一个名为vptr
的指针。它指向其实际类的vtbl
(每个具有虚函数的类至少有一个;对于某些多继承方案,可能不止一个)。
vtbl
包含一堆指针,每个指针对应一个虚函数。因此,在运行时,代码只使用对象的vptr
来定位vtbl
,并从那里找到实际被覆盖函数的地址。
在您的具体情况下,Polygon
,Rectangle
和Triangle
每个都有一个vtbl
,每个都有一个条目指向其相关的area
方法。您的ppoly1
将vptr
指向Rectangle
的{{1}},vtbl
指向ppoly2
的{{1}}。希望这有帮助!
答案 1 :(得分:6)
Chris Jester-Young给出了这个问题的基本答案。
Wikipedia有更深入的治疗。
如果你想知道这类事物是如何工作的全部细节(以及所有类型的继承,包括多重和虚拟继承),最好的资源之一是Stan Lippman的“Inside the C++ Object Model”。 / p>
答案 2 :(得分:3)
忽略绑定的各个方面,实际上并不是编译器决定了这一点。
C ++运行时通过vtable和vpointers评估派生对象在运行时实际上是什么。
我强烈推荐Scott Meyer的书“Effective C ++”,以便详细说明如何做到这一点。
甚至涵盖了如何忽略派生类中方法中的默认参数,并且仍然采用基类中的任何默认参数!那是有约束力的。
答案 3 :(得分:1)
回答问题的第二部分:该地址可能没有正确位置的v-table,并且随之而来的是疯狂。此外,它根据标准未定义。
答案 4 :(得分:1)
cout << ((Polygon *)0x12345678)->area() << endl;
此代码是等待发生的灾难。编译器可以正确编译它,但是当涉及到运行时,你将不会指向一个有效的v表,如果你很幸运,程序就会崩溃。
在C ++中,你不应该像这样使用旧的C风格的强制转换,你应该像这样使用 dynamic_cast :
Polygon *obj = dynamic_cast<Polygon *>(0x12345678)->area();
ASSERT(obj != NULL);
cout << obj->area() << endl;
如果给定的指针不是有效的Polygon对象,dynamic_cast将返回NULL,因此它将被ASSERT捕获。
答案 5 :(得分:1)
虚拟功能表。也就是说,两个Polygon派生对象都有一个虚函数表,其中包含指向所有(非静态)函数实现的函数指针。当你实例化一个Triangle时,area()函数的虚函数指针指向Triangle :: area()函数;当您实例化一个Rectangle时,area()函数指向Rectangle :: area()函数。由于虚函数指针与内存中对象的数据一起存储,因此每次将该对象作为Polygon引用时,都将使用该对象的相应area()。