我对虚拟表的理解是,只要编译器在类中找到虚函数,它就会为类创建一个虚拟表,虚函数的所有函数指针都将放在该表中.
但是当谈到纯虚函数时,我们不会在任何时候调用该函数.那么为什么在虚拟表中需要输入纯虚函数.
virtual void myFunction() = 0 ;
Run Code Online (Sandbox Code Playgroud)
声明是必需的,因为您需要告诉编译器在 vtable 中为从声明它的基类开始的特定方法保留一个插槽(这是在派生类上调用方法时可能想要使用的类型) )
只是为了给你一个想法,让我们举一个例子(这不是真正考虑在引擎盖下发生的事情)。假设您在 中有三个虚拟方法Base,其中一个是纯方法,
class Base {
virtual void pure() = 0;
virtual void nonpure() { }
virtual void nonpure2() { }
};
Run Code Online (Sandbox Code Playgroud)
所以Basevtable 看起来像
0 [ pure ] -> nothing
1 [ nonpure ] -> address of Base::nonpure
2 [ nonpure2] -> address of Base::nonpure2
Run Code Online (Sandbox Code Playgroud)
现在让我们推导出它
class Derive : public Base {
virtual pure() override { }
virtual nonpure2() override { }
};
Run Code Online (Sandbox Code Playgroud)
Derived vtable 看起来像
0 [ pure ] -> address of Derived::pure
1 [ nonpure ] -> address of Base::nonpure
2 [ nonpure2 ] -> address of Derived::nonpure2
Run Code Online (Sandbox Code Playgroud)
当你然后尝试做
Base* derived = new Derived();
derived->pure();
Run Code Online (Sandbox Code Playgroud)
该方法大致编译为
address = derived->vtable[0];
call address
Run Code Online (Sandbox Code Playgroud)
如果您不在类中声明纯虚方法,则Base在这种情况下,在编译时无法知道它在 vtable (0) 中的索引,因为该方法根本不存在。
但是作为 vtable 中的一个洞,你不能实例化一个具有空“槽”的 vtable 的类。
您无法实例化抽象类的对象.这实际上使你的问题变得毫无意义:因为你永远不会实例化你的抽象类,所以根本不需要该类的虚拟表.(实际上,在施工/毁坏期间可能需要暂时使用,但这是另一回事.)
当您实际实例化一个对象时,它是某个派生类的对象,它不再是抽象的.它不再具有任何纯虚函数.实际实例化的派生类具有被该时间覆盖的所有纯虚函数.这就是虚方法表中需要一个条目 - 存储指向实际覆盖函数的指针的原因.
稍后在代码中,您可以myFunction()通过指向该抽象基类的指针进行调用
MyAbstractBaseClass *ptr = some_function();
// Pointer actually points to some non-abstract derived object
ptr->myFunction();
Run Code Online (Sandbox Code Playgroud)
编译器将生成将进入与*ptr对象关联的虚方法表的代码,提取对应的指针条目myFunction()并通过该指针传递控制.如上所述,该指针实际上将指向某些派生类的重写函数.这正是该条目的保留.