Низкий уровень детализации наследования и полиморфизма - PullRequest
6 голосов
/ 28 декабря 2010

Этот вопрос - одно из больших сомнений, которое возникает у меня в голове, и его также сложно описать словами.Иногда это кажется очевидным, а иногда трудным для взлома. Итак, вопрос звучит так: *

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_ptr -> function4 () выдает ошибку компиляции.Или b_ptr может получить доступ только до этого размера Базового класса VTABLE в Производном VTABLE?

Q2.Поскольку макет памяти Derived должен быть (Base, Derived), включен ли VTABLE класса Base в макет памяти класса Derived?

Q3.Поскольку function1 и function2 базового класса Vtable указывают на реализацию Базового класса, а function2 производного класса указывает на функцию2 Базового класса, действительно ли существует потребность в VTABLE в Базовом классе ??(Это может быть самый глупый вопрос, который я когда-либо мог задать, но все же я сомневаюсь в этом в моем нынешнем состоянии, и ответ должен быть связан с ответом на вопрос 1 :))

Пожалуйста, прокомментируйте.

Спасибо за терпение.

Ответы [ 6 ]

7 голосов
/ 28 декабря 2010

В качестве дополнительной иллюстрации приведена версия вашей программы на C ++ для языка C, на которой показаны vtables и все.

#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;
}
3 голосов
/ 28 декабря 2010

A1. Указатель vtable указывает на производную vtable, но компилятор этого не знает. Вы сказали ему обрабатывать его как указатель Base, чтобы он мог вызывать только те методы, которые допустимы для базового класса, независимо от того, на что указывает указатель.

A2. Макет vtable не указан стандартом, он даже не является частью класса. Это самый распространенный метод реализации на 99,99%. Vtable не является частью макета объекта, но есть указатель на vtable, который является скрытым членом объекта. Он всегда будет находиться в одном и том же относительном местоположении в объекте, поэтому компилятор всегда может сгенерировать код для доступа к нему, независимо от того, какой указатель класса он имеет. С множественным наследованием все усложняется, но пока не пойдем.

A3. Таблицы существуют один раз для каждого класса, а не один раз для каждого объекта. Компилятору нужно сгенерировать его, даже если он никогда не будет использован, потому что он не знает этого заранее.

3 голосов
/ 28 декабря 2010

Q1 - разрешение имен статическое.Поскольку b_ptr имеет тип Base *, компилятор не может видеть ни одно из имен, уникальных для Derived, чтобы получить доступ к их записям в v_table.

Q2 - Возможно, возможно, нет.Вы должны помнить, что сама vtable является просто очень распространенным методом реализации полиморфизма во время выполнения и фактически нигде не является частью стандарта.Нельзя сделать однозначного заявления о том, где он находится.Фактически, vtable может быть какой-то статической таблицей где-то в программе, на которую указывают из описания объектов экземпляры.

Q3 - Если в одном месте есть виртуальная запись, она должна быть во всех местах, в противном случае группасложные / невозможные проверки будут необходимы для обеспечения возможности переопределения.Если компилятор ЗНАЕТ, что у вас есть Base и вы вызываете переопределенную функцию, он не требует доступа к vtable, но может просто использовать функцию напрямую;он может даже встроить его, если захочет.

1 голос
/ 28 декабря 2010

Все это зависит от реализации.Но вот ответы для обычного простейшего способа с использованием «vtables».

Класс 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;
}

"vtable для базового класса", __Base_vtable в моем псевдокоде, необходим в случаекто-то пытается new Base(); или Base obj;.

Все вышеперечисленное усложняется, когда задействовано множественное наследование или виртуальное наследование ....

Для строки b_ptr -> function4(); этоошибка времени компиляции, мало связанная с vtables.Когда вы приводите указатель Base*, вы можете использовать этот указатель только способами, определенными class Base (потому что компилятор больше не «знает», действительно ли это Derived, Base иликакой-то другой класс).Если Derived имеет собственный элемент данных, вы не сможете получить к нему доступ через этот указатель.Если Derived имеет собственную функцию-член, виртуальную или нет, вы не можете получить к ней доступ через этот указатель.

1 голос
/ 28 декабря 2010

Во-первых, и это самое главное, помните, что C ++ не делает много самоанализа во время выполнения. По сути, он должен знать все об объектах во время компиляции.

Q1 - b_ptr - указатель на базу. Поэтому он может получить доступ только к тем вещам, которые присутствуют в базовом объекте. Без исключений. Теперь фактическая реализация может меняться в зависимости от фактического типа объекта, но нет способа обойти метод, который должен быть определен в Base, если вы хотите вызывать его через указатель Base.

В2. Простой ответ: «Да, виртуальная таблица для базы должна присутствовать в производной», но существует множество возможных стратегий компоновки виртуальной таблицы, поэтому не зацикливайтесь на ней. Точная структура.

Q3 - Да, в базовом классе должен быть vtable. Все, что вызывает виртуальные функции в классе, будет проходить через виртуальную таблицу, так что если базовый объект на самом деле является производным, все может работать.

Теперь это не абсолют, потому что если компилятор АБСОЛЮТНО уверен в том, что он знает, что он получил (как в случае с объектом Base, объявленным в локальном стеке), то компилятору разрешается оптимизировать vtable ищет, и может даже позволить встроить функцию.

1 голос
/ 28 декабря 2010

b_ptr указывает на производную vtable, но компилятор не может гарантировать, что в производном классе есть функция function_4, так как она не содержится в базовой vtable, поэтому компилятор не знает, как выполнить вызов и выдает ошибка.

Нет, vtable является статической константой где-то еще в программе. Базовый класс просто содержит указатель на него. Производный класс может содержать два указателя vtable, но это не так.

В контексте этих двух классов для Base требуется виртуальная таблица, чтобы найти функцию Derived's1, которая на самом деле является виртуальной, даже если вы не пометили ее как таковую, поскольку она перекрывает виртуальную функцию базового класса , Тем не менее, даже если это не так, я уверен, что компилятор все равно необходим для создания vtables, так как он не знает, какие другие классы у вас есть в других модулях перевода, которые могут наследовать или не наследоваться от этих классов. и переопределить их виртуальные функции непостижимым образом.

...