Pourquoi avons-nous besoin de fonctions virtuelles en C++?

j'apprends le C++ et j'entre dans des fonctions virtuelles.

D'après ce que j'ai lu (dans le livre et en ligne), les fonctions virtuelles sont des fonctions dans la classe de base que vous pouvez outrepasser dans les classes dérivées.

mais plus tôt dans le livre, en apprenant sur l'héritage de base, j'ai été capable de passer outre les fonctions de base dans les classes dérivées sans utiliser virtual .

alors qu'est-ce que je rate ici? Je sais qu'il est plus à des fonctions virtuelles, et il semble être important donc, je veux être clair sur ce qu'il est exactement. Je juste ne pouvez pas trouver une réponse claire en ligne.

996
demandé sur Peter Mortensen 2010-03-06 10:10:35

22 réponses

Voici comment j'ai compris non seulement ce que virtual fonctions sont, Mais pourquoi ils sont nécessaires:

disons que vous avez ces deux classes:

class Animal
{
    public:
        void eat() { std::cout << "I'm eating generic food."; }
};

class Cat : public Animal
{
    public:
        void eat() { std::cout << "I'm eating a rat."; }
};

dans votre fonction principale:

Animal *animal = new Animal;
Cat *cat = new Cat;

animal->eat(); // Outputs: "I'm eating generic food."
cat->eat();    // Outputs: "I'm eating a rat."

jusqu'ici, tout va bien, non? Les animaux mangent des aliments génériques, les chats mangent des rats, tous sans virtual .

changeons un peu maintenant pour que eat() s'appelle via une fonction Intermédiaire (une fonction triviale dans cet exemple):

// This can go at the top of the main.cpp file
void func(Animal *xyz) { xyz->eat(); }

Maintenant notre fonction principale est:

Animal *animal = new Animal;
Cat *cat = new Cat;

func(animal); // Outputs: "I'm eating generic food."
func(cat);    // Outputs: "I'm eating generic food."

Uh oh... nous avons passé un chat dans func() , mais il ne mange pas les rats. Faut-il surcharger func() pour qu'il faille un Cat* ? Si vous devez obtenir plus d'animaux D'animaux, ils auraient tous besoin de leur propre func() .

La solution est de faire eat() de la Animal classe virtuelle de la fonction:

class Animal
{
    public:
        virtual void eat() { std::cout << "I'm eating generic food."; }
};

class Cat : public Animal
{
    public:
        void eat() { std::cout << "I'm eating a rat."; }
};
"1519150920 Principal":

func(animal); // Outputs: "I'm eating generic food."
func(cat);    // Outputs: "I'm eating a rat."

fait.

2327
répondu M Perry 2018-04-12 16:36:42

Sans "virtuel", vous obtenez "la liaison anticipée". L'implémentation de la méthode utilisée est décidée au moment de la compilation en fonction du type de pointeur que vous utilisez.

Avec "virtuel", vous obtenez "late binding". La mise en œuvre de la méthode est décidée au moment de l'exécution en fonction du type de l'objet pointé-sous quelle forme il a été construit à l'origine. Ce n'est pas nécessairement ce que vous pensez selon le type de pointeur qui pointe à ce objet.

class Base
{
  public:
            void Method1 ()  {  std::cout << "Base::Method1" << std::endl;  }
    virtual void Method2 ()  {  std::cout << "Base::Method2" << std::endl;  }
};

class Derived : public Base
{
  public:
    void Method1 ()  {  std::cout << "Derived::Method1" << std::endl;  }
    void Method2 ()  {  std::cout << "Derived::Method2" << std::endl;  }
};

Base* obj = new Derived ();
  //  Note - constructed as Derived, but pointer stored as Base*

obj->Method1 ();  //  Prints "Base::Method1"
obj->Method2 ();  //  Prints "Derived::Method2"

EDIT - voir cette question .

Aussi ce tutoriel couvre début et à la fin de la liaison en C++.

553
répondu Steve314 2017-05-23 12:10:47

vous avez besoin d'au moins un niveau d'héritage et un downcast pour le démontrer. Voici un exemple très simple:

class Animal
{        
    public: 
      // turn the following virtual modifier on/off to see what happens
      //virtual   
      std::string Says() { return "?"; }  
};

class Dog: public Animal
{
    public: std::string Says() { return "Woof"; }
};

