15

每个包含一个或多个虚函数的类都有一个与之关联的 Vtable。一个名为 vptr 的 void 指针指向该 vtable。该类的每个对象都包含指向同一个 Vtable 的 vptr。那为什么不是 vptr static ?与其将 vptr 与对象相关联,不如将其与类相关联?

在此处输入图像描述

4

7 回答 7

9

对象的运行时类是对象本身的一个属性。实际上,vptr表示运行时类,因此不能是static. 但是,它所指向的内容可以由同一运行时类的所有实例共享。

于 2012-12-17T12:28:38.570 回答
6

你的图表是错误的。没有一个 vtable,每个多态类型都有一个 vtable。vptr forA指向 vtable for A, vptr forA1指向 vtable forA1等。

鉴于:

class A {
public:
  virtual void foo();
  virtual void bar();
};
class A1 : public A {
  virtual void foo();
};
class A2 : public A {
  virtual void foo();
};
class A3 : public A {
  virtual void bar();
  virtual void baz();
};

包含 的虚表包含A的虚表包含 的虚表包含 的虚表{ &A::foo, &A::bar }
A1{ &A1::foo, &A::bar }
A2{ &A2::foo, &A::bar }
A3{ &A::foo, &A3::bar, &A3::baz }

因此,当您调用a.foo()编译器时,编译器会按照对象的 vptr 查找 vtable,然后调用 vtable 中的第一个函数。

假设一个编译器使用了你的想法,我们写:

A1 a1;
A2 a2;
A& a = (std::rand() % 2) ? a1 : a2;
a.foo();

编译器在基类中A查找并找到该类的 vptr A(根据您的想法),该类是static类型的属性,而不是引用绑定到A的对象的成员。a该 vptr 是否指向 vtable for A, or A1orA2或其他东西?如果它指向 vtable ,A1则在 50% 的时间里a引用是错误的a2,反之亦然。

现在假设我们写:

A1 a1;
A2 a2;
A& a = a1;
A& aa = a2;
a.foo();
aa.foo();

aaa都是对 的引用A,但它们需要两个不同的 vptr,一个指向 vtable for A1,一个指向 vtable for A2。如果 vptr 是一个静态成员,A它怎么能同时有两个值呢?唯一合乎逻辑、一致的选择是静态 vptrA指向 vtable for A

但这意味着a.foo()调用A::foo()在它应该调用A1::foo()的时候调用,并且调用aa.foo()也在A::foo()它应该调用的时候调用A2::foo()

显然,您的想法未能实现所需的语义,证明使用您的想法的编译器不能是 C++ 编译器。A1编译器无法在a不知道派生类型是什么的情况下获取 vtable for甚至尚未编写的派生类型!)或将 vptr 直接存储在对象中。

和 的 vptr 必须不同,a1并且a2在通过指针或对 base 的引用访问它们时,必须在不知道动态类型的情况下可访问,这样当您通过对基类的引用获取 vptr 时a,它仍然指向右侧vtable,而不是基类 vtable。最明显的方法是将 vptr 直接存储在对象中。另一种更复杂的解决方案是将对象地址映射到 vptrs,例如类似的东西,并通过查找std::map<void*, vtable*>找到 vtablea&a,但这仍然为每个对象存储一个 vptr 而不是每个类型一个,并且每次创建和销毁多态对象时都需要更多的工作(和动态分配)来更新映射,并且会增加整体内存使用量,因为映射结构会占用空间。将 vptr 嵌入对象本身更简单。

于 2012-12-17T14:10:53.717 回答
1

虚拟表(顺便说一下,C++ 标准中没有提到的一种实现机制)用于在运行时识别对象的动态类型。因此,对象本身必须持有一个指向它的指针。如果它是静态的,那么它只能识别静态类型,它是无用的。

如果您正在考虑以某种方式在typeid()内部使用来识别动态类型,然后用它调用静态指针,请注意typeid()仅返回属于具有虚函数类型的对象的动态类型;否则它只返回静态类型(当前 C++ 标准中的第 5.2.8 节)。是的,这意味着它以相反的方式工作:typeid()通常使用虚拟指针来标识动态类型。

于 2012-12-17T12:35:20.690 回答
1

正如每个人都证明 Vptr 是对象的属性。让我们看看为什么?

假设我们有三个对象 Class Base{ virtual ~Base(); //Class Definition }; Class Derived: public Base{ //Class Definition }; Class Client: public Derived{ //Class Definition };

持有关系 Base<---Derived<----Client. 客户端类派生自派生类,派生类又派生自基类

Base * Ob = new Base; Derived * Od = new Derived; Client* Oc = new Client;

每当 Oc 被破坏时,它应该破坏数据的基础部分、派生部分和客户端部分。为了帮助这个序列,基本析构函数应该是虚拟的,并且对象 Oc 的析构函数指向客户端的析构函数。当对象 Oc 的基析构函数是虚拟编译器时,将代码添加到对象 Oc 的析构函数以调用派生的析构函数,派生的析构函数调用基的析构函数。当 Client 对象被销毁时,此链接会看到所有基础数​​据、派生数据和客户端数据都被销毁。

如果 vptr 是静态的,那么 Oc 的 vtable 条目仍将指向 Base 的析构函数,并且只有 Oc 的基本部分被销毁。oc 的 vptr 应该始终指向大多数派生对象的析构函数,如果 vptr 是静态的,这是不可能的。

于 2014-07-01T03:51:38.050 回答
0

的全部要点vptr是因为您不确切知道对象在运行时具有哪个类。如果您知道这一点,那么虚拟函数调用将是不必要的。也就是说,事实上,当您不使用虚函数时会发生什么。但是对于虚拟功能,如果我有

class Sub : Parent {};

和 type 的值Parent*,我不知道在运行时这是否真的是 type 的对象Parent或 type 之一Sub。vptr让我弄清楚了。

于 2012-12-17T12:28:54.727 回答
-1

虚拟方法表是每个类。一个对象包含一个指向运行时类型 vptr 的指针。

我不认为这是标准半身像中的要求,我使用过的所有编译都以这种方式进行。

即使在您的示例中也是如此。

于 2012-12-17T12:31:29.903 回答
-1

@Harsh Maurya:原因可能是,静态成员变量必须在程序中的 Main 函数之前定义。但是,如果我们希望 _vptr 是静态的,其责任(编译器/程序员)在 main 之前在程序中定义 _vptr。以及程序员如何知道 VTABLE 的指针以将其分配给 _vptr。这就是编译器负责将值分配给指针(_vptr)的原因。这发生在类的构造函数(隐藏功能)中。现在,如果 Constructor 出现,每个对象都应该有一个 _vptr。

于 2018-03-31T15:38:39.573 回答