9

这是来自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 = &rect;
    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 是 Triangle,以便它可以调用正确的 area() 函数?它可以通过查看“Polygon * ppoly1 = ▭”行并知道 rect 是一个 Rectangle 来发现这一点,但这并不是在所有情况下都有效,不是吗?如果你做了这样的事情怎么办?

cout << ((Polygon *)0x12345678)->area() << endl;

假设您被允许访问该随机内存区域。

我会对此进行测试,但我目前无法在我使用的计算机上进行测试。

(我希望我没有遗漏一些明显的东西......)

4

6 回答 6

25

每个对象(属于具有至少一个虚函数的类)都有一个指针,称为 a vptr。它指向vtbl它的实际类(每个具有虚函数的类至少有一个;对于某些多重继承场景可能不止一个)。

vtbl包含一堆指针,每个虚函数一个。所以在运行时,代码只使用对象vptr来定位vtbl,并从那里找到实际覆盖函数的地址。

在您的特定情况下,Polygon,RectangleTriangle每个都有一个vtbl, 每个都有一个指向其相关area方法的条目。您ppoly1将有一个vptr指向Rectangle's的指向vtbl,并且与'sppoly2类似。希望这可以帮助!Trianglevtbl

于 2008-10-14T22:46:42.170 回答
6

Chris Jester-Young给出了这个问题的基本答案。

维基百科有更深入的处理。

如果您想了解此类事物如何工作的全部细节(以及所有类型的继承,包括多重继承和虚拟继承),最好的资源之一是 Stan Lippman 的“ C++ 对象模型内部”。

于 2008-10-14T22:52:25.310 回答
3

忽略绑定的各个方面,实际上并不是编译器决定了这一点。

正是 C++ 运行时通过 vtables 和 vpointers 评估派生对象在运行时的实际情况。

我强烈推荐 Scott Meyer 的《Effective C++》一书,它很好地描述了这是如何完成的。

甚至涵盖了如何忽略派生类中的方法中的默认参数,并且仍然采用基类中的任何默认参数!那是有约束力的。

于 2008-10-14T22:46:31.373 回答
1

要回答您问题的第二部分:该地址可能不会在正确的位置有一个 v-table,然后会发疯。此外,根据标准,它是未定义的。

于 2008-10-14T22:48:45.707 回答
1
cout << ((Polygon *)0x12345678)->area() << endl;

这段代码是一场等待发生的灾难。编译器会编译它,但是当涉及到运行时,你不会指向一个有效的 v-table,如果你幸运的话,程序就会崩溃。

在 C++ 中,您不应该像这样使用旧的 C 风格强制转换,您应该像这样使用dynamic_cast

Polygon *obj = dynamic_cast<Polygon *>(0x12345678)->area();
ASSERT(obj != NULL);

cout << obj->area() << endl;

如果给定的指针不是有效的 Polygon 对象,dynamic_cast 将返回 NULL,因此它将被 ASSERT 捕获。

于 2008-10-14T23:04:50.603 回答
1

虚函数表。也就是说,您的两个 Polygon 派生对象都有一个虚函数表,其中包含指向所有(非静态)函数实现的函数指针;当你实例化一个 Triangle 时, area() 函数的虚函数指针指向 Triangle::area() 函数;当你实例化一个 Rectangle 时, area() 函数指向 Rectangle::area() 函数。因为虚函数指针与对象的数据一起存储在内存中,所以每次将该对象作为 Polygon 引用时,都会使用该对象的相应 area()。

于 2008-10-14T23:06:58.137 回答