void test()
{
    Dog* d = new Dog();
    Animal* a = d;       // refer to Dog instance with Animal pointer

    cout << d->Says();   // always Woof
    cout << a->Says();   // Woof or ?, depends on virtual
}
75
répondu Henk Holterman 2014-03-02 17:01:50

Vous avez besoin de méthodes virtuelles pour sûr de passer , simplicité et concision .

C'est ce que font les méthodes virtuelles: elles émettent en toute sécurité, avec un code apparemment simple et concis, en évitant les moulages manuels dangereux dans le code plus complexe et verbeux que vous auriez autrement.


Non virtuel méthode ⇒ statique de la liaison

Le code suivant est intentionnellement "incorrect". Il ne déclare pas la méthode value comme virtual , et produit donc un résultat "erroné" inattendu, à savoir 0:

#include <iostream>
using namespace std;

class Expression
{
public:
    auto value() const
        -> double
    { return 0.0; }         // This should never be invoked, really.
};

class Number
    : public Expression
{
private:
    double  number_;

public:
    auto value() const
        -> double
    { return number_; }     // This is OK.

    Number( double const number )
        : Expression()
        , number_( number )
    {}
};

class Sum
    : public Expression
{
private:
    Expression const*   a_;
    Expression const*   b_;

public:
    auto value() const
        -> double
    { return a_->value() + b_->value(); }       // Uhm, bad! Very bad!

    Sum( Expression const* const a, Expression const* const b )
        : Expression()
        , a_( a )
        , b_( b )
    {}
};

auto main() -> int
{
    Number const    a( 3.14 );
    Number const    b( 2.72 );
    Number const    c( 1.0 );

    Sum const       sum_ab( &a, &b );
    Sum const       sum( &sum_ab, &c );

    cout << sum.value() << endl;
}

dans la ligne commentée comme" mauvais "la méthode Expression::value est appelée, parce que le statically known type (le type connu au moment de la compilation) est Expression , et la méthode value n'est pas virtuelle.


méthode virtuelle ⇒ liaison dynamique.

déclarant value comme virtual dans le type statiquement connu Expression garantit que chaque appel vérifiera ce qu'est le type réel de l'objet, et appeler l'application pertinente de value pour que type dynamique :

#include <iostream>
using namespace std;

class Expression
{
public:
    virtual
    auto value() const -> double
        = 0;
};

class Number
    : public Expression
{
private:
    double  number_;

public:
    auto value() const -> double
        override
    { return number_; }

    Number( double const number )
        : Expression()
        , number_( number )
    {}
};

class Sum
    : public Expression
{
private:
    Expression const*   a_;
    Expression const*   b_;

public:
    auto value() const -> double
        override
    { return a_->value() + b_->value(); }    // Dynamic binding, OK!

    Sum( Expression const* const a, Expression const* const b )
        : Expression()
        , a_( a )
        , b_( b )
    {}
};

auto main() -> int
{
    Number const    a( 3.14 );
    Number const    b( 2.72 );
    Number const    c( 1.0 );

    Sum const       sum_ab( &a, &b );
    Sum const       sum( &sum_ab, &c );

    cout << sum.value() << endl;
}

Voici la sortie est 6.86 , comme il se doit, puisque la méthode virtuelle est appelé pratiquement . Cela est également appelé liaison dynamique des appels. Une petite vérification est effectuée, pour trouver le type dynamique réel de l'objet, et l'implémentation de la méthode appropriée pour ce type dynamique, est appelée.

l'implémentation pertinente est celle de la classe la plus spécifique (la plus dérivée).

noter que la méthode les implémentations dans les classes dérivées ici ne sont pas marquées virtual , mais sont marquées override . Ils peuvent être marqués virtual mais ils sont automatiquement virtuels. Le mot-clé override garantit que s'il y a pas une telle méthode virtuelle dans une classe de base, alors vous obtiendrez une erreur (ce qui est souhaitable).


la laideur de faire cela sans méthodes virtuelles

sans virtual il faudrait mettre en œuvre une version Do It Yourself de la reliure dynamique. C'est ce qui implique généralement la downcasting manuelle dangereuse, la complexité et la verbosité.

pour le cas d'une seule fonction, comme ici, il suffit de stocker un pointeur de fonction dans l'objet et d'appeler via ce pointeur de fonction, mais même si elle implique certains downcasts dangereux, la complexité et verbosité, à savoir:

#include <iostream>
using namespace std;

class Expression
{
protected:
    typedef auto Value_func( Expression const* ) -> double;

