Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?
Autor: pekfos
Programowanie obiektowe, C++

Metody wirtualne

[lekcja] 13. Lekcja o metodach wirtualnych

Wprowadzenie

Przeanalizujmy poniższy kod:
C/C++
#include <iostream>

class Bazowa
{
public:
    void metoda()
    {
        std::cout << "Bazowa::metoda" << std::endl;
    }
};

class Pochodna
    : public Bazowa
{
public:
    void metoda() //Przesłaniamy metode z klasy bazowej
    {
        std::cout << "Pochodna::metoda" << std::endl;
    }
};

int main()
{
    Bazowa b;
    Pochodna p;
   
    b.metoda();
    p.metoda();
   
    Bazowa * bptr = & p; //rzutowanie w górę
    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:
C/C++
#include <iostream>

class B
{
public:
    virtual void a()
    {
        std::cout << "B::a\n";
    }
};

class P
    : public B
{
public:
    virtual void a();
};

void P::a() //tu już nie piszemy virtual
{
    std::cout << "P::a\n";
}

int main()
{
    B * b = new P;
   
    b->a(); //wskaźnik typu B, metoda z P
    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.
C/C++
class KlasaAbstrakcyjna
{
public:
    virtual void PrzeciazMnie() = 0; // =0 czyli czysto wirtualna
};
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.
C/C++
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:
C/C++
class B
{
    virtual void x() { }
};

class D
    : public B
{
    virtual void x() override { } //OK
};

class E
    : public B
{
    virtual void x() const override { } //Bład
};

class F
    : public B
{
    virtual void y() override { } //Błąd
};
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.
C/C++
int override = 1; // OK

Przykład

C/C++
#include <iostream>

class Ksztalt
{
public:
    virtual ~Ksztalt()
    { }
   
    virtual void rysuj() = 0; //każdy określony kształt musi dać sie narysować
};

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: [ ]
Poprzedni dokument Następny dokument
Konwersja w górę i rzutowanie w dół Zabranianie dziedziczenia i przesłaniania