Virtuell funksjon - Virtual function
| Polymorfisme |
|---|
| Ad hoc polymorfisme |
| Parametrisk polymorfisme |
| Subtyping |
I objektorientert programmering , på språk som C ++ og Object Pascal , er en virtuell funksjon eller virtuell metode en arvelig og overridbar funksjon eller metode som dynamisk utsendelse er mulig for. Dette konseptet er en viktig del av (kjøretid) polymorfisme- delen av objektorientert programmering (OOP). Kort sagt, en virtuell funksjon definerer en målfunksjon som skal utføres, men målet er kanskje ikke kjent ved kompileringstidspunktet.
De fleste programmeringsspråk, for eksempel Java , PHP og Python , behandler alle metoder som virtuelle som standard og gir ikke en modifikator for å endre denne oppførselen. Noen språk gir imidlertid modifikatorer for å forhindre at metoder overstyres av avledede klasser (for eksempel det siste søkeordet i Java og PHP ).
Hensikt
Konseptet med den virtuelle funksjonen løser følgende problem:
I objektorientert programmering , når en avledet klasse arver fra en basisklasse, kan det refereres til et objekt av den avledede klassen via en peker eller referanse til baseklassetypen i stedet for den avledede klassetypen. Hvis det er grunnklassemetoder som overstyres av den avledede klassen, kan metoden som faktisk kalles av en slik referanse eller peker, bindes (kobles) enten 'tidlig' (av kompilatoren), i henhold til den deklarerte typen pekeren eller referansen, eller 'sent' (dvs. av språkets kjøretidssystem), i henhold til den faktiske typen av objektet det refereres til.
Virtuelle funksjoner løses 'sent'. Hvis den aktuelle funksjonen er 'virtuell' i baseklassen, kalles den mest avledede klassens implementering av funksjonen i henhold til den faktiske typen objektet det refereres til, uavhengig av den deklarerte typen pekeren eller referansen. Hvis den ikke er 'virtuell', løses metoden 'tidlig' og velges i henhold til den deklarerte typen pekeren eller referansen.
Virtuelle funksjoner lar et program ringe til metoder som ikke nødvendigvis eksisterer i det øyeblikket koden blir kompilert.
I C ++ deklareres virtuelle metoder ved å føre søkeordet til funksjonens erklæring i baseklassen. Denne modifikatoren arves av alle implementeringer av denne metoden i avledede klasser, noe som betyr at de kan fortsette å overkjøre hverandre og bli sentbundet. Og selv om metoder som eies av basisklassen kaller den virtuelle metoden, vil de i stedet kalle den avledede metoden. Overbelastning oppstår når to eller flere metoder i en klasse har samme metodenavn, men forskjellige parametere. Overstyring betyr å ha to metoder med samme metodenavn og parametere. Overbelastning kalles også funksjonsmatching, og overstyring som dynamisk funksjonskartlegging.
virtual
Eksempel
For eksempel kan en basisklasse Animalha en virtuell funksjon Eat. Underklasse Llamaville implementere Eatannerledes enn underklasse Wolf, men man kan påberope Eatseg hvilken som helst klasseinstans som kalles Animal, og få Eatoppførselen til den spesifikke underklassen.
class Animal {
public:
// Intentionally not virtual:
void Move(void) {
std::cout << "This animal moves in some way" << std::endl;
}
virtual void Eat(void) = 0;
};
// The class "Animal" may possess a definition for Eat if desired.
class Llama : public Animal {
public:
// The non virtual function Move is inherited but not overridden.
void Eat(void) override {
std::cout << "Llamas eat grass!" << std::endl;
}
};
Dette gjør det mulig for en programmerer å behandle en liste over klasseobjekter Animalog fortelle hver for seg å spise (ved å ringe Eat), uten å måtte vite hva slags dyr som kan være på listen, hvordan hvert dyr spiser, eller hva det komplette settet med mulige dyretyper kan være.
Vi kan bedre se hvordan virtuelle funksjoner fungerer ved å implementere eksemplet ovenfor i C
#include <stdio.h>
/* an object points to its class... */
struct Animal {
const struct AnimalClass * class;
};
/* which contains the virtual function Animal.Eat */
struct AnimalClass {
void (*Eat)(struct Animal *); // 'virtual' function
};
/* Since Animal.Move is not a virtual function
it is not in the structure above. */
void Move(struct Animal * self)
{
printf("<Animal at %p> moved in some way\n", (void *) self);
}
/* unlike Move, which executes Animal.Move directly,
Eat cannot know which function (if any) to call at compile time.
Animal.Eat can only be resolved at run time when Eat is called. */
void Eat(struct Animal * self)
{
const struct AnimalClass * class = *(const void **) self;
if (class->Eat)
class->Eat(self); // execute Animal.Eat
else
fprintf(stderr, "Eat not implemented\n");
}
/* implementation of Llama.Eat this is the target function
to be called by 'void Eat(struct Animal *).' */
static void _Llama_eat(struct Animal * self)
{
printf("<Llama at %p> Llama's eat grass!\n", (void *) self);
}
/* initialize class */
const struct AnimalClass Animal = {(void *) 0}; // base class does not implement Animal.Eat
const struct AnimalClass Llama = {_Llama_eat}; // but the derived class does
int main(void)
{
/* init objects as instance of its class */
struct Animal animal = {& Animal};
struct Animal llama = {& Llama};
Move(& animal); // Animal.Move
Move(& llama); // Llama.Move
Eat(& animal); // cannot resolve Animal.Eat so print "Not Implemented" to stderr
Eat(& llama); // resolves Llama.Eat and executes
}
Abstrakte klasser og rene virtuelle funksjoner
En ren virtuell funksjon eller ren virtuell metode er en virtuell funksjon som må implementeres av en avledet klasse hvis den avledede klassen ikke er abstrakt . Klasser som inneholder rene virtuelle metoder kalles "abstrakte", og de kan ikke umiddelbart instantieres. En underklasse av en abstrakt klasse kan bare instantieres direkte hvis alle arvelige rene virtuelle metoder har blitt implementert av den klassen eller en foreldreklasse. Rene virtuelle metoder har vanligvis en deklarasjon ( signatur ) og ingen definisjon ( implementering ).
Som et eksempel kan en abstrakt basisklasse MathSymbolgi en ren virtuell funksjon doOperation(), og avledede klasser Plusog Minusimplementere for doOperation()å gi konkrete implementeringer. Implementering doOperation()ville ikke være fornuftig i MathSymbolklassen, det samme MathSymboler et abstrakt konsept hvis oppførsel er definert utelukkende for hver gitt type (underklasse) MathSymbol. På samme måte ville en gitt underklasse av MathSymbolikke være komplett uten en implementering av
doOperation().
Selv om rene virtuelle metoder vanligvis ikke har noen implementering i klassen som deklarerer dem, har rene virtuelle metoder på noen språk (f.eks. C ++ og Python) lov til å inneholde en implementering i sin deklarerende klasse, noe som gir tilbakefall eller standardatferd som en avledet klasse kan delegere til , hvis det passer.
Rene virtuelle funksjoner kan også brukes der metodeerklæringene brukes til å definere et grensesnitt - på samme måte som grensesnittnøkkelordet i Java eksplisitt angir. I en slik bruk vil avledede klasser levere alle implementeringer. I en slik utforming mønster , vil abstrakt klasse som tjener som et grensesnitt inneholde bare rene virtuelle funksjoner, men ingen datamedlemmer eller vanlige metoder. I C ++ fungerer det å bruke slike abstrakte klasser som grensesnitt fordi C ++ støtter flere arv . Men fordi mange OOP -språk ikke støtter flere arv, gir de ofte en egen grensesnittmekanisme. Et eksempel er programmeringsspråket Java .
Atferd under konstruksjon og ødeleggelse
Språk varierer i oppførselen mens konstruktøren eller destruktoren til et objekt kjører. Av denne grunn frarådes det vanligvis å ringe til virtuelle funksjoner i konstruktører.
I C ++ kalles "base" -funksjonen. Nærmere bestemt kalles den mest avledede funksjonen som ikke er mer avledet enn den nåværende konstruktørklassen. Hvis den funksjonen er en ren virtuell funksjon, oppstår det en udefinert oppførsel . Dette er sant, selv om klassen inneholder en implementering for den rene virtuelle funksjonen. En samsvarende C ++ - implementering er ikke nødvendig (og generelt sett ikke i stand) for å oppdage indirekte anrop til rene virtuelle funksjoner ved kompileringstidspunkt eller koblingstidspunkt . Noen kjøretidssystemer vil utstede en ren virtuell funksjonsanropsfeil når de møter et anrop til en ren virtuell funksjon ved kjøretid .
I Java og C#kalles den avledede implementeringen, men noen felt er ennå ikke initialisert av den avledede konstruktøren (selv om de er initialisert til standard nullverdier). Noen designmønstre , for eksempel det abstrakte fabrikkmønsteret , fremmer aktivt denne bruken på språk som støtter denne evnen.
Virtuelle ødeleggere
Objektorienterte språk administrerer vanligvis minnetildeling og avallokering automatisk når objekter blir opprettet og ødelagt. Noen objektorienterte språk tillater imidlertid at en egendefinert destruktormetode implementeres, om ønskelig. Hvis det aktuelle språket bruker automatisk minnestyring, er den egendefinerte destruktoren (vanligvis kalt en sluttbehandler i denne sammenhengen) som er den riktige for det aktuelle objektet. For eksempel, hvis et objekt av typen Wolf som arver Animal blir opprettet, og begge har egendefinerte destruktorer, vil den som kalles være den som er erklært i Wolf.
I kontekster med manuell minnestyring kan situasjonen være mer kompleks, spesielt i forhold til statisk utsendelse . Hvis et objekt av typen Wolf blir opprettet, men pekt på av en Animal -peker, og det er denne Animal -pekertypen som blir slettet, kan destruktoren som kalles faktisk være den som er definert for Animal og ikke den for Wolf, med mindre destruktoren er virtuell . Dette er spesielt tilfellet med C ++, der atferden er en vanlig kilde til programmeringsfeil hvis destruktører ikke er virtuelle.
Se også
- Abstrakt metode
- Arv
- Superklasse
- Virtuell arv
- Grensesnitt (objektorientert programmering)
- Komponentobjektmodell
- Virtuell metodebord