10

更新:将析构函数示例替换为直接方法调用示例。

你好,

如果我有以下代码:

class a
{
public:
    virtual void func0(); // a has a VTable now
    void func1();
};
class b : public a
{
public:
    void func0() { a::func0(); }
    void func2();
};
  1. B中有VTable吗?B 没有虚函数,但从 b::func0() 调用 a::func0()
  2. func1 是否驻留在 VTable 中?它不是虚拟的。
  3. func2 是否驻留在 VTable 中?
  4. 如果 b::func0() 中没有 aa::func0() 调用,上述答案是否会有所不同?

谢谢

4

4 回答 4

19

如果您声明虚函数,您还应该声明您的析构函数 virtual ;-)。

  1. B 有一个虚表,因为它有一个虚函数,即func0()。如果在基类中声明一个函数(包括析构函数) virtual,则其所有派生类也将具有具有相同签名 virtual 的函数。这将导致他们拥有一个 vtable。此外,即使您没有func0在其中明确声明,B 也会拥有 vtable。

  2. 非虚函数不通过虚表引用。

  3. 见 2。

  4. 不会。类的 vtable 是基于类声明构建的。不考虑类函数的主体(更不用说其他函数了)。因此,B 有一个 vtable,因为它的功能func0()是虚拟的。

还有一个棘手的细节,虽然这不是你问题的要点。您将函数声明B::func0()为内联。在gcc编译器中,如果一个虚函数被声明为内联,它会在虚拟表中保留它的槽,该槽指向为该内联函数发出的一个特殊函数(这算作获取它的地址,这使得内联发出)。这意味着,函数是否内联不会影响 vtable 中的插槽数量及其对类的必要性。

于 2009-09-05T16:04:57.187 回答
5
  1. 是的,因为它的基类有一个;它的析构函数也是虚拟的(即使您没有将其声明为虚拟),因为基类的析构函数是虚拟的。

  2. 不。

  3. 不。实际上我不认为当前的代码是合法的:编译器会在调用 B 析构函数之后调用 A 析构函数,即使你没有从 ~B 显式调用 ~A;所以我认为你不应该从 ~B 调用 ~A ,即使编译器允许你这样做。

于 2009-09-05T16:01:26.243 回答
3

参考更新的示例:

  1. 是的,b有一个 vtable。请注意,b::func0() 是虚拟的(覆盖 a::func0()),即使您没有明确将其标记为虚拟。我猜是奇怪的 C++“漏洞”。
  2. 不,非虚函数不驻留在 vtable 中。
  3. 见 2。
  4. 不,你已经覆盖了 a:func0(); 是否调用 a::func0() 并不重要。

一些额外的注释(依赖于编译器,但这些是非常常见的概括):

  • b的每个实例都有一个指向 vtable 的指针,因为您是从具有虚函数的类派生的。
  • 即使您没有定义 b::func0() 也是如此
  • 在这种情况下,编译器可能有b的实例指向a的静态 vtable,或者它可能为b创建一个静态 vtable 并用指向a成员的指针的指针填充它。
  • 但它仍然是必需的,以便您可以通过指向 a 的指针正确访问b的实例。
于 2009-09-05T17:00:45.273 回答
1
  • 如果基类函数是虚拟的,那么如果您在派生类中覆盖该函数,即使您没有明确指定,它也是隐式虚拟的。如果类具有虚函数,则它具有 av 表。
  • 只有虚函数存在于 vtable 中,function1 不会驻留在 vtable 中
  • function2 不会驻留在 vtable 中,原因与上述相同
  • Creation of vtable does not depend on whether you call that function from base class or from somewhere else. Function call does not decide creation of vtable.
于 2009-09-05T17:55:32.963 回答