    Value_func* value_func_;

public:
    auto value() const
        -> double
    { return value_func_( this ); }

    Expression(): value_func_( nullptr ) {}     // Like a pure virtual.
};

class Number
    : public Expression
{
private:
    double  number_;

    static
    auto specific_value_func( Expression const* expr )
        -> double
    { return static_cast<Number const*>( expr )->number_; }

public:
    Number( double const number )
        : Expression()
        , number_( number )
    { value_func_ = &Number::specific_value_func; }
};

class Sum
    : public Expression
{
private:
    Expression const*   a_;
    Expression const*   b_;

    static
    auto specific_value_func( Expression const* expr )
        -> double
    {
        auto const p_self  = static_cast<Sum const*>( expr );
        return p_self->a_->value() + p_self->b_->value();
    }

public:
    Sum( Expression const* const a, Expression const* const b )
        : Expression()
        , a_( a )
        , b_( b )
    { value_func_ = &Sum::specific_value_func; }
};


auto main() -> int
{
    Number const    a( 3.14 );
    Number const    b( 2.72 );
    Number const    c( 1.0 );

    Sum const       sum_ab( &a, &b );
    Sum const       sum( &sum_ab, &c );

    cout << sum.value() << endl;
}

une façon positive de voir ceci est, si vous rencontrez dangerous downcasting, complexité et verbosité comme ci-dessus, alors souvent une méthode virtuelle ou des méthodes peuvent vraiment aider.

33
répondu Cheers and hth. - Alf 2014-11-24 07:24:45

si la classe de base est Base , et une classe dérivée est Der , vous pouvez avoir un pointeur Base *p qui pointe en fait vers une instance de Der . Quand vous appelez p->foo(); , si foo est pas virtuel, puis Base la version de celui-ci exécute, ignorant le fait que p pointe effectivement à un Der . Si foo est virtuel, p->foo() exécute le" leafmost " override de foo , en tenant pleinement compte de la classe réelle de l'article pointé. Ainsi , la différence entre le virtuel et le non-virtuel est en fait assez cruciale: le premier autorise la durée d'exécution polymorphisme , le concept de base de la programmation OO, tandis que les derniers ne le font pas.

29
répondu Alex Martelli 2010-03-06 16:56:57

Fonctions Virtuelles sont utilisées à l'appui Polymorphisme d'Exécution .

C'est-à-dire, virtuel le mot-clé indique au compilateur de ne pas prendre la décision (de lier la fonction) au moment de la compilation, reportez plutôt pour l'exécution " .

  • vous pouvez faire une fonction virtuelle en précédant le mot-clé virtual dans sa déclaration de classe de base. Exemple,

     class Base
     {
        virtual void func();
     }
    
  • Lorsqu'une classe de Base a une fonction de membre virtuel, toute classe qui hérite de la classe de Base peut redéfinir la fonction avec exactement le même prototype c.-à-d. que seule la fonctionnalité peut être redéfinie, pas l'interface de la fonction.

     class Derive : public Base
     {
        void func();
     }
    
  • un pointeur de classe de Base peut être utilisé pour pointer vers la Base objet de classe ainsi qu'un objet de classe dérivé.

  • lorsque la fonction virtuelle est appelée en utilisant un pointeur de classe de base, le compilateur décide à l'exécution quelle version de la fonction-c'est - à - dire la version de classe de Base ou la version dérivée de classe écrasée-doit être appelée. Cela s'appelle Runtime Polymorphism .
28
répondu KIN 2018-06-25 19:53:24

explication du besoin D'une fonction virtuelle [facile à comprendre]

#include<iostream>

using namespace std;

class A{
public: 
        void show(){
        cout << " Hello from Class A";
    }
};

class B :public A{
public:
     void show(){
        cout << " Hello from Class B";
    }
};


int main(){

    A *a1 = new B; // Create a base class pointer and assign address of derived object.
    a1->show();

}

sortie sera:

Hello from Class A.

mais avec une fonction virtuelle:

#include<iostream>

using namespace std;

class A{
public:
    virtual void show(){
        cout << " Hello from Class A";
    }
};

class B :public A{
public:
    virtual void show(){
        cout << " Hello from Class B";
    }
};


int main(){

    A *a1 = new B;
    a1->show();

}

sortie sera:

Hello from Class B.

donc avec la fonction virtuelle vous pouvez atteindre le polymorphisme d'exécution.

26
répondu Ajay GU 2014-12-12 11:56:42

