Virtuel / virtuel pur expliqué

346

Qu'est-ce que cela signifie exactement si une fonction est définie comme virtuelle et est-ce la même chose que virtuelle pure?

Justin
la source

Réponses:

339

De la fonction virtuelle de Wikipedia ...

Dans la programmation orientée objet, dans des langages tels que C ++ et Object Pascal, une fonction virtuelle ou une méthode virtuelle est une fonction ou une méthode héritable et remplaçable pour laquelle la répartition dynamique est facilitée. Ce concept est une partie importante de la portion (d'exécution) du polymorphisme de la programmation orientée objet (POO). En bref, une fonction virtuelle définit une fonction cible à exécuter, mais la cible peut ne pas être connue au moment de la compilation.

Contrairement à une fonction non virtuelle, lorsqu'une fonction virtuelle est remplacée, la version la plus dérivée est utilisée à tous les niveaux de la hiérarchie des classes, plutôt qu'au niveau auquel elle a été créée. Par conséquent, si une méthode de la classe de base appelle une méthode virtuelle, la version définie dans la classe dérivée sera utilisée à la place de la version définie dans la classe de base.

Cela contraste avec les fonctions non virtuelles, qui peuvent toujours être remplacées dans une classe dérivée, mais la "nouvelle" version ne sera utilisée que par la classe dérivée et ci-dessous, mais ne changera pas du tout la fonctionnalité de la classe de base.

tandis que..

Une fonction virtuelle pure ou une méthode virtuelle pure est une fonction virtuelle qui doit être implémentée par une classe dérivée si la classe dérivée n'est pas abstraite.

Lorsqu'une méthode virtuelle pure existe, la classe est "abstraite" et ne peut pas être instanciée seule. Au lieu de cela, une classe dérivée qui implémente la ou les méthodes pure-virtuelles doit être utilisée. Un virtuel pur n'est pas du tout défini dans la classe de base, donc une classe dérivée doit le définir, ou cette classe dérivée est également abstraite et ne peut pas être instanciée. Seule une classe qui n'a pas de méthodes abstraites peut être instanciée.

Un virtuel fournit un moyen de remplacer la fonctionnalité de la classe de base, et un pur-virtuel l' exige .

Diego Dias
la source
10
Alors ... le virtuel pur est-il un mot-clé ou simplement un terme utilisé?
Justin
197
fonction de vide virtuel () = 0; est un pur virtuel. Le "= 0" indique la pureté.
Goz
8
Justin, «pur virtuel» est juste un terme (pas un mot-clé, voir ma réponse ci-dessous) utilisé pour signifier «cette fonction ne peut pas être implémentée par la classe de base. Comme l'a dit Goz, en ajoutant le« = 0 »à la fin d'un virtuel la fonction le rend "pur"
Nick Haddad
14
Je crois que Stroustrup a dit qu'il voulait ajouter un puremot - clé, mais que Bell Labs était sur le point de faire une version majeure de C ++, et son manager ne l'autoriserait pas à ce stade tardif. L'ajout de mots clés est un gros problème.
quark
14
Ce n'est pas une bonne réponse. Toute méthode peut être remplacée, pas seulement les méthodes virtuelles. Voir ma réponse pour plus de détails.
Asik
212

Je voudrais commenter la définition de Wikipédia du virtuel, comme plusieurs l'ont répété ici. [Au moment où cette réponse a été écrite,] Wikipedia a défini une méthode virtuelle comme une méthode qui peut être remplacée dans les sous-classes. [Heureusement, Wikipédia a été modifié depuis, et il explique maintenant cela correctement.] C'est incorrect: toute méthode, pas seulement virtuelle, peut être remplacée dans les sous-classes. Ce que le virtuel fait est de vous donner un polymorphisme, c'est-à-dire la possibilité de sélectionner au moment de l'exécution le remplacement le plus dérivé d'une méthode .

Considérez le code suivant:

#include <iostream>
using namespace std;

class Base {
public:
    void NonVirtual() {
        cout << "Base NonVirtual called.\n";
    }
    virtual void Virtual() {
        cout << "Base Virtual called.\n";
    }
};
class Derived : public Base {
public:
    void NonVirtual() {
        cout << "Derived NonVirtual called.\n";
    }
    void Virtual() {
        cout << "Derived Virtual called.\n";
    }
};

int main() {
    Base* bBase = new Base();
    Base* bDerived = new Derived();

    bBase->NonVirtual();
    bBase->Virtual();
    bDerived->NonVirtual();
    bDerived->Virtual();
}

Quelle est la sortie de ce programme?

Base NonVirtual called.
Base Virtual called.
Base NonVirtual called.
Derived Virtual called.

Derived remplace toutes les méthodes de Base: non seulement la virtuelle, mais aussi la non-virtuelle.

Nous voyons que lorsque vous avez un pointeur de base vers dérivé (bDerived), appeler NonVirtual appelle l'implémentation de la classe de base. Ceci est résolu au moment de la compilation: le compilateur voit que bDerived est une Base *, que NonVirtual n'est pas virtuel, donc il fait la résolution sur la classe Base.

Cependant, l'appel de Virtual appelle l'implémentation de la classe Derived. En raison du mot-clé virtual, la sélection de la méthode se produit au moment de l' exécution , pas au moment de la compilation. Ce qui se passe ici au moment de la compilation, c'est que le compilateur voit qu'il s'agit d'une Base *, et qu'il appelle une méthode virtuelle, donc il insère un appel à la table virtuelle au lieu de la classe Base. Cette table virtuelle est instanciée au moment de l'exécution, d'où la résolution au moment de la substitution la plus dérivée.

J'espère que ce n'était pas trop déroutant. En bref, n'importe quelle méthode peut être remplacée, mais seules les méthodes virtuelles vous donnent le polymorphisme, c'est-à-dire la sélection au moment de l'exécution du remplacement le plus dérivé. Dans la pratique, cependant, remplacer une méthode non virtuelle est considéré comme une mauvaise pratique et rarement utilisé, de nombreuses personnes (y compris celui qui a écrit cet article Wikipedia) pensent que seules les méthodes virtuelles peuvent être remplacées.

Asik
la source
6
Ce n'est pas parce que l'article Wikipédia (que je ne défends aucunement) définit une méthode virtuelle "comme une méthode qui peut être remplacée dans les sous-classes" n'exclut pas la possibilité de déclarer d'autres méthodes non virtuelles du même nom. Ceci est connu sous le nom de surcharge.
26
La définition est néanmoins incorrecte. Une méthode qui peut être remplacée dans une classe dérivée n'est pas virtuelle par définition; si la méthode peut être remplacée n'a pas d'importance pour la définition de "virtuel". De plus, la «surcharge» fait généralement référence à la présence de plusieurs méthodes avec le même nom et le même type de retour mais des arguments différents, dans la même classe; il est très différent de "surcharger" qui implique exactement la même signature mais dans une classe dérivée. Lorsqu'elle est effectuée de manière non polymorphe (base non virtuelle), elle est souvent appelée "masquage".
Asik
5
Ce devrait être la réponse acceptée. Cet article de Wikipedia particulier que je prendrai le temps de relier ici puisque personne d'autre sur cette question ne l'a fait , est une ordure complète. +1, bon monsieur.
josaphatv
2
MAINTENANT, cela a du sens. Merci, bon monsieur, d'avoir bien expliqué que toute méthode peut être remplacée par des classes dérivées et le changement concerne la façon dont le compilateur se comportera pour choisir la fonction appelée dans différentes situations.
Doodad
3
Il peut être utile d'ajouter un Derived*avec les mêmes appels de fonction pour ramener le point d'origine. Sinon, bonne réponse
Jeff Jones
114

Le mot-clé virtuel donne à C ++ sa capacité à supporter le polymorphisme. Lorsque vous avez un pointeur sur un objet d'une classe telle que:

class Animal
{
  public:
    virtual int GetNumberOfLegs() = 0;
};

class Duck : public Animal
{
  public:
     int GetNumberOfLegs() { return 2; }
};

class Horse : public Animal
{
  public:
     int GetNumberOfLegs() { return 4; }
};

void SomeFunction(Animal * pAnimal)
{
  cout << pAnimal->GetNumberOfLegs();
}

Dans cet exemple (idiot), la fonction GetNumberOfLegs () renvoie le nombre approprié en fonction de la classe de l'objet pour lequel il est appelé.

Maintenant, considérez la fonction 'SomeFunction'. Peu importe le type d'objet animal qui lui est transmis, tant qu'il est dérivé d'Animal. Le compilateur convertira automatiquement toute classe dérivée d'un animal en animal car il s'agit d'une classe de base.

Si nous faisons cela:

Duck d;
SomeFunction(&d);

il produirait «2». Si nous faisons cela:

Horse h;
SomeFunction(&h);

il produirait «4». On ne peut pas faire ça:

Animal a;
SomeFunction(&a);

car il ne se compile pas car la fonction virtuelle GetNumberOfLegs () est pure, ce qui signifie qu'elle doit être implémentée en dérivant des classes (sous-classes).

Les fonctions virtuelles pures sont principalement utilisées pour définir:

a) classes abstraites

