什么时候用C++创建vtable?

use*_*215 24 c++ polymorphism virtual-functions vtable

什么时候编译器创建一个虚函数表?

1)当类包含至少一个虚函数时.

要么

2)当直接基类包含至少一个虚函数时.

要么

3)当层次结构的任何级别的任何父类包含至少一个虚函数时.

与此相关的问题:是否可以放弃C++层次结构中的动态调度?

例如,考虑以下示例.

#include <iostream>
using namespace std;
class A {
public:
  virtual void f();
};
class B: public A {
public:
  void f();
};
class C: public B {
public:
  void f();
};
Run Code Online (Sandbox Code Playgroud)

哪些类将包含V表?

既然B没有将f()声明为虚拟,那么C类是否会获得动态多态性?

小智 23

除了"vtables是特定于实现的"(它们是),如果使用vtable:每个类都有独特的vtable.即使B :: fC :: f未声明为虚拟,因为来自基类(代码中的A)的虚方法上存在匹配的签名,B :: fC :: f都是隐式虚拟的.因为每个类具有至少一个唯一的虚拟方法(B ::˚F覆盖A ::˚F实例和Ç::˚F类似地对于Ç实例),需要三个虚函数表.

您通常不应该担心这些细节.重要的是你是否有虚拟派遣. 您不必使用虚拟分派,通过显式指定要调用的函数,但这通常仅在实现虚方法时有用(例如调用base的方法).例:

struct B {
  virtual void f() {}
  virtual void g() {}
};

struct D : B {
  virtual void f() { // would be implicitly virtual even if not declared virtual
    B::f();
    // do D-specific stuff
  }
  virtual void g() {}
};

int main() {
  {
    B b; b.g(); b.B::g(); // both call B::g
  }
  {
    D d;
    B& b = d;
    b.g(); // calls D::g
    b.B::g(); // calls B::g

    b.D::g(); // not allowed
    d.D::g(); // calls D::g

    void (B::*p)() = &B::g;
    (b.*p)(); // calls D::g
    // calls through a function pointer always use virtual dispatch
    // (if the pointed-to function is virtual)
  }
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

一些可能有用的具体规则; 但是不要引用我这些,我可能会错过一些边缘情况:

  • 如果一个类有虚拟方法或虚拟基础,即使是继承的,那么实例也必须有一个vtable指针.
  • 如果一个类声明了非继承的虚方法(例如当它没有基类时),那么它必须有自己的vtable.
  • 如果一个类具有与其第一个基类不同的重写方法集,则它必须具有自己的vtable,并且不能重用该基类.(析构函数通常需要这个.)
  • 如果一个类有多个基类,则第二个或后一个基类具有虚拟方法:
    • 如果没有早期基础具有虚拟方法并且空基础优化应用于所有早期基础,则将此基础视为第一个基类.
    • 否则,该类必须有自己的vtable.
  • 如果一个类有任何虚拟基类,它必须有自己的vtable.

请记住,vtable类似于类的静态数据成员,而实例只有指向这些的指针.

另见Jan Gray 的综合性文章C++:Under the Hood(March 1994).(如果该链接死亡,请尝试使用Google.)

重用vtable的示例:

struct B {
  virtual void f();
};
struct D : B {
  // does not override B::f
  // does not have other virtuals of its own
  void g(); // still might have its own non-virtuals
  int n; // and data members
};
Run Code Online (Sandbox Code Playgroud)

特别是,通知B的dtor不是虚拟的(这可能是实际代码中的错误),但在此示例中,D实例将指向与B实例相同的vtable .


bma*_*ies 8

答案是,"这取决于".它取决于'包含vtbl'的含义,它取决于特定编译器的实现者所做出的决定.

严格来说,没有"类"包含虚函数表.某些类的某些实例包含指向虚函数表的指针.但是,这只是语义的一种可能实现.

在极端情况下,编译器可以假设将唯一编号放入索引到用于选择适当虚拟函数实例的数据结构的实例中.

如果你问,'海湾合作委员会做什么?' 或者'Visual C++做什么?' 然后你可以得到一个具体的答案.

@Hassan Syed的答案可能更接近你的要求,但在这里保持概念是非常重要的.

行为(动态调度基于什么类是新的)和实现.您的问题使用了实现术语,但我怀疑您正在寻找行为答案.

行为答案是:任何声明或继承虚函数的类都会在调用该函数时表现出动态行为.任何没有的课程,都不会.

在实现方面,允许编译器做任何想要完成该结果的事情.


Has*_*yed 7

回答

当类声明包含虚函数时,会创建一个vtable.当父级 - 在层次结构中的任何地方 - 具有虚函数时,引入vtable,让我们调用这个父Y.Y的任何父级都不会有vtable(除非他们virtual在其层次结构中有一些其他函数).

继续阅读以进行讨论和测试

- 解释 -

当您将成员函数指定为虚拟时,您可能会尝试在运行时通过基类以多态方式使用子类.为了保持c ++在语言设计上的性能保证,他们提供了最轻的实现策略 - 即一个间接级别,并且只有在运行时可以多态地使用类时,程序员通过设置至少一个函数来指定它虚拟.

如果避开虚拟关键字,则不会产生vtable的成本.

- 编辑:反映您的编辑 -

仅当基类包含虚函数时,任何其他子类才包含vtable.所述基类的父母没有vtable.

在你的例子中,所有三个类都有一个vtable,这是因为你可以尝试通过一个使用所有三个类 A*.

--test - GCC 4+ -

#include <iostream>

class test_base
{
  public:
    void x(){std::cout << "test_base" << "\n"; };
};

class test_sub : public test_base
{
public:
  virtual void x(){std::cout << "test_sub" << "\n"; } ;
};

class test_subby : public test_sub
{
public:
  void x() { std::cout << "test_subby" << "\n"; }
};

int main() 
{
  test_sub sub;
  test_base base;
  test_subby subby;

  test_sub * psub;
  test_base *pbase;
  test_subby * psubby;

  pbase = &sub;
  pbase->x();
  psub = &subby;
  psub->x();

  return 0;
}
Run Code Online (Sandbox Code Playgroud)

产量

test_base
test_subby
Run Code Online (Sandbox Code Playgroud)

test_base没有虚拟表,因此任何输入它的东西都将使用x()from test_base.test_sub在另一方面改变的性质x()和它的指针就通过一虚表间接的,这是由所示test_subbyx()被执行.

因此,只有在使用关键字virtual时才会在层次结构中引入vtable.较老的祖先没有vtable,如果发生垂头丧气,它将与祖先功能硬连线.