Yritän tässä opiskella hieman C++:n luokkia syvällisemmin. Ensin tutkiskelin virtuaalifunktioita ja huomasin, että ilman pointtereita niistä ei ole mitään hyötyä (tai oikeastaan vaikutusta).
class Yliluokka { public: void Tulosta() { cout << "yli" << endl; } }; class Alaluokka : public Yliluokka { public: void Tulosta() { cout << "ala" << endl; } }; int main() { Yliluokka a; Alaluokka b; a.Tulosta(); // tulostaa "yli" b.Tulosta(); // tulostaa "ala" }
On siis ihan sama, onko parent-luokan funktio määritelty virtualiseksi vai ei, lapsiluokan funktiota kutsutaan joka tapauksessa.
Kuitenkin, kun teen parent-luokan pointterin, joka osoittaa lapsiluokkaan, niin virtuaalifunktion avulla pystytään käyttämään lapsiluokan funktiota, vaikka pointteri onkin tyyppiä parent. Miksen sitten pääse käsiksi lapsiluokan muuttujiin, vaikka funktio toimii ihan oikein? Ymmärrän toki, että virtuaaliseksi määritellään vaan tuo yksi funktio, mutta sitä en ymmärrä, miten pääluokkapointteri pääsee käsiksi pelkästään lapsiluokan funktioon, mutta ei muuttujiin.
class Yliluokka { public: virtual void Tulosta() { cout << "yli" << endl; } }; class Alaluokka : public Yliluokka { public: void Tulosta() { cout << "ala: " << i << endl; } int i; Alaluokka(int x = 10) : i(x) { } }; int main() { Alaluokka a; Yliluokka *b = &a; Alaluokka *c = &a; b->Tulosta(); // tulostaa "ala: 10" c->Tulosta(); // tulostaa "ala: 10" // b->i ei toimi cout << endl; system("pause"); }
Osaako joku selittää, että miksi tuohon i-muuttujaan ei päästä käsiksi, mutta funktioon pääsee?
yliluokka ei sisällä muuttujaa 'i', joten sitä pitäisi kutsua jotenkin a.i, koska a ei ole pointteri. En löytänyt yhtään virtual sanaa tuosta koodistasi, joten mitä tarkoitat virtuaalisella funktiolla?
Normaalistihan yliluokassa esitellään funktio virtuaaliseksi, jonka perivä luokka toteuttaa jotenkin mielekkäästi.
Alemmassa esimerkissä Yliluokan funktio on määritetty virtuaaliseksi. Mietin vain sitä, että miten Yliluokan pointteri, joka osoittee Alaluokkaan pystyy käyttämään alaluokan funktiota, mutta muuttujiin ei päästä käsiksi. Tuntuu jotenkin tyhmältä rajoitukselta. Vähän sama kun pystyisit tekemään pikakuvakkeen jonkun kansion alakansioon, mutta et itse tiedostoihin.
Tämän hämmästyksen aiheuttaa se, että kuitenkin tuossa kun kutsutaan b->Tulosta(); eli parent-luokan pointteria, joka osoittaa alaluokkaan, niin ohjelma pystyy tulostamaan alaluokan arvoja, mutta arvoihin ei päästä manuaalisesti käsiksi.
Luinkin tuota ylempää koodia pahoittelut. Minusta tuo ion kyllä selkeää siinä mielessä, että eihän se yliluokka oikeastaan tiedä siitä perittävästä luokasta mitään. Toisinpäin tuo taas on ihan selkeää kun perivä luokka saa perittävän luokan.
Tämän virtuaalisuuden ideana on juuri, että on monta luokkaa, jotka näyttävät ulkoisesti samalta (Yliluokka) mutta toimivat eri tavalla (Alaluokka). Et voi hakea Alaluokan muuttujaa Yliluokka-osoittimen kautta, koska Yliluokka ei sisällä sellaista.
Jos välttämättä haluat päästä muuttujaan käsiksi, sinun pitää muuntaa osoitin dynamic_cast-muunnoksella ja tarkistaa, että muunnos onnistui.
#include <iostream> struct Yliluokka { virtual ~Yliluokka() { } }; struct Alaluokka: Yliluokka { int i; }; void tulosta(Yliluokka* y) { Alaluokka* a = dynamic_cast<Alaluokka*>(y); if (a) { std::cout << "Alaluokka, i = " << a->i << "." << std::endl; } else { std::cout << "Ei ole Alaluokka." << std::endl; } } int main() { Yliluokka y; Alaluokka a; a.i = 123; tulosta(&y); // Ei ole Alaluokka. tulosta(&a); // Alaluokka, i = 123. }
punppis kirjoitti:
Tämän hämmästyksen aiheuttaa se, että kuitenkin tuossa kun kutsutaan b->Tulosta(); eli parent-luokan pointteria, joka osoittaa alaluokkaan, niin ohjelma pystyy tulostamaan alaluokan arvoja, mutta arvoihin ei päästä manuaalisesti käsiksi.
(Alla oleva ei ole absoluuttisen tarkka selostus tapahtumista, mutta tarjoaa ajattelumallin)
Kaikilla luokan jäsenfunktioilla (tai metodilla, miten vain haluat) on implisiittisenä argumenttina this
, joka on joko tyyppiä luokka*
tai const luokka*
riippuen siitä, minkälaiselle oliolle kyseistä funktiota kutsuttiin (non-const, const, non-const&, jne). Virtuaalisten funktioiden tapauksessa tämä this
on muunnettu aliluokan tyyppiin.
Luokan muuttujat kertovat tavallaan vain muuttujan etäisyyden (offset) muistissa luokan "alusta" (this
). Kääntäjän täytyy tietää luokan tyyppi, jotta etäisyys voidaan laskea, voihan esimerkiksi seuraava ehto olla tosi:
A *a = new A; B *b = dynamic_cast<B*>(a); A != 0 && B != 0 && A != B // eli cast voi muuttaa osoittimen arvoa riippuen tyyppien asettelusta // muistissa; moniperinnän yhteydessä näin varsinkin käy
Aihe on jo aika vanha, joten et voi enää vastata siihen.