8
votes

Détails de faible niveau d'héritage et de polymorphisme

Cette question est l'un des gros doutes qui se tient autour de ma tête et sont également difficiles à décrire en termes de mots. Parfois, il semble évident et parfois difficile à craquer. La question va comme ça ::

xxx

Q1. Bien que B_PTR soit dirigé vers un objet dérivé, dans lequel il est livré qu'il accède et comment? Comme B_PTR -> Fonction4 () donne une erreur de compilation. Ou est-ce que B_PTR ne peut accéder que jusqu'à cette taille de la classe de base dans une circonscription dérivée?

Q2. Étant donné que la disposition de la mémoire du dérivé doit être (base, dérivée), est la table de la classe de base également incluse dans la disposition de la mémoire de la classe dérivée?

Q3. Étant donné que la fonction1 et la fonction2 de la classe Table de classe de base indiquent la mise en œuvre de la classe de base et la fonction2 de points de classe dérivée sur la fonction2 de la classe de base, est-il vraiment nécessaire de livrer dans la classe de base ?? (C'est peut-être la question la plus stupide que je puisse jamais demander, mais je doute toujours à ce sujet dans mon état actuel et la réponse doit être liée à la réponse de Q1 :))

Commentez s'il vous plaît.

Merci pour la patience.


1 commentaires

Oui oui oui. Seulement un non ferait une réponse intéressante.


6 Réponses :


3
votes

Q1 - La résolution du nom est statique. Étant donné que B_PTR est de type base *, le compilateur ne peut voir aucun des noms propices à dérivés afin d'accéder à leurs entrées dans la V_TABLE.

Q2 - Peut-être, peut-être pas. Vous devez vous rappeler que la livable elle-même est simplement une méthode très courante de mise en œuvre du polymorphisme d'exécution et ne fait en réalité pas partie de la norme nulle part. Aucune déclaration définitive ne peut être faite sur l'endroit où elle réside. La table de distribution pourrait en réalité être une table statique quelque part dans le programme qui est désigné de la description de l'objet des instances.

Q3 - S'il y a une entrée virtuelle à un endroit, il doit y avoir dans tous les endroits, sinon un tas de contrôles difficiles / impossibles seraient nécessaires pour fournir une capacité de remplacement. Si le compilateur sait que vous avez une base et appelez une fonction remplacée cependant, il n'est pas nécessaire d'accéder à la tablette mais pourrait simplement utiliser la fonction directement; Il peut même l'aligner si elle le souhaite.


0 commentaires

1
votes

B_PTR pointe sur la circonte dérivée- mais le compilateur ne peut garantir que la classe dérivée a une fonction_4, car ce n'est pas contenue dans la table de la base, le compilateur ne sait pas comment faire l'appel et la lancée une erreur.

Non, la tablette est une constante statique ailleurs dans le programme. La classe de base contient simplement un pointeur. Une classe dérivée peut contenir deux pointeurs de fourchette, mais cela pourrait ne pas.

Dans le contexte de ces deux classes, la base a besoin d'une machine à opérer pour trouver la fonction de dérivée1, qui est virtuel, même si vous ne l'avez pas marqué comme ça, car il a choisi une classe de base virtuelle une fonction. Cependant, même si ce n'était pas le cas, je suis sûr que le compilateur est tenu de produire de toute façon les vtables, car il ne contient 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 inconnaissable.


0 commentaires

3
votes

A1. Le pointeur à fourre-mains est dirigé vers un fourmis dérivé, mais le compilateur ne le sait pas. Vous l'avez dit de le traiter comme un pointeur de base. Il ne peut donc appeler que des méthodes valides pour la classe de base, quel que soit le pointeur pointe vers.

A2. La mise en page disponible 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 de mise en œuvre la plus courante de 99,99%. La machine à outils ne fait pas partie de la mise en page d'objet, mais il y a un pointeur sur la circonscription qui est un membre caché de l'objet. Il sera toujours dans le même emplacement relatif de 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 a. Les choses sont plus compliquées avec plusieurs héritages, mais n'allons pas encore là.

a3. Il existe une fois par classe, pas une fois par objet. Le compilateur doit générer un même s'il n'est jamais utilisé, car il ne sait pas qu'avez le temps.


0 commentaires

1
votes

Premier, et le plus important, rappelez-vous que C ++ ne fait pas beaucoup d'introspection d'exécution de quelque nature que ce soit. Fondamentalement, il doit tout savoir sur les objets au moment de la compilation.

Q1 - B_PTR est un pointeur à une base. Par conséquent, il ne peut accéder à des choses que présentes dans un objet de base. Aucune exception. Maintenant, la mise en œuvre effective peut changer en fonction du type réel de l'objet, mais il n'y a pas de contourner la méthode qui doit être définie dans la base si vous souhaitez l'appeler via un pointeur de base.

Q2 - La réponse simple est «Oui, la tablette pour une base doit être présente dans un dérivé», mais il y a beaucoup de stratégies possibles pour la mise en place d'une machine à remettre, alors ne vous accrochez pas Structure exacte.

Q3 - Oui, il doit y avoir un circuit bancaire dans la classe de base. Tout ce qui appelle des fonctions virtuelles dans une classe passera à travers la tablette afin que si l'objet sous-jacent soit réellement dérivé, tout peut fonctionner.

Maintenant que ce n'est pas un absolu, car si le compilateur peut être absolument sûr qu'il sait ce qu'il est obtenu (comme pourrait être le cas d'un objet de base qui est déclaré sur la pile locale), le compilateur est autorisé à optimiser la recherches à fourtiles et peut même être autorisée à aligner la fonction.


0 commentaires

1
votes

Tout cela dépend de la mise en œuvre. Mais voici les réponses pour le moyen le plus simple habituel à l'aide de "VTables".

La classe BASE a un pointeur à fourre-vtable, la représentation sous-jacente est donc quelque chose comme ce pseudo-code: < Pré> xxx

La classe dérivée comprend un base classe de classe. Puisque cela a une place pour un circuit équitable, dérivé n'a pas besoin d'ajouter un autre. xxx

la "tablette pour la classe de base", __ base_vtable dans mon pseudocode est nécessaire au cas où quelqu'un essaie nouvelle base (); ou base obj;

tout de ce qui précède devient plus compliqué lorsque plusieurs héritages ou héritage virtuel sont impliqués ...

pour la ligne B_PTR -> Fonction4 (); , il s'agit d'une erreur de compilation , pas très lié aux vtables. Lorsque vous lancez dans un pointeur BASE * , vous ne pouvez utiliser que ce pointeur de la manière définie par base de classe (car le compilateur ne "sait pas", que ce soit Vraiment un dérivé , une base ou une autre classe). Si dérivé a un membre de données propre, vous ne pouvez pas y accéder via ce pointeur. Si dérivé a une fonction de membre de son propre, virtuel ou non, vous ne pouvez pas y accéder via ce pointeur.


0 commentaires

8
votes

comme une autre illustration, voici une version C de votre programme C ++, montrant des vtables et tout.

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


2 commentaires

Bien fait. Sauf que je préférerais que je préférerais base * b_ptr = & der_ptr-> base; et la fonction fonction1 appel virtuel doit être b_ptr-> vtable_ptr-> base.function1 (b_ptr) ;


@Aschepler: Je suis d'accord sur la distribution (changée). Le VCALL doit rester comme indiqué: B_PTR est un pointeur de base, de sorte que sa table équitable n'a pas de champ de base. Essayez de le compiler.