Funcție virtuală - Virtual function
| Polimorfism |
|---|
| Polimorfism ad hoc |
| Polimorfism parametric |
| Subtipare |
În programarea orientată obiect , în limbaje precum C ++ și Object Pascal , o funcție virtuală sau o metodă virtuală este o funcție sau metodă moștenitoare și care poate fi înlocuită pentru care este facilitată distribuirea dinamică . Acest concept este o parte importantă a porțiunii de polimorfism (runtime) a programării orientate obiect (OOP). Pe scurt, o funcție virtuală definește o funcție țintă care urmează să fie executată, dar ținta ar putea să nu fie cunoscută în momentul compilării.
Majoritatea limbajelor de programare, cum ar fi Java , PHP și Python , tratează toate metodele ca virtuale în mod implicit și nu oferă un modificator pentru a schimba acest comportament. Cu toate acestea, unele limbi oferă modificatori pentru a preveni ca metodele să fie suprascrise de clasele derivate (cum ar fi cuvântul cheie final în Java și PHP ).
Scop
Conceptul funcției virtuale rezolvă următoarea problemă:
În programarea orientată obiect , atunci când o clasă derivată moștenește dintr-o clasă de bază, un obiect din clasa derivată poate fi menționat printr-un pointer sau referință de tipul clasei de bază în loc de tipul clasei derivate. Dacă există metode ale clasei de bază suprascrise de clasa derivată, metoda numită efectiv de o astfel de referință sau pointer poate fi legată (legată) fie „timpurie” (de către compilator), în funcție de tipul declarat de pointer sau referință, fie „târziu” (adică, prin sistemul de rulare al limbajului), în funcție de tipul real al obiectului la care se face referire.
Funcțiile virtuale sunt rezolvate „târziu”. Dacă funcția în cauză este „virtuală” în clasa de bază, implementarea celei mai derivate clase a funcției este apelată în funcție de tipul real al obiectului la care se face referire, indiferent de tipul declarat de pointer sau referință. Dacă nu este „virtuală”, metoda este rezolvată „devreme” și selectată în funcție de tipul declarat de pointer sau referință.
Funcțiile virtuale permit unui program să apeleze metode care nici măcar nu există în momentul compilării codului.
În C ++, metodele virtuale sunt declarate prin prelungirea cuvântului cheie la declarația funcției din clasa de bază. Acest modificator este moștenit de toate implementările acelei metode în clase derivate, ceea ce înseamnă că pot continua să se depășească reciproc și să fie legați târziu. Și chiar dacă metodele deținute de clasa de bază apelează metoda virtuală, ele vor chema în schimb metoda derivată. Supraîncărcarea apare atunci când două sau mai multe metode dintr-o clasă au același nume de metodă, dar parametri diferiți. Suprascrierea înseamnă a avea două metode cu același nume și parametri. Supraîncărcarea este denumită și potrivirea funcției și suprascrierea ca mapare dinamică a funcției.
virtual
Exemplu
De exemplu, o clasă de bază Animalar putea avea o funcție virtuală Eat. Subclasa Llamas-ar implementa Eatdiferit față de subclasă Wolf, dar se poate invoca Eatîn orice instanță de clasă denumită Animal și să obțină Eatcomportamentul subclasei specifice.
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;
}
};
Acest lucru permite unui programator să proceseze o listă de obiecte din clasă Animal, spunându-le fiecăruia pe rând să mănânce (apelând Eat), fără a fi nevoie să știe ce fel de animal poate fi în listă, cum mănâncă fiecare animal sau care este setul complet posibil tipurile de animale ar putea fi.
Putem vedea mai bine cum funcționează funcțiile virtuale implementând exemplul de mai sus în 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
}
Clase abstracte și funcții virtuale pure
O funcție virtuală pură sau o metodă virtuală pură este o funcție virtuală care trebuie să fie implementată de o clasă derivată dacă clasa derivată nu este abstractă . Clasele care conțin metode virtuale pure sunt denumite „abstracte” și nu pot fi instanțiate direct. O subclasă a unei clase abstracte poate fi instanțiată direct numai dacă toate metodele virtuale pure moștenite au fost implementate de acea clasă sau de o clasă părinte. Metodele virtuale pure au de obicei o declarație ( semnătură ) și nicio definiție ( implementare ).
De exemplu, o clasă de bază abstractă MathSymbolpoate oferi o funcție virtuală pură doOperation()și clase derivate Plusși Minusimplementare doOperation()pentru a furniza implementări concrete. Implementarea doOperation()nu ar avea sens în MathSymbolclasă, așa cum MathSymboleste un concept abstract al cărui comportament este definit exclusiv pentru fiecare tip dat (subclasă) de MathSymbol. În mod similar, o anumită subclasă de MathSymbolnu ar fi completă fără o implementare a
doOperation().
Deși metodele virtuale pure nu au de obicei nicio implementare în clasa care le declară, metodele virtuale pure în unele limbi (de exemplu, C ++ și Python) sunt permise să conțină o implementare în clasa lor declarantă, oferind un comportament de rezervă sau implicit pe care o clasă derivată îl poate delega , dacă este cazul.
Funcțiile virtuale pure pot fi, de asemenea, utilizate în cazul în care declarațiile metodei sunt utilizate pentru a defini o interfață - similar cu ceea ce cuvântul cheie de interfață din Java specifică în mod explicit. Într-o astfel de utilizare, clasele derivate vor furniza toate implementările. Într-un astfel de model de proiectare , clasa abstractă care servește ca o interfață va conține doar funcții virtuale pure, dar fără membri de date sau metode obișnuite. În C ++, utilizarea unor astfel de clase pur abstracte ca interfețe funcționează deoarece C ++ acceptă moștenirea multiplă . Cu toate acestea, deoarece multe limbi OOP nu acceptă moștenirea multiplă, ele oferă adesea un mecanism de interfață separat. Un exemplu este limbajul de programare Java .
Comportament în timpul construcției și distrugerii
Limbajele diferă în comportamentul lor în timp ce rulează constructorul sau distructorul unui obiect. Din acest motiv, apelarea funcțiilor virtuale în constructori este în general descurajată.
În C ++, funcția „bază” se numește. Mai exact, se numește cea mai derivată funcție care nu este mai derivată decât clasa constructorului curent. Dacă această funcție este o funcție virtuală pură, atunci apare un comportament nedefinit . Acest lucru este adevărat chiar dacă clasa conține o implementare pentru acea funcție virtuală pură. O implementare C ++ conformă nu este necesară (și, în general, nu este capabilă) pentru a detecta apelurile indirecte către funcții virtuale pure în timpul compilării sau al timpului de legătură . Unele sisteme de runtime vor emite o eroare de apel pură funcție virtuală atunci când întâmpină un apel către o funcție virtuală pură în timpul rulării .
În Java și C #, se numește implementarea derivată, dar unele câmpuri nu sunt încă inițializate de constructorul derivat (deși sunt inițializate la valorile lor zero implicite). Unele modele de proiectare , cum ar fi Modelul de fabrică abstractă , promovează în mod activ această utilizare în limbile care susțin această abilitate.
Distructori virtuali
Limbajele orientate spre obiecte gestionează de obicei alocarea și delocarea memoriei în mod automat atunci când obiectele sunt create și distruse. Cu toate acestea, unele limbaje orientate către obiecte permit implementarea unei metode de distrugere personalizate, dacă se dorește. Dacă limbajul în cauză folosește gestionarea automată a memoriei, destructorul personalizat (denumit în general un finalizator în acest context) care este numit este sigur că este cel potrivit pentru obiectul în cauză. De exemplu, dacă se creează un obiect de tip Lup care moștenește Animal și ambii au distructori personalizați, cel numit va fi cel declarat în Lup.
În contextele de gestionare manuală a memoriei, situația poate fi mai complexă, în special în ceea ce privește expedierea statică . Dacă un obiect de tip Wolf este creat, dar indicat de un indicator Animal, și este acest tip de indicator Animal care este șters, distructorul numit poate fi cel definit pentru Animal și nu cel pentru Lup, cu excepția cazului în care distructorul este virtual . Acesta este în special cazul C ++, unde comportamentul este o sursă obișnuită de erori de programare dacă distructorii nu sunt virtuali.
Vezi si
- Metoda abstractă
- Moştenire
- Superclasă
- Moștenirea virtuală
- Interfață (programare orientată obiect)
- Model de obiect component
- Tabel de metode virtuale