1 / 122

Jani Rönkkönen jani.ronkkonen@lut.fi

Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, periytymisen huomioon ottamisesta, operaattoreiden uudelleenmäärittely. Jani Rönkkönen jani.ronkkonen@lut.fi Luennot muokattu Sami Jantusen ja Kari Smolanderin aikaisempien vuosien luennoista. Sisältö. Rajapinnoista Esimerkki

aerona
Télécharger la présentation

Jani Rönkkönen jani.ronkkonen@lut.fi

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Olio-ohjelmoinnin perusteetluento 5: Rajapinnoista, periytymisen huomioon ottamisesta, operaattoreiden uudelleenmäärittely Jani Rönkkönen jani.ronkkonen@lut.fi Luennot muokattu Sami Jantusen ja Kari Smolanderin aikaisempien vuosien luennoista

  2. Sisältö • Rajapinnoista • Esimerkki • Abstrakti luokka • Puhdas virtuaalinen funktio • Rajapinnan käytöstä • Komponentteihin jaottelusta • Periytymisen vaikutus olion luontiin ja tuhoamiseen • Muodostimet ja periytyminen • Purkajat ja periytyminen • Perityn luokan eri tyypit • Aliluokan ja kantaluokan suhde • Tyyppimuunnokset • Olioiden sijoitus ja kopiointi • Olioiden kopiointi • Olioiden sijoitus • Puhdasoppinen luokka • Serialisaatio • Operaattoreiden uudelleenmäärittely • Ystäväfunktiot • Ystäväluokat • Yhteenveto

  3. Tarina… • Olipa kerran insinööri, joka työskenteli palvelimen parissa • Palvelimen oli tarkoitus pystyä kommunikoimaan lukuisten erilaisten asiakasohjelmien kanssa

  4. Tarina jatkuu…. • Palvelimen ja asiakasohjelmien väliseksi kommunikointitavaksi valittiin 2-suuntainen putki

  5. Tarina jatkuu…. • Pian hän huomasi, että 2-suuntaisen putken käyttö ei ollut ihan helppoa • 2-suuntaisen liikenteen hallinta vaati synkronointitaitoja • Putkesta “tipoittain” lukeminen tukkeutti putken • Putkia piti tarjota sitä mukaan kun asiakasohjelmat ottivat palvelimeen yhteyttä Säikeistyksen hallinta • Kaikki asiakasohjelmat eivät olleet tiedossa ja niitä tehtiin muiden henkilöiden voimin.kommunikointimekanismi ei saa olla sen käyttäjälle vaikeaa! • …

  6. Tarina jatkuu… • Niinpä hän päätti soveltaa yhtä olioajattelun perusajatuksista: Tiedon piilottamista Hän loi kirjaston, joka piilotti putken monimutkaisuuden (synkronointi, säikeiden hallinta, viestien puskurointi, ym.)

  7. Tarina jatkuu…. • Ja sen putken käyttö oli niin mukavaa… • Viis hankalista hallinnoitiasioista. • Riitti kun avaa ja lähettää…. PipeServer create() send() disconnect() getNumberOfClients() PipeClient open() send() disconnect()

  8. Tarina jatkuu… • Entäpä viestin vastaanottaminen? • Olisipa mukavaa kun putki osaisi itse kutsua asiakkaan messageArrived –funktiota kun viesti on saapunut • Ainoa asia mitä asiakkaan tarvitsisi tehdä on toteuttaa messageArrived funktio, mihin määriteltäisiin viestin saapumisesta aiheutuva toimintalogiikka.

  9. Ja sitten tarinan kysymys! Mistä putkikirjasto voi tietää ketä kutsua kun viesti saapuu???

  10. Ratkaisu? • Mitä jos kukin putkea käyttävä olio esittelee itsensä ja antaa osoittimen itseensä. • Putki voisi sitten jatkossa vain käyttää osoitinta ja kutsua sen avulla käyttäjäolion messageArrived-funktiota

  11. Taustatietoa • Jokaisella oliolla on olemassa osoitinmuuttuja this, joka osoittaa itseensä • this –osoitin on aina samaa tyyppiä kun siihen liittyvä osoitinkin aivan kun this olisi määritelty luokassa tyyliin: MyClass *this; This

  12. Lähdetään ratkaisemaan ongelmaa • Oletetaan että putkea käyttävä olio identifioi itsensä kun se avaa putken: PipeClient _myPipe; _myPipe.open(this); • Nyt putki tietää sitä käyttävän olion osoitteen. Ratkaisiko tämä ongelman?

  13. Vielä ongelmia • Okei, nyt tiedetään putkea käyttävän olion osoite. Se ei kuitenkaan riitä • Mistä ihmeestä putkikirjasto tietää minkä tyyppinen annettu osoitin on? • Eihän se muuten voi kutsua annettua oliota

  14. Heureka! • Mitäs jos vaadittaisiin, että kaikki putken käyttäjäluokat periytyvät MessageReader-luokasta • Silloinhan tiedettäisiin, että asiakkaat ovat aina myös tyyppiä MessageReader! • putkikirjastoon voitaisiin siis kirjoittaa seuraava koodipätkä: //Luokan määrittelyssäMessageReader *_addressOfClient;...//Putkea avattaessaPipeClient::open(MessageReader *client){ _addressOfClient=client;}...//jossain päin missä luetaan putkea_addressOfClient->messageArrived();

  15. Mitä taas tuli tehtyä? • Loimme luokan (MessageReader), joka ei itse tee yhtään mitään. • Tämähän on ihan selvä rajapintaluokka! • Ne luokat jotka haluavat tarjota rajapintaluokan määrittelemiä palveluita perivät itsensä rajapintaluokasta MessageReader virtual void messageArrived(CArchive *message) = 0; PipeUser PipeClient *myPipe

  16. Rajapintaluokista • Rajapintaluokat ovat yleensä abstrakteja luokkia • Eivät sisällä mitään muuta kuin rajapinnan määrittelyjä • Ei siis jäsenmuuttujia eikä jäsenfunktioiden toteutuksia • Jossain oliokielissä (kuten Java) tällaisille puhtaille rajapinnoille on oma syntaksinsa eikä niitä silloin varsinaisesti laskeata edes luokiksi

  17. Abstrakti luokka • Mikä hyvänsä luokka, jossa on yksi tai useampi puhdas virtuaalifunktio, on abstrakti luokka eikä sen tyyppisiä olioita voi luoda. • Puhdas virtuaalifunktio kertoo luokan käyttäjälle kaksi asiaa: • Luokan tyyppistä oliota ei voida luoda vaan siitä pitää periyttää aliluokkia • Jokainen puhdas virtuaalifunktio pitää korvata uudella funktiolla abstraktista luokasta periytetyssä luokassa

  18. Puhdas virtuaalifunktio • Abstrakti luokka tehdään käyttämällä puhtaita virtuaalifunktioita (pure virtual function) • Virtuaalifunktio on puhdas, jos se alustetaan nollalla, esimerkiksi:virtual void Piirra () = 0;

  19. Puhtaan virtuaalifunktion ohjelmointi • Yleensä abstraktissa kantaluokassa olevalle puhtaalle virtuaalifunktiolle ei kirjoiteta funktion määrittelyä • Koska luokan tyyppisiä olioita ei voida koskaan luoda, niin ei ole mitään syytä ohjelmoida luokkaan mitään toiminnallisuuttakaan. • Abstrakti luokka on siitä periytetyille luokille yhteinen käyttörajapinta • On toki mahdollista tehdä kantaluokkaan puhtaalle virtuaalifunktiolle toteutus • Sitä kutsutaan silloin lapsiluokista käsin. • Esim. se toiminnallisuus, joka on yhteistä kaikille lapsille siirretään kantaluokkaan.

  20. Milloin kannattaa käyttää abstrakteja luokkia? • Ei yksiselitteistä vastausta • Päätös tehtävä sen perusteella onko luokan abstraktisuudesta jotain hyötyä • Esimerkki: Eläin-luokka kannattaa olla abstrakti, mutta Koira-luokka ei, jotta ohjelmassa voidaan käyttää koira-olioita • Toisaalta: Jos ohjelmassa simuloidaan kenneliä, koira-luokka kannattaa jättää abstraktiksi ja periyttää siitä erirotuisia koiria. Käytettävä abstraktiotaso määräytyy sen mukaan, kuinka hienojakoisesti ohjelman luokat pitää erotella toisistaan

  21. Muistatko viel?Moniperiytyminen -käyttökohteita • Rajapintojen yhdistäminen. • Halutaan oman luokan toteuttavan useiden eri rajapintojen toiminnallisuus • Luokkien yhdistäminen. • Halutaan esimerkiksi käyttää hyväksi muutamaa yleiskäyttöistä luokkaa oman luokan kehitystyössä. • Luokkien koostaminen valmiista ominaisuuskokoelmista. Esimerkki: • Kaikki lainaamiseen liittyvät toiminnot on kirjoitettu Lainattava-luokkaan. • Vastaavasti kaikki tuotteen myymiseen liittyvät aisat ovat luokassa Myytävät. • Voimme luoda KirjastonKirja –luokan perimällä sen Kirja-kantaluokasta ja maustamalla sen Lainattava-luokasta saaduilla ominaisuuksilla • Voimme yhtä lailla luoda KaupallinenCD-ROM-luokan perimällä sen CD-ROM kantaluokasta ja ottaa käyttöön ominaisuudet Myytävä-luokasta

  22. Rajapintaluokat ja moniperiytyminen • Jos abstraktit kantaluokat sisältävät ainoastaan puhtaita virtuaalifunktioita • Moniperiytymisen käytöstä ei aiheudu yleensä ongelmia. • Jos moniperiytymisessä kantaluokat sen sijaan sisältävät myös rajapintojen toteutuksia ja jäsenmuuttujia • Moniperiytyminen aiheuttaa yleensä enemmän ongelmia kuin ratkaisee.

  23. Rajapinnoista • Rajapintojen käyttö ja toteutuksen kätkentä on yksi tärkeimmistä ohjelmistotuotannon perusperiaatteista • Tästä huolimatta sen tärkeyden perustelu uraansa aloittelevalle ohjelmistoammattilaiselle on vaikeaa • Merkityksen tajuaa yleensä itsestäänselvyytenä sen jälkeen, kun on osallistunut tekemään niin isoa ohjelmistoa, ettei sen sisäistä toteutusta pysty kerralla hallitsemaan ja ymmärtämään yksikään ihminen.

  24. Komponentteihin jaottelusta • Isoissa ohjelmissa komponenttijako helpottaa huomattavasti kehitystyötä. • Yksittäinen ohjelmoijan ei enää tarvitse jatkuvasti hahmottaa kokonaisuutta • Kehittäjä voi enemmän keskittyä oman komponenttiensa vastuiden toteutukseen.

  25. Missä mennään? • Rajapinnoista • Esimerkki • Abstrakti luokka • Puhdas virtuaalinen funktio • Rajapinnan käytöstä • Komponentteihin jaottelusta • Periytymisen vaikutus olion luontiin ja tuhoamiseen • Muodostimet ja periytyminen • Purkajat ja periytyminen • Perityn luokan eri tyypit • Aliluokan ja kantaluokan suhde • Tyyppimuunnokset • Olioiden sijoitus ja kopiointi • Olioiden kopiointi • Olioiden sijoitus • Serialisaatio • Operaattoreiden uudelleenmäärittely • Ystäväfunktiot • Ystäväluokat • Yhteenveto • Puhdasoppinen luokka • Kertaus

  26. Sä muistatko viel? Muodostimen käyttö periytymisen yhteydessä Isäluokan muodostinta kutsutaan aina!* CPoodle.cpp CPoodle::CPoodle(int x, string y) : CDog (x,y) { cout << “Tuli muuten tehtyä puudeli" << endl; } Normaali muodostin Luokkia perittäessä on muodostimien ja purkajien käytössä on paljon huomioitavaa

  27. Periytyminen ja muodostimet • Jokainen aliluokan olio koostuu kantaluokkaosasta (tai osista) sekä aliluokan lisäämistä laajennuksista  Aliluokalla on oltava oma muodostimensa. • Mutta miten pitäisi hoitaa kantaluokkien alustus? Mammal int weight giveBirth( ) Land-Mammal int numLegs Dog boolean rabid SheepDog

  28. Periytyminen ja muodostimetVastuut • Aliluokan vastuulla on: • Aliluokan mukanaan tuomien uusien jäsenmuuttujien ja muiden tietorakenteiden alustaminen. • Em. vastuita varten aliluokkiin toteutetaan oma(t) muodostimet/muodostin • Kantaluokan vastuulla on: • Pitää huoli siitä, että aliluokan olion kantaluokkaosa tulee alustetuksi oikein, aivan kun se olisi irrallinen kantaluokan olio • Tämän alustuksen hoitavat aivan normaalit kantaluokan muodostimet

  29. Periytyminen ja muodostimetParametrit? • Miten taataan että kaikki muodostimet saavat tarvitsemansa parametrit? • Päivänselvää aliluokalle. Sitä luodessahan kutsutaan aliluokan itse määrittelemiä muodostimia • Kantaluokan parametrien saannin takaamiseksi C++:n tarjoama ratkaisu on, että aliluokan muodostimen alustuslistassa kutsutaan kantaluokan muodostinta ja välitetään sille tarvittavat parametrit CPoodle.cpp CPoodle::CPoodle(int x, string y) : CDog (x,y) { cout << “Tuli muuten tehtyä puudeli" << endl; }

  30. Entä jos? • Jos aliluokan muodostimen alustuslistassa ei kutsuta mitään kantaluokan muodostinta: • Kääntäjä kutsuu automaattisesti kantaluokan oletusmuodostinta (joka ei siis tarvitse parametreja) • Tällainen ratkaisu ei läheskään aina johda toivottuun tulokseen Muista siis kutsua aliluokan muodostimessa kantaluokan muodostinta itse!

  31. Muodostimien suoritusjärjestys • Huipusta alaspäin • Olio ikäänkuin rakentuu vähitellen laajemmaksi ja laajemmaksi. • Näin taataan se, että aliluokan muodostin voi jo turvallisesti käyttää kantaluokan jäsenfunktioita.

  32. Periytyminen ja purkajat • Alustamisen tapaan myös olion siivoustoimenpiteet vaativat erikoiskohtelua luokan “kerrosrakenteen” vuoksi • Purkajien vastuut jaettu samalla lailla kuin muodostimienkin • Kantaluokan tehtävänä on siivota kantaluokkaolio sellaiseen kuntoon, että se voi rauhassa tuhoutua • Aliluokat puolestaan siivoavat periytymisessä lisätyt laajennusosat tuhoamiskuntoon

  33. Purkajien suoritusjärjestys • Purkajia kutsutaan päinvastaisessa järjestyksessä kuin muodostimia • Ensin kutsutaan aliluokan purkajia ja siitä siirrytään periytymishierakiassa ylöspäin • Näin varmistetaan se, että aliluokan purkajassa voidaan vielä kutsua kantaluokkien toiminnallisuutta

  34. Esimerkki Jotain pahasti pielessä! -Mitä? • Mammal *myMammal; • myMammal = new SheepDog(); • ... • //koodia missä käytetään SheepDog-luokkaa • ... • delete myMammal; Mammal int weight ~Mammal ( ) Land-Mammal int numLegs Dog boolean rabid SheepDog

  35. Esimerkki • Vain kantaluokan purkajaa kutsutaan! Kuinka korjata tilanne? • Mammal *myMammal; • myMammal = new SheepDog(); • ... • //koodia missä käytetään SheepDog-luokkaa • ... • delete myMammal; Mammal int weight ~Mammal ( ) Land-Mammal int numLegs Dog boolean rabid SheepDog

  36. Virtuaalipurkaja • Jos luokasta peritään muita luokkia, muista aina määritellä purkaja virtuaaliseksi! • Ei haittaa vaikka purkaja on eri niminen lapsiluokassa.

  37. Missä mennään? • Rajapinnoista • Esimerkki • Abstrakti luokka • Puhdas virtuaalinen funktio • Rajapinnan käytöstä • Komponentteihin jaottelusta • Perinnän vaikutus olion luontiin ja tuhoamiseen • Muodostimet ja periytyminen • Purkajat ja periytyminen • Perityn luokan eri tyypit • Aliluokan ja kantaluokan suhde • Tyyppimuunnokset • Olioiden sijoitus ja kopiointi • Olioiden kopiointi • Olioiden sijoitus • Puhdasoppinen luokka • Serialisaatio • Operaattoreiden uudelleenmäärittely • Ystäväfunktiot • Ystäväluokat • Yhteenveto

  38. Aliluokan ja kantaluokan suhde • Aliluokka tarjoaa kaikki ne palvelut mitä kantaluokkakin (+ vähän lisää omia ominaisuuksia) • Periytymisessähän vaan lisätään ominaisuuksia • Aliluokkaa voi siis käyttää kantaluokan sijasta missä päin hyvänsä koodia

  39. Aliluokan ja kantaluokan suhde • Voidaan siis ajatella, että aliluokan olio on tyypiltään myös kantaluokan olio! • Aliluokan oliot kuuluvat ikään kuin useaan luokaaan: • Aliluokkaan itseensä • Kantaluokkaan • Kantaluokan kantaluokkaan, jne • Tämä is-a suhde tulisi pitää mielessä aina kun periytymistä käytetään! • Jos aliluokka on muuttunut vastuualueeltaan niin paljon, että se ei enää ole kantaluokan mukainen, periytymistä on ilmeisesti käytetty väärin

  40. Aliluokan ja kantaluokan suhde • C++:ssa aliluokan olio kelpaa kaikkialle minne kantaluokan oliokin. • Kantaluokan osoittimen tai viitteen voi laittaa osoittamaan myös aliluokan olioon: class Kantaluokka {…}; class Aliluokka : public Kantaluokka {…}; void funktio (Kantaluokka& kantaolio); Kantaluokka *k_p =0; Aliluokka aliolio; k_p = &aliolio; funktio(aliolio);

  41. Olion tyypin ajonaikainen tarkastaminen • Kantaluokkaosoittimen päässä olevalle oliolle voi kutsua vain kantaluokan rajapinnassa olevia funktioita • Ei auta vaikka osoittimen päässä todellisuudessa olisikin aliluokan olio. • Normaalisti kantaluokan rajapinnan käyttö onkin aivan riittävää • Joskus tulee kuitenkin tarve päästä käsiksi aliluokan rajapintaan.

  42. Olion tyypin ajonaikainen tarkastaminen • Jos aliluokan olio on kantaluokkaosoittimen päässä ei aliluokan rajapinta ole siis näkyvissä • Ainoa vaihtoehto on luoda uusi osoitin aliluokkaan ja laittaa se osoittamaan kantaluokkaosoittimen päässä olevaan olioon

  43. Tyyppimuunnokset (type cast) • Tyyppimuunnos on operaatio, jota ohjelmoinnissa tarvitaan, kun käsiteltävä tieto ei ole jotain operaatiota varten oikean tyyppistä • Tyyppimuunnos on terminä hieman harhaanjohtava • tyyppiä ei oikeastaan muuteta vaan luodaan pikemminkin uusi arvo haluttua tyyppiä, joka vastaa vanhaa arvoa • Tyyppimuunnos muistuttaa tässä suhteessa suuresti kopiointia. Erona on se, että uusi ja vanha olio on kopioinnista poiketen eri tyyppiä

  44. C++ tyyppimuunnosoperaattorit • Vanha C-kielinen tyyppimuunnos:(uusiTyyppi)vanhaArvo • sulkujen sijainti hieman epälooginen • C++ kielessä mahdollista myös: uusiTyyppi(vanhaArvo)

  45. Ongelmia tyyppimuunnosten kanssa • Tyyppimuunnoksia voidaan käyttää suorittamaan kaikenlaisia muunnoksia. Esim: • kokonaisluvuista liukuluvuiksi • olio-osoittimista kokonaisluvuiksi • Kaikki tyyppimuunnokset eivät ole järkeviä! • Kääntäjä ei tarkista tyyppimuunnosten järkevyyttä • Kääntäjä luottaa täysin ohjelmoijan omaan harkintaan • Tyyppimuunnoksiin jää helposti kirjoitusvirheitä • Tyyppimuunnosvirheitä on vaikea löytää

  46. Parannellut tyyppimuunnosoperaattorit • Parannellut tyyppimuunnosoperaattorit ovat: • static_cast<uusiTyyppi>(vanhaArvo) • const_cast<uusiTyyppi>(vanhaArvo) • dynamic_cast<uusiTyyppi>(vanhaArvo) • reinterpret_cast<uusiTyyppi>(vanhaArvo) • Yhteensopivia mallien käyttämän syntaksin kanssa (malleista puhutaan myöhemmin) • Kukin operaattoreista on tarkoitettu vain tietynlaisen mielekkään muunnoksen tekemiseen • kääntäjä antaa virheilmoituksen jos niitä yritetään käyttää väärin. • Vanhat tavat tehdä tyyppimuunnokset ovat yhteensopivuuden takia edelleen käytettävissä • vältä niiden käyttöä ja suosi uusia operaattoreita

  47. static_cast • Suorittaa tyyppimuunnoksia, joiden mielekkyydestä kääntäjä voi varmistua jo käännösaikana. • Esimerkkejä: • muunnokset eri kokonaislukutyyppien välillä • muunnokset enum-luettelotyypeistä kokonaisluvuiksi ja takaisin • muunnokset kokonaislukutyyppien ja likulukutyyppien välillä • Käyttöesimerkki. Lasketaan kahden kokonaisluvun keskiarvo liukulukuna:double ka = (static_cast<double>(i1) + static_cast<double>(i2))/ 2.0;

  48. static_cast • static_cast ei suostu suorittamaan sellaisia muunnoksia, jotka ei ole mielekkäitä. Esimerkki:Paivays* pvmp = new Paivays();int* ip = static_cast<int*>(pvmp); //KÄÄNNÖSVIRHE! • static_cast:ia voidaan käyttää myös osoittimen tyyppimuutokseen • muunnoksen mielekkyyttä ei tällaisessa tapauksessa testata ajon aikana • Pitää olla itse varma, että kantaluokkaosoittimen päässä on varmasti aliluokan olio • dynamic_cast:n käytto olisi turvallisempaa! • static_cast on nopeampi kuin dynamic_cast

  49. const_cast • joskus const-sanan käyttö tuo ongelmia • const_cast tarjoaa mahdollisuuden poistaa const-sanan vaikutuksen • voi tehdä vakio-osoittimesta ja –viitteestä ei-vakio-osoittimen tai –viitteen • const_cast-muunnoksen käyttö rikkoo C++ “vakiota ei voi muuttaa” periaatetta vastaan. • sen käyttö osoittaa että jokin osa ohjelmasta on suunniteltu huonosti • Pyri pikemminkin korjaamaan varsinainen ongelma kuin käyttämään const_cast:ia

  50. dynamic_cast • Muunnos kantaluokkaosoittimesta aliluokkaosoittimeksi onnistuuu tyyppimuunnoksella:dynamic_cast<Aliluokka*>(kluokkaosoitin) • Muunnoksen toiminta on kaksivaiheinen: • Ensin tarkastetaan, että kantaluokkaosoittimen päässä oleva olio todella on aliluokan olio. • Jos kantaluokkaosoittimen päässä on väärän tyyppinen olio, palautetaan tyhjä osoitin 0. • Jos kantaluokkaosoittimen päässä on oikean tyyppinen olio, palautetaan kyseiseen olioon osoittava aliluokkaosoitin.

More Related