Waarom hebben we virtuele functies nodig in C++?

Ik leer C++ en ik kom gewoon in virtuele functies.

Van wat ik heb gelezen (in het boek en online) zijn virtuele functies functies in de basisklasse die u in afgeleide klassen kunt overschrijven.

Maar eerder in het boek, bij het leren over fundamenteel overerving, was ik in staat om basisfuncties in afgeleide klassen te negeren zonder virtualte gebruiken.

Dus wat mis ik hier? Ik weet dat er meer is aan virtuele functies, en het lijkt belangrijk te zijn, dus ik wil duidelijk zijn over wat het precies is. Ik kan gewoon niet online een rechte antwoord vinden.


1, Autoriteit 100%

Hier is hoe ik het niet alleen begreep wat virtualfuncties zijn, maar waarom ze verplicht zijn:

Laten we zeggen dat je deze twee klassen hebt:

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."; }
};

In uw hoofdfunctie:

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

Tot nu toe zo goed, toch? Dieren eten generiek eten, katten eten ratten, allemaal zonder virtual.

Laten we het nu een beetje wijzigen, zodat eat()is opgeroepen via een tussenfunctie (een triviale functie alleen voor dit voorbeeld):

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

Onze belangrijkste functie is nu:

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… we hebben een kat doorgegeven aan func(), maar hij eet geen ratten. Moet je func()overbelasten zodat er een Cat*voor nodig is? Als je meer dieren uit Animal moet halen, hebben ze allemaal hun eigen func()nodig.

De oplossing is om van eat()van de klasse Animaleen virtuele functie te maken:

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."; }
};

Hoofd:

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

Gereed.


Antwoord 2, autoriteit 25%

Zonder “virtueel” krijg je “vroege binding”. Welke implementatie van de methode wordt gebruikt, wordt bepaald tijdens het compileren op basis van het type aanwijzer dat u aanroept.

Met “virtueel” krijg je “late binding”. Welke implementatie van de methode wordt gebruikt, wordt tijdens runtime bepaald op basis van het type van het object waarnaar wordt verwezen – hoe het oorspronkelijk was geconstrueerd. Dit is niet noodzakelijkerwijs wat je zou denken op basis van het type aanwijzer dat naar dat object verwijst.

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* basePtr = new Derived ();
  //  Note - constructed as Derived, but pointer stored as Base*
basePtr->Method1 ();  //  Prints "Base::Method1"
basePtr->Method2 ();  //  Prints "Derived::Method2"

BEWERKEN– zie deze vraag.

Ook – deze tutorialbehandelt vroege en late binding in C++.


Antwoord 3, autoriteit 3%

Je hebt minimaal 1 overervingsniveau en een upcast nodig om dit aan te tonen. Hier is een heel eenvoudig voorbeeld:

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
    std::cout << d->Says();   // always Woof
    std::cout << a->Says();   // Woof or ?, depends on virtual
}

4

Ik zou graag een ander gebruik van virtuele functie toevoegen, hoewel het hetzelfde concept gebruikt als hierboven vermelde antwoorden, maar ik denk dat het vermelden waard is.

VIRTUELE DESTRUCTOR

Beschouw dit programma hieronder, zonder Base class destructor als virtueel te verklaren; geheugen voor Cat wordt mogelijk niet opgeschoond.

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

Uitvoer:

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

Uitvoer:

Deleting an Animal name Cat
Deleting an Animal

Antwoord 5

Behoefte aan virtuele functie uitgelegd [Eenvoudig te begrijpen]

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

Uitvoer zal zijn:

Hello from Class A.

Maar met virtuele functie:

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

Uitvoer zal zijn:

Hello from Class B.

Daarom kunt u met een virtuele functie runtime-polymorfisme bereiken.


Antwoord 6

Je moet onderscheid maken tussen overriding en overloading. Zonder het virtualtrefwoord overbelast je alleen een methode van een basisklasse. Dit betekent niets anders dan verbergen.
Laten we zeggen dat je een basisklasse Baseen een afgeleide klasse Specializedhebt die beide void foo()implementeren. Nu heb je een pointer naar Basedie verwijst naar een instantie van Specialized. Als je foo()erop aanroept, kun je het verschil zien dat virtualmaakt: als de methode virtueel is, zal de implementatie van Specializedgebruikt, als deze ontbreekt, wordt de versie van Basegekozen.
Het is het beste om methoden uit een basisklasse nooit te overbelasten. Een methode niet-virtueel maken is de manier van de auteur om je te vertellen dat de uitbreiding ervan in subklassen niet de bedoeling is.


