大佬教程收集整理的这篇文章主要介绍了c – 编译器如何知道vtable中的哪个条目对应于虚函数?,大佬教程大佬觉得挺不错的,现在分享给大家,也给大家做个参考。
编译器如何知道vtable中的哪个条目对应于哪个虚函数?
class Animal{ public: void fakeMethod1(){} virtual void getWeight(){} void fakeMethod2(){} virtual void getHeight(){} virtual void getType(){} }; class Tiger:public Animal{ public: void fakeMethod3(){} virtual void getWeight(){} void fakeMethod4(){} virtual void getHeight(){} virtual void getType(){} }; main(){ Animal a* = new Tiger(); a->getHeight(); // A will Now point to the base address of vtable Tiger //How will the compiler kNow which entry in the vtable corresponds to the function getHeight()? }
我的研究中@R_944_10739@确切的解释 –
https://stackoverflow.com/a/99341/437894 =
假设我们有以下内容:
#include <iostream> struct Animal { int age; Animal(int a) : age {a} {} virtual int setAge(int); virtual void sayHello() const; }; int Animal::setAge(int a) { int prev = this->age; this->age = a; return prev; } void Animal::sayHello() const { std::cout << "Hello,I'm an " << this->age << " year old animal.\n"; } struct Tiger : Animal { int Stripes; Tiger(int a,int s) : Animal {a},Stripes {s} {} virtual void sayHello() const override; virtual void doTigerishThing(); }; void Tiger::sayHello() const { std::cout << "Hello,I'm a " << this->age << " year old tiger with " << this->Stripes << " Stripes.\n"; } void Tiger::doTigerishThing() { this->Stripes += 1; } int main() { Tiger * tp = new Tiger {7,42}; Animal * ap = tp; tp->sayHello(); // call overridden function via derived pointer tp->doTigerishThing(); // call child function via derived pointer tp->setAge(8); // call parent function via derived pointer ap->sayHello(); // call overridden function via base pointer }
我忽略了一个好的建议,即虚拟函数成员的类应该有一个虚拟析构函数用于此示例.无论如何我要泄漏物体.
让我们看看我们如何将这个例子转化为没有成员函数的好旧C,而不管虚拟成员.以下所有代码均为C,而不是C.
结构动物很简单:
struct animal { const void * vptr; int age; };
除了age成员之外,我们还添加了一个vptr,它将成为vtable的指针.我正在使用一个无效指针,因为无论如何我们都必须做丑陋的演员,而使用void *可以减少丑陋.
接下来,我们可以实现成员函数.
static int animal_set_age(void * p,int a) { struct animal * this = (struct animal *) p; int prev = this->age; this->age = a; return prev; }
注意附加的第0个参数:在C中隐式传递的this指针.同样,我正在使用void *指针,因为它将在以后简化.请注意,在任何成员函数中,我们总是静态地知道this指针的类型,因此强制转换没有问题. (而在机器级别,它根本不会做任何事情.)
sayHello成员同样被定义,除了this指针这次是const限定的.
static void animal_say_Hello(const void * p) { const struct animal * this = (const struct animal *) p; printf("Hello,I'm an %d year old animal.\n",this->agE); }
时间为动物vtable.首先,我们必须给它一个类型,这是直截了当的.
struct animal_vtable_type { int (*setAgE)(void *,int); void (*sayHello)(const void *); };
然后我们创建一个vtable的单个实例,并使用正确的成员函数进行设置.如果Animal有一个纯虚拟成员,相应的条目将具有NULL值,最好不要取消引用.
static const struct animal_vtable_type animal_vtable = { .setAge = animal_set_age,.sayHello = animal_say_Hello,};
请注意,animal_set_age和animal_say_Hello被声明为static.这是onkay,因为它们永远不会被引用,而只能通过vtable引用(而vtable只能通过vptr,所以它也可以是静态的).
我们现在可以实现Animal的构造函数了…
void animal_ctor(void * p,int agE) { struct animal * this = (struct animal *) p; this->vptr = &animal_vtable; this->age = age; }
…和相应的运算符new:
void * animal_new(int agE) { void * p = malloc(sizeof(struct animal)); if (p != NULL) animal_ctor(p,agE); return p; }
关于唯一有趣的是在构造函数中设置vptr的行.
让我们继续老虎.
Tiger继承自Animal,因此它获得了一个struct tiger子对象.我是通过放置一个结构动物作为第一个成员来做到这一点的.至关重要的是,这是第一个成员,因为它意味着该对象的第一个成员 – vptr – 与我们的对象具有相同的地址.我们稍后会做一些棘手的演员时需要这个.
struct tiger { struct animal base; int Stripes; };
我们也可以在struct tiger的定义开头简单地复制struct animal的成员,但这可能更难维护.编译器不关心这样的风格问题.
void tiger_say_Hello(const void * p) { const struct tiger * this = (const struct tiger *) p; printf("Hello,I'm an %d year old tiger with %d Stripes.\n",this->base.age,this->Stripes); } void tiger_do_tigerish_thing(void * p) { struct tiger * this = (struct tiger *) p; this->Stripes += 1; }
请注意,这次我们将此指针转换为struct tiger.如果调用了tiger函数,那么即使我们通过基指针调用,这个指针最好指向老虎.
vtable旁边:
struct tiger_vtable_type { int (*setAgE)(void *,int); void (*sayHello)(const void *); void (*doTigerishThing)(void *); };
请注意,前两个成员与animal_vtable_type完全相同.这是必不可少的,基本上是你问题的直接答案.如果我将struct_vtable_type作为第一个成员放置,那么它可能会更明确.我想强调的是,对象布局将完全相同,只是在这种情况下我们无法发挥我们讨厌的铸造技巧.同样,这些是C语言的各个方面,不存在于机器级别,因此编译器不会为此烦恼.
static const struct tiger_vtable_type tiger_vtable = { .setAge = animal_set_age,.sayHello = tiger_say_Hello,.doTigerishThing = tiger_do_tigerish_thing,};
并实现构造函数:
void tiger_ctor(void * p,int age,int Stripes) { struct tiger * this = (struct tiger *) p; animal_ctor(this,agE); this->base.vptr = &tiger_vtable; this->Stripes = Stripes; }
老虎构造函数做的第一件事是调用动物构造函数.还记得动物构造函数如何将vptr设置为& animal_vtable吗?这就是为什么从基类构造函数调用虚拟成员函数让人惊讶的原因.只有在基类构造函数运行之后,我们才将vptr重新分配给派生类型,然后进行自己的初始化.
operator new只是样板.
void * tiger_new(int age,int Stripes) { void * p = malloc(sizeof(struct tiger)); if (p != NULL) tiger_ctor(p,age,Stripes); return p; }
我们完成了.但是我们如何调用虚拟成员函数?为此,我将定义一个辅助宏.
#define INVOKE_VIRTUAL_ARGS(STYPE,THIS,FUNC,...) \ (*((const struct STYPE ## _vtable_type * *) (THIS)))->FUNC( THIS,__VA_ARGS__ )
现在,这很难看.它的作用是采用静态类型STYPE,这个指针THIS和成员函数FUNC的名称以及传递给函数的任何其他参数.
然后,它从静态类型构造vtable的类型名称. (##是预处理程序的标记粘贴操作符.例如,如果STYPE是动物,则STYPE ## _vtable_type将扩展为animal_vtable_type.)
接下来,THIS指针被转换为指向刚刚派生的vtable类型的指针.这是有效的,因为我们已经确保将vptr作为每个对象中的第一个成员,因此它具有相同的地址.这很重要.
完成后,我们可以取消引用指针(获取实际的vptr),然后询问其FUNC成员并最终调用它. (__VA_ARGS__扩展为附加的可变参数宏参数.)注意,我们还将THIS指针作为第0个参数传递给成员函数.
现在,事实上我必须为不带参数的函数再次定义一个几乎相同的宏,因为预处理器不允许可变参数宏参数包为空.它应该是.
#define INVOKE_VIRTUAL(STYPE,FUNC) \ (*((const struct STYPE ## _vtable_type * *) (THIS)))->FUNC( THIS )
它有效:
#include <stdio.h> #include <stdlib.h> /* Insert all the code from above here... */ int main() { struct tiger * tp = tiger_new(7,42); struct animal * ap = (struct animal *) tp; INVOKE_VIRTUAL(tiger,tp,sayHello); INVOKE_VIRTUAL(tiger,doTigerishThing); INVOKE_VIRTUAL_ARGS(tiger,setAge,8); INVOKE_VIRTUAL(animal,ap,sayHello); return 0; }
你可能想知道发生了什么
INVOKE_VIRTUAL_ARGS(tiger,8);
呼叫.我们正在做的是在通过struct tiger指针引用的Tiger对象上调用Animal的未重写的setAge成员.该指针首先被隐式地转换为void指针,因此作为this指针传递给animal_set_age.该函数然后将其转换为结构动物指针.它是否正确?这是因为我们小心地将struct animal作为struct tiger中的第一个成员,因此struct tiger对象的地址与struct animal子对象的地址相同.这是与我们使用vptr一样的技巧(只有一个级别).
以上是大佬教程为你收集整理的c – 编译器如何知道vtable中的哪个条目对应于虚函数?全部内容,希望文章能够帮你解决c – 编译器如何知道vtable中的哪个条目对应于虚函数?所遇到的程序开发问题。
如果觉得大佬教程网站内容还不错,欢迎将大佬教程推荐给程序员好友。
本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。