Vous entrez dans des hypothèses avec ces réponses, alors je vais essayer de faire une explication plus simple et plus terre-à-terre par souci de clarté.
Les relations de base de la conception orientée objet sont au nombre de deux: IS-A et HAS-A. Je n'ai pas inventé ceux-là. C'est comme ça qu'on les appelle.
IS-A indique qu'un objet particulier s'identifie comme faisant partie de la classe qui se trouve au-dessus de lui dans une hiérarchie de classes. Un objet banane est un objet fruit s'il s'agit d'une sous-classe de la classe fruit. Cela signifie que partout où une classe de fruits peut être utilisée, une banane peut être utilisée. Ce n'est cependant pas réflexif. Vous ne pouvez pas substituer une classe de base à une classe spécifique si cette classe spécifique est appelée.
Has-a a indiqué qu'un objet fait partie d'une classe composite et qu'il existe une relation de propriété. Cela signifie en C ++ qu'il s'agit d'un objet membre et qu'en tant que tel, il incombe à la classe propriétaire de s'en débarrasser ou de transférer la propriété avant de se détruire.
Ces deux concepts sont plus faciles à réaliser dans les langages à héritage unique que dans un modèle d'héritage multiple comme C ++, mais les règles sont essentiellement les mêmes. La complication survient lorsque l'identité de la classe est ambiguë, comme le passage d'un pointeur de classe Banana dans une fonction qui prend un pointeur de classe Fruit.
Les fonctions virtuelles sont, tout d'abord, une chose au moment de l'exécution. Il fait partie du polymorphisme en ce qu'il est utilisé pour décider quelle fonction exécuter au moment où elle est appelée dans le programme en cours d'exécution.
Le mot-clé virtual est une directive du compilateur pour lier les fonctions dans un certain ordre s'il y a ambiguïté sur l'identité de la classe. Les fonctions virtuelles sont toujours dans les classes parentes (pour autant que je sache) et indiquent au compilateur que la liaison des fonctions membres à leurs noms doit avoir lieu avec la fonction de sous-classe en premier et la fonction de classe parente après.
Une classe Fruit peut avoir une fonction virtuelle color () qui renvoie "NONE" par défaut. La fonction color () de la classe Banana renvoie "YELLOW" ou "BROWN".
Mais si la fonction prenant un pointeur Fruit appelle color () sur la classe Banana qui lui est envoyée - quelle fonction color () est appelée? La fonction appelle normalement Fruit :: color () pour un objet Fruit.
Ce ne serait pas 99% du temps ce qui était prévu. Mais si Fruit :: color () était déclaré virtual alors Banana: color () serait appelé pour l'objet car la fonction color () correcte serait liée au pointeur Fruit au moment de l'appel. Le moteur d'exécution vérifiera sur quel objet le pointeur pointe car il a été marqué comme virtuel dans la définition de la classe Fruit.
Ceci est différent du remplacement d'une fonction dans une sous-classe. Dans ce cas, le pointeur Fruit appellera Fruit :: color () si tout ce qu'il sait, c'est qu'il est un pointeur IS-A vers Fruit.
Alors maintenant, l'idée d'une "fonction virtuelle pure" surgit. C'est une phrase plutôt malheureuse car la pureté n'a rien à voir avec cela. Cela signifie qu'il est prévu que la méthode de classe de base ne soit jamais appelée. En effet, une fonction virtuelle pure ne peut pas être appelée. Cependant, il doit encore être défini. Une signature de fonction doit exister. De nombreux codeurs font une implémentation vide {} par souci d'exhaustivité, mais le compilateur en générera une en interne sinon. Dans ce cas, lorsque la fonction est appelée même si le pointeur est sur Fruit, Banana :: color () sera appelée car c'est la seule implémentation de color () qui existe.
Maintenant, la dernière pièce du puzzle: les constructeurs et les destructeurs.
Les constructeurs virtuels purs sont complètement illégaux. C'est juste sorti.
Mais les destructeurs virtuels purs fonctionnent dans le cas où vous souhaitez interdire la création d'une instance de classe de base. Seules les sous-classes peuvent être instanciées si le destructeur de la classe de base est purement virtuel. la convention est de l'attribuer à 0.
virtual ~Fruit() = 0; // pure virtual
Fruit::~Fruit(){} // destructor implementation
Vous devez créer une implémentation dans ce cas. Le compilateur sait que c'est ce que vous faites et s'assure que vous le faites correctement, ou il se plaint fortement de ne pas pouvoir se lier à toutes les fonctions dont il a besoin pour compiler. Les erreurs peuvent être déroutantes si vous n'êtes pas sur la bonne voie quant à la façon dont vous modélisez votre hiérarchie de classes.
Il vous est donc interdit dans ce cas de créer des instances de Fruit, mais autorisé à créer des instances de Banana.
Un appel à la suppression du pointeur Fruit qui pointe vers une instance de Banana appellera d'abord Banana :: ~ Banana () puis appellera Fuit :: ~ Fruit (), toujours. Parce que quoi qu'il arrive, lorsque vous appelez un destructeur de sous-classe, le destructeur de classe de base doit suivre.
Est-ce un mauvais modèle? C'est plus compliqué dans la phase de conception, oui, mais cela peut garantir que la liaison correcte est effectuée au moment de l'exécution et qu'une fonction de sous-classe est exécutée là où il y a une ambiguïté quant à la sous-classe exacte à laquelle on accède.
Si vous écrivez C ++ de façon à ne transmettre que des pointeurs de classe exacts sans pointeurs génériques ou ambigus, alors les fonctions virtuelles ne sont pas vraiment nécessaires. Mais si vous avez besoin d'une flexibilité d'exécution des types (comme dans Apple Banana Orange ==> Fruit), les fonctions deviennent plus faciles et plus polyvalentes avec un code moins redondant. Vous n'avez plus besoin d'écrire une fonction pour chaque type de fruit, et vous savez que chaque fruit répondra à color () avec sa propre fonction correcte.
J'espère que cette longue explication solidifie le concept plutôt que de confondre les choses. Il y a beaucoup de bons exemples là-bas à regarder, à regarder suffisamment et à les exécuter et à les manipuler et vous les obtiendrez.