C ++编译器如何知道要调用哪个虚函数实现?

时间:2008-10-14 22:43:24

标签: c++ oop polymorphism

以下是来自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是一个三角形,以便它可以调用正确的area()函数?它可以通过查看“Polygon * ppoly1 =▭”行并知道rect是一个Rectangle来找到它,但是在所有情况下都无法工作,不是吗?如果你这样做会怎么样?

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

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

我会测试一下,但我现在无法在计算机上测试。

(我希望我不会错过任何明显的东西......)

6 个答案:

答案 0 :(得分:24)

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

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

在您的具体情况下,PolygonRectangleTriangle每个都有一个vtbl,每个都有一个条目指向其相关的area方法。您的ppoly1vptr指向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()。