Ce sont des classes de base où vous devez en dériver puis implémenter les fonctions virtuelles pures.

b) interfaces

Ce sont des classes «vides» où toutes les fonctions sont entièrement virtuelles et donc vous devez dériver puis implémenter toutes les fonctions.

JBRWilkinson
la source
Dans votre exemple, vous ne pouvez pas faire # 4 car vous n'avez pas fourni d'implémentation de la méthode virtuelle pure. Ce n'est pas strictement parce que la méthode est purement virtuelle.
iheanyi
@iheanyi Vous ne pouvez pas fournir d'implémentation à une méthode virtuelle pure dans la classe de base. Par conséquent, le cas # 4 est toujours une erreur.
prasad
32

Dans une classe C ++, virtual est le mot-clé qui désigne cela, une méthode peut être remplacée (c'est-à-dire implémentée par) une sous-classe. Par exemple:

class Shape 
{
  public:
    Shape();
    virtual ~Shape();

    std::string getName() // not overridable
    {
      return m_name;
    }

    void setName( const std::string& name ) // not overridable
    {
      m_name = name;
    }

  protected:
    virtual void initShape() // overridable
    {
      setName("Generic Shape");
    }

  private:
    std::string m_name;
};

Dans ce cas, une sous-classe peut remplacer la fonction initShape pour effectuer un travail spécialisé:

class Square : public Shape
{
  public: 
    Square();
    virtual ~Square();

  protected:
    virtual void initShape() // override the Shape::initShape function
    {
      setName("Square");
    }
}

Le terme virtuel pur fait référence aux fonctions virtuelles qui doivent être implémentées par une sous-classe et qui n'ont pas été implémentées par la classe de base. Vous désignez une méthode comme virtuelle pure en utilisant le mot clé virtual et en ajoutant a = 0 à la fin de la déclaration de méthode.

Donc, si vous vouliez rendre Shape :: initShape pur virtuel, vous feriez ce qui suit:

class Shape 
{
 ...
    virtual void initShape() = 0; // pure virtual method
 ... 
};

En ajoutant une méthode virtuelle pure à votre classe, vous faites de la classe une classe de base abstraite qui est très pratique pour séparer les interfaces de l'implémentation.

Nick Haddad
la source
1
Concernant les "fonctions virtuelles qui doivent être implémentées par une sous-classe" - ce n'est pas strictement vrai, mais la sous-classe est également abstraite si elles ne le sont pas. Et les classes abstraites ne peuvent pas être instanciées. En outre, «ne peut pas être implémenté par la classe de base» semble trompeur; Je dirais que "n'ont pas été" serait mieux car il n'y a aucune restriction aux modifications du code pour ajouter une implémentation dans la classe de base.
NVRAM
2
Et "la fonction getName ne peut pas être implémentée par une sous-classe" n'est pas tout à fait correct. Les sous-classes peuvent implémenter la méthode (avec la même signature ou une signature différente) mais cette implémentation ne REMPLACERA PAS la méthode. Vous pouvez implémenter Circle en tant que sous-classe et implémenter "std :: string Circle :: getName ()" - alors vous pouvez appeler l'une ou l'autre méthode pour une instance de Circle. Mais s'il est utilisé via un pointeur ou une référence Shape, le compilateur appellera Shape :: getName ().
NVRAM
1
Bons points sur les deux fronts. J'essayais de ne pas discuter de cas spéciaux pour cet exemple, je vais modifier la réponse pour être plus indulgent. Merci!
Nick Haddad
@NickHaddad Ancien thread, mais vous vous demandez pourquoi vous avez appelé votre variable m_name. Qu'est-ce que cela m_signifie?
Tqn
1
@Tqn en supposant que NickHaddad a suivi les conventions, m_name est une convention de dénomination communément appelée notation hongroise. Le m indique un membre d'une structure / classe, un entier.
Ketcomp
16

"Virtuel" signifie que la méthode peut être remplacée dans les sous-classes, mais a une implémentation directement appelable dans la classe de base. "Virtuel pur" signifie qu'il s'agit d'une méthode virtuelle sans implémentation directement appelable. Une telle méthode doit être remplacée au moins une fois dans la hiérarchie d'héritage - si une classe a des méthodes virtuelles non implémentées, les objets de cette classe ne peuvent pas être construits et la compilation échouera.

@quark souligne que les méthodes pure-virtual peuvent avoir une implémentation, mais comme les méthodes pure-virtual doivent être remplacées, l'implémentation par défaut ne peut pas être appelée directement. Voici un exemple d'une méthode pure-virtuelle avec une valeur par défaut:

#include <cstdio>

class A {
public:
    virtual void Hello() = 0;
};

void A::Hello() {
    printf("A::Hello\n");
}

class B : public A {
public:
    void Hello() {
        printf("B::Hello\n");
        A::Hello();
    }
};

int main() {
    /* Prints:
           B::Hello
           A::Hello
    */
    B b;
    b.Hello();
    return 0;
}

Selon les commentaires, l'échec de la compilation est spécifique au compilateur. Dans GCC 4.3.3 au moins, il ne compilera pas:

class A {
public:
    virtual void Hello() = 0;
};

int main()
{
    A a;
    return 0;
}

Production:

$ g++ -c virt.cpp 
virt.cpp: In function int main()’:
virt.cpp:8: error: cannot declare variable a to be of abstract type A
virt.cpp:1: note:   because the following virtual functions are pure within A’:
virt.cpp:3: note:   virtual void A::Hello()
John Millikin
la source
elle doit être remplacée si vous souhaitez instancier une instance de la classe. Si vous ne créez aucune instance, le code se compilera très bien.
Glen
1
la compilation n'échouera pas. S'il n'y a pas d'implémentation d'une méthode virtuelle (pure), cette classe / cet objet ne peut pas être instancié. Il ne peut pas LINK, mais il se compilera.
Tim
@Glen, @tim: sur quel compilateur? Quand j'essaye de compiler un programme qui construit une classe abstraite, il ne compile pas.
John Millikin
@John Compilation échouera uniquement si vous essayez d'instancier une instance d'une classe qui contient un PVF. Vous pouvez bien sûr instancier des valeurs de pointeur ou de référence pour ces classes.
5
En outre, John, ce qui suit n'est pas tout à fait exact: "'Pure virtual' signifie que c'est une méthode virtuelle sans implémentation." Les méthodes virtuelles pures peuvent avoir des implémentations. Mais vous ne pouvez pas les appeler directement: vous devez remplacer et utiliser l'implémentation de la classe de base à partir de la sous-classe. Cela vous permet de fournir une partie par défaut de l'implémentation. Ce n'est pas une technique courante cependant.
quark
9

Comment fonctionne le mot-clé virtuel?

Supposons que l'homme est une classe de base, l'indien est dérivé de l'homme.

Class Man
{
 public: 
   virtual void do_work()
   {}
}

Class Indian : public Man
{
 public: 
   void do_work()
   {}
}

Déclarer do_work () comme virtuel signifie simplement: quel do_work () appeler sera déterminé UNIQUEMENT au moment de l'exécution.

Supposons que je le fasse,

Man *man;
man = new Indian();
man->do_work(); // Indian's do work is only called.

Si virtual n'est pas utilisé, le même est statiquement déterminé ou lié statiquement par le compilateur, selon l'objet qui appelle. Donc, si un objet de Man appelle do_work (), le do_work () de Man est appelé MÊME QUAND IL POINT SUR UN OBJET INDIEN

Je crois que la réponse la plus votée est trompeuse - Toute méthode, virtuelle ou non, peut avoir une implémentation remplacée dans la classe dérivée. En ce qui concerne spécifiquement C ++, la différence correcte est la liaison au moment de l'exécution (lorsque le virtuel est utilisé) et la compilation (lorsque le virtuel n'est pas utilisé mais qu'une méthode est remplacée et qu'un pointeur de base pointe vers un objet dérivé) la liaison des fonctions associées.

