Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/143.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 为什么vptr不是静态的?_C++_Function_Virtual_Vtable_Vptr - Fatal编程技术网

C++ 为什么vptr不是静态的?

C++ 为什么vptr不是静态的?,c++,function,virtual,vtable,vptr,C++,Function,Virtual,Vtable,Vptr,每个包含一个或多个虚拟函数的类都有一个与其关联的Vtable。一个名为vptr的空指针指向该vtable。该类的每个对象都包含指向同一Vtable的vptr。那么为什么vptr不是静态的呢?与其将vptr与对象关联,为什么不将其与类关联 对象的运行时类是对象本身的属性。实际上,vptr表示运行时类,因此不能是静态的。但是,它所指向的内容可以被同一运行时类的所有实例共享。vptr的整个要点是,您不知道对象在运行时具有哪个类。如果您知道这一点,那么虚拟函数调用将是不必要的。事实上,这就是不使用虚拟函

每个包含一个或多个虚拟函数的类都有一个与其关联的Vtable。一个名为vptr的空指针指向该vtable。该类的每个对象都包含指向同一Vtable的vptr。那么为什么vptr不是静态的呢?与其将vptr与对象关联,为什么不将其与类关联


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

vptr的整个要点是,您不知道对象在运行时具有哪个类。如果您知道这一点,那么虚拟函数调用将是不必要的。事实上,这就是不使用虚拟函数时会发生的情况。但如果我有虚拟函数的话

class Sub : Parent {};

还有一个类型为
Parent*
的值,我不知道在运行时这是
Parent
类型的对象还是
Sub
类型的对象。vptr让我明白了这一点。

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

我不认为这是标准半身像中的一个要求,我使用过的所有编译都是这样做的

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

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


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

您的图表是错误的。没有单个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();
A1 a1;
A2 a2;
A& a = a1;
A& aa = a2;
a.foo();
aa.foo();
编译器在基类
A
中查找并找到类
A
的vptr,该类(根据您的想法)是
A
类型的
静态属性,而不是引用
A
绑定到的对象的成员。该vptr是否指向
A
、或
A1
A2
或其他内容的vtable?如果它指向
A1
的vtable,当
a
引用
a2
时,50%的时间是错误的,反之亦然

现在假设我们写:

A1 a1;
A2 a2;
A& a = (std::rand() % 2) ? a1 : a2;
a.foo();
A1 a1;
A2 a2;
A& a = a1;
A& aa = a2;
a.foo();
aa.foo();
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必须不同,并且在通过poiner或对base的引用访问它们时,必须在不知道动态类型的情况下进行访问,这样,当您通过对基类
a
的引用获得vptr时,它仍然指向右侧的vtable,而不是基类vtable。最明显的方法是将vptr直接存储在对象中。另一种更复杂的解决方案是保留对象地址到vptr的映射,例如类似于
std::map
,并通过查找
&a
找到
a
的vtable,但这仍然会为每个对象存储一个vptr,而不是每种类型一个,并且需要更多的工作(和动态分配)在每次创建和销毁多态对象时更新映射,会增加总体内存使用,因为映射结构会占用空间。只需将vptr嵌入对象本身就更简单。

因为每个人都证明vptr是对象的属性。 让我们看看为什么

假设我们有三个物体

类基{
虚拟~Base();
//类定义
};
派生类:公共基{
//类定义
};
类客户端:公共De