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