如果使用vtables实现具有虚拟功能的类,那么如何实现不具有虚拟功能的类?

时间:2020-03-06 14:25:39  来源:igfitidea点击:

特别是,不是一定要有某种函数指针吗?

解决方案

不需要函数指针,因为它在运行时无法更改。

分支直接生成到方法的已编译代码。就像我们拥有的功能根本不在类中一样,分支直接针对它们生成。

当我们要使用多态时,需要使用虚方法。 " virtual"修饰符将方法放入VMT中进行后期绑定,然后在运行时确定从哪个类执行哪个方法。

如果该方法不是虚方法,则在编译时决定从哪个类实例执行该方法。

函数指针主要用于回调。

编译器/链接器直接链接将调用哪些方法。无需vtable间接。顺便说一句,"堆栈与堆"有什么关系?

如果使用vtable实现具有虚拟功能的类,则无需vtable即可实现不具有虚拟功能的类。

vtable包含将调用分派到适当方法所需的函数指针。如果该方法不是虚拟的,则调用转到该类的已知类型,并且不需要间接调用。

对于非虚拟方法,编译器可以生成常规函数调用(例如,通过将此指针作为参数传递到CALL到特定地址)甚至内联它。对于虚函数,编译器通常在编译时不知道调用该代码的地址,因此,它会生成代码,该代码在运行时在vtable中查找该地址,然后调用该方法。没错,即使对于虚函数,编译器有时也可以在编译时正确解析正确的代码(例如,在没有指针/引用的情况下调用局部变量的方法)。

非虚拟成员函数实际上只是一个语法糖,因为它们几乎像一个普通函数,但是具有访问检查和隐式对象参数。

struct A 
{
  void foo ();
  void bar () const;
};

基本上与以下内容相同:

struct A 
{
};

void foo (A * this);
void bar (A const * this);

需要vtable,以便我们为特定的对象实例调用正确的函数。例如,如果我们有:

struct A 
{
  virtual void foo ();
};

'foo'的实现可能类似于:

void foo (A * this) {
  void (*realFoo)(A *) = lookupVtable (this->vtable, "foo");
  (realFoo)(this);   // Make the call to the most derived version of 'foo'
}

我认为"具有虚函数的类是通过vtables实现的"这一短语会误导我们。

这句话听起来像具有虚拟功能的类是通过"方式A"实现的,而没有虚拟功能的类则是通过"方式B"实现的。

实际上,具有虚拟功能的类除了按类实现外,还具有一个vtable。另一种看待它的方式是"'vtables实现类的'虚函数'部分"。

有关它们如何工作的更多详细信息:

所有类(使用虚拟或者非虚拟方法)都是结构。 C ++中的结构和类之间的唯一区别是,默认情况下,成员在结构中是公共的,而在类中是私有的。因此,在这里我将使用术语类来同时引用结构和类。记住,它们几乎是同义词!

数据成员

类(与结构一样)只是连续存储器的块,其中每个成员按顺序存储。请注意,由于CPU架构的原因,有时成员之间会存在间隙,因此该块可能大于其各个部分的总和。

方法

方法或者"成员函数"是一种幻想。实际上,没有"成员函数"之类的东西。函数始终只是存储在内存中某个位置的一系列机器代码指令。要进行呼叫,处理器将跳至该内存位置并开始执行。我们可以说所有方法和函数都是"全局"的,任何相反的指示都是编译器强制执行的一种错觉。

显然,方法的行为就像它属于特定对象一样,因此显然还有很多事情在进行。为了将方法(函数)的特定调用绑定到特定对象,每个成员方法都具有一个隐藏参数,该参数是指向所讨论对象的指针。该成员的隐藏之处在于我们不必自己将其添加到C ++代码中,但是它并没有什么神奇之处-它是真实的。当你这样说:

void CMyThingy::DoSomething(int arg);
{
    // do something
}

编译器确实做到了这一点:

void CMyThingy_DoSomething(CMyThingy* this, int arg)
{
    /do something
}

最后,当我们编写此代码时:

myObj.doSomething(aValue);

编译器说:

CMyThingy_DoSomething(&myObj, aValue);

无需在任何地方使用函数指针!编译器已经知道我们正在调用哪个方法,因此它将直接调用它。

静态方法甚至更简单。它们没有this指针,因此它们是在我们编写它们时完全实现的。

那是!剩下的只是方便的语法加糖:编译器知道方法属于哪个类,因此可以确保它在不指定哪个函数的情况下不会让我们调用该函数。当明确地做到这一点时,它还利用该知识将" myItem"转换为" this-> myItem"。

(是的,没错:方法中的成员访问总是通过指针间接完成,即使我们看不到它也是如此)

(编辑:删除最后一句,并单独发布,以便可以分别批评)

(我从原始答案中删除了此部分,以便可以分别对其进行批评。它更加简洁明了,可以直达问题,因此在某种程度上,它是一个更好的答案)

不,没有函数指针;相反,编译器将问题由内而外地解决了。

编译器调用带有指向对象的指针的全局函数,而不是调用对象内部的某些指向函数

为什么?因为这样做通常效率更高。间接调用是昂贵的指令。