如果使用vtable实现具有虚函数的类,那么如何实现没有虚函数的类?

时间:2008-09-19 12:03:25

标签: c++ virtual-functions

特别是,无论如何都不会有某种功能指针?

9 个答案:

答案 0 :(得分:15)

我认为“带有虚函数的类是用vtable 实现的”这句话会误导你。

该短语听起来像具有虚拟功能的类实现为“ in A A ”,而没有虚函数的类则实现为“ in B ”。

实际上,具有虚函数的类,除了 之外的 被实现为类,它们也有一个vtable。另一种看待它的方法是“'vtable'实现类的'虚函数'部分”。

关于它们如何工作的更多细节:

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

数据成员

类(就像结构一样)只是连续内存块,其中每个成员按顺序存储。请注意,由于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

(是的,这是正确的:方法中的成员访问始终通过指针间接完成,即使您没有看到它)

编辑:删除了最后一句并单独发布,因此可以单独批评)

答案 1 :(得分:12)

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

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'
}

答案 2 :(得分:3)

当您想要使用多态时,需要虚拟方法。 virtual修饰符将该方法放入VMT以进行后期绑定,然后在运行时决定执行哪个类的方法。

如果方法不是虚拟的 - 在编译时决定从哪个类实例执行。

函数指针主要用于回调。

答案 3 :(得分:1)

如果使用vtable实现具有虚函数的类,则在没有vtable的情况下实现没有虚函数的类。

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

答案 4 :(得分:1)

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

答案 5 :(得分:1)

(我从原来的答案中删除了这一部分,以便可以单独批评。它更简洁,更贴切你的问题,所以在某种程度上它是一个更好的答案)

不,没有函数指针;相反,编译器将问题内向外

编译器使用指向对象的指针调用全局函数,而不是在对象内部调用一些指向的函数

为什么呢?因为这种方式通常效率更高。间接电话是昂贵的指示。

答案 6 :(得分:0)

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

答案 7 :(得分:0)

分支直接生成方法的编译代码;就像你有完全不在类中的函数一样,分支直接生成它们。

答案 8 :(得分:0)

编译器/链接器直接链接将调用哪些方法。不需要vtable间接。顺便说一下,这与“堆栈与堆”有什么关系?