Antwoord 7

Ik heb mijn antwoord in de vorm van een gesprek om het beter te kunnen lezen:


Waarom hebben we virtuele functies nodig?

Vanwege polymorfisme.

Wat is polymorfisme?

Het feit dat een basisaanwijzer ook kan verwijzen naar afgeleide typeobjecten.

Hoe leidt deze definitie van polymorfisme tot de behoefte aan virtuele functies?

Nou, via vroege binding.

Wat is vroege binding?

Vroege binding (compilatiebinding) in C++ betekent dat een functieaanroep wordt hersteld voordat het programma wordt uitgevoerd.

Dus…?

Dus als je een basistype als parameter van een functie gebruikt, zal de compiler alleen de basisinterface herkennen, en als je die functie aanroept met argumenten van afgeleide klassen, wordt deze afgesplitst, wat niet is wat je wilt gebeuren.

Als dit niet is wat we willen, waarom is dit dan toegestaan?

Omdat we polymorfisme nodig hebben!

Wat is dan het voordeel van polymorfisme?

U kunt een aanwijzer van het basistype gebruiken als de parameter van een enkele functie, en dan kunt u in de runtime van uw programma zonder problemen toegang krijgen tot elk van de afgeleide type-interfaces (bijv. hun lidfuncties) door middel van dereferentie van die enkele basisaanwijzer.

Ik weet nog steeds niet waar virtuele functies goed voor zijn…! En dit was mijn eerste vraag!

Nou, dit komt omdat je je vraag te vroeg hebt gesteld!

Waarom hebben we virtuele functies nodig?

Stel dat je een functie hebt aangeroepen met een basispointer, die het adres had van een object uit een van zijn afgeleide klassen. Zoals we hierboven hebben besproken, wordt in de runtime de verwijzing naar deze aanwijzer verwijderd, tot nu toe zo goed, maar we verwachten dat een methode (== een lidfunctie) “van onze afgeleide klasse” wordt uitgevoerd! Eenzelfde methode (een met dezelfde kop) is echter al gedefinieerd in de basisklasse, dus waarom zou uw programma de moeite nemen om de andere methode te kiezen? Met andere woorden, ik bedoel, hoe kun je dit scenario onderscheiden van wat we vroeger normaal zagen gebeuren?

Het korte antwoord is “een virtuele lidfunctie in de basis”, en een iets langer antwoord is dat “in deze stap, als het programma een virtuele functie in de basisklasse ziet, het weet (beseft) dat u proberen polymorfisme te gebruiken” en gaat dus naar afgeleide klassen (met behulp van v-table , een vorm van late binding) om die andere methodete vinden met dezelfde header, maar met -verwacht- een andere implementatie.

Waarom een andere implementatie?

Jij knokkelkop! Ga een goed boeklezen!

OK, wacht wacht wacht, waarom zou iemand de moeite nemen om basispointers te gebruiken, als hij/zij gewoon afgeleide typepointers kan gebruiken? Jij bent de rechter, is al deze hoofdpijn het waard? Bekijk deze twee fragmenten:

//1:

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

//2:

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

Ok, hoewel ik denk dat 1nog steeds beter is dan 2, je zou 1ook zo kunnen schrijven:

//1:

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

en bovendien moet je je ervan bewust zijn dat dit slechts een verzonnen gebruik is van alle dingen die ik je tot nu toe heb uitgelegd. Neem in plaats hiervan bijvoorbeeld een situatie aan waarin u een functie in uw programma had die de methoden van elk van de afgeleide klassen respectievelijk gebruikte (getMonthBenefit()):

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

Probeer dit nu eens te herschrijven, zonder hoofdpijn!

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();

En eigenlijk zou dit ook nog een gekunsteld voorbeeld kunnen zijn!


Antwoord 8

Waarom hebben we virtuele methoden nodig in C++?

Snel antwoord:

  1. Het geeft ons een van de benodigde “ingrediënten”1voor objectgeoriënteerd programmeren.

In Bjarne Stroustrup C++ Programmeren: Principes en Praktijk, (14.3):

De virtuele functie biedt de mogelijkheid om een functie in een basisklasse te definiëren en een functie met dezelfde naam en type in een afgeleide klasse te hebben die wordt aangeroepen wanneer een gebruiker de basisklassefunctie aanroept. Dat wordt vaak runtime polymorfisme, dynamische verzendingof runtime verzendinggenoemd omdat de aangeroepen functie tijdens runtime wordt bepaald op basis van de type van het gebruikte object.

  1. Het is de snelste, efficiëntere implementatie als je een virtuele functie-aanroep2nodig hebt.

