- TituCPP 9. luento 29.11.
- Materiaali
- Esimerkit
- Virhetilanteet ja poikkeukset
- c++ faq lite, poikkeustenkäsittelyosio
- [ http://www.parashift.com/c++-faq-lite/exceptions.html ]
- reagointitapoja virheisiin
- suorituksen keskeytys (termination)
- hallittu lopetus (abort)
- resurssien siivous, virhetilanteen kirjaus
- jatkaminen (continuation)
- virheen jättäminen huomiotta (jos mahdollista)
- peruuttaminen (rollback)
- järjestelmän tilan palauttaminen tilaan ennen virheen aiheuttaneen operaation käynnistämistä
- toteutus usein mutkikasta, erikoistapauksena tietokantojen transaktioiden peruuttaminen
- toipuminen (recovery)
- ohjelman osan paikallinen toteutus hallitusta lopetuksesta
- esim. poikkeusten käsittely
- Virheiden käsittelystä
- idea: ohjelmakohta, jossa virhe havaitaan (esim. aliohjelmakirjasto) ei yleensä ole oikea paikka virheen käsittelyyn
- Stroustrup: "Kirjaston tekijä pystyy havaitsemaan ajonaikaiset virheet, mutta
hänellä ei yleensä ole aavistustakaan, mitä niille pitäisi tehdä. Kirjaston
käyttäjä saattaa tietää, miten virheistä selviydytään, mutta hän ei pysty
havaitsemaan niitä - muuten ne olisi käsitelty käyttäjän kirjoittamassa
ohjelmassa eikä jätetty kirjaston huoleksi.
- poikkeuksen sieppaavalla funktiolla tulisi olla riittävästi tietoa virhetilanteen käsittelyyn ja toipumiseen
- toisaalta useimpia poikkeuksia ei missään tapauksessa pidä "valuttaa" pääohjelmaan
- poikkeuksen voi tarvittaessa myös heittää uudelleen (esim. paikallisen muistinsiivouksen jälkeen)
- käytännön ongelma: mikä on "oikea" virheenkäsittelytaso?
- poikkeus voidaan ajatella myös kontrolloituna goto-lauseena
- pyrkivät korvaamaan C-tyylisen virhetilanteen käsittelyn funktion paluuarvon testauksena
- => ohjelma yksinkertaistuu ja koodin määrä vähenee, koska jatkuvia paluuarvon if-tarkastuksia ei yleensä tarvita
- joskus myös perinteisellä paluuarvonkin tarkastuksella on paikkansa - käsittely usein poikkeuksia nopeampaa, koska pinoa ei tarvitse purkaa
- esim. tietovirtojen oletuskäsittely ei aiheuta poikkeuksia
- pino-oliot tuhotaan automaattisesti poikkeuksen heiton yhteydessä
- dynaamisia olioita _ei_ tuhota automaattisesti
- Poikkeushierarkia
- poikkeukset ovat yleensä (mutta ei välttämättä) exception-luokasta perittyjä olioita - heitetyn poikkeuksen mukana voidaan lähettää ylemmälle tasolle tietoa virhetilanteesta
- Myös omia poikkeusluokkia (voi käyttää myös perustyyppejä) voi määritellä - ei tarvitse (mutta yleensä kannattaa) olla perittyjä standardiluokista
- muita keskeisiä poikkeusluokkia
- [ http://www.roguewave.com/support/docs/leif/sourcepro/html/stdlibref/exception.html ]
- logic_error
- kaikki virheet, jotka aiheutuvat ohjelman toimintalogiikassa havaituista virheistä
- esim. out_of_range - osoittaminen tietorakenteen rajan yli
- runtime_error
- ajoympäristö aiheuttaa tilanteen, jota ohjelma ei pysty hallitsemaan
- esim. overflow_error - aritmeettinen ylivuoto
- Poikkeusmääreet
- hyvä käytäntö (esim. javassa pakollinen): merkitse poikkeusmääreillä, mitä poikkeuksia funktion odotetaan heittävän
- ongelma: c++:n throw() antaa "takuun", että funktio voi heittää _vain_ listassa annetut poikkeukset
- jos poikkeusmäärettä ei ole, funktio voi aiheuttaa minkä tahansa poikkeuksen
- määritys throw() edellyttää, että funktio ei aiheuta mitään poikkeuksia
- jos jokin muu poikkeus kuin listassa mainittu poikkeus tulee heitettyä, seurauksena on kutsu std::unexpected() (oletuksena ohjelman suorituksen lopettaminen)
- Huom. aina ei ole mahdollista tietää, mitä poikkeuksia funktio voi heittää (esim. template-funktioilla)
- vältettävissä sieppaamalla funktiossa kaikki poikkeukset (mutta onko tämä aina toivottavaa?)
- RAII-idiomi (resource aquisition is initialization - resurssin varaaminen on alustamista)
- resurssi: esim. muisti, tiedostot, ikkunakahvat, tietokantayhteydet => jotain, minkä varaamisesta ja tuhoamisesta käyttäjä vastaa
- "c-tyylillä" tehty poikkeuskäsittely näyttäisi seuraavalta:
- // huonoa koodia
try {
fopen();
...
}
{catch ...} {
fclose(); // jos jokin meni vikaan
}
fclose(); // tavanomainen tilanne
- javassa vastaavassa käytetään try-finally -rakennetta (finally-lohko suoritetaan aina tryn jälkeen, vaikka heitettäisiin poikkeus) => lähellä "c-tyyliä":
- // pseudojavaa
try {
fopen();
...
}
finally {
fclose(); // suoritetaan aina
}
fclose()
- parempi tapa: käytetään paikallisia muuttujia, jotka omistavat resurssin
- resurssi varataan konstruktorissa (=varaaminen on alustamista) ja vapautetaan destruktorissa
- jos resurssin käytön aikana sattuu poikkeus, pino puretaan automaattisesti ja destruktoria tulee kutsuttua
- varsinainen poikkeuksenkäsittely voidaan tehdä ohjelman "ylemmällä" tasolla, jolloin todennäköisesti tiedetään paremmin, miten tilanteeseen tulisi reagoida
- auttaa välttämään suurta määrää try/catch-lohkoja
- ks. jon hanna: the raii programming idiom
- [ http://www.hackcraft.net/raii/ ]
- "fiksuista" osoittimista
- auto_ptr
- esimerkki raii-idiomin käytöstä
- alustetaan osoittimella dynaamisesti new:lla luotuun olioon ja sisältö voidaan lukea kuten osoittimen sisältö (kuormitettu *-operaattori)
- huom. ei auto_ptr:lle ei ole määritelty osoitinaritmetiikkaa (++, -- jne)
- auto_ptr:n viittaama olio tuhoutuu automaattisesti viittausalueen lopussa
- huom. ei käy stl-säiliöiden alkioksi (vector) - sijoitukset eivät toimi oikein, koska omistajuus muuttuu kopioitaessa
- => vain yksi automaattiosoitin voi osoittaa samaan olioon kerrallaan
- tr1-määrityksessä (tod. näk. osa tulevaa c++ -standardia) lisäksi:
- [ http://aristeia.com/EC3E/TR1_info_frames.html ]
- jaettu osoitin (käyttää viitelaskuria)
- heikko osoitin
- Poikkeustakuut
- eräs tapa dokumentoida luokkien poikkeusturvallisuutta
- takuukategoriat
- perustakuu
- mikäli olion palvelu keskeytyy, olio ei hukkaa resursseja ja on sellaisessa tilassa, että sen voi tuhota
- vähimmäisvaatimus kaikilta luokilta.
- c++ -standardikirjaston lähes kaikki operaatiot toteuttavat vähintään perustakuun
- vahva takuu
- luokan operaatio saadaan suoritettua ilman virheitä tai virheen sattuessa olion tila pysyy alkuperäisenä (rollback)
- toteutus esim. tekemällä oliosta työkopio => hidastaa ja kasvattaa koodia
- stl:n säiliöiden push_back-operaatiot antavat vahvan takuun
- nothrow-takuu
- operaation suorituksessa ei saa sattua virheitä eikä poikkeuksia heitetä
- esim. auto_ptr:n operaatiot, throw()-poikkeusmääreellä merkityt metodit
- poikkeusneutraalius
- luokka päästää kaikki poikkeukset muuttamatta läpi kutsujalle
- voi kuitenkin käsitellä poikkeuksia esim. resurssien vapauttamista varten (ja heittää tämän jälkeen uudelleen)
- esim. vector-luokan operaatiot
- Virheet ja muistinkäsittely
- jos poikkeus heitetään konstruktorissa, destruktoria _ei_ kutsuta (pl. mahd. yliluokan destruktori), koska olio ei ole valmis
- jos konstruktorissa varattu muistia dynaamisesti, seurauksena muistivuota
- => älä varaa muistia dynaamisesti konstruktorissa, vaan käytä RAII-idiomia
- [ http://www.parashift.com/c++-faq-lite/exceptions.html#faq-17.4 ]
- huom. poikkeuksen heittämisessä konstruktorista ei sinänsä ole mitään väärää (vrt. destruktori)
- poikkeus (stroustrup, s. 393): kopionmuodostimen ei tulisi heittää poikkeuksia (kopionmuodostinta kutsutaan implisiittisesti ja standardikirjasto olettaa, että kopioinnin aikana ei tule poikkeuksia)
- jos tuhottava pino-olio heittää destruktorissa poikkeuksen (toisen poikkeuksenkäsittelyn aikana), ohjelman suoritus lopetetaan
- Esimerkki: poikkeusturvallinen kirjaluokka
- ongelma: merkkijonon sijiottaminen voi epäonnistua - myös poikkeuskäsittelijän aikana
- (epätehokas) ratkaisu: dynaamisten muuttujien käyttö (osoittimen sijoittaminen ei epäonnistu)
- tehokkaampi ratkaisu: PIMPL (pointer/private implementation) -idiom (kahvaluokka)
- Olion tilan käsittely omassa sisäluokassaan
- Aliluokan muistinhallinnassa voidaan käyttää automaattiosoitinta
- Kenttien määrästä riippumatta sijoitusoperaattorissa tarvitaan vain yksi sijoitus
- kirja-luokan erikoistapauksessa ratkaisua voidaan tehostaa entisestään käyttämällä string-luokan (poikkeusturvallista) swap-funktiota
- Kerho: tiedostonkäsittely
- Tiedostonkäsittely Kerhoon: lisää Jäsen- ja Harrastusluokkiin getAsString- ja setAsString-metodit ja tietorakenneluokkiin tiedoston luku ja luettujen olioiden lisääminen rakenteeseen
- eri tietotyyppien lukua varten kuormitetut ota-funktiot (tässä mahdollista yleistää edelleen)
- Kerho: päätesyöttö
- Päätesyötön lähtökohta: kysy_tiedot -metodin kehittäminen
- Indeksoidut kentät - keino käyttöliittymän ja sovelluslogiikan erottamiseen päätesyötössä
- Ei vaadi välttämättä omaa kenttäluokkaa - rajapinnan kannalta indeksoidut saanti- ja asetusmetodit riittävät
- Tietojen siirto näytön ja jäsenen välillä kannattaa pitää merkkijonomuotoisena (tyyppimuunnokset vain jäsenessä)
- eka: ensimmäinen _käyttäjälle näytettävä_ kenttä (id-numerot ennen ekaa)
- Toisesta tietorakenteesta haettavat valintalistat aiheuttavat hieman lisävaivaa
- toimintaa siirrettävä jäsentasolta kerhotasolle
- esim. harrastusten kysely on helppoa toteuttaa ilman lisätoimintoja (oleellisesti sama koodi kuin jäsenen tietojen kysymisessä), mutta esim. genre- tai esittäjälistan tulostaminen syötettäessä tietoja cd-rekisteriin vaatii jo pohjaa hakujärjestelmälle.
- Esimerkki:kaupunkien lisääminen jäsenrekisteriin (tietokannan normalisointia)
- Tehdään uudet luokat Kaupungit ja Kaupunki (sisältää vain nimen ja id-numeron)
- Rakenteen muutoksen seurauksena Jäsen ei enää tiedä suoraan kotikaupunkiaan, vaan ainoastaan sen ID-numeron
- Koska Jäsen ei saa olla suoraan tietoinen Kaupunki-luokasta, Jäsen ei tämän jälkeen mm. pysty tulostamaan itseään käyttäjille kaupunkitiedon kanssa => tulostustoiminto siirrettävä Kerho-luokkaan
- Lisäksi tietoja kysyttäessä käyttäjän on saatava tarvittaessa nähdä kaupunkilista, josta valita jäsenen kotipaikka => monimutkaistaa näyttöluokkaa
- Toiminta kannattaa toteuttaa täysimääräisenä vasta, kun käytössä on hakujärjestelmä - aluksi voidaan toteuttaa tietorakenne ja esim. jäsenen tulostus
- Periaate: siirretään kaupungin id-numero kenttiin, joita ei kysytä käyttäjältä silmukassa ja siirretään kysyminen suoraan näyttöön
- Käyttöliittymä esim: käyttäjä voi kirjoittaa kaupungin nimen tai pyytää listan, josta valita
- Jos kirjotettua nimeä ei löydy kaupungeista, varmistetaan kaupungin lisääminen
- Asetetaan jäsenen kapunkiID lisätyn/etsityn kaupungin ID:n mukaisesti
- Mallit (templatet) ja geneerisyys
- Abstraktiotason nostamista!
- Mallit vs abstraktit luokat
- Esim. taulukkoluokka
- Ilman malleja vaatii typecasteja ja yhteisen yliluokan määritystä säilöttäville olioille
- Taulukko voi sisältää samanaikaisesti useisiin eri aliluokkiin kuuluvia olioita (usein tämä voi olla hyödyllistäkin)
- Erilaisia malleja
- Funktiomallit
- Luokkamallit
- Jäsenfunktiomallit
- Raa'asti yksinkertaistaen tyyppiturvallinen tapa käyttää makroja
- Malleista käännetään versioita eri tyypeille tarpeen mukaan
- Seuraus: mallin lähdekoodin on oltava tiedossa käännösvaiheessa (mallin koodi kirjoitetaan otsikkotiedostoon, ellei käytetä export-avainsanaa)
- STL: kattava esimerkki geneerisyyden soveltamisesta