vous devez distinguer entre le dépassement et la surcharge. Sans le mot-clé virtual vous surchargez seulement une méthode d'une classe de base. Cela ne signifie rien d'autre que se cacher. Disons que vous avez une classe de base Base et une classe dérivée Specialized qui tous les deux implémentent void foo() . Maintenant, vous avez un pointeur vers Base pointant vers une instance de Specialized . Quand vous appelez foo() dessus vous pouvez observer la différence que virtual fait: si la méthode est virtuelle, l'implémentation de Specialized sera utilisée, si elle est manquante, la version de Base sera choisie. Il est conseillé de ne jamais surcharger les méthodes d'une classe de base. Rendre une méthode non-virtuelle est la façon de son auteur de vous dire que son extension dans les sous-classes n'est pas prévue.

21
répondu h0b0 2010-03-06 07:27:04

je voudrais ajouter une autre utilisation de la fonction virtuelle bien qu'elle utilise le même concept que les réponses ci-dessus mentionnées, mais je suppose qu'il vaut la peine de mentionner.

DESTRUCTEUR VIRTUEL

considérez ce programme ci-dessous, sans déclarer que la classe de base destructor est virtuelle; la mémoire pour Cat peut ne pas être nettoyée.

class Animal {
    public:
    ~Animal() {
        cout << "Deleting an Animal" << endl;
    }
};
class Cat:public Animal {
    public:
    ~Cat() {
        cout << "Deleting an Animal name Cat" << endl;
    }
};

int main() {
    Animal *a = new Cat();
    delete a;
    return 0;
}

sortie:

Deleting an Animal
class Animal {
    public:
    virtual ~Animal() {
        cout << "Deleting an Animal" << endl;
    }
};
class Cat:public Animal {
    public:
    ~Cat(){
        cout << "Deleting an Animal name Cat" << endl;
    }
};

int main() {
    Animal *a = new Cat();
    delete a;
    return 0;
}

sortie:

Deleting an Animal name Cat
Deleting an Animal
19
répondu Aryaman Gupta 2018-04-12 16:39:59

Pourquoi avons-nous besoin de méthodes virtuelles en C++?

Réponse Rapide:

  1. Il nous fournit l'un des "ingrédients" 1 pour programmation orientée objet .

De Bjarne Stroustrup la Programmation en C++: Principes et Pratiques, (14.3):

la fonction virtuelle fournit possibilité de définir une fonction dans une classe de base et d'avoir une fonction du même nom et de taper dans une classe dérivée appelée quand un utilisateur appelle la fonction de classe de base. Qui est souvent appelé polymorphisme de l'exécution , répartition dynamique , ou moment de l'exécution de l'expédition parce que la fonction appelée est déterminée au moment de l'exécution en fonction du type de l'objet utilisé.

  1. C'est le plus rapide en plus mise en œuvre efficace si vous avez besoin d'un appel de fonction virtuelle 2 .

pour traiter un appel virtuel, on a besoin d'une ou plusieurs données relatives à l'Objet dérivé 3 . La manière qui est généralement fait, c'est d'ajouter l'adresse de la table des fonctions. Ce tableau est généralement dénommé "1519100920 table virtuelle ou table de fonction virtuelle et son adresse est souvent appelé le pointeur virtuel . Chaque fonction virtuelle devient une fente dans la table virtuelle. Selon le type d'Objet (dérivé) de l'appelant, la fonction virtuelle, à son tour, invoque le contournement respectif.


1.L'utilisation de l'héritage, le polymorphisme de l'exécution, et l'encapsulation est la définition la plus courante de la programmation orientée objet .

2. Vous ne pouvez pas coder fonctionnalité pour être plus rapide ou d'utiliser moins de mémoire en utilisant d'autres fonctionnalités de langue pour sélectionner parmi les alternatives à l'exécution. Bjarne Stroustrup la Programmation en C++: les Principes et la Pratique.(14.3.1) .

3. Quelque chose pour dire quelle fonction est réellement invoquée lorsque nous appelons la classe de base contenant la fonction virtuelle.

16
répondu Ziezi 2015-09-27 09:37:47

Quand vous avez une fonction dans la classe de base, vous pouvez Redefine ou Override dans la classe dérivée.

redéfinir une méthode : Une nouvelle implémentation pour la méthode de classe de base est donnée dans la classe dérivée. N'est pas faciliter Dynamic binding .