Om een virtueel gesprek af te handelen, heeft u een of meer gegevens nodig die betrekking hebben op het afgeleide object3. De manier waarop dat meestal wordt gedaan, is door het adres van de functietabel toe te voegen. Deze tabel wordt meestal virtuele tabelof virtuele functietabelgenoemd en het adres wordt vaak de virtuele aanwijzergenoemd. Elke virtuele functie krijgt een plaats in de virtuele tafel. Afhankelijk van het object (afgeleid) type van de aanroeper, roept de virtuele functie op zijn beurt de respectieve override aan.


1.Het gebruik van overerving, runtime-polymorfisme en inkapseling is de meest gebruikelijke definitie van objectgeoriënteerd programmeren.

2. U kunt functionaliteit niet coderen om sneller te zijn of om minder geheugen te gebruiken met behulp van andere taalfuncties om tijdens runtime uit alternatieven te kiezen. Bjarne Stroustrup C++ Programmeren: Principes en Praktijk.(14.3.1).

3. Iets om te vertellen welke functie echt wordt aangeroepen wanneer we de basisklasse aanroepen die de virtuele functie bevat.


Antwoord 9

Als je een functie in de basisklasse hebt, kun je deze Redefineof Overridein de afgeleide klasse.

Een methode opnieuw definiëren:
Een nieuwe implementatie voor de methode van basisklasse wordt gegeven in de afgeleide klasse. Vergemakkelijkt nietDynamic binding.

Een methode overschrijven:
Redefineeen virtual methodvan de basisklasse in de afgeleide klasse. Virtuele methode faciliteert Dynamic Binding.

Dus toen je zei:

Maar eerder in het boek, toen ik leerde over elementaire overerving, was ik…
in staat om basismethoden in afgeleide klassen te overschrijven zonder gebruik te maken van
‘virtueel’.

je negeerde het niet omdat de methode in de basisklasse niet virtueel was, je definieerde het eerder


Antwoord 10

Het helpt als je de onderliggende mechanismen kent. C++ formaliseert enkele coderingstechnieken die worden gebruikt door C-programmeurs, “klassen” vervangen door “overlays” – structs met gemeenschappelijke kopsecties zouden worden gebruikt om objecten van verschillende typen te verwerken, maar met enkele algemene gegevens of bewerkingen. Normaal gesproken heeft de basisstructuur van de overlay (het gemeenschappelijke deel) een aanwijzer naar een functietabel die verwijst naar een andere reeks routines voor elk objecttype. C++ doet hetzelfde, maar verbergt de mechanismen, dat wil zeggen de C++ ptr->func(...)waar func virtueel is zoals C zou zijn (*ptr->func_table[func_num])(ptr,...), waarbij de inhoud van de func_table verandert tussen afgeleide klassen. [Een niet-virtuele methode ptr->func() vertaalt zich gewoon naar mangled_func(ptr,..).]

Het resultaat hiervan is dat u alleen de basisklasse hoeft te begrijpen om de methoden van een afgeleide klasse aan te roepen, dwz als een routine klasse A begrijpt, kunt u deze een afgeleide klasse B-aanwijzer doorgeven en vervolgens de virtuele methoden die worden genoemd zullen die van B zijn in plaats van A, aangezien je de functietabel doorloopt waar B naar wijst.


Antwoord 11

Het trefwoord virtual vertelt de compiler dat deze geen vroege binding moet uitvoeren. In plaats daarvan zou het automatisch alle mechanismen moeten installeren die nodig zijn om late binding uit te voeren.
Om dit te bereiken, maakt de typische compiler1 een enkele tabel (de VTABLE genoemd) voor elke klasse die virtuele functies bevat. De compiler plaatst de adressen van de virtuele functies voor die bepaalde klasse in de VTABLE. In elke klasse met virtuele functies plaatst het in het geheim een aanwijzer, de vpointer genaamd (afgekort als VPTR), die verwijst naar de VTABLE voor dat object.
Wanneer je een virtuele functie-aanroep doet via een base-class pointer, voegt de compiler stilletjes code in om de VPTR op te halen en het functie-adres op te zoeken in de VTABLE, waardoor de juiste functie wordt aangeroepen en late binding plaatsvindt.

