Wprowadzenie
Przeanalizujmy poniższy kod:
#include <iostream>
class Bazowa
{
public:
void metoda()
{
std::cout << "Bazowa::metoda" << std::endl;
}
};
class Pochodna
: public Bazowa
{
public:
void metoda()
{
std::cout << "Pochodna::metoda" << std::endl;
}
};
int main()
{
Bazowa b;
Pochodna p;
b.metoda();
p.metoda();
Bazowa * bptr = & p;
bptr->metoda();
}
Bazowa::metoda
Pochodna::metoda
Bazowa::metoda
Pierwsze dwie linie standardowego wyjścia programu nie powinny nikogo zdziwić. Co z trzecią? Została wywołana metoda klasy bazowej, pomimo tego, że
bptr
w rzeczywistości wskazuje na obiekt klasy pochodnej! Dzieje się tak, ponieważ jedyną informacją o typie obiektu, jest typ wskaźnika.
Metody wirtualne
Określenie metody jako wirtualnej sprawia, że będzie wywoływana w wersji odpowiadającej rzeczywistemu typowi:
#include <iostream>
class B
{
public:
virtual void a()
{
std::cout << "B::a\n";
}
};
class P
: public B
{
public:
virtual void a();
};
void P::a()
{
std::cout << "P::a\n";
}
int main()
{
B * b = new P;
b->a();
delete b;
}
P::a
Metody czysto wirtualne
Metodę wirtualną można zadeklarować jako
czysto wirtualną (ang.
pure virtual), czyli taką, która powinna być przesłonięta w klasie pochodnej. Jeżeli w klasie jest przynajmniej jedna metoda czysto wirtualna, to jest to klasa
abstrakcyjna. Jeżeli klasa pochodna nie przesłoni wszystkich metod czysto wirtualnych, również jest klasą abstrakcyjną. Nie można utworzyć obiektów klasy abstrakcyjnej.
class KlasaAbstrakcyjna
{
public:
virtual void PrzeciazMnie() = 0;
};
Nie trzeba definiować metod czysto wirtualnych, ponieważ dla każdego istniejącego obiektu klasy pochodzącej od klasy abstrakcyjnej te metody są przesłonięte.
Metod wirtualnych używa się, by narzucić ogólny interfejs całej hierarchii dziedziczenia. Należy zauważyć, że jeśli używamy wskaźnika na klasę bazową, to każdy, prawidłowy obiekt, wskazywany przez ten wskaźnik, jest
co najmniej obiektem klasy bazowej. Może to być obiekt klasy pochodnej, dziedziczącej bezpośrednio po klasie bazowej, a może to być obiekt klasy pochodnej, będącej kilkadziesiąt pięter niżej w hierarchii dziedziczenia, a, jeśli klasa bazowa nie jest abstrakcyjna, to może to być też, po prostu, obiekt klasy bazowej. Jedyny zestaw metod, którego możemy używać, to ten, z klasy bazowej. Jeśli potrzeba metod z klas pochodnych, trzeba się przebijać w dół rzutowaniami.
Wirtualny destruktor
Niewirtualny destruktor może powodować wycieki pamięci i niezdefiniowane zachowanie programu, ponieważ będzie wywoływany ten, który odpowiada typowi wskaźnika, a nie ten, który odpowiada rzeczywistemu typowi obiektu.
class Klasa
{
public:
virtual ~Klasa();
};
Override (C++11)
W C++11 wprowadzono słowo
override dla podkreślenia, że dana metoda w klasie pochodnej przesłania metodę wirtualną z klasy bazowej. Słowo
override umieszcza się przy deklaracjach metod tak, jak
const przy metodach stałych:
class B
{
virtual void x() { }
};
class D
: public B
{
virtual void x() override { }
};
class E
: public B
{
virtual void x() const override { }
};
class F
: public B
{
virtual void y() override { }
};
W klasie
E dodanie
const zmieniło deklarację, więc tworzona jest całkiem nowa metoda, a nie przesłaniana stara. Dzięki
override kompilator sygnalizuje ten błąd. W klasie
F nazwa się zmieniła, więc też nie dochodzi do przesłaniania.
override nie jest słowem kluczowym i nie jest nazwą zarezerwowaną. Ma swoje specjalne znaczenie tylko w powyższym przypadku.
|
Przykład
#include <iostream>
class Ksztalt
{
public:
virtual ~Ksztalt()
{ }
virtual void rysuj() = 0;
};
class Kolo
: public Ksztalt
{
public:
virtual void rysuj()
{
std::cout << "Rysuje kolo: ( )" << std::endl;
}
};
class Kwadrat
: public Ksztalt
{
public:
virtual void rysuj()
{
std::cout << "Rysuje kwadrat: [ ]" << std::endl;
}
};
int main()
{
Ksztalt * a = new Kolo, * b = new Kwadrat;
a->rysuj();
b->rysuj();
delete a;
delete b;
}
Rysuje kolo: ( )
Rysuje kwadrat: [ ]