priorité à une méthode : Redefining un virtual method de la classe de base dans la classe dérivée. La méthode virtuelle facilite la liaison dynamique .

donc quand vous avez dit:

mais plus tôt dans le livre, en apprenant sur l'héritage de base, j'étais capable de surpasser les méthodes de base dans les classes dérivées sans utiliser "virtuel".

vous n'étiez pas l'emporter car la méthode dans la classe de base n'était pas virtuelle, plutôt vous étiez le redéfinir

12
répondu nitin_cherian 2012-02-06 08:29:56

j'ai ma réponse sous la forme d'une conversation à une meilleure lecture:


Pourquoi avons-nous besoin de fonctions virtuelles?

à cause du polymorphisme.

Qu'est-ce que le polymorphisme?

le fait qu'un pointeur de base peut aussi pointer vers des objets de type dérivés.

comment cette définition du polymorphisme mener à la nécessité de fonctions virtuelles?

eh Bien, par le biais de au début de liaison .

Qu'est-ce que la reliure anticipée?

Early binding (compilation-time binding) in C++ signifie qu'un appel de fonction est fixé avant que le programme ne soit exécuté.

So...?

donc si vous utilisez un type de base comme paramètre d'un fonction, le compilateur ne reconnaîtra que l'interface de base, et si vous appelez cette fonction avec des arguments provenant de classes dérivées, elle est coupée, ce qui n'est pas ce que vous voulez faire.

Si ce n'est pas ce que nous voulons arriver, pourquoi est-ce possible?

parce qu'on a besoin de polymorphisme!

Quel est l'avantage du polymorphisme alors?

Vous pouvez utiliser un pointeur de type de base comme paramètre d'une fonction unique, et ensuite dans le temps d'exécution de votre programme, vous pouvez accéder à chacune des interfaces de type dérivées(par exemple leurs fonctions de membre) sans aucun problème, en utilisant le déréférencement de ce pointeur de base unique.

Je ne sais toujours pas à quoi servent les fonctions virtuelles...! Et ce fut ma première question!

Eh bien, c'est parce que vous avez posé votre question trop tôt!

Pourquoi avons-nous besoin de fonctions virtuelles?

suppose que vous avez appelé une fonction avec un pointeur de base, qui avait l'adresse d'un objet d'une de ses classes dérivées. Comme nous en avons parlé ci-dessus, dans le run-time, ce pointeur est déréférencé, jusqu'à présent si bon, cependant, nous nous attendons à ce qu'une méthode(== une fonction membre) "de notre classe dérivée" soit exécutée! Cependant, une même méthode (qui a un même en-tête) est déjà définie dans la base la classe, alors pourquoi votre programme devrait-il prendre la peine de choisir l'autre méthode? En d'autres termes, je veux dire, comment pouvez-vous distinguer ce scénario de ce que nous voyons normalement se produire avant?

la réponse brève est "une fonction de membre virtuel dans la base", et une réponse un peu plus longue est que, "à cette étape, si le programme voit une fonction virtuelle dans la classe de base, il sait (réalise) que vous essayez d'utiliser le polymorphisme" et va donc aux classes dérivées (en utilisant V-table , une forme de reliure tardive) pour trouver que une autre méthode avec le même en-tête, mais avec - de manière prévisible-une mise en œuvre différente.

pourquoi une mise en œuvre différente?

espèce de crétin! Allez lire un bon livre !

OK, attendez attendez attendez, pourquoi aurait-on pris la peine d'utiliser la base de pointeurs, quand il ou elle pourrait simplement utiliser des dérivés de type les pointeurs? Tu es le juge, est-ce que tout ce mal de tête en vaut la peine? Regardez ces deux extraits:

//1:

Parent* p1 = &boy;
p1 -> task();
Parent* p2 = &girl;
p2 -> task();

//2:

Boy* p1 = &boy;
p1 -> task();
Girl* p2 = &girl;
p2 -> task();

OK, bien que je pense que 1 est encore mieux que 2 , vous pourriez écrire 1 comme ceci soit:

/ / 1:

Parent* p1 = &boy;
p1 -> task();
p1 = &girl;
p1 -> task();

et de plus, vous devriez être conscient que ceci n'est encore qu'une utilisation artificielle de toutes les choses que je vous ai expliquées jusqu'à présent. Au lieu de cela, supposons par exemple une situation dans laquelle vous aviez une fonction dans votre programme qui utilisait les méthodes de chacune des classes dérivées respectivement(getMonthBenefit ()):

