Ogólna odpowiedź co do rzutowania brzmi:
1. Z klasy pochodnej do bazowej z użyciem static_cast<Bazowa>.
2. Z klasy bazowej na pochodną nie można (*)
Być może zaskakujące jest to 2. Na gruncie teorii OOD (ang. Object Oriented Design), łatwe rzutowanie nie powinno się powieść bo typ ogólny rzutowany jest na specjalizację a dla specjalizacji jest on.. zbyt ogólny :) Ale wyjaśnia się to także jeśli zerknąć na rozmiar obydwu klas.
#include <iostream>
struct Base {
Base(int value1, double value2)
: value1{value1}, value2{value2} {}
void getInfo() const {
std::cout << "value1 = " << value1 << " value2 = " << value2;
}
private:
int value1;
double value2;
};
struct Derived: public Base {
Derived(int value1, double value2, double value3)
: Base(value1, value2), value3{value3} {}
void getInfo() const {
Base::getInfo();
std::cout << " value3 = " << value3;
}
private:
double value3;
};
int main() {
Base bs1 = static_cast<Base>(Derived(1, 2.3, 3.55));
bs1.getInfo();
std::cout << '\n';
// Derived dr1 = ???_???<Derived>(Base(1, 2.3));
std::cout << "Base size = " << sizeof(bs1) << '\n'
<< "Derived size = " << sizeof(Derived) << '\n';
}
Jak widzisz, klasa pochodna może być (i w mojej implementacji jest) większa lub .. taka sama (o ile nie dochodzą nowe atrybuty). Stąd nie da się jej "upchnąć" w przestrzeni pamięci przeznaczonej na klasę bazową bez straty. Zachowuje się jak klasa bazowa bo została "przycięta" do rozmiaru bazowej.
Klasy bazowej w przestrzeni pamięci klasy pochodnej z kolei nie da się umieścić. W przeciwnym wypadku powstaje wiele pytań. Np. pytanie jak ma zachować dana metoda z klasy bazowej. Ma działać jak pochodna czy może jak bazowa? Jak pochodna nie może bo nie ma kompletnych danych (ma dane bazowej a nie ma pochodnej). Jak bazowa nie może bo ... ma być pochodną... A co zrobić z metodami obecnymi w klasie pochodnej jeśli były by dodatkowe? Przecież jeśli pozwolić na rzutowanie to.. nie było by ich! Lepiej więc nie pozwolić i tyle.
(*) pomijam tu fakt brutalnych rozwiązań z udowodnieniem że się da poprzez wskaźnik...
Derived dr1 = *(reinterpret_cast<Derived*>(static_cast<void *>(&bs1)));
...nie sądzę żeby to było kształcące a i do wskaźników dojdę.
Nie czas i miejsce także na to by wyciągać jakieś wnioski z teorii typów czy modelu ułożenia klasy w pamięci. Jeśli chcesz tu polecę literaturę.
Trochę inaczej jest jeśli decydujesz się na wskaźniki lub referencje:
1. Ze wskaźnika klasy pochodnej do klasy bazowej
2. Ze wskaźnika klasy bazowej do klasy pochodnej z użyciem static_cast
#include <iostream>
struct Base {
Base(int value1, double value2): value1{value1}, value2{value2} {}
void getInfo() const {
std::cout << "value1 = " << value1 << " value2 = " << value2;
}
private:
int value1;
double value2;
};
struct Derived: public Base {
Derived(int value1, double value2, double value3)
: Base(value1, value2), value3{value3} {}
void getInfo() const {
Base::getInfo();
std::cout << " value3 = " << value3;
}
private:
double value3;
};
int main() {
Derived dr1{1, 2.3, 3.55};
Base * bs1Ptr = &dr1;
bs1Ptr->getInfo();
std::cout << '\n';
Base bs1{1, 2.3};
Derived * dr1Ptr = static_cast<Derived *>(&bs1);
dr1Ptr->getInfo();
std::cout << '\n';
}
Proste jeśli by rozważyć informację czym jest wskaźnik. Z tego powodu kompilator nie ma pytań jeśli chodzi o rzutowanie ze wskaźnika pochodnej na wskaźnik bazowej. Także spowoduje to że wołany będzie kontekst metod z klasy bazowej a nie pochodnej! No ale ... chciałeś!
W przypadku wskaźnika z pochodnej na bazową znów jest problem. Tu jest niebezpieczeństwo bo nie wiadomo jaką wartość ma value3 z klasy pochodnej więc ma ... śmieci bo ten atrybut nie był inicjowany. No ale metoda jest z klasy pochodnej a nie ze źródła rzutowania (bazowej). Więc jest prawie .. prawie... :/ Tu także pytanie (do samodzielnego znalezienia) skąd i czy zawsze można być pewnym że metoda będzie z klasy pochodnej a nie z bazowej.
Pozostaje więc pytanie co zrobić jeśli absolutnie jesteś pewien że chcesz mieć takie rzutowania.... wszystkie! Można decydować się na konwersje. Te można zrobić na kilka sposobów:
1. Dokonując implementacji odpowiednich konstruktorów.
2. Dokonując implementacji funkcji (lub metod) konwersji.
3. Jeszcze inne...
Była luka z bazowej na pochodną. Teraz ją "załatam".
Poprzez konstruktor kopiujący czyli "u źródła"...
#include <iostream>
struct Base {
Base(int value1, double value2): value1{value1}, value2{value2} {}
void getInfo() const {
std::cout << "value1 = " << value1 << " value2 = " << value2;
}
private:
int value1;
double value2;
};
struct Derived: public Base {
Derived(int value1, double value2, double value3)
: Base(value1, value2), value3{value3} {}
// Tu konwersja poprzez konstruktor kopiujący
Derived(const Base& src)
: Base(src), value3{} {}
void getInfo() const {
Base::getInfo();
std::cout << " value3 = " << value3;
}
private:
double value3;
};
int main() {
Derived dr1 = Base(1, 2.3);
dr1.getInfo();
std::cout << '\n';
Przez funkcję konwersji "czyli u celu"...
#include <iostream>
struct Derived;
struct Base {
Base(int value1, double value2);
// Tu konwersja poprzez operator konwersji
operator Derived();
void getInfo() const;
private:
int value1;
double value2;
};
struct Derived: public Base {
Derived(int value1, double value2, double value3);
void getInfo() const;
private:
double value3;
};
Base::Base(int value1, double value2): value1{value1}, value2{value2} {}
// I tenże...
Base::operator Derived() {
return Derived(value1, value2, 0);
}
void Base::getInfo() const {
std::cout << "value1 = " << value1 << " value2 = " << value2;
}
Derived::Derived(int value1, double value2, double value3)
: Base(value1, value2), value3{value3} {}
void Derived::getInfo() const {
Base::getInfo();
std::cout << " value3 = " << value3;
}
int main() {
Derived dr1 = Base(1, 2.3);
dr1.getInfo();
std::cout << '\n';
}
Niezła ekwilibrystyka z implementacjami... Już samo to intuicyjnie wskazuje że coś jest robione "pod włos".
Takie działanie można osiągnąć także z użyciem innych metod (zaprzyjaźniania, semantyki przenoszenia...)
Radzę także doczytać czy i co z tego co tu napisałem jest UB a co nie jest :)
Z kolei wskaźniki (czy referencje) i metody wirtualne oraz polimorfizm, to zupełnie inne zagadnienie. Tu było pytanie o rzutowanie a nie zachowania polimorficzne.
Podtrzymuję także to co napisałem w komentarzu. Konwersja w górę relacji dziedziczenia (czyli generalizacja) jest patologiczna i prędzej czy później będzie się mściła.
Nie odpowiedziałem także w pełni bo i pytanie było ogólne. No i nie w jednym zdaniu... :)