Meer details in deze link
http://cplusplusinterviews.blogspot.sg/2015/04/virtual-mechanism.html


Antwoord 12

Het virtuelesleutelwoord dwingt de compiler om de methode-implementatie te kiezen die gedefinieerd is in de object’sklasse in plaats van in de pointer’sklasse.

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

In het bovenstaande voorbeeld wordt Shape::getName standaard aangeroepen, tenzij getName() als virtueel is gedefinieerd in de basisklasse Shape. Dit dwingt de compiler om naar de getName()-implementatie te zoeken in de Triangle-klasse in plaats van in de Shape-klasse.

De virtuele tabelis het mechanisme waarin de compiler de verschillende virtuele-methode-implementaties van de subklassen bijhoudt. Dit wordt ook dynamische verzending genoemd, en er is iswat overhead aan verbonden.

Ten slotte, waarom is virtueel zelfs nodig in C++, waarom zou je het niet het standaardgedrag maken zoals in Java?

  1. C++ is gebaseerd op de principes van “Zero Overhead” en “Pay for what you use”. Het probeert dus geen dynamische verzending voor u uit te voeren, tenzij u het nodig heeft.
  2. Om de interface meer controle te geven. Door een functie niet-virtueel te maken, kan de interface/abstract-klasse het gedrag in al zijn implementaties controleren.

13

Waarom hebben we virtuele functies nodig?

Virtuele functies vermijden onnodig typecastingprobleem, en sommigen van ons kunnen debatteren, waarom hebben we virtuele functies nodig wanneer we de afgeleide klassenwijzer kunnen gebruiken om de functie die specifiek is in de afgeleide klasse te bellen! Het antwoord is – het is het hele idee van erfenis uit het antwoord In de ontwikkeling van het grote systeem, waar het hebben van het voorwerp van de Single Pointer-basisklasse veel gewenst is.

Laten we ons hieronder vergelijken met twee eenvoudige programma’s om het belang van virtuele functies te begrijpen:

Programma zonder virtuele functies:

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

UITGANG:

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

Programma met virtuele functie:

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

UITGANG:

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

Door beide outputs nauwkeurig te analyseren, kan men het belang van virtuele functies begrijpen.


Antwoord 14

Het probleem met uitleg met virtuele functies, is dat ze niet uitleggen hoe het in de praktijk wordt gebruikt en hoe het helpt bij het onderhoudbaarheid. Ik heb een virtuele functie-tutorial gemaakt die mensen al erg nuttig zijn gevonden. Bovendien is het gebaseerd op een Battlefield-uitgangspunt, waardoor het een beetje spannender is: https://nrecursions.blogspot.com/2015/06/so-why-doW-need-virtual-functions.html .

Overweeg dit slagveld-applicatie:

#include "iostream"
//This class is created by Gun1's company
class Gun1 {public: void fire() {std::cout<<"gun1 firing now\n";}};
//This class is created by Gun2's company
class Gun2 {public: void shoot() {std::cout<<"gun2 shooting now\n";}};
//We create an abstract class to interface with WeaponController
class WeaponsInterface {
 public:
 virtual void shootTarget() = 0;
};
//A wrapper class to encapsulate Gun1's shooting function
class WeaponGun1 : public WeaponsInterface {
 private:
 Gun1* g;
 public:
 WeaponGun1(): g(new Gun1()) {}
 ~WeaponGun1() { delete g;}
 virtual void shootTarget() { g->fire(); }
};
//A wrapper class to encapsulate Gun2's shooting function
class WeaponGun2 : public WeaponsInterface {
 private:
 Gun2* g;
 public:
 WeaponGun2(): g(new Gun2()) {}
 ~WeaponGun2() { delete g;}
 virtual void shootTarget() { g->shoot(); }
};
class WeaponController {
 private:
 WeaponsInterface* w;
 WeaponGun1* g1;
 WeaponGun2* g2;
 public:
 WeaponController() {g1 = new WeaponGun1(); g2 = new WeaponGun2(); w = g1;}
 ~WeaponController() {delete g1; delete g2;}
 void shootTarget() { w->shootTarget();}
 void changeGunTo(int gunNumber) {//Virtual functions makes it easy to change guns dynamically
   switch(gunNumber) {
     case 1: w = g1; break;
     case 2: w = g2; break;
   }
 }
};
class BattlefieldSoftware {
 private:
 WeaponController* wc;
 public:
 BattlefieldSoftware() : wc(new WeaponController()) {}
 ~BattlefieldSoftware() { delete wc; }
 void shootTarget() { wc->shootTarget(); }
 void changeGunTo(int gunNumber) {wc->changeGunTo(gunNumber); }
};
int main() {
 BattlefieldSoftware* bf = new BattlefieldSoftware();
 bf->shootTarget();
 for(int i = 2; i > 0; i--) {
     bf->changeGunTo(i);
     bf->shootTarget();
 }
 delete bf;
}