double totalMonthBenefit = 0;    
std::vector<CentralShop*> mainShop = { &shop1, &shop2, &shop3, &shop4, &shop5, &shop6};
for(CentralShop* x : mainShop){
     totalMonthBenefit += x -> getMonthBenefit();
}

maintenant, essayez de réécrire ceci, sans aucune migraine!

double totalMonthBenefit=0;
Shop1* branch1 = &shop1;
Shop2* branch2 = &shop2;
Shop3* branch3 = &shop3;
Shop4* branch4 = &shop4;
Shop5* branch5 = &shop5;
Shop6* branch6 = &shop6;
totalMonthBenefit += branch1 -> getMonthBenefit();
totalMonthBenefit += branch2 -> getMonthBenefit();
totalMonthBenefit += branch3 -> getMonthBenefit();
totalMonthBenefit += branch4 -> getMonthBenefit();
totalMonthBenefit += branch5 -> getMonthBenefit();
totalMonthBenefit += branch6 -> getMonthBenefit();

et en fait, ce pourrait être encore un exemple artificiel non plus!

11
répondu M-J 2018-04-12 16:38:26

cela aide si vous connaissez les mécanismes sous-jacents. C++ formalise certaines techniques de codage utilisées par les programmeurs C, Les "classes" remplacées par des "superpositions" - les structures avec des sections d'en-tête communes seraient utilisées pour manipuler des objets de différents types, mais avec des données ou des opérations communes. Normalement la structure de base de la superposition (la partie commune) a un pointeur vers une table de fonction qui pointe vers un ensemble différent de routines pour chaque type d'objet. C++ fait la même chose, mais cache les mécanismes c'est-à-dire le c++ ptr->func(...) où func est virtuel comme C serait (*ptr->func_table[func_num])(ptr,...) , où ce qui change entre les classes dérivées est le contenu func_table. [Une méthode non-virtuelle ptr - > func () se traduit par mangled_func (ptr,..).]

le résultat de cela est que vous avez seulement besoin de comprendre la classe de base pour appeler les méthodes d'une classe dérivée, c.-à-d. si une routine comprend la classe A, vous pouvez passer un pointeur dérivé de classe B alors les méthodes virtuelles appelées seront celles de B plutôt que de A puisque vous passez par la table de fonction b points à.

10
répondu Kev 2012-11-30 08:43:26

le mot-clé virtual indique au compilateur qu'il ne doit pas effectuer de reliure anticipée. Au lieu de cela, il devrait installer automatiquement tous les mécanismes nécessaires pour effectuer la reliure tardive. Pour ce faire, le compilateur Type1 crée une table unique (appelée la table des variables) pour chaque classe qui contient des fonctions virtuelles.Le compilateur place les adresses des fonctions virtuelles pour cette classe particulière dans le VTABLE. Dans chaque classe avec des fonctions virtuelles,il place secrètement un pointeur, appelé le vpointer (Abrégé en VPTR), qui pointe vers le VTABLE pour cet objet. Lorsque vous faites un appel à une fonction virtuelle à travers un pointeur de classe de base, le compilateur insère tranquillement du code pour récupérer le VPTR et rechercher l'adresse de la fonction dans le VTABLE, appelant ainsi la fonction correcte et provoquant une liaison tardive.

Plus de détails dans ce lien http://cplusplusinterviews.blogspot.sg/2015/04/virtual-mechanism.html

7
répondu rvkreddy 2015-04-23 12:49:31

le mot-clé virtuel oblige le compilateur à choisir l'implémentation de la méthode définie dans la classe object plutôt que dans la classe pointer .

Shape *shape = new Triangle(); 
cout << shape->getName();

dans l'exemple ci-dessus, Shape::getName sera appelé par défaut, à moins que getName() ne soit défini comme virtuel dans la forme de la classe de base. Cela force le compilateur à rechercher l'implémentation de getName() dans la classe Triangle plutôt que dans la classe des formes.

la table virtuelle est le mécanisme dans lequel le compilateur garde la trace des différentes implémentations de la méthode virtuelle des sous-classes. C'est ce qu'on appelle aussi la régulation dynamique, et là est certains au-dessus de lui.

