包含一个或多个虚函数的每个类都有一个与之关联的Vtable。一个名为vptr的void指针指向该vtable。该类的每个对象都包含指向同一Vtable的vptr。那为什么vptr不是静态的呢?而不是将vptr与对象相关联,为什么不将它与类相关联?
答案 0 :(得分:9)
对象的运行时类是对象本身的属性。实际上,vptr
表示运行时类,因此不能是static
。但是,它指向的内容可以由同一运行时类的所有实例共享。
答案 1 :(得分:3)
你的图表错了。没有单个vtable,每种多态类型都有一个vtable。 A
的vptr指向A
的vtable,A1
的vptr指向A1
等的vtable。
假设:
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
的vtable包含{ &A::foo, &A::bar }
A1
的vtable包含{ &A1::foo, &A::bar }
A2
的vtable包含{ &A2::foo, &A::bar }
A3
的vtable包含{ &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
并找到类A
的vptr(根据您的想法)static
属性A
的{{1}}属性不是引用a
绑定的对象的成员。该vptr是否指向A
或A1
或A2
或其他内容的vtable?如果它指向A1
的vtable,则a
引用a2
的时间为50%,反之亦然。
现在假设我们写:
A1 a1;
A2 a2;
A& a = a1;
A& aa = a2;
a.foo();
aa.foo();
a
和aa
都是对A
的引用,但是它们需要两个不同的vptrs,一个指向A1
的vtable,另一个指向vtable for { {1}}。如果vptr是A2
的静态成员,它怎么能一次有两个值?唯一合乎逻辑的一致选择是A
的静态vptr指向A
的vtable。
但这意味着调用A
调用a.foo()
时应调用A::foo()
,调用A1::foo()
调用aa.foo()
时调用A::foo()
1}}。
显然,您的想法无法实现所需的语义,证明使用您的想法的编译器不能是C ++编译器。编译器无法从A2::foo()
获取A1
的vtable,而无需知道派生类型是什么(一般来说,这是不可能的,也可能是从在不同的库中定义的函数,可以引用尚未编写的派生类型!)或者将vptr直接存储在对象中。
a
和a1
的vptr必须不同,并且在通过poiner或base引用访问它们时必须可以访问它们而不知道动态类型,这样当你获得vptr时引用基类a2
,它仍然指向正确的vtable,而不是基类vtable。最明显的方法是将vptr直接存储在对象中。另一种更复杂的解决方案是将对象地址映射到vptrs,例如像a
这样的东西,通过查找std::map<void*, vtable*>
找到a
的vtable,但是这仍然存储每个对象一个vptr而不是每个类型一个,并且需要更多工作(并且动态)每次创建和销毁多态对象时更新映射,并且会增加总体内存使用量,因为映射结构会占用空间。将vptr嵌入对象本身更简单。
答案 2 :(得分:1)
虚拟表(顺便说一句,这是C ++标准中未提及的实现机制)用于在运行时标识对象的动态类型。因此,对象本身必须保持指向它的指针。如果它是静态的,那么它只能识别静态类型并且它将是无用的。
如果你想在某种程度上在内部使用typeid()
来识别动态类型,然后用它调用静态指针,请注意typeid()
仅返回属于虚拟类型的对象的动态类型功能;否则它只返回静态类型(当前C ++标准中的第5.2.8节)。是的,这意味着它以相反的方式工作:typeid()
通常使用虚拟指针来识别动态类型。
答案 3 :(得分:0)
vptr
的重点是因为您不确切知道对象在运行时具有哪个类。如果您知道,则不需要虚函数调用。也就是说,实际上,当您不使用虚拟功能时会发生什么。但是对于虚拟功能,如果我有
class Sub : Parent {};
和类型Parent*
的值,我不知道它在运行时是否真的是Parent
类型的对象或Sub
类型的对象。 vptr让我想出来。
答案 4 :(得分:0)
每个人都证明Vptr是一个对象的属性。 让我们看看为什么?
假设我们有三个对象
Class Base{
virtual ~Base();
//Class Definition
};
Class Derived: public Base{
//Class Definition
};
Class Client: public Derived{
//Class Definition
};
持有关系Base&lt; --- Derived&lt; ---- Client。 客户端类派生自派生类,派生类又派生自基础
Base * Ob = new Base;
Derived * Od = new Derived;
Client* Oc = new Client;
每当Oc被破坏时,它应该破坏基础部分,派生部分和客户端部分数据。为了帮助这个序列,Base析构函数应该是虚拟的,而对象Oc的析构函数指向Client的析构函数。当对象Oc的基础析构函数是虚拟编译器时,将代码添加到对象Oc的析构函数中以调用派生的析构函数和派生的析构函数来调用base的析构函数。此链接会在客户端对象被销毁时看到所有基础,派生和客户端数据被破坏。
如果该vptr是静态的,那么Oc的vtable条目仍将指向Base的析构函数,并且只有Oc的基本部分被销毁。 Oc的vptr应始终指向大多数派生对象的析构函数,如果vptr是静态的,这是不可能的。
答案 5 :(得分:-1)
虚拟方法表是每个类。对象包含指向运行时类型vptr。
的指针我不认为这是标准破坏中的一项要求我所使用的所有编译都是这样做的。
即使在你的例子中也是如此。
答案 6 :(得分:-1)
@Harsh Maurya:原因可能是,必须在程序中的Main函数之前定义静态成员变量。但是如果我们希望_vptr是静态的,那么它的责任(编译器/程序员)在main之前定义程序中的_vptr。程序员如何知道VTABLE的指针将其分配给_vptr。这就是为什么编译器负责将值赋给指针(_vptr)。这发生在类的构造函数(隐藏功能)中。现在,如果构造函数出现,每个对象应该有一个_vptr。