Ik raad je aan om eerst het bericht op de blog te lezen om te begrijpen waarom de wrapper-klassen zijn gemaakt.

Zoals zichtbaar in de afbeelding, zijn er verschillende wapens/raketten die kunnen worden aangesloten op een slagveldsoftware en kunnen commando’s aan die wapens worden gegeven, om te vuren of opnieuw te kalibreren enz. De uitdaging hier is om te kunnen veranderen /vervang de wapens/raketten zonder wijzigingen aan te brengen in de blauwe slagveldsoftware, en om tijdens runtime tussen wapens te kunnen schakelen, zonder wijzigingen in de code aan te brengen en opnieuw te compileren.

De bovenstaande code laat zien hoe het probleem wordt opgelost en hoe virtuele functies met goed ontworpen wrapper-klassen functies kunnen inkapselen en helpen bij het toewijzen van afgeleide klasse-pointers tijdens runtime. De creatie van klasse WeaponGun1zorgt ervoor dat je de behandeling van Gun1volledig in de klasse hebt gescheiden. Welke wijzigingen u ook aanbrengt in Gun1, u hoeft alleen wijzigingen aan te brengen in WeaponGun1en u kunt erop vertrouwen dat geen enkele andere klasse wordt beïnvloed.

Vanwege de klasse WeaponsInterfacekunt u nu elke afgeleide klasse toewijzen aan de basisklasse-aanwijzer WeaponsInterfaceen omdat de functies virtueel zijn, wanneer u WeaponsInterface‘s shootTarget, de afgeleide klasse shootTargetwordt aangeroepen.

Het beste is dat je tijdens runtime van wapen kunt wisselen (w=g1en w=g2). Dit is het belangrijkste voordeel van virtuele functies en daarom hebben we virtuele functies nodig.

Dus geen noodzaak meer om code op verschillende plaatsen te becommentariëren bij het wisselen van wapens. Het is nu een eenvoudige en duidelijke procedure en het toevoegen van meer wapenklassen is ook gemakkelijker omdat we gewoon een nieuwe WeaponGun3of WeaponGun4-klasse hoeven te maken en we kunnen erop vertrouwen dat deze heeft gewonnen verpest de code van BattlefieldSoftwareof de code van WeaponGun1/WeaponGun2niet.


Antwoord 15

Over efficiëntie: de virtuele functieszijn iets minder efficiënt dan de vroeg-bindende functies.

“Dit virtuele aanroepmechanisme kan bijna net zo efficiënt worden gemaakt als het “normale functieaanroep”-mechanisme (binnen 25%). De ruimteoverhead is één pointer in elk object van een klasse met virtuele functies plus één vtbl voor elke dergelijke klasse ” [Een rondleiding door C++door Bjarne Stroustrup]


Antwoord 16

Virtuele methoden worden gebruikt bij het ontwerpen van interfaces. In Windows is er bijvoorbeeld een interface genaamd IUnknown zoals hieronder:

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

Deze methoden worden overgelaten aan de interfacegebruiker om te implementeren. Ze zijn essentieel voor de creatie en vernietiging van bepaalde objecten die IUnknown moeten erven. In dit geval is de runtime op de hoogte van de drie methoden en verwacht dat ze worden geïmplementeerd wanneer ze worden aangeroepen. Dus in zekere zin fungeren ze als een contract tussen het object zelf en alles wat dat object gebruikt.


Antwoord 17

Hier is een compleet voorbeeld dat illustreert waarom de virtuele methode wordt gebruikt.

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

Antwoord 18

ik denk dat je doelt op het feit dat als een methode eenmaal virtueel is verklaard, je het ‘virtuele’ sleutelwoord niet meer hoeft te gebruiken in overschrijvingen.

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

Als je ‘virtual’ niet gebruikt in de foo-declaratie van Base, dan zou de foo van Derived het alleen maar schaduwen.


Antwoord 19

Hier is een samengevoegde versie van de C++-code voor de eerste twee antwoorden.

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

