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();
};
每个人都证明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被析构时,它应该析构数据的基础部分、派生部分,然后是客户端部分。为了帮助这个顺序,Base析构函数应该是虚拟的,对象Oc的析构函数指向Client的析构函数。当对象Oc的基类析构函数为虚时,编译器向对象Oc的析构函数添加代码,调用派生的析构函数,派生的析构函数调用基类析构函数。当客户端对象被销毁时,这种链接会看到所有的基数据、派生数据和客户端数据都被销毁。 如果该vptr是静态的,则Oc的vtable条目仍将指向Base的析构函数,并且仅销毁Oc的base部分。Oc的vptr应该总是指向大多数派生对象的析构函数,如果vptr是静态的,这是不可能的。
8条答案
按热度按时间tpgth1q71#
对象的运行时类是对象本身的属性。实际上,
vptr
表示运行时类,因此不能是static
。但是,它所指向的对象可以由同一个运行时类的所有示例共享。6jygbczu2#
你的图表错了。没有一个单独的vtable,每个多态类型都有一个vtable。
A
的vptr指向A
的vtable,A1
的vptr指向A1
的vtable,等等。给出:
字符串
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中的第一个函数。假设一个编译器使用了你的想法,我们这样写:
型
编译器在基类
A
中查找并找到类A
的vptr,该类(根据您的想法)是A
类型的static
属性,而不是引用a
绑定到的对象的成员。该vptr是否指向A
或A1
或A2
的vtable或其他内容?如果它指向A1
的vtable,那么当a
引用a2
时,有50%的时间是错误的,反之亦然。现在假设我们这样写:
型
a
和aa
都是对A
的引用,但它们需要两个不同的vptr,一个指向A1
的vtable,另一个指向A2
的vtable。如果vptr是A
的静态成员,它怎么会同时有两个值呢?唯一合乎逻辑的一致选择是A
的静态vptr指向A
的vtable。但这意味着调用
a.foo()
在应该调用A1::foo()
时调用A::foo()
,调用aa.foo()
在应该调用A2::foo()
时也调用A::foo()
。很明显,你的想法无法实现所需的语义,证明使用你的想法的编译器不可能是C++编译器。如果不知道派生类型是什么,编译器就无法从
a
中获取A1
的vtable(通常这是不可能的,对base的引用可能是从不同库中定义的函数返回的,并且可能引用甚至还没有编写的派生类型!)或通过将VPTR直接存储在对象中。a1
和a2
的vptr必须不同,并且在通过pointer或base引用访问它们时,必须可以在不知道动态类型的情况下访问它们,以便当您通过基类a
的引用获得vptr时,它仍然指向正确的vtable,而不是基类vtable。最明显的方法是将vptr直接存储在对象中。另一种更复杂的解决方案是保持对象地址到vptr的Map,例如类似于std::map<void*, vtable*>
,并通过查找&a
找到a
的vtable,但这仍然为每个对象存储一个vptr,而不是为每个类型存储一个vptr,并且每次创建和销毁多态对象时都需要更多的工作(和动态分配)来更新map,并且会增加整体内存使用,因为map结构会占用空间。将vptr嵌入到对象本身中会更简单。ndasle7k3#
虚表(顺便说一句,这是C标准中没有提到的一种实现机制)用于在运行时识别对象的动态类型。因此,对象本身必须持有指向它的指针。如果它是静态的,那么只有静态类型可以被它识别,它将是无用的。
如果你想在内部使用
typeid()
来标识动态类型,然后用它调用静态指针,请注意typeid()
只返回属于虚函数类型的对象的动态类型;否则,它只返回静态类型(当前C标准中的5.2.8节)。是的,这意味着它的工作方式相反:typeid()
通常使用虚拟指针来标识动态类型。tkqqtvp14#
每个人都证明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被析构时,它应该析构数据的基础部分、派生部分,然后是客户端部分。为了帮助这个顺序,Base析构函数应该是虚拟的,对象Oc的析构函数指向Client的析构函数。当对象Oc的基类析构函数为虚时,编译器向对象Oc的析构函数添加代码,调用派生的析构函数,派生的析构函数调用基类析构函数。当客户端对象被销毁时,这种链接会看到所有的基数据、派生数据和客户端数据都被销毁。
如果该vptr是静态的,则Oc的vtable条目仍将指向Base的析构函数,并且仅销毁Oc的base部分。Oc的vptr应该总是指向大多数派生对象的析构函数,如果vptr是静态的,这是不可能的。
nkoocmlb5#
字符串
现在考虑上面的例子,如果我们使_vptr静态,那么它的内存将在编译时只分配一次。因此,_vptr对于类A和类B是相同的
型
现在,考虑上述情况。编译器如何知道哪个虚拟table _vptr指向哪个虚拟table _vptr?
因此,不能使其为静态的,因为它需要可用于每个对象来调用其虚拟表。
xxls0lw86#
vptr
的全部意义在于,您无法确切知道对象在运行时拥有哪个类。如果你知道这一点,那么虚函数调用就没有必要了。实际上,这就是不使用虚函数时发生的情况。但是对于虚函数,如果我有字符串
和一个
Parent*
类型的值,在运行时我不知道这是一个Parent
类型的对象还是一个Sub
类型的对象。VPTR让我弄明白了。mznpcxlj7#
虚拟方法表是每个类。对象包含指向运行时类型vptr的指针。
我不认为这是一个要求在标准的胸围所有编译,我已经这样做。
即使在你的例子中也是如此。
tmb3ates8#
@凶孔雀:原因可能是,静态成员变量必须在程序中的Main函数之前定义。但是如果我们希望_vptr是静态的,那么谁的责任(编译器/程序员)在main之前定义程序中的_vptr。以及程序员如何知道VTABLE的指针并将其分配给_vptr。这就是为什么编译器负责将值分配给指针(_vptr)。这发生在类的构造函数中(隐藏功能)。现在,如果构造函数进入画面,每个对象都应该有一个_vptr。