6

特别是,无论如何都不需要某种函数指针吗?

4

9 回答 9

15

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

这句话听起来像是具有虚函数的类是“以 A 方式”实现的,而没有虚函数的类是“以 B 方式”实现的。

实际上,具有虚函数的类,除了作为类来实现之外,还有一个虚表。另一种看待它的方式是“'vtables' 实现了类的 'virtual function' 部分”。

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

所有类(具有虚拟或非虚拟方法)都是结构。在 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指针,因此它们完全按照您编写它们的方式实现。

那是!其余的只是方便的语法糖化:编译器知道一个方法属于哪个类,因此它确保它不会让您在没有指定哪个类的情况下调用该函数。它还使用该知识来转换myItemthis->myItem明确的情况。

(是的,没错:方法中的成员访问总是通过指针间接完成,即使你没有看到)

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

于 2008-09-19T13:01:04.697 回答
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'
}
于 2008-09-19T12:49:14.100 回答
3

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

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

函数指针主要用于回调。

于 2008-09-19T12:09:54.913 回答
1

如果一个有虚函数的类是用一个虚表来实现的,那么一个没有虚函数的类是不用虚表来实现的。

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

于 2008-09-19T12:10:56.047 回答
1

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

于 2008-09-19T12:36:22.557 回答
1

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

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

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

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

于 2008-09-19T13:12:17.913 回答
0

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

于 2008-09-19T12:07:44.807 回答
0

分支直接生成到方法的编译代码;就像如果您有根本不在类中的函数一样,分支会直接生成给它们。

于 2008-09-19T12:07:46.757 回答
0

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

于 2008-09-19T12:10:12.463 回答