关于c ++:继承和多态的低级细节

Low level details of inheritance and polymorphism

这个问题是困扰我的一大疑问,也很难用语言来描述它。有时它看起来很明显,有时很难破解。所以问题是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Base{
public:
     int a_number;

     Base(){}
     virtual void function1() {}
     virtual void function2() {}
     void function3() {}
};

class Derived:public Base{
public:
     Derived():Base() {}
     void function1() {cout &lt&lt"Derived from Base" &lt&lt endl;
     virtual void function4() {cout &lt&lt"Only in derived" &lt&lt endl;}
};

int main(){

      Derived *der_ptr = new Derived();
      Base *b_ptr = der_ptr;  // As just address is being passed , b_ptr points to derived                object

      b_ptr -> function4(); // Will Give Compilation ERROR!!

      b_ptr -> function1(); // Calls the Derived class overridden method

      return 0;

}

Q1。虽然b_ptr指向派生对象,但它访问哪个vtable以及如何访问?因为b eu ptr->function4()给出了编译错误。或者b_ptr只能访问派生vtable中的基类vtable的大小?

Q2。由于派生类的内存布局必须是(base,derived),那么派生类的内存布局中是否也包括基类的vtable?

Q3。因为基类vtable的function1和function2指向基类实现,派生类的function2指向基类的function2,所以基类中真的需要vtable吗??(这可能是我问过的最愚蠢的问题,但在我目前的状态下,我仍然对此表示怀疑,答案必须与问题1的答案相关。)

请评论。

谢谢你的耐心。


作为进一步的说明,这里是C++程序的C版本,显示了VTABLE和ALL。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#include <stdlib.h>
#include <stdio.h>

typedef struct Base Base;
struct Base_vtable_layout{
    void (*function1)(Base*);
    void (*function2)(Base*);
};

struct Base{
    struct Base_vtable_layout* vtable_ptr;
    int a_number;
};

void Base_function1(Base* this){}

void Base_function2(Base* this){}

void Base_function3(Base* this){}

struct Base_vtable_layout Base_vtable = {
    &Base_function1,
    &Base_function2
};

void Base_Base(Base* this){
    this->vtable_ptr = &Base_vtable;
};

Base* new_Base(){
    Base *res = (Base*)malloc(sizeof(Base));
    Base_Base(res);
    return res;
}

typedef struct Derived Derived;
struct Derived_vtable_layout{
    struct Base_vtable_layout base;
    void (*function4)(Derived*);
};

struct Derived{
    struct Base base;
};

void Derived_function1(Base* _this){
    Derived *this = (Derived*)_this;
    printf("Derived from Base
"
);
}

void Derived_function4(Derived* this){
    printf("Only in derived
"
);
}

struct Derived_vtable_layout Derived_vtable =
{
    { &Derived_function1,
      &Base_function2},
    &Derived_function4
};

void Derived_Derived(Derived* this)
{
    Base_Base((Base*)this);
    this->base.vtable_ptr = (struct Base_vtable_layout*)&Derived_vtable;
}      

Derived* new_Derived(){
    Derived *res = (Derived*)malloc(sizeof(Derived));
    Derived_Derived(res);
    return res;
}



int main(){
      Derived *der_ptr = new_Derived();
      Base *b_ptr = &der_ptr->base;
      /* b_ptr->vtable_ptr->function4(b_ptr); Will Give Compilation ERROR!! */
      b_ptr->vtable_ptr->function1(b_ptr);
      return 0;
}


A1The vtable pointer is pointing to a derived vtable,but the compiler doesn't know that.你说要把它当作一个基准指针,所以它只能叫一种基准指针有效的方法,不管指针点是什么。

A2The Vtable layout is not specified by the standard,it isn't even officially part of the class.这是99.99%最常见的执行方法。The Vtable isn't part of the object layout,but there's a pointer to the Vtable that's a hidden member of the object.它将始终处于物体的相对位置上,因此编译器可以生成代码来存取它,无论是哪一类指针都没有。事物与多重遗传更加复杂,但却不去那里。

A3Vtables exist once per class,not once per object.编译器需要一个生成,即使它从未使用过,因为它不知道有多久。


Q1-name resolution is static.自从B ptr为类型库*,编译器无法从中找到任何一个唯一的名称,以使其输入到V table。

也许,也许不是。你必须记住,VTABE ITSELF是一种非常常用的方法来实现runtime多态性,而且实际上在任何地方都不是标准的一部分。没有关于住所的明确声明。该VTable实际上可以是一个静态表格,在程序中的某一个地方,该程序被指向在对象描述中的实例。

Q3-If there's a虚拟entry in one place there must be in all otherwise a bunch of difficult/impossible checks would be necessary to provide override capability.如果编译器知道你有一个底座并呼叫一个超Ridden函数,则不需要访问VTable,而是可以直接使用VTable;如果需要,它甚至可以插入。


所有这些都取决于实现。但下面是使用"vtables"的最简单方法的答案。

Base类有一个vtable指针,因此底层表示类似于此伪代码:

1
2
3
4
5
6
struct Base {
  void** __vtable;
  int a_number;
};
void* __Base_vtable[] = { &Base::function1, &Base::function2 };
void __Base_ctor( struct Base* this_ptr ) { this_ptr->__vtable = __Base_vtable; }

Derived类包括Base类子对象。因为它有一个vtable的位置,所以Derived不需要再添加一个vtable。

1
2
3
4
5
6
7
8
9
struct Derived {
  struct Base __base;
};
void* __Derived_vtable[] =
  { &Derived::function1, &Base::function2, &Derived::function4 };
void __Derived_ctor( struct Derived* this_ptr ) {
  __Base_ctor( &this_ptr->__base );
  this_ptr->__base.__vtable = __Derived_vtable;
}

如果有人试图使用new Base();Base obj;,在我的伪代码中需要"vtable for the base class"(基类vtable),__Base_vtable

当涉及多个继承或虚拟继承时,以上所有内容都变得更加复杂….

对于行b_ptr -> function4();,这是一个编译时错误,与vtables无关。当您强制转换为Base*指针时,只能以class Base定义的方式使用该指针(因为编译器不再"知道"它是否真的是DerivedBase或其他类)。如果Derived有自己的数据成员,则不能通过该指针访问它。如果Derived有自己的、虚拟的或非虚拟的成员函数,则不能通过该指针访问它。


首先,而且最重要的是,记住C++doesn't do a lot of run-time introspection of any kind.基本上,它需要在编译时间了解所有关于物体的信息。

Q1-B PTR是一个指针到一个基地。因此,它只能存取在一个基本对象中的东西。无例外。现在,实际实现可能取决于物体的实际类型,但如果你想通过一个基准点来称呼它,则不需要在方法的周围加以界定。

简单的答案是对的,一个基地的VTable必须在一个后期出现,但有许多可能的策略可以用来填补一个VTable,所以不要在它的确切结构上消失。

Q3-yes,there must be a vtable in the base class.凡在一类中被称为虚拟函数的东西都会通过VTable,因此,如果所述未被理解的对象实际上是一个衍生的,那么一切都可以工作。

现在这不是绝对的,因为如果编译器能够绝对确定它知道它得到了什么(如可能是一个关于当地堆栈的声明的基础对象),那么编译器就可以优化输出VTable lookups,甚至可以被允许输入该功能。


b ptr points to the derived vtable-but the compiler can't guarantee that the derived class has a function 4 in it,a s that's not contained in the base vtable,so the compiler doesn't know how to make the call and throws an error.

不,VTABE是一个静态的常数,在程序的某个地方。基地组的梅里利握着一个指针。一个来自阶级的人可以握住两个vtable指针,但可能不是。

在这两个类别的情况下,基地需要一个VTable,以确定产生的函数1,即便实际的函数是虚拟的,但你并没有标记它,因为它超出了一个基层虚拟函数。然而,即使这不是个案,我也很高兴地确认,编译器是需要以任何方式制作VTABLES的,因为它不知道其他类别的人在其他翻译单元中有什么可以或可能不在这些类别中生根,并超越了他们在不知道的方式中的虚拟功能。