Dlaczego nie mogę wywołać metody, której nie ma w interfejsie?

0

Stworzyłem z pozoru bardzo łatwy kod składający się z pięciu klas. Moim zamiarem jest doszlifowanie swojej wiedzy na tematy wielobazowego dziedziczenia.
Klasa animal jest klasą główną, której typu wskaźnik stworzyłem. Dziedziczą z niej klasy bird i insects. Z klasy insects dziedziczą kolejne dwie klasy, których instancje stworzyłem. Następnie do wskaźnika typu animal przypisałem adresy różnych obiektów, na których wywołałem różne metody. Wszystko działa do momentu wywołania metody aboutMe() na instancji klasy spider. Kompilator pokazuje błąd o treści:

main.cpp: In function ‘int main()’:
main.cpp:40:20: error: ‘class animal’ has no member named ‘aboutMe’
     animalPointer->aboutMe();


, którego raczej na język polski tłumaczyć nie trzeba. I tutaj mam pytanie. Czy da się zrobić aby wskaźnik animalPointer mógł wskazywać na obiekt każdej klasy dziedziczącej z niego, nawet tych, które nie robią tego bezpośrednio.
Rozwiązanie polegające na umieszczeniu metody aboutMe() w definicji klasy animal wypróbowałem, ale nie wchodzi ono w grę, bo nie po to stworzono potężny mechanizm dziedziczenia, aby odwalać taką fuszerkę ;).

Kod ten napisałem aby zilustrować mój problem. Nie czepiajcie się proszę o sposób i styl w jakim został on przedstawiony :).

#include <iostream>
using namespace std;

class animal {
    public:
        virtual void printSomeData() = 0;
};

class insects : public animal{
    protected:
        virtual void printSomeData() {
            cout << "Insect Here" << endl;                
        }
};

class spiders : public insects {
    public:     
        void aboutMe() {
            cout << "I am ugly!!!";
        }
};

class grasshopper : public insects {
    public:     
        void aboutMe() {
            cout << "I am green!!!";
        }
};

class bird : public animal {
    public:
        virtual void printSomeData() {
            cout << "Bird Here " << endl;             
        }
};

int main() {
    animal * animalPointer;
    
    bird Pelican;
    animalPointer = &Pelican;
    animalPointer->printSomeData();

    spiders bigBadSpider;
    animalPointer = &bigBadSpider;
    animalPointer->printSomeData();
    animalPointer->aboutMe(); // tutaj wywala błąd. 

    return 0;
}

3

Przede wszystkim, fuszerką jest właśnie łamanie zasady podstawienia Liskov. Wskaźnik na klasę bazową pozwala na to, co umie klasa bazowa. I tyle.

W tym momencie masz trzy możliwości:

  1. static_cast<spiders*>(animalPointer)->aboutMe() lub static_cast<spiders&>(*animalPointer).aboutMe()
  2. dynamic_cast<spiders*>(animalPointer)->aboutMe()
  3. dynamic_cast<spiders&>(*animalPointer).aboutMe()

Dlaczego opcje 2. i 3. są rozdzielone, a te z 1. nie? Bo mają różną semantykę.

Opcja 1. może być użyta dla klas niepolimorficznych i polimorficznych, i nie dokonuje sprawdzenia dynamicznego typu obiektu. To oznacza, że jak zrobisz fałszywy cast (np. na bird) to masz UB.
Opcja 2. tylko typy polimorficzne, dokonuje dynamicznego sprawdzenia typu obiektu, zwraca nullptr jak cast jest niepoprawny (ale w tym przypadku zamiast sprawdzić zwrócony wskaźnik wywołuję na nim metodę, co oznacza UB)
Opcja 3. tylko typy polimorficzne, dokonuje dynamicznego sprawdzenia typu obiektu, jeśli cast jest niepoprawny rzuca std::bad_cast.

1 użytkowników online, w tym zalogowanych: 0, gości: 1