Twee verschillende resultaten zijn:

Zonder #define virtual, bindt het tijdens het compileren. Animal *ad en func(Animal *) verwijzen allemaal naar de Says()-methode van Animal.

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

met #define virtueel , het bindt op looptijd. Hond * D, Animal * AD en FUNC (DIER *) Point / Raadpleeg de Dog’s Says () Methode AS Dog is hun objecttype. Tenzij [Hond zegt () “WOOF”] -methode niet gedefinieerd, zal het eerst worden doorzocht in de klasse-boom, d.w.z. afgeleide klassen kunnen methoden van hun basisklassen overschrijven [DIER’s zegt ()]. ​​

$ 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

Het is interessant om op te merken dat alle klasse kenmerken (gegevens en methoden) in python zijn effectief virtueel . Aangezien alle objecten dynamisch worden gemaakt op runtime, is er geen typeaangifte of behoefte aan zoekwoord virtueel. Hieronder is de Python’s versie van Code:

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)))

De uitvoer is:

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

wat identiek is aan de virtuele definitie van C++. Houd er rekening mee dat den adtwee verschillende aanwijzervariabelen zijn die verwijzen naar/verwijzen naar dezelfde instantie van Dog. De uitdrukking (ad is d) retourneert True en hun waarden zijn hetzelfde <hoofd.Dog object op 0xb79f72cc>.


Antwoord 20

Bent u bekend met functieaanwijzers? Virtuele functies zijn een soortgelijk idee, behalve dat u eenvoudig gegevens aan virtuele functies kunt binden (als klasleden). Het is niet zo eenvoudig om gegevens aan functiewijzers te binden. Voor mij is dit het belangrijkste conceptuele onderscheid. Veel andere antwoorden hier zeggen gewoon “omdat… polymorfisme!”


Antwoord 21

We hebben virtuele methoden nodig om ‘Runtime Polymorphism’ te ondersteunen.
Wanneer u naar een afgeleid klasseobject verwijst met een pointer of een verwijzing naar de basisklasse, kunt u een virtuele functie voor dat object aanroepen en de afgeleide klasseversie van de functie uitvoeren.


Antwoord 22

Het komt erop neer dat virtuele functies het leven gemakkelijker maken. Laten we enkele van M Perry’s ideeën gebruiken en beschrijven wat er zou gebeuren als we geen virtuele functies hadden en in plaats daarvan alleen lid-functie-aanwijzers konden gebruiken. We hebben, in de normale schatting zonder virtuele functies:

class base {
 public:
 void helloWorld() { std::cout << "Hello World!"; }
  };
 class derived: public base {
 public:
 void helloWorld() { std::cout << "Greetings World!"; }
 };
 int main () {
      base hwOne;
      derived hwTwo = new derived();
      base->helloWorld(); //prints "Hello World!"
      derived->helloWorld(); //prints "Hello World!"

Ok, dus dat is wat we weten. Laten we het nu eens proberen met aanwijzers voor ledenfuncties:

#include <iostream>
 using namespace std;
 class base {
 public:
 void helloWorld() { std::cout << "Hello World!"; }
 };
 class derived : public base {
 public:
 void displayHWDerived(void(derived::*hwbase)()) { (this->*hwbase)(); }
 void(derived::*hwBase)();
 void helloWorld() { std::cout << "Greetings World!"; }
 };
 int main()
 {
 base* b = new base(); //Create base object
 b->helloWorld(); // Hello World!
 void(derived::*hwBase)() = &derived::helloWorld; //create derived member 
 function pointer to base function
 derived* d = new derived(); //Create derived object. 
 d->displayHWDerived(hwBase); //Greetings World!
 char ch;
 cin >> ch;
 }

Hoewel we sommige dingen kunnen doen met aanwijzers voor lidfuncties, zijn ze niet zo flexibel als virtuele functies. Het is lastig om een lid-functieaanwijzer in een klasse te gebruiken; de lidfunctie-aanwijzer moet bijna, althans in mijn praktijk, altijd worden aangeroepen in de hoofdfunctie of vanuit een lidfunctie zoals in het bovenstaande voorbeeld.

Aan de andere kant vereenvoudigen virtuele functies, hoewel ze misschien wat overhead van de functieaanwijzer hebben, de zaken aanzienlijk.

EDIT: Er is een andere methode die vergelijkbaar is met eddietree: c++ virtuele functie versus lidfunctieaanwijzer (prestatievergelijking).

Other episodes