enfin, pourquoi virtual est-il même nécessaire en C++, pourquoi ne pas en faire le comportement par défaut comme en Java?

  1. C++ est basé sur les principes de "zéro frais généraux" et "payer pour ce que vous utilisez". Il n'essaie donc pas d'exécuter dynamic dispatch pour vous, sauf si vous en avez besoin.
  2. pour fournir plus de contrôle à l'interface. En rendant une fonction non-virtuelle, la classe interface/abstract peut contrôler le comportement dans toutes ses implémentations.
6
répondu javaProgrammer 2017-06-07 17:59:06

Pourquoi avons-nous besoin de fonctions virtuelles?

les fonctions virtuelles évitent les problèmes inutiles de typographie, et certains d'entre nous peuvent débattre que Pourquoi avons-nous besoin de fonctions virtuelles alors que nous pouvons utiliser le pointeur de classe dérivé pour appeler la fonction spécifique dans la classe dérivée!la réponse est-il annule l'idée entière de l'héritage dans le développement du grand système, où avoir un seul objet de classe de base de pointeur est très désiré.

comparons ci-dessous deux programmes simples pour comprendre l'importance des fonctions virtuelles:

programme sans fonctions virtuelles:

#include <iostream>
using namespace std;

class father
{
    public: void get_age() {cout << "Fathers age is 50 years" << endl;}
};

class son: public father
{
    public : void get_age() { cout << "son`s age is 26 years" << endl;}
};

int main(){
    father *p_father = new father;
    son *p_son = new son;

    p_father->get_age();
    p_father = p_son;
    p_father->get_age();
    p_son->get_age();
    return 0;
}

sortie:

Fathers age is 50 years
Fathers age is 50 years
son`s age is 26 years

programme avec fonction virtuelle:

#include <iostream>
using namespace std;

class father
{
    public:
        virtual void get_age() {cout << "Fathers age is 50 years" << endl;}
};

class son: public father
{
    public : void get_age() { cout << "son`s age is 26 years" << endl;}
};

int main(){
    father *p_father = new father;
    son *p_son = new son;

    p_father->get_age();
    p_father = p_son;
    p_father->get_age();
    p_son->get_age();
    return 0;
}

sortie:

Fathers age is 50 years
son`s age is 26 years
son`s age is 26 years

En analysant les résultats, on peut comprendre l'importance de fonctions virtuelles.

4
répondu akshaypmurgod 2017-11-11 12:07:16

en ce qui concerne l'efficacité, les fonctions virtuelles sont légèrement moins efficaces que les fonctions de liaison précoce.

" ce mécanisme d'appel virtuel peut être rendu presque aussi efficace que le mécanisme "appel de fonction normale" (à moins de 25%). Son espace de surcharge est un pointeur dans chaque objet d'une classe virtuelle fonctions, ainsi que d'un vtbl pour chaque classe" [ Un tour du C++ par Bjarne Stroustrup]

2
répondu Duke 2014-12-28 14:19:30
Les méthodes virtuelles

sont utilisées dans la conception des interfaces. Par exemple dans Windows il y a une interface appelée IUnknown comme ci-dessous:

interface IUnknown {
  virtual HRESULT QueryInterface (REFIID riid, void **ppvObject) = 0;
  virtual ULONG   AddRef () = 0;
  virtual ULONG   Release () = 0;
};

ces méthodes sont laissées à l'utilisateur de l'interface à mettre en œuvre. Ils sont essentiels pour la création et la destruction de certains objets qui doivent hériter IUnknown. Dans ce cas, le run-time est conscient des trois méthodes et s'attend à ce qu'elles soient implémentées quand il les appelle. Ainsi, dans un certain sens, ils agissent comme un contrat entre objet lui-même et tout ce qui utilise cet objet.

2
répondu 2015-02-20 03:01:32

voici un exemple complet qui illustre pourquoi la méthode virtuelle est utilisée.

#include <iostream>

using namespace std;

class Basic
{
    public:
    virtual void Test1()
    {
        cout << "Test1 from Basic." << endl;
    }
    virtual ~Basic(){};
};
class VariantA : public Basic
{
    public:
    void Test1()
    {
        cout << "Test1 from VariantA." << endl;
    }
};
class VariantB : public Basic
{
    public:
    void Test1()
    {
        cout << "Test1 from VariantB." << endl;
    }
};

int main()
{
    Basic *object;
    VariantA *vobjectA = new VariantA();
    VariantB *vobjectB = new VariantB();

    object=(Basic *) vobjectA;
    object->Test1();

    object=(Basic *) vobjectB;
    object->Test1();

    delete vobjectA;
    delete vobjectB;
    return 0;
}
1
répondu user3371350 2018-02-02 10:29:55

Voici une version fusionnée du code C++ pour les deux premières réponses.

#include        <iostream>
#include        <string>

using   namespace       std;

class   Animal
{
        public:
#ifdef  VIRTUAL
                virtual string  says()  {       return  "??";   }
#else
                string  says()  {       return  "??";   }
#endif
};

class   Dog:    public Animal
{
        public:
                string  says()  {       return  "woof"; }
};

string  func(Animal *a)
{
        return  a->says();
}

int     main()
{
        Animal  *a = new Animal();
        Dog     *d = new Dog();
        Animal  *ad = d;

        cout << "Animal a says\t\t" << a->says() << endl;
        cout << "Dog d says\t\t" << d->says() << endl;
        cout << "Animal dog ad says\t" << ad->says() << endl;

        cout << "func(a) :\t\t" <<      func(a) <<      endl;
        cout << "func(d) :\t\t" <<      func(d) <<      endl;
        cout << "func(ad):\t\t" <<      func(ad)<<      endl;
}

deux résultats différents sont:

sans #define virtual , il se lie au moment de la compilation. Animal * ad et func (Animal *) pointent tous vers la méthode dit () de L'Animal.

$ g++ virtual.cpp -o virtual
$ ./virtual 
Animal a says       ??
Dog d says      woof
Animal dog ad says  ??
func(a) :       ??
func(d) :       ??
func(ad):       ??

avec #define virtual , il se lie au moment de l'exécution. Chien *d, Animal *ad et func(Animal *) point/reportez-vous à la Le chien dit () la méthode comme le chien est leur type d'objet. À moins que la méthode [Dog says() "woof"] ne soit pas définie, elle sera celle qui sera recherchée en premier dans l'arbre des classes, c'est-à-dire que les classes dérivées peuvent supplanter les méthodes de leurs classes de base [Animal says()].

$ g++ virtual.cpp -D VIRTUAL -o virtual
$ ./virtual 
Animal a says       ??
Dog d says      woof
Animal dog ad says  woof
func(a) :       ??
func(d) :       woof
func(ad):       woof

il est intéressant de noter que tous les attributs de classe (données et méthodes) dans Python sont effectivement virtuels . Puisque tous les objets sont créés dynamiquement à l'exécution, il n'y a pas de déclaration de type ou une nécessité pour le mot-clé virtual. Voici la version de code de Python:

class   Animal:
        def     says(self):
                return  "??"

class   Dog(Animal):
        def     says(self):
                return  "woof"

def     func(a):
        return  a.says()

if      __name__ == "__main__":

        a = Animal()
        d = Dog()
        ad = d  #       dynamic typing by assignment

        print("Animal a says\t\t{}".format(a.says()))
        print("Dog d says\t\t{}".format(d.says()))
        print("Animal dog ad says\t{}".format(ad.says()))

        print("func(a) :\t\t{}".format(func(a)))
        print("func(d) :\t\t{}".format(func(d)))
        print("func(ad):\t\t{}".format(func(ad)))

la sortie est:

Animal a says       ??
Dog d says      woof
Animal dog ad says  woof
func(a) :       ??
func(d) :       woof
func(ad):       woof

qui est identique à la définition virtuelle de C++. Notez que d et ad sont deux variables pointeur se référant/pointant vers la même instance de chien. L'expression (ad est d) renvoie True et leurs valeurs sont les mêmes < principal .Objet chien à 0xb79f72cc>.

1
répondu Leon Chang 2018-09-05 23:12:13

Nous avons besoin de méthodes virtuelles pour soutenir "Exécuter en temps Polymorphisme". Lorsque vous vous référez à un objet de classe dérivé en utilisant un pointeur ou une référence à la classe de base, vous pouvez appeler une fonction virtuelle pour cet objet et exécuter la version de la fonction de la classe dérivée.

0
répondu rashedcs 2017-03-07 03:23:26

je pense que vous faites référence au fait une fois qu'une méthode est déclarée virtuelle vous n'avez pas besoin d'utiliser le "virtuel" mot-clé dans les remplacements.

class Base { virtual void foo(); };

class Derived : Base 
{ 
  void foo(); // this is overriding Base::foo
};

si vous n'utilisez pas 'virtual' dans la déclaration foo de Base, alors la déclaration foo dérivée ne ferait que l'observer.

0
répondu edwinc 2018-07-19 20:15:55