Il semble y avoir un autre commentaire trompeur qui dit:

"Justin, 'pur virtuel' est juste un terme (pas un mot-clé, voir ma réponse ci-dessous) utilisé pour signifier" cette fonction ne peut pas être implémentée par la classe de base. "

C'EST FAUX! Les fonctions purement virtuelles peuvent également avoir un corps ET PEUVENT ÊTRE MISES EN ŒUVRE! La vérité est que la fonction virtuelle pure d'une classe abstraite peut être appelée statiquement! Deux très bons auteurs sont Bjarne Stroustrup et Stan Lippman .... parce qu'ils ont écrit la langue.

McMurdo
la source
2
Malheureusement, une fois qu'une réponse commence à être votée, toutes les autres seront ignorées. Même s'ils pouvaient être meilleurs.
LtWorf
3

Une fonction virtuelle est une fonction membre déclarée dans une classe de base et redéfinie par une classe dérivée. Les fonctions virtuelles sont hiérarchiques par ordre d'héritage. Lorsqu'une classe dérivée ne remplace pas une fonction virtuelle, la fonction définie dans sa classe de base est utilisée.

Une fonction virtuelle pure est une fonction qui ne contient aucune définition par rapport à la classe de base. Il n'a aucune implémentation dans la classe de base. Toute classe dérivée doit remplacer cette fonction.

rashedcs
la source
2

Simula, C ++ et C #, qui utilisent la liaison de méthode statique par défaut, le programmeur peut spécifier que des méthodes particulières doivent utiliser la liaison dynamique en les étiquetant comme virtuelles. La liaison de méthode dynamique est au cœur de la programmation orientée objet.

La programmation orientée objet nécessite trois concepts fondamentaux: l'encapsulation, l'héritage et la liaison de méthode dynamique.

L'encapsulation permet de masquer les détails d'implémentation d'une abstraction derrière une interface simple.

L'héritage permet de définir une nouvelle abstraction comme une extension ou un raffinement d'une abstraction existante, obtenant automatiquement certaines ou toutes ses caractéristiques.

La liaison de méthode dynamique permet à la nouvelle abstraction d'afficher son nouveau comportement même lorsqu'elle est utilisée dans un contexte qui attend l'ancienne abstraction.

PJT
la source
1

Les méthodes virtuelles PEUVENT être écrasées en dérivant des classes, mais ont besoin d'une implémentation dans la classe de base (celle qui sera écrasée)

Les méthodes virtuelles pures n'ont pas d'implémentation de la classe de base. Ils doivent être définis par des classes dérivées. (Donc, techniquement, ce n'est pas le bon terme, car il n'y a rien à remplacer).

Virtual correspond au comportement java par défaut, lorsque la classe dérivée remplace une méthode de la classe de base.

Les méthodes Pure Virtual correspondent au comportement des méthodes abstraites au sein des classes abstraites. Et une classe qui ne contient que des méthodes et des constantes virtuelles pures serait le cpp-pendant d'une interface.

johannes_lalala
la source
0

Fonction virtuelle pure

essayez ce code

#include <iostream>
using namespace std;
class aClassWithPureVirtualFunction
{

public:

    virtual void sayHellow()=0;

};

class anotherClass:aClassWithPureVirtualFunction
{

public:

    void sayHellow()
    {

        cout<<"hellow World";
    }

};
int main()
{
    //aClassWithPureVirtualFunction virtualObject;
    /*
     This not possible to create object of a class that contain pure virtual function
    */
    anotherClass object;
    object.sayHellow();
}

Dans la classe anotherClass, supprimez la fonction sayHellow et exécutez le code. vous obtiendrez une erreur! Parce que lorsqu'une classe contient une fonction virtuelle pure, aucun objet ne peut être créé à partir de cette classe et il est hérité alors sa classe dérivée doit implémenter cette fonction.

Fonction virtuelle

essayez un autre code

#include <iostream>
using namespace std;
class aClassWithPureVirtualFunction
{

public:

    virtual void sayHellow()
    {
        cout<<"from base\n";
    }

};

class anotherClass:public aClassWithPureVirtualFunction
{

public:

    void sayHellow()
    {

        cout<<"from derived \n";
    }

};
int main()
{
    aClassWithPureVirtualFunction *baseObject=new aClassWithPureVirtualFunction;
    baseObject->sayHellow();///call base one

    baseObject=new anotherClass;
    baseObject->sayHellow();////call the derived one!

}

Ici, la fonction sayHellow est marquée comme virtuelle dans la classe de base.Elle dit le compilateur qui essaie de rechercher la fonction dans la classe dérivée et implémente la fonction.Si elle n'est pas trouvée, exécutez la base.Merci

Tunvir Rahman Tusher
la source
Haha, il m'a fallu 30 secondes pour comprendre ce qui ne va pas ici ... BonjourW :)
hans
0

"Une fonction virtuelle ou une méthode virtuelle est une fonction ou une méthode dont le comportement peut être remplacé dans une classe héritée par une fonction avec la même signature" - wikipedia

Ce n'est pas une bonne explication pour les fonctions virtuelles. Parce que, même si un membre n'est pas virtuel, l'héritage des classes peut le remplacer. Vous pouvez essayer de le voir vous-même.

La différence se montre lorsqu'une fonction prend une classe de base comme paramètre. Lorsque vous donnez une classe héritée en entrée, cette fonction utilise l'implémentation de classe de base de la fonction surchargée. Cependant, si cette fonction est virtuelle, elle utilise celle qui est implémentée dans la classe dérivée.

pouvez
la source
0
  • Les fonctions virtuelles doivent avoir une définition dans la classe de base et également dans la classe dérivée, mais pas nécessaire, par exemple la fonction ToString () ou toString () est une fonction virtuelle afin que vous puissiez fournir votre propre implémentation en la remplaçant dans la ou les classes définies par l'utilisateur.

  • Les fonctions virtuelles sont déclarées et définies en classe normale.

  • La fonction virtuelle pure doit être déclarée se terminant par "= 0" et elle ne peut être déclarée qu'en classe abstraite.

  • Une classe abstraite ayant une ou des fonctions virtuelles pures ne peut pas avoir de définition (s) de ces fonctions virtuelles pures, ce qui implique que l'implémentation doit être fournie dans des classes dérivées de cette classe abstraite.

Sohail xIN3N
la source
Même remarque que pour @rashedcs: En effet, une fonction virtuelle pure peut avoir sa définition ...
Jarek C