继承和多态的低级细节

时间:2010-12-28 17:56:58

标签: c++ inheritance polymorphism vtable

这个问题是我头脑中隐藏的一大疑问,也难以用言语来描述。有时候它似乎很明显,有时候很难破解。所以问题就像这样::

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 << "Derived from Base" << endl; 
     virtual void function4() {cout << "Only in derived" << 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指向Derived对象,但它访问的VTABLE和HOW? as b_ptr - &gt; function4()给出了编译错误。或者是b_ptr只能访问Derived VTABLE中基本类VTABLE的大小?

Q2。由于Derived的内存布局必须是(B​​ase,Derived),Base类的VTABLE是否也包含在Derived类的内存布局中?

Q3。由于基类Vtable的function1和function2指向基类实现,Derived类的function2指向Base类的function2,在Base类中是否真的需要VTABLE? (这可能是我可以提出的最愚蠢的问题,但我仍然对目前的状态存在疑问,答案必须与Q1的答案相关:))

请评论。

谢谢你的耐心等待。

6 个答案:

答案 0 :(得分:7)

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

#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\n");
}

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

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;
}

答案 1 :(得分:3)

Q1 - 名称解析是静态的。由于b_ptr的类型为Base *,因此编译器无法看到Derived特有的任何名称,以便访问v_table中的条目。

Q2 - 也许,也许不是。您必须记住,vtable本身只是实现运行时多态性的一种非常常见的方法,实际上并不是标准的一部分。没有关于它所在位置的明确陈述。 vtable实际上可以是程序中某处的某个静态表,它是从实例的对象描述中指向的。

Q3 - 如果在一个地方存在虚拟条目,则必须在所有地方进行,否则将需要一堆难以/不可能的检查来提供覆盖能力。如果编译器知道你有一个Base并且正在调用一个被覆盖的函数,则不需要访问vtable但可以直接使用该函数;如果它想要它甚至可以内联它。

答案 2 :(得分:3)

A1。 vtable指针指向Derived vtable,但编译器不知道。你告诉它把它当作Base指针,所以无论指针指向什么,它都只能调用对Base类有效的方法。

A2。 vtable布局没有由标准指定,它甚至不是正式的类。它只是99.99%最常见的实现方法。 vtable不是对象布局的一部分,但是有一个指向vtable的指针,它是对象的隐藏成员。它始终位于对象的相同位置,因此编译器始终可以生成访问它的代码,无论它具有哪个类指针。随着多重继承,事情变得更加复杂,但不能再去那里了。

A3。 Vtables每个类存在一次,而不是每个对象一次。编译器需要生成一个,即使它永远不会被使用,因为它不会提前知道。

答案 3 :(得分:1)

b_ptr指向Derived vtable-但是编译器不能保证派生类中有一个function_4,因为它不包含在基本vtable中,因此编译器不知道如何进行调用和抛出错误。

不,vtable是程序中其他地方的静态常量。基类只保存指向它的指针。派生类可能包含两个vtable指针,但可能没有。

在这两个类的上下文中,Base需要一个vtable来查找Derived的function1,即使你没有将它标记为也是虚拟,因为它覆盖了一个基类虚拟功能。但是,即使不是这种情况,我也很确定编译器无论如何都需要生成vtable,因为它不知道你在其他翻译单元中有哪些其他类可能会也可能不会从这些类继承并以不可知的方式覆盖他们的虚函数。

答案 4 :(得分:1)

首先,最重要的是,请记住C ++没有进行任何类型的运行时内省。基本上,它需要在编译时了解有关对象的所有内容。

Q1 - b_ptr是指向Base的指针。因此,它只能访问Base对象中存在的内容。没有例外。现在,实际的实现可能会根据对象的实际类型而改变,但如果您想通过Base指针调用它,则无法在Base中定义该方法。

Q2 - 简单的回答是'是的,基础的vtable必须存在于派生'中,但是有很多可能的策略用于如何布局vtable,所以不要挂在它上面确切的结构。

Q3 - 是的,基类中必须有一个vtable。在类中调用虚函数的所有东西都将通过vtable,这样如果底层对象实际上是Derived,那么一切都可以工作。

现在这不是绝对的,因为如果编译器可以绝对确定它知道它得到了什么(可能是在本地堆栈上声明的Base对象的情况),那么编译器可以优化出来vtable查找,甚至可以允许内联函数。

答案 5 :(得分:1)

所有这些都取决于实施。但是这里有使用“vtable”的常用最简单方法的答案。

Base类有一个vtable指针,所以底层表示就像这个伪代码:

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不需要添加另一个。

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;
}

如果有人尝试__Base_vtablenew Base();,则需要我的伪代码中的“基类的vtable”Base obj;

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

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