8 votes

Détails de bas niveau de l'héritage et du polymorphisme

Cette question est l'un des grands doutes qui plane dans ma tête et il est également difficile de le décrire en termes de mots. Parfois, elle semble évidente et parfois, elle est difficile à résoudre :

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. Bien que b_ptr pointe vers un objet dérivé, à quel VTABLE accède-t-il et COMMENT ? car b_ptr -> function4() donne une erreur de compilation. Ou est-ce que b_ptr ne peut accéder qu'à la taille de la classe de base VTABLE dans le VTABLE dérivé ?

Q2. Étant donné que la disposition de la mémoire de la classe dérivée doit être (Base,Derived) , le VTABLE de la classe Base est-il également inclus dans la disposition de la mémoire de la classe dérivée ?

Q3. Puisque la fonction1 et la fonction2 de la classe de base Vtable pointe vers l'implémentation de la classe de base et la fonction2 de la classe dérivée pointe vers la fonction2 de la classe de base, y a-t-il vraiment un besoin de VTABLE dans la classe de base ? (C'est peut-être la question la plus stupide que je puisse poser, mais je suis toujours dans le doute à ce sujet dans mon état actuel et la réponse doit être liée à la réponse de Q1 :) )

Veuillez commenter.

Merci de votre patience.

8voto

Martin v. Löwis Points 61768

À titre d'illustration supplémentaire, voici une version C de votre programme C++, montrant les vtables et tout le reste.

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

3voto

Crazy Eddie Points 23778

Q1 - la résolution des noms est statique. Puisque b_ptr est de type Base*, le compilateur ne peut voir aucun des noms uniques à Derived afin d'accéder à leurs entrées dans la v_table.

Q2 - Peut-être, peut-être pas. Il faut se rappeler que la vtable elle-même est simplement une méthode très courante d'implémentation du polymorphisme d'exécution et qu'elle ne fait partie d'aucune norme. Aucune déclaration définitive ne peut être faite sur l'endroit où elle se trouve. La vtable pourrait en fait être une table statique située quelque part dans le programme et pointée à partir de la description d'objet des instances.

Q3 - S'il y a une entrée virtuelle à un endroit, il doit y en avoir une partout, sinon un tas de vérifications difficiles/impossibles seraient nécessaires pour fournir une capacité d'annulation. Si le compilateur SAIT que vous avez une base et que vous appelez une fonction surchargée, il n'est pas obligé d'accéder à la table virtuelle, mais peut simplement utiliser la fonction directement ; il peut même la mettre en ligne s'il le souhaite.

3voto

Mark Ransom Points 132545

A1. Le pointeur vtable pointe vers une vtable dérivée, mais le compilateur ne le sait pas. Vous lui avez dit de le traiter comme un pointeur de base, de sorte qu'il ne peut appeler que les méthodes qui sont valables pour la classe de base, peu importe ce que le pointeur pointe.

A2. La disposition de la vtable n'est pas spécifiée par la norme, elle ne fait même pas officiellement partie de la classe. C'est juste la méthode d'implémentation la plus courante à 99,99%. La vtable ne fait pas partie du layout de l'objet, mais il y a un pointeur vers la vtable qui est un membre caché de l'objet. Il sera toujours dans le même emplacement relatif dans l'objet afin que le compilateur puisse toujours générer du code pour y accéder, quel que soit le pointeur de classe qu'il possède. Les choses se compliquent avec l'héritage multiple, mais n'allons pas encore jusque là.

A3. Les Vtables existent une fois par classe, pas une fois par objet. Le compilateur doit en générer un même s'il n'est jamais utilisé, car il ne le sait pas à l'avance.

1voto

Puppy Points 90818

B_ptr pointe vers la vtable dérivée - mais le compilateur ne peut pas garantir que la classe dérivée contient une fonction_4, car elle n'est pas contenue dans la vtable de base, donc le compilateur ne sait pas comment faire l'appel et jette une erreur.

Non, la vtable est une constante statique quelque part ailleurs dans le programme. La classe de base contient simplement un pointeur vers elle. Une classe dérivée peut contenir deux pointeurs vtable, mais pas nécessairement.

Dans le contexte de ces deux classes, alors Base a besoin d'une vtable pour trouver la fonction1 de Derived, qui en réalité est virtuelle même si vous ne l'avez pas marquée comme telle, parce qu'elle a surmonté une fonction virtuelle de la classe de base. Cependant, même si ce n'était pas le cas, je suis presque sûr que le compilateur doit produire les vtables de toute façon, car il n'a aucune idée des autres classes que vous avez dans d'autres unités de traduction qui peuvent ou non hériter de ces classes et remplacer leurs fonctions virtuelles de manière inconnue.

1voto

Michael Kohne Points 8233

Tout d'abord, et c'est le plus important, rappelez-vous que le C++ ne fait pas beaucoup d'introspection à l'exécution, quelle qu'elle soit. En fait, il doit tout savoir sur les objets au moment de la compilation.

Q1 - b_ptr est un pointeur vers une Base. Par conséquent, il ne peut accéder qu'aux éléments présents dans un objet Base. Aucune exception. Maintenant, l'implémentation réelle peut changer en fonction du type réel de l'objet, mais il n'y a aucun moyen de contourner le fait que la méthode doit être définie dans la Base si vous voulez l'appeler à travers un pointeur de la Base.

Q2 - La réponse simple est "oui, la table virtuelle d'une base doit être présente dans une dérivée", mais il y a BEAUCOUP de stratégies possibles pour la mise en page d'une table virtuelle, donc ne vous attachez pas à sa structure exacte.

Q3 - Oui, il doit y avoir une vtable dans la classe de base. Tout ce qui appelle des fonctions virtuelles dans une classe passera par la vtable de sorte que si l'objet sous-jacent est en fait un dérivé, tout peut fonctionner.

Ce n'est pas un absolu, car si le compilateur peut être ABSOLUMENT sûr de savoir ce qu'il a (comme dans le cas d'un objet Base déclaré sur la pile locale), alors le compilateur est autorisé à optimiser les recherches dans les tables v, et peut même être autorisé à mettre la fonction en ligne.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X