Si des classes avec des fonctions virtuelles sont implémentées avec vtables, comment une classe sans fonction virtuelle est-elle implémentée?

StackOverflow https://stackoverflow.com/questions/101329

  •  01-07-2019
  •  | 
  •  

Question

En particulier, ne devrait-il pas y avoir un type de pointeur de fonction de toute façon?

Était-ce utile?

La solution

Les fonctions membres non virtuelles ne sont en réalité qu'un sucre syntaxique car elles ressemblent presque à une fonction ordinaire mais avec une vérification d'accès et un paramètre d'objet implicite.

struct A 
{
  void foo ();
  void bar () const;
};

est fondamentalement la même chose que:

struct A 
{
};

void foo (A * this);
void bar (A const * this);

La vtable est nécessaire pour que nous puissions appeler la fonction appropriée pour notre instance d'objet spécifique. Par exemple, si nous avons:

struct A 
{
  virtual void foo ();
};

La mise en oeuvre de 'foo' pourrait ressembler à quelque chose comme:

void foo (A * this) {
  void (*realFoo)(A *) = lookupVtable (this->vtable, "foo");
  (realFoo)(this);   // Make the call to the most derived version of 'foo'
}

Autres conseils

Je pense que les expressions "" avec les fonctions virtuelles sont implémentées avec vtables " vous induit en erreur.

Cette phrase donne l’impression que les classes avec des fonctions virtuelles sont implémentées " de la manière A ". et les classes sans fonctions virtuelles sont implémentées " de la manière B ".

En réalité, les classes avec des fonctions virtuelles, en plus de , sont également dotées d'une table vtable. Une autre façon de le voir est que "'vtables' implémente la partie 'fonction virtuelle' d'une classe".

Plus de détails sur leur fonctionnement:

Toutes les classes (avec des méthodes virtuelles ou non virtuelles) sont des structures. La différence seulement entre une structure et une classe en C ++ est que, par défaut, les membres sont publics dans les structures et privés dans les classes. Pour cette raison, je vais utiliser le terme classe ici pour désigner à la fois les structures et les classes. N'oubliez pas qu'ils sont presque synonymes!

Membres de données

Les classes (comme les structures) ne sont que des blocs de mémoire contiguës dans lesquels chaque membre est stocké en séquence. Notez que, parfois, il y aura des espaces entre les membres pour des raisons d’architecture de la CPU, ainsi le bloc peut être plus grand que la somme de ses parties.

Méthodes

Méthodes ou "fonctions membres" sont une illusion. En réalité, il n’existe pas de "fonction membre". Une fonction est toujours juste une séquence d'instructions de code machine stockées quelque part en mémoire. Pour passer un appel, le processeur saute à cette position de la mémoire et commence à s'exécuter. On pourrait dire que toutes les méthodes et fonctions sont «globales», et toute indication contraire est une illusion commode imposée par le compilateur.

Évidemment, une méthode agit comme si elle appartenait à un objet spécifique. Il est donc clair qu’il se passe plus de choses. Pour lier un appel particulier d'une méthode (une fonction) à un objet spécifique, chaque méthode membre possède un argument masqué qui est un pointeur sur l'objet en question. Le membre est caché en ce sens que vous ne l'ajoutez pas vous-même à votre code C ++, mais il n'a rien de magique, c'est très réel. Quand vous dites ceci:

void CMyThingy::DoSomething(int arg);
{
    // do something
}

Le compilateur vraiment fait ceci:

void CMyThingy_DoSomething(CMyThingy* this, int arg)
{
    /do something
}

Enfin, lorsque vous écrivez ceci:

myObj.doSomething(aValue);

le compilateur dit:

CMyThingy_DoSomething(&myObj, aValue);

Pas besoin de pointeurs de fonction n'importe où! Le compilateur sait déjà quelle méthode vous appelez et l'appelle directement.

Les méthodes statiques sont encore plus simples. Ils n'ont pas de pointeur this , ils sont donc implémentés exactement comme vous les écrivez.

C'est ça! Le reste n’est que pratique: le compilateur sait à quelle classe appartient une méthode; il s’assure donc qu’il ne vous laisse pas appeler la fonction sans spécifier laquelle. Il utilise également cette connaissance pour traduire myItem en this- > myItem lorsqu'il est clair de le faire.

(oui, c'est vrai: l'accès des membres à une méthode est toujours indirectement via un pointeur, même si vous n'en voyez pas un)

( Modifier : suppression de la dernière phrase et publication séparée afin qu'elle puisse être critiquée séparément)

Les méthodes virtuelles sont obligatoires lorsque vous souhaitez utiliser le polymorphisme. Le modificateur virtual place la méthode dans le VMT pour une liaison tardive, puis au moment de l'exécution, la méthode à partir de laquelle la classe est exécutée.

Si la méthode n'est pas virtuelle, il est décidé lors de la compilation de quelle instance de classe elle sera exécutée.

Les pointeurs de fonction sont principalement utilisés pour les rappels.

Si une classe avec une fonction virtuelle est implémentée avec une vtable, une classe sans fonction virtuelle est implémentée sans une vtable.

Une vtable contient les pointeurs de fonction nécessaires pour envoyer un appel à la méthode appropriée. Si la méthode n'est pas virtuelle, l'appel passe au type connu de la classe et aucun indirection n'est nécessaire.

Pour une méthode non virtuelle, le compilateur peut générer une invocation de fonction normale (par exemple, CALL à une adresse particulière avec ce pointeur passé en tant que paramètre) ou même la mettre en ligne. Pour une fonction virtuelle, le compilateur ne sait généralement pas, au moment de la compilation, à quelle adresse appeler le code. Il génère donc un code qui recherche l'adresse dans la table vtable au moment de l'exécution, puis appelle la méthode. Certes, même pour les fonctions virtuelles, le compilateur peut parfois résoudre correctement le bon code au moment de la compilation (par exemple, les méthodes sur des variables locales invoquées sans pointeur / référence).

(J'ai tiré cette section de ma réponse initiale afin qu'elle puisse être critiquée séparément. Elle est beaucoup plus concise et va au-delà de votre question, donc, dans un sens, c'est une bien meilleure réponse)

Non, il n'y a pas de pointeur de fonction; au lieu de cela, le compilateur retourne le problème à l'envers .

Le compilateur appelle une fonction globale avec un pointeur sur l'objet au lieu d'appeler une fonction pointée dans l'objet

Pourquoi? Parce que c'est généralement beaucoup plus efficace de cette façon. Les appels indirects sont des instructions coûteuses.

Il n'y a pas besoin de pointeurs de fonction car ils ne peuvent pas changer pendant l'exécution.

Les branches sont générées directement dans le code compilé pour les méthodes; comme si vous aviez des fonctions qui ne font pas partie de la classe, des branches leur sont directement générées.

Le compilateur / éditeur de liens relie directement les méthodes qui seront appelées. Pas besoin de vtable indirection. BTW, qu'est-ce que cela a à voir avec "pile contre tas"?

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top