Esittely latautuu. Ole hyvä ja odota

Esittely latautuu. Ole hyvä ja odota

Sami Jantunen LTY/Tietotekniikan osasto

Samankaltaiset esitykset


Esitys aiheesta: "Sami Jantunen LTY/Tietotekniikan osasto"— Esityksen transkriptio:

1 Sami Jantunen LTY/Tietotekniikan osasto
Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset Sami Jantunen LTY/Tietotekniikan osasto

2 Sisältö C++ tekniikoita Virhetilanteet ja poikkeukset Yhteenveto
Nimiavaruus Luokkamuuttujat Luokkafunktiot Operaattorien uudelleenmäärittely Ystäväfunktiot Ystäväluokat Virhetilanteet ja poikkeukset Syitä poikkeuksiin Virhetilanteisiin reagointi Virhehierarkiat Poikkeuksien heitto ja sieppaaminen Poikkeukset ja oliot Yhteenveto

3 Ongelma Löydätkö esimerkistä ongelman? // vendor1.h ... various stuff
class String { ... }; myProgram.cpp #include "vendor1.h" #include "vendor2.h" void main() { } // vendor2.h ... various stuff class String { ... }; Löydätkö esimerkistä ongelman?

4 Ongelman kuvaus Käyttökelpoisia luokkien, funktioiden ja muuttujien nimiä on rajallinen määrä Isommissa ohjelmistoissa törmätään helposti nimikonflikteihin. Nimikonflikteja syntyy etenkin silloin, kun käytetään hyväksi monta eri ohjelmistomoduulia: Esimerkkejä: Ohjelmistossa käytössä olevista kirjastoista 2 määrittelee Tulosta() -funktion. Käyttämässäsi 3. osapuolen luokkakirjastossa on määritelty samanniminen luokka kuin omassa koodissasi. ohjelma ei käänny

5 Nimiavaruudet (Namespaces)
C++ ratkaiseen suurten ohjelmistojen rajapintojen nimikonfliktit nimiavaaruutta käyttämällä Nimiavaruuksien tarkoituksena on tarjota kielen syntaksin tasolla oleva hierarkkinen nimeämiskäytäntö. Hierarkia auttaa jakamaan ohjelmistoa osiin Samalla estetään nimikonfliktit eri ohjelmiston osien välillä

6 Nimiavaruuden määrittely
Toisiinsa liittyvät ohjelmakokonaisuudet voidaan koota yhteen nimiavaruuteen namespace –avainsanalla Käyttöesimerkki. Kootaan kaikki päiväykseen liittyvät tiedot (tietorakenteet, tietotyypit, vakiot, oliot ja funktiot) yhteen nimikkeen Paivays alle: paivays.cpp #include “paivays.h” namespace Paivays { Pvm luo (int paiva, int kuukausi, int vuosi) { Pvm paluuarvo; ... return paluuarvo; } void tulosta (Pvm kohde) paivays.h namespace Paivays { struct Pvm { int p_, k_, v_; }; Pvm luo (int paiva, int kuukausi, int vuosi); void tulosta (Pvm kohde); ... }

7 Näkyvyystarkenninoperaattori
Nimiavaruuden sisällä määritetyt jäsenet ovat näkyvissä vain kyseisen nimiavaruuden sisällä Nimiavaruuden ulkopuolelta em. jäseniin pääsee käsiksi näkyvyystarkenninoperaattorin :: avulla. Ohjelmoijan tulee jäseniä käytettäessä ilmaista mitä kokonaisuutta ja mite alkiota sen sisällä hän haluaa käyttää Paivays::tulosta() Kirja::tulosta() Ylimääräistä kirjoittelua, mutta toisaalta selkiyttää koodia

8 Nimiavaruudet Hyödyt Nimikonfliktien vaara vähenee merkittävästi
jokaisen moduulin rajapintanimet ovat omassa nimetyssä näkyvyysalueessaan Moduulin määrittelemien rakenteiden käyttö on kielen syntaksin tasolla näkyvän rakenteen (näkyvyystarkennin::) vuoksi selkeämpää Hierarkisuudesta huolimatta moduulin sisällä on käytettävissä lyhyet nimet. Koodista nähdään syntaksin tasolla esimerkiksi, mitkä funktiokutsut kohdistuvat saman moduulin sisälle ja mitkä muualle ohjelmistoon

9 Korjataan ongelma Enää ei ole käytössä kahta String-luokka
// vendor1.h ... various stuff namespace Vendor1 { class String { ... }; } // vendor2.h ... various stuff namespace Vendor2 { class String { ... }; } Enää ei ole käytössä kahta String-luokka String-luokkien sijasta meillä on käytössä luokat: Vendor1::String Vendor2::String

10 std nimiavaruus Kaikki C++ standardi määrittelee omaan käyttöönsä std-nimiavaruuden Käytössä kaikissa nykyaikaisissa kääntäjissä std-nimiavaruus sisältää lähestulkoon kaikki C++ ja C-kielissä määritellyt rakenteet. Esim: std::prinf std::cout Varattu pelkästään sisäiseen käyttöön. Et saa lisätä omia rakenteita std-nimiavaruuteen

11 Uudet otsikkotiedostot
std nimiavaruuden käyttöönotto aiheuttaa muutoksia myös otsikkotiedostojen nimissä Kääntäjän omat otsikkotiedostot sisällytetään ilman .h –ekstensiota #include <iostream.h>  #include<iostream> C-kielestä perittyihin otsikkotiedostoiden nimiin lisätää ‘c’ eteen #include <cstring>

12 Esimerkki std-nimiavaruuden käytöstä
paivays.cpp #include <cstdlib> //pääohjelman paluuarvo EXIT_SUCCESS #include <iostream> //C++ tulostus #include <cstring> //C:n merkkitaulukkofunktiot int main() { const char* const p = “Jyrki Jokinen”; char puskuri [42]; std::strcpy(puskuri, “Jyke “); std::strcat(puskuri, std::strstr(p, “Jokinen”) ); std::cout << puskuri << std::endl; return EXIT_SUCCESS; }

13 std::cout, std::endl joko väsyttää?
std:: toistaminen jatkuvasti turhauttaa Jos samassa ohjelmalohkossa käytetään useita kertoja samaa nimiavaruuden sisällä olevaa nimeä, kannattaa käytttää using -lausetta using mahdollistaa valittujen rakenteiden käytön nimiavaruuden ulkopuolella ilman :: -tarkenninta normaali using -lause nostaa näkyville yhden nimen using namespace nostaa näkyville kaikki nimiavaruuteen kuuluvat rakenteet

14 using Esimerkki paivays.cpp #include “paivays.h”
void kerroPaivays (Paivays::Pvm p ) { using std::cout; using std::endl; using namespace Paivays; //Käytetään kaikkia nimiavaruuden nimiä cout << “Tänään on: “; tulosta(p); //Kutsuu Paivays::Tulosta cout << endl; }

15 using Ohjeita Käytä using-lausetta mielellään vasta kaikkien #include-käskyjän jälkeen Vältä using-lauseen käyttöä otsikkotiedostossa Käytä using-lausetta mahdollisimman lähellä sitä aluetta, jossa sen on tarkoitus olla voimassa Paljon käytetyissä rakenteissa on usein kuitenkin selkeämpää kirjoittaa using heti siihen liittyvän otsikkotiedoston #include-käskyn jälkeen.

16 ehdotus using-lauseen käyttöstä eri otsikkotiedostojen kanssa
//Omat rakenteet esitellään std-kirjastoja ennen //(tämä siksi että saamme tarkastettua niiden sisältävän kaikki //tarvittavat #include-käskyt ts. ne ovat itsenäisesti kääntyviä yksikköjä #include “paivays.h” #include “swbus.h” #include “tietokanta.h” #include “loki.h” //Kaikista yleisimmin tässä tiedostossa käytetyt std-rakenteet esitellään //heti niihin liittyvän otsikkotiedoston jälkeen #include <iostream> using std::cout; using std::endl; #include <vector> using std::vector; #include <string> using std::string //lopuksi omiin moduuleihin liittyvät using-lauseet using Paivays::PVM; using Loki::varoitus; using Loki::virhe;

17 Nimiavaruuden synonyymi
Nimiavaruudelle voidaan määritellä synonyymi (alias) alias-nimeen tehdyt viittaukset käyttäytyvät alkuperäisen nimen tavoin Käyttökohteet moduulin korvattavuus helpottuu helpompi nimi pitkille nimiavaruuksien nimille

18 Nimiavaruuden synonyymi Esimerkki
#include “prjlib/string.h” #include <string> int main() { #ifdef PRJLIB_OPTIMOINNIT_KAYTOSSA namespace Str = ComAcmeFastPrjlib; #else namespace Str = std; #endif Str::string esimerkkijono; . }

19 Missä mennään? C++ tekniikoita Virhetilanteet ja poikkeukset
Nimiavaruus Luokkamuuttujat Luokkafunktiot Operaattorien uudelleenmäärittely Ystäväfunktiot Ystäväluokat Virhetilanteet ja poikkeukset Syitä poikkeuksiin Virhetilanteisiin reagointi Virhehierarkiat Poikkeuksien heitto ja sieppaaminen Poikkeukset ja oliot Yhteenveto

20 Ongelma Haluamme pitää kirjaa siitä kuinka monta samaa tyyppiä olevaa oliota on hengissä kullakin hetkellä Mikä olisi hyvä ratkaisu? Milloin tiedetään, että olio syntyy tai kuolee? Missä pidetään kirjaa hengissä olevien olioiden lukumäärästä?

21 Aloitetaan ongelman ratkaisu
Meillä pitää olla laskuri! laskuria lisätään kun olion rakentajaa kutsutaan laskuria vähennetään kun olion purkajaa kutsutaan Millä näkyvyysalueella laskuri sijaitsee? Luokan jäsenmuuttujana? Globaali muuttuja?

22 Hmmm… Laskurin sijoittaminen olion jäsenmuuttujaksi ei toimi
se olisi oliokohtainen muuttuja. Muut eivät pääsisi päivittämään sitä Globaali muuttuja toimisi Olio-ohjelmoijina emme tykkäisi ideasta. Tämä olisi sotkuinen ratkaisu

23 Hmmm… Huomaamme, että joskus olisi tarvetta sellaisille jäsenille, jotka on yhteisiä kaikille luokan olioille! toisaalta kaikille olioille toisaalta ei millekkään niistä

24 Ratkaisu! C++ kielessä on mekanismi, mikä ratkaisee ongelmamme
On mahdollista esitellä luokan jäsen luokkamuuttujana (static data member) Luokkamuuttujan esittely on muuten samanlainen kuin jäsenmuuttujankin, mutta esittely alkaa avainsanalla static

25 Luokkamuuttuja Luokkamuuttuja on luonteeltaan hyvin lähellä normaalia globaalia muuttujaa Se on olemassa, vaikka luokasta ei olisi vielä luotu ainuttakaan oliota. Koska luokkamuuttuja ei kuulu mihinkään olioista, sitä ei voi alustaa luokan rakentajassa. Jossain päin koodia täytyy olla erikseen luokkamuuttujan määrittely, jonka yhteydessä muuttuja alustetaan: int X::luokkamuuttuja_ = 2;

26 Luokkamuuttujan käyttö
Luokan omassa koodissa luokkamuuttujaan voi viitata aivan kuten jäsenmuuttujaankin Luokan ulkopuolelta luokkamuuttujaan voi viitata syntaksilla: Luokka::lmuuttuja Toinen tapa on viitata luokkamuuttujiin luokan olion kautta: X xolio; int arvo = xolio.luokkamuuttuja_; Käytetään pitkälti saman tyylisesti kuin jäsenmuuttujiakin pyri pitämään luokkamuuttujat privaatteina

27 Luokkafunktiot (static member functions)
Edustavat sellaisia luokan palveluja ja operaatioita, jotka eivät kohdistu mihinkään yksittäiseen olioon Samankaltainen jäsenfunktion määrittelyn kanssa Lisätään static –avainsana Ei saa käyttää toteutuksessa minkään olion jäsenmuuttujia eikä this-osoittimia

28 Ja ratkaistaan ongelma!
// static members in classes #include <iostream.h> class CDummy { public: static int n; CDummy () { n++; }; ~CDummy () { n--; }; }; int CDummy::n=0; int main () CDummy a; CDummy b[5]; CDummy * c = new CDummy; cout << a.n << endl; delete c; cout << CDummy::n << endl; return 0; } Mikä on oheisen koodin lopputulos? Vastaus: 7 6

29 Hieman konkreettisempi esimerkki!
On hyvin tavallista että tietystä luokasta pitäisi olla olemassa vain ja ainoastaan yksi instanssi Kyseinen instanssi pitäisi olla kuitenkin mahdollisimman helposti saatavilla muille olioille Ongelma: Kuinka varmistat, että luokkaa ei missään tilanteessa luoda enemää kuin yksi olio? Kuinka voit samalla taata sen, että kuka tahansa voi päästä käsiksi kyseiseen olioon?

30 Ratkaistaan ongelma! Mitä jos loisimme luokkamuuttujan joka olisi osoitin luokan tyyppiseen olioon? jos osoitin = 0, yhtään oliota ei ole vielä luotu jos osoitin != 0, olio on jo luotu Voisimme vielä luoda luokkafunktion (getInstance), joka palauttaisi osoittimen yhteen ja ainoaan olioon jos luokkamuuttujaosoitin = 0, funktio loisi uuden olion ja palauttaisi sen osoitteen jos olio olisi jo olemassa, funktio palauttaisi sen osoitteen

31 Singleton Pattern class Singleton { public: static Singleton *get_instance(); protected: Singleton(); Singleton( const Singleton& s); private: static Singleton *instance; }; Singleton::instance = 0; Singleton *Singleton::get_instance() { if ( instance == 0 ) { instance = new Singleton; } return instance; } «Singleton» theInstance getInstance Company theCompany Company «private» getInstance if (theCompany==null) theCompany= new Company(); return theCompany;

32 Mitä tuli tehtyä? Käytimme luokkamuuttujaa ja luokkafunktiota fiksusti yhteen nyt voimme olla varmoja, että ei ole koskaan mahdollista luoda enempää kuin yksi olio kyseistä tyyppiä Olioon on kuitenkin todella helppo päästä käsiksi Loimme itse asiassa yhden yleisimmistä Design Patterneista (singleton)!

33 Missä mennään? C++ tekniikoita Virhetilanteet ja poikkeukset
Nimiavaruus Luokkamuuttujat Luokkafunktiot Operaattorien uudelleenmäärittely Ystäväfunktiot Ystäväluokat Virhetilanteet ja poikkeukset Syitä poikkeuksiin Virhetilanteisiin reagointi Virhehierarkiat Poikkeuksien heitto ja sieppaaminen Poikkeukset ja oliot Yhteenveto

34 Operaattoreiden uudelleenmäärittely
Olemme tottuneet käyttämään operaatioita kuten +, -, ==, *, /, jne… Nämä ovat itse asiassa funktioita! Tällaisia funktioita vain kutsutaan hieman eri tavalla! Esimerkki x + 7; Esitetty ihmiselle helpossa muodossa Voidaan kuitenkin ajatella myös muodossa +(x,7), missä: ‘+’ on funktion nimi x,7 ovat argumentit Funktio paluattaa argumenttiensa summan

35 Operaattorien uudelleenmäärittely
Olisi kiva, jos voisimme operoida omiakin olioita tutuilla operaattoreilla! Olioiden tietojen käsittely operaattorien avulla on ihmiselle luontevampaa Kääntäjä ei voi kuitenkaan tietää miten tuttuja operaatioita voidaan soveltaa oikein eri tyyppisten luokkien kanssa  Luokan kirjoittajan tulisi itse määritellä miten operaatiot tulisi suorittaa.

36 Uudelleenmääriteltävät operaattorit C++ kielessä
+ - * / % ^ & | ~ ! = < > += -= *= /= %= ^= &= |= << >> >>= <<= == != <= >= && || >* , -> [] () new delete

37 Harjoituksia Mitä seuraavien operaatioiden pitäisi mielestäsi tehdä?
PipeClient myPipe; myPipe.open(this); myPipe << “heihei”; string kokoNimi = “Sami” + “Jantunen”; Time myTime1,mytTime2, elapsedTime; myTime1.now(); myTime2.now(); elapsedTime=myTime2 - myTime1;

38 Operaattorien uudelleenmäärittely Perusteet
Hyvin samanlainen funktioiden uudelleenmäärittelyn kanssa Operaattori on itse asiassa funktion “nimi” Kaksi eri toteutustapaa Jäsenfunktiona Tavallisena funktiona Joidenkin operaattorien uudelleenmääritys on pakko olla toteutettuna jäsenfunktiona: sijoitusoperaattori = taulukon indeksointioperaattori [] funktiokutsu () jäsenen osoitusoperaattori ->

39 Ohjeita uudelleenmäärittelystä
Määrittele, jos se selventää ohjelmaa Älä uudelleenmäärittele siten, että operaattorin merkitys ei ole itsestäänselvä Palauta operaattorifunktiosta aina kyseisen luokan tyyppinen olio

40 Operaattorin uudelleenmäärittely jäsenfunktiona
Määritellään Mjono-luokalle jäseneksi ‘+’ –operaattori: const Mjono operator +(const Mjono& teksti); Huomaa vain yksi argumentti! Operaation toinen osapuoli on olio jota kutsutaan Käyttöesimerkki: Mjono sukuNimi = “Jantunen”; Mjono etuNimi = “Sami”; Mjono kokoNimi = etuNimi + sukuNimi;

41 Operaattorin uudelleenmäärittely jäsenfunktiona
Esitetty yhteenlasku voidaan esittää funktiokutsuna muodossa: kokoNimi = etuNimi.+(sukuNimi); kutsuttava olio funktion nimi argumentti

42 Ongelma! Miksi seuraava ei onnistu?: char cMjono[] = {“Hello”}; Mjono mjono(“World!”); Mjono mJonoKaksi = cMjono + mMjono; //Virhe

43 Syy ongelmaan cMjono on C-tyyppinen merkkijono
Mjono mJonoKaksi = cMjono + mMjono; //Virhe kutsuttava olio funktion nimi argumentti cMjono on C-tyyppinen merkkijono sillä ei voi olla jäsenfunktioita se ei voi suoriutua komennosta: cMjono.operator+(mMjono)

44 Ratkaisu Mikä olisi ongelmaan ratkaisu?
Loogisesti ajateltuna edellä mainittu kahden merkkijonon yhteen liittäminen tulisi onnistua!!! Mikä olisi ongelmaan ratkaisu? Määritetään funktio: const Mjono operator +( const Mjono& teksti1, const Mjono& teksti2);

45 Mitä tuli tehtyä? Uudelleenmäärittelimme ‘+’ –operaattorin tavallisena funktiona: const Mjono operator +( const Mjono& teksti1, const Mjono& teksti2); Yhden argumentin sijasta annamme kaksi argumenttia C-tyyppinen merkkijono voidaan muuntaa sopivan tyyliseksi oikeanlaisella rakentajalla

46 Vielä pieni ongelma Tavallisina funktioina toteutetut operaattorien uudelleenmäärittelyt ovat tehottomia Olioiden tietoihin voi päästä käsiksi vain olioiden julkisten rajapintojen kautta (get-metodit, muunnosmetodit) Ylimääräistä työtä! Olisi parempi, jos operaattorin uudelleenmäärittelyn toteuttavassa funktiossa päästäisiin käsiksi käsiteltävien olioiden tietoihin suoraan

47 Missä mennään? C++ tekniikoita Virhetilanteet ja poikkeukset
Nimiavaruus Luokkamuuttujat Luokkafunktiot Operaattorien uudelleenmäärittely Ystäväfunktiot Ystäväluokat Virhetilanteet ja poikkeukset Syitä poikkeuksiin Virhetilanteisiin reagointi Virhehierarkiat Poikkeuksien heitto ja sieppaaminen Poikkeukset ja oliot Yhteenveto

48 Ystäväfunktio Luokalla on mahdollisuus määritellä joukon funktioita “ystävikseen” Luokan ystäväfunktioiden koodi pääsee käsiksi myös luokan olioiden private-osiin. ystäväfunktioilla on käytännössä katsoen samat oikeudet luokan olioihin kuin luokan omilla jäsenfunktioilla Ystäväfunktiot ei kuitenkaan pääse käsiksi olioiden this-osoittimeen

49 Ystäväfunktion määrittely
Ystäväfunktio määritellään kirjoittamalla varattu sana friend ja sen jälkeen funktion koko nimi Kyseinen esittely EI tee ystäväfunktiosta luokan jäsenfunktiota! kyseessä on täysin erillinen normaali funktio, jolle vain sallitaan pääsy luokan olioiden private-osaan

50 ‘+’-operaattorin uudelleenmäärittely ystäväfunktiona
class Mjono { public: //rakentajat Mjono(); Mjono(const char *const); Mjono(const Mjono &) ~Mjono; Mjono operator+(const Mjono&); friend Mjono operator+(const Mjono&, const Mjono&); .

51 Ystäväfunktiot yhteenveto
Ystäväfunktioilla on lupa päästä käsiksi olioiden kaikkiin tietoihin Ystäväfunktiot heikentää tiedon kapselointia, mutta parantaa suorituskykyä Tyypillisin käyttökohde on operaaattoreiden uudelleenmäärittely

52 C++ Ystäväluokat Toimii samoin kuin ystäväfunktiot
Jos joku luokka liittyy kiinteästi kehitteillä olevaan luokkaan, voidaan liittyvä luokka määritellä ystäväluokaksi Luokkaystävyys saadaan aikaiseksi määreellä friend class Luokkanimi Luokkaystävyys tulee ilmaista sen luokan esittelyssä, joka haluaa sallia toisen luokan jäsenfunktioille vapaan pääsyn omien olioidensa sisälle

53 C++ ystäväluokka Esimerkki
class KirjastonKirja { public: //rakentajat . private: void asetaPalautusPvm(const Paivays& uusiPvm); Paivays palautusPvm_; friend class LainausJarjestelma; //Lainausjärjestelman oliot kutsuvat funktiota asetaPalautusPvm };

54 Missä mennään? C++ tekniikoita Virhetilanteet ja poikkeukset
Nimiavaruus Luokkamuuttujat Luokkafunktiot Operaattorien uudelleenmäärittely Ystäväfunktiot Ystäväluokat Virhetilanteet ja poikkeukset Syitä poikkeuksiin Virhetilanteisiin reagointi Virhehierarkiat Poikkeuksien heitto ja sieppaaminen Poikkeukset ja oliot Yhteenveto

55 Virhetilanteet ja poikkeukset
Virhetilanteisiin varautuminen ja niihin reagoiminen on aina ollut yksi vaikeimpia ohjelmoinnin haasteista Virhetilanteissa ohjelman täytyy yleensä toimia normaalista poikkeavalla tavalla Uusien ohjelman suoritusreittien koodaaminen tekee ohjelmakoodista helposti sekavaa Useiden erilaisten virhetilanteiden viidakossa ohjelmoijalta jää helposti tekemättä tarvittavia siivoustoimenpiteet Usein myös vaaditaan, että ohjelman tulee toipua virheistä täytyy pystyä peruuttamaan virhetilanteen vuoksi kesken jääneet operaatiot

56 Poikkeukset (exception)
C++ tarjoaa virheiden käsittelyyn erityisen mekanismin, poikkeukset (exception) Poikkeusten toiminta perustuu luokkahierarkioihin

57 Syitä virheisiin Määrittelyvirheet Suunnitteluvirheet
yritetään tehdä ohjelmalla jotain mihin sitä alunperinkään ei ole tarkoitettu Suunnitteluvirheet toteutukseen ei ole otettu mukaan kaikkia määrittelyssä olleeita asioita toteutus on suunniteltu virheelliseksi Ohjelmointivirheet ohjelmointityössä on tapahtunut virhe

58 Virheisiin varautuminen
Ohjelmointityössä ei pysty vaikuttamaan määrittelyn ja suunnittelun aikaisiin virheisiin ne paljastuvat ohjelmiston testauksessa tai huonoimmassa tapauksessa vasta tuotantokäytössä Ohjelmoinnissa voidaan varautua etukäteen pohdittuihin vikatilanteisiin laitteistovirheet ohjelmistovirheet

59 Laitteistovirheet Näkyvät ohjelmistolle sen ympäristön käyttäytymisenä eri tavoin kuin on oletettu. Esimerkkejä: viallinen muistipiiri saattaa esim. aiheuttaa odottomattoman muuttujan arvon muutoksen. tiedoston käsittely voi mennä pieleen levyn täyttymisen tai vikaantumisen vuoksi Ohjelman latauksessa on voinut tapahtua virhe, mikä on johtanut ohjelman suorituksessa tapahtuneisiin muutoksiin

60 Laitteistovirheet Laitteistovirheistä saadaan tietoa yleensä käyttöjärjestelmän kautta Laitteistovirheitä voi kuitenkin tapahtua siten, ettei niistä tule mitään ilmoitusta Mihin laitteistovirheisiin tulisi reagoida? Kaikkia laitteistovirheitä on vaikea ottaa huomioon. On tehtävä kompromissi. Yleinen tapa on varautua käyttöjärjestelmän ilmoittamiin vikoihin ja jättää muut huomimoimatta luottaen niiden olvan erittäin harvinaisia

61 Ohjelmistovirheet Ulkoiset virheet: Koodia pyydetään tekemään jotain, mitä se ei osaa tai mihin se ei pysty. Esim: Funktion parametrilla on väärä arvo syötetiedosto ei noudata määriteltyä muotoa käyttäjä on valinnut toimintosekvenssin jossa ei ole “järkeä” Sisäiset virheet: Toteutus ajautuu itse tilanteeseen jossa jotain menee pieleen. Esim: muisti loppuu toteutusalgoritmissa tulee jokin ääriraja vastaan

62 Virheiden havaitsemisesta
Virheiden havaitseminen on yleensä helppoa tähän toimintaan käyttöjärjestelmät, ohjelmakirjastot ja ohjelmointikielet tarjoavat lähes aina keinoja Havaitsemista paljon vaikeampaa on suunnitella ja toteuttaa se, mitä vikatilanteessa tehdään!

63 Varautuva ohjelmointi (defensive programming)
Ohjelmointityyli, jota voisi verrata autolla ajossa ennakoivaan ajotapaan. Vaikka oma toiminta olisi täysin oikein ja sovittujen sääntöjen mukaista, kannattaa silti varautua siihen, että muut osallistujat voivat toimia väärin. Usein ajoissa tapahtunut virheiden ja ongelmien havaitseminen mahdollistaa niihin sopeutumisen jopa siten, että ohjelmissa käyttäjän ei tarvitse huomata mitään erityistilannetta edes syntyneen

64 Virhetilanteisiin reagointi
Sopiva suhtautuminen virheeseen on ohjelmakomponentin suunnitteluun kuuluva asia Ei ole olemassa yhtä ainoaa oikeata tai väärää tapaa Hyvin suunniteltu komponentti voi ottaa virheisiin reagoinnin omalle vastuulleen Yhtä hyvänä ratkaisuna voidaan pitää myös sellaista, joka “ainoastaan” ilmoittaa havaitsemansa virheet komponentin käyttäjälle

65 Virhetilanteisiin reagointi Suorituksen keskeytys (abrupt termination)
Äärimmäinen tapa toimia kun ohjelmassa kohdataan virhe. Järjestelmän suorittaminen keskeytetään välittömästi ja usein ilman, että virhetilannetta yritetään edes mitenkään kirjata myöhempää tarkastelua varten Tulisi vältää, sillä pysähtyneestä ohjelmasta ei edes aina tiedetä miksi pysähtyminen tapahtui Useissa käyttöjärjestelmissä ja ohjelmointikielten ajoympäristöissä (valitettavasti) oletustoimintana

66 Virhetilanteisiin reagointi Suorituksen hallittu lopetus (abort, exit)
Keskeytystä lievempi tapa Yritetään: siivota ohjelmiston tila vapauttamalla kaikki sen varaamat resurssit kirjata virhetilanne pysyvään talletuspaikkaan ilmoittamaan virheestä käyttäjälle ennen suorituksen lopettamista

67 Virhetilanteisiin reagointi Jatkaminen (continuation)
Jätetään havaittu virhe huomiotta Harvinainen toimintamalli Esimerkki: hiirikohdistimen paikkatieto katoaa (ei vaikuta juurikaan ohjelmiston toimintaan).

68 Virhetilanteisiin reagointi Peruuttaminen (rollback)
Palautetaan järjestelmä virhettä edeltäneeseen tilaan Helpottaa huomattavasti operaation yrittämistä uudelleen Tiedämme tarkkaan missä tilassa ohjelmisto on, vaikka virhe on tapahtunut Peruuttamisen toteutus on valitettavasti usein mutkikasta ja resursseja kuluttavaa Palautus voi itsessään aiheuttaa virheitä ohjelmistoon Eräs yksinkertainen toteutustapa on: luoda kopio muutettavasta tiedosta ennen operaatiota tehdä muutokset kopioon operaation onnistuessa vaihtaa kopion tiedot alkuperäisen tiedon tilalle operaation epäonnistuessa tuhota kopio

69 Virhetilanteisiin reagointi Toipuminen (recovery)
Ohjelman osan paikallinen toteutus hallitusta lopetuksesta Osanen ei pysty itse käsittelemään havaittua virhettä. Sen sijaan osanen: pyrkii vapauttamaan kaikki varaamansa resurssit tiedottamaan ohjelmistossa toisaalle (usein loogisesti ylemmälle tasolle) jonka toivotaan pystyvän käsittelemään havaittu virhe paremmin

70 Virhetilanteen korjaus
Peruuttaminen ja toipuminen antavat mahdollisuuden yrittää korjata virhetilanteeseen johtanutta tilannetta Pieleen menneen operaation yrittäminen uudelleen on virheisiin reagoinnin suunnittelussa hankalinta Helpoimmassa tapauksessa vain toistetaan operaatio (esim. tietoliikenteessä uudelleenlähetys) Valitettavasi usein virhetilanne johtuu ongelmista resursseista, joiden puuttuessa tilanteen korjaaminen on hankalaa

71 Virheestä toipuminen Esimerkki: Muistin loppuminen
Helppo havaita. Miten toipua? Jos muisti on loppu, niin toipumisoperaatio ei saa viedä yhtään lisää muistia Järkevintä olisi varmaan vapauttaa muistia Hyvin toteutetuissa ohjelmissa ei kuitenkaan ole “turhia” muistivarauksia Voidaan yrittää vapauttaa “vähemmän tärkeitä” muistivarauksia. Tämä saattaa kuitenkin johtaa virhetilanteisiin. Yksi tapa selviytyä muistin loppumisesta on varata kasa “turhaa” muistia tämä voidaan turvallisesti vapauttaa uusiokäyttöön, jos joudutaan tilanteeseen, missä muisti on lopussa

72 Virhehierarkiat Ohjelmassa tapahtuvat virhetilanteet voidaan jakaa kategorioihin sen perusteella, mihin virhe liittyy Vieressä on malli siitä, miten C++:n omat poikkeukset kategorisoidaan Ohjelmoija voi joko: laajentaa C++ poikkeushierarkiaa kirjoittaa täysin oman hierarkian C++ poikkeushierarkian laajentaminen lienee suositellumpaa se yhtenäistää virheiden käsittelyä

73 Virhehierarkiat jatkuu….
Virhehierarkiat mahdollistavat virhekäsittelyn jakamisen eri tasoihin Tietty ohjelman osa voi esimerkiksi käsitellä ylivuodot ja jättää muut virheet ohjelman ylempien tasojen huoleksi Poikkeuksia voi mallintaa ja toteuttaa aivan kuin luokkiakin

74 Virhetyypit C++ luokkina
class exception { public: exception() throw(); //throw() selitetään myöhemmin exception (const exception& e) throw(); exception& operator =(const exception& e) throw(); virtual ~exception() throw(); virtual const char* what() const throw(); . }; class runtime_error : public exception runtime_error(const std::string& msg); class overflow_error : public runtime_error overflow_error(const std::string& msg);

75 Esimerkki omasta virheluokasta
class LiianPieniArvo : public std::domain_error { public: LiianPieniArvo(const std::string&viesti, int luku, int minimi); LiianPieniArvo(const LiianPieniArvo& virhe); virtual ~LiianPieniArvo() throw(); int annaLuku() const; int annaMinimi() const; private: int luku_; int minimi_; };

76 Poikkeusten heittäminen ja sieppaaminen
Virheen sattuessa ohjelma heittää (throw) “ilmaan” poikkeusolion, joka kuvaa kyseistä virhettä Tämän jälkeen ohjelma alkaa”peruuttaa” funktioiden kutsuhierarkiassa ylöspäin ja yrittää etsiä lähimmän poikkeuskäsittelijän (exception handler), joka pystyy sieppaamaan (catch) virheolion ja reagoimaan virheeseen.

77 Poikkeuskäsittelijä Jokaisella poikkeuskäsittelijällä on oma koodilohkonsa, valvontalohko (try-block), jonka sisällä syntyvät virheet ovat sen vastuulla Virhekäsittelyn yhteydessä poikkeusoliosta tehdään kopio, joten on tärkeää, että poikkeusluokilla on toimiva kopiorakentaja!

78 Esimerkki poikkeuskäsittelijästä
Tarkastellaan keskiarvon laskentaa esimerkkinä Virhemahdollisuudet: lukujen lukumäärä saattaa olla nolla lukujen summa saattaa kasvaa liian suureksi

79 Keskiarvoesimerkki: Lukujen yhteenlasku
void lueLuvut Taulukkoon (vector<double>& taulu); double summaaLuvut (const vector<double>& luvut) { double summa = 0.0; for (unsigned int i =0; i< luvut.size(); ++i) if (summa >= 0 && luvut[i] > numeric_limits<double>::max()-summa) throw std::overflow_error(“Summa liian suuri”); } else if ( summa < 0 && luvut[i] < -numeric_limits<double>::max()-summa) throw std:overflow_error(“Summa on liian pieni”); summa += luvut[i]; return summa;

80 Keskiarvoesimerkki: Keskiarvon lasku
double laskeKeskiarvo (const vector<double>& luvut) { unsigned int lukumaara = luvut.size(); if (lukumaara ==0) throw std::range_error(“Lukumäärä keskiarvossa 0”); } return summaaLuvut(luvut) / static_cast<double>(lukumaara); void keskiarvoLaskuri(vector<double>& lukutaulu) try lueLuvutTaulukkoon(lukutaulu); double keskiarvo = laskeKeskiarvo(lukutaulu); cout << “Keskiarvo: “ <<keskiarvo << endl; catch (const std::range_error& virhe) cerr <<“Lukualuevirhe: “ << virhe.what() endl; catch (const std::overflow_error& virh) cerr << “Ylivuoto: “ << virhe.what() << endl; cout << “Loppu” << endl;

81 Keskiarvoesimerkki: Katsotaan tarkemmin
try { } catch (std::ragne_error& virhe) catch (std::overflow_error& virhe) keskiarvolaskuri laskeKeskiarvo summaaLuvut throw std::range_error(“Lukumäärä keskiarvossa 0”); throw std::overflow_error(“Summa liian suuri”); throw std::overflow_error(“Summa liian pieni”);

82 Poikkeuksien kiinni ottamisesta
On mahdollista ottaa kiinni kaikki tiettyyn virhekategoriaan kuuluvat poikkeukset ottamalla kiinni kantaluokkana oleva poikkeus: void keskiarvo2 (vector<double>& lukutaulu) { try lueLuvutTaulukkoon(lukutaulu); double keskiarvo = laskeKeskiarvo(lukutaulu); cout << “Keskiarvo: “ << keskiarvo << endl; } catch (const std::runtime_error& virhe) //Tänne tullaan minkä tahansa ajonaikaisen virheen seurauksena cerr << “ajonaikainen virhe: “ << virhe.what() << endl; cout << “Loppu” << endl;

83 Poikkeukset, joita ei oteta kiinni
Keskiarvoesimerkkimme otti kiinni vain 3 tiettyä poikkeusta Muitakin poikkeuksia voi kuitenkin tapahtua muisti loppuu taulukkoon luettaessa bad_alloc Muut poikkeukset pääsevät vuotamaan ohjelmastamme pois seurauksena on yleensä ohjelman terminointi

84 Kaikkien poikkeuksien kiinni ottaminen
Poikkeukset, joihin ei ole varauduttu, aiheuttavat ohjelman kaatumisen on tärkeää, että ohjelmassa otetaan jollain tasolla kiinni kaikki aiheutetut poikkeukset. Ohjelmaan voi lisätä “yleispoikkeuskäsittelijöitä”, jotka ottavat vastaan kaikki valvontalohkossaan tapahtuvat poikkeukset: catch(…) //Todellakin … eli kolme pistettä { //Tämä poikkeuskäsittelijä sieppaa kaikki poikkeukset } Yleensä tällaisia “kaikkivoipia” yleispoikkeuskäsittelijöitä ei kannata kirjoittaa kuin korkeintaan pääohjelmaan Yleispoikkeuskäsittelijähän sieppaa myös sellaiset virheet, joihin kenties voitaisiin paremmin reagoida ylemmällä tasolla ohjelmassa

85 Keskiarvoesimerkin parantaminen Sisäkkäiset valvontalohkot
try { } catch (std::bac_alloc& virhe) catch (std::exception& virhe) Pääohjelma try { } catch (std::ragne_error& virhe) catch (std::overflow_error& virhe) keskiarvolaskuri laskeKeskiarvo throw std::range_error(“Lukumäärä keskiarvossa 0”); throw std::overflow_error(“Summa liian suuri”); throw std::overflow_error(“Summa liian pieni”); summaaLuvut

86 Sisäkkäiset valvontalohkot
Hyödyllinen ominaisuus! alemmissa osassa voidaan käsitellä tietyt spesifiset virheet ylemmällä tasolla voidaan käsitellä laajempia virhekategorioita int main() { vector<doulbe> taulu; try keskiarvolaskuri(taulu); } catch (const std::bad_alloc& virhe) cerr << “Muisti loppui!“ << endl; return EXIT_FAILURE; catch (const std::exception& virhe) cerr << “Virhe pääohjelmassa << virhe.what() << endl; return EXIT_SUCCESS;

87 Poikkeukset ja olioiden tuhoaminen
Kun poikkeus tapahtuu, ohjelma palaa takaisin koodilohkoista ja funktoista, kunnes se löytää sopivan poikkeuskäsittelijän Peruuttamisen tuloksena saatetaan poistua usean olion ja muuttujan näkyvyysalueelta C++ tuhoaa automaattisesti kaikki oliot ja muuttujat, joiden näkyvyysalue loppuu poikkeuksen tuloksena Olioiden purkajia kutsutaan automaattisesti, joten siivoustoimenpiteet suoritetaan niin kuin pitääkin poikkeukset eivät aiheuta mitään ongelmia olioille, joiden elinkaari on staattisesti määritelty

88 Poikkeukset ja dynaamisesti luodut oliot
Dynaamisen elinkaaren oliot ovat ongelmallisia niitä ei tuhota automaattisesti poikkeustilanteissa niihin osoittavat osoittimet (usein staattisesti luotu) puolestaan tuhotaan muistiin jää helposti tuhoamattomia olioita, joita on mahdoton tuhota. Ratkaisuna ongelmaan on ympäröidä ne koodialueet, missä käytetään osoittimia aina omilla valvontalohkoilla

89 Esimerkki dynaamisen olion siivoamisesta
void siivousfunktio1() { vector<double>* taulup = new vector<double>() ; try { //Jos täällä sattuu virhe, vektori pitää tuhota keskiarvolaskuri(*taulup); } catch (...) { //Otetaan kiinnin kaikki virheet ja tuhotaan vektori delete taulup; taulup = 0; throw; //Heitetään poikkeus edelleen käsiteltäväksi //Tänne päästään, jos virheitä ei satu

90 Muistivuotojen välttäminen auto_ptr
Dynaamisen elinkaaren oliot tuottavat poikkeuskäsittelyssä paljon ongelmia. Niitä on kuitenkin pakko käyttää, jos olion elinkaari ei osu yksiin minkään koodilohkon näkyvyysalueen kanssa Toisaalta dynaamisesti luotujen olioiden tuhoaminen kaikissa mahdollisissa virhetilanteissa lisää tarvittavan koodin määrää Koodi tulee vaikealukuisemmaksi auto_ptr ratkaisee osan dynaamisten olioiden ongelmista auto_ptr ei valitettavasti ole käytettävissä vanhemmissa kääntäjissä

91 Automaattiosoittimet ja muistinhallinta
auto_ptr:n saa käyttöön komennolla #include <memory> auto_ptr käyttäytyy täsmälleen samoin kuin tavallinen osoitinkin sen saa alustaa osoittamaan dynaamisesti luotuun olioon siihen voi sijoittaa uuden olion sen päässä olevaan olioon pääsee käsiksi normaalisti operaattoreilla * ja ->

92 Automaattiosoittimet vs. tavalliset osoittimet
Erona automaattisoittimen ja tavallisten osoittimien välillä: automaattiosoitin omistaa päässään olevan olion automaattisoittimilla ei voi tehdä osoitinaritmetiikkaa automaattiosoittimia ei voi käyttää osoittamaan taulukoihin automaattiosoittimesta on mahdollisuus saada ulos “ei-omistava” osoitin jäsenfunktiokutsulla osoitin.get() Automattiosoittimen päähän saa sijoittaa vain dynaamisesti new’llä luotuja olioita

93 Automaattisoittimien hyödyllisyys
Automaattiosoitinta tuhottaessa se suorittaa automaattisesti delete-operaation päässään olevalle oliolle! Ohjelmoijan ei tarvitse välittää dynaamisesti luotujen olioiden tuhoamisesta Jos olio on laitettu automaattiosoittimen päähän, tuhoamisvastuu siirtyy osoittimelle Koska automaattiosoittimen elinkaari on staattinen, kääntäjä pitää huolen olion tuhoamisesta

94 Esimerkki automaattiosoittimen käytöstä
#include <memory> void siivousfunktio2() { auto_ptr < vector<double> > taulup(new vector<double>()); keskiarvolaskuri(*taulup); auto_ptr < vector<double> > taulu2p(nyw vector<double>()); for (unsigned int i = 0; i < taulup->size(); ++i) { //lasketaan taulukon neliöt taulu2p->push_back((*taulup)[i] * (*taulup)[i]); } cout << “Neliöiden keskiarvo: “ << laskeKeskiarvo(*taulu2p) << endl;

95 Automaattiosoittimien sijoitus ja kopiointi
Ohjelmoinnissa on yleistä, että monta osoitinta osoittaa samaan olioon Olion voi kuitenkin omistaa vain yksi automaattiosoitin kerrallaan! automaattiosoittimien kopiointi ja sijoittaminen siirtävät olion osoittimesta toiseen Siirtämisen seuraus kopionnissa: uusi osoitin osoittaa olioon ja omistaa sen vanha osoitin on “tyhjentynyt” eikä enää osoita minnekkään Siirtämisen seuraus sijoituksessa: uusi osoitin tuhoaa vanhan olionsa siirtyy omistamaan uutta oliota alkuperäinen osoitin on “tyhjentynyt” eikä enää osoita minnekkään

96 Automaattiosoittimen käytön rajoitukset
Automaattiosoittimen päähän saa laittaa vain dynaamisesti new’llä luotuja olioita Vain yksi automaattiosoitin voi osoittaa samaan olioon kerrallaan. Automaattiosoittimien sijoitus ja kopiointi siirtävät olion omistuksen automaattiosoittimelta toiselle Sijoituksen ja kopioinnin jälkeen olioon ei pääseä käsiksi vanhan automaattiosoittimen kautta Automaattiosoittimelle ei voi tehdä osoitinaritmetiikkaa (++, --, inksointi ynnä muut) Automaattiosoittimen päähän ei voi panna tavallisia taulukoita (sen sijaan vector ei tuota ongelmia) Automaattiosoittimia ei voi laittaa STL:n tietorakenteiden sisälle. Tämä tarkoittaa, että esimerkiksi vector< auto_ptr<int> > ei toimi

97 automaattiosoittimet Lopuksi
Automaattiosoittimet helpottavat huomattavasti dynaamisten olioiden hallintaa välillä ongelmia tuottaa se, että dynaamisesti luodun olion voi omistaa vain yksi osoitin kerrallaan Yksi vaihtoehto automaattiosoittimille on “älykkäät osoittimet” (smart pointer) käyttävät viitelaskureita olion elinkaaren määräämiseen Pitävät siis yllä laskuria siitä, kuinka monta osoitinta olioon osoittaa. Kun viimeinen älykäs osoitin tuhoutuu tai lakkaa osoittamsta olioon, se tuhoaa olion Älykkäät osoittimet eivät kuuluu ISO C++ kieleen, mutta verkosta saa useita toimivia toteutuksia

98 Olio-ohjelmointi ja poikkeusturvallisuus
Vikasietoisen ja poikkeuksiin varautuvan ohjelman kirjoittaminen on yleensä erittäin monimutkaista ja tarkkuutta vaativaa työtä. virhetilanteita (=poikkeuksia) voi tapahtua lähes missä tahansa kohdassa ohjelmaa Olio-ohjelmoinnin käsitteet tekevät virhetilanteista entistäkin vaikeampia kapselointi piilottaa toteutukset, joten luokan käyttäjä ei voi nähdä millaisia virheitä koodissa voi syntyä kapselointi mahdollistaa myös luokan sisällä tehtävät muutokset, jolloin reagointi virheisiin voi poiketa aiemmasta periytyminen ja polymorfismi aiheuttavat sen, ettei luokkahierarkian käyttäjä edes välttämättä tarkasti tiedä minkä luokan oliota käyttää

99 Olio-ohjelmointi ja poikkeusturvallisuus
Vikasietoisten luokkien kirjoittaminen tulee helpommaksi, jos ensin määritellään joukko pelisääntöjä, joita olioiden tulee virhetilanteissa noudattaa Olioiden käyttäytyminen virhetilanteissa voidaan jakaa selkeisiin kategorioihin rajapintadokumentaation kirjoittaminen ja ymmärtäminen tulee helpommaksi Poikkeusturvallisuuden tasoja on: perustakuu vahva takuu nothrow-takuu poikkeusneutraalius

100 Perustakuu (basic guarantee)
Takuun sisältö: olio ei hukkaa resursseja olio on edelleen sellaisessa tilassa, että sen voi tuhota Olion ei tarvitse olla ennustettavassa tilassa virhetilanteen jälkeen. Olio pitää vain pystyä tuhoamaan ilman ongelmia Löyhin takuu

101 Vahva takuu (strong guarantee)
Takuun sisältö: Operaatio joko: suoritetaan loppuun ilman virhettä poikkeuksen sattuessa olion tila pysyy alkuperäisenä Luokan käyttäjän kannalta mukavia voidaan luottaa siihen, että virheen sattuessa olion tila on säilynyt ennallaan Luokan toteuttajalle vahva takuu tuottaa lisävaivaa Yksi ratkaisu on kopioida olion tila talteen ennen operaation suorittamista. Seurauksena usein suurempi muistin kulutus ja pienempi suoritusnopeus  Vahvan takuun käyttöä tulee harkita tarkoin

102 Nothrow-takuu (nothrow guarantee)
Takuu: Operaation suorituksessa ei voi sattua virheitä! Kaikkein vahvin poikkeustakuu Mitään poikkeuksia ei vuoda operaatiosta ulos Operaatio onnistuu aina Käyttäjän kannalta ideaalinen. Virheisiin ei tarvitse varautua lainkaan Luokan kehittäjän kannalta vaikea toteuttaa.

103 Poikkeusneutraalius (exception neutrality)
Takuu: komponentit vuotavat poikkeukset ulos muuttumattomina Luokka saa ottaa poikkeuksia väliaikaisesti kiinni

104 Poikkeukset ja rakentajat
C++ määritelmän mukaan olio katsotaan syntyneeksi kun kaikki olion luomiseen kuuluvat rakentajat on suoritettu onnistuneesti loppuun “syntymishetken” ymmärtäminen on oleellista tilanteessa, missä olion luonnin yhteydessä tapahtuu poikkeus Jos virhe on tapahtunut ennen kuin olio on luotu loppuun asti, ei oliota ole C++:n kannalta saatu luoduksi. Tällöin C++ tuhoaa automaattisesti kaikki jo luomansa olion osat (poikkeuksena taas dynaamisesti luodut asiat)

105 Dynaamisesti luodut osaoliot
Dynaamisia olioita ei tuhota automaattisesti poikkeustilanteissa On erittäin tärkeää, että olioita ei luoda dynaamisesti rakentajan alustuslistassa! Tällaiseen virheeseen ei voi reagoida edes rakentajan rungon koodissa olevalla virhekäsittelijällä, sillä rakentajan koodiin siirrytään vasta,kun kaikki jäsenmuuttujat on onnistuneesti luotu. Parempi alustaa jäsenmuuttujaosoittimet rakentajan alustuslistassa nolliksi ja luoda oliot dynaamisesti vasta rakentajan rungossa Näin oliot luodaan vasta kun kaikki normaalit jäsenmuuttujat on onnistuneesti luotu

106 Luomisvirheisiin reagoiminen
Olion luonnissa tapahtuneesta virheestä ei voi toipua tällöin koko olion luonti epäonnistuu jo luotujen olioiden purkajia kutsutaan automaattisesti Joskus on kuitenkin pakko yrittää toipua virheestä. Sellaiset osaoliot, joiden luonti voi epäonnistua kannattaa luoda tavallisen jäsenmuuttujan sijaan dynaamisesti osoittimen päähän Tällöin osaolion luomisen new’llä voi tehdä rakentajan rungossa, jonka virheenkäsittelykoodi sitten yrittää luomista tarvittaessa uudelleen Vaikka olion luonnin epäonnistumisesta ei voi toipua, voi silti olla tarve reagoida epäonnistumiseen Tätä tarvetta varten on olemassa funktion valvontalohko (function try block), jota voi käyttää rakentajissa ja purkajissa Funktion valvontalohko ottaa kiinni poikkeukset, jotka tapahtuvat jäsenmuuttujien tai kantaluokkaosioen luomisen (tai tuhoamisen) yhteydessä

107 Funktion valvontalohko Esimerkki
Avainsana try tulee jo ennen rakentajan alustuslistaa, eikä sen jälkeen tule enää aaltosulkuja Valvontalohko kattaa automaattisesti virheet, jotka syntyvät osaolioita luotaessa tai itse rakentajan rungossa Valvontalohkoon liittyvät virhekäsittelijät tulevat aivan rakentajan loppuun sen rungon jälkeen On huomattava, että virhekäsittelijöihin päästäessä olion jo luodut jäsenmuuttujat ja kantaluokkaosat on jo tuhottu! Henkilo::Henkilo( int p, int k, int v, const std::string& nimi, const std::string& hetu) try //huomaa try –sanan paikka! : syntymapvm_(p, k, v), nimi_(nimi), hetup_(0) { ... } catch ( ...) //Tänne päästään , jos jäsenmuuttujien (tai kantaluokan) //luominen epäonnistuu. Tehdään tarvittaessa toimenpiteitä. throw; //tai heitetään jokin toinen poikkeus

108 Poikkeukset ja purkajat
Poikkeuksen sattuessa kutsutaan automaattisesti kaikkien sellaisten olioiden purkajia, joiden elinkaari loppuu virhekäsittelijän etsimisen yhteydessä Näin olion purkajaa saatetaan kutsua sellaisessa tilanteessa, jossa vireillä on jo yksi poikkeustilanne. Jos purkaja vuotaa ulos vielä toisen poikkeuksen, ei C++:n poikkeusmekanismi pysty selviytymään tilanteesta Ohjelman suoritus keskeytetään Älä koskaan vuoda purkajasta poikkeuksia ulos!

109 Mitä opimme tänään? C++ tekniikoita Virhetilanteet ja poikkeukset
Opimme hallitsemaan nimikonfliteja käyttämällä nimiavaruuksia Luokan kaikkille oliolle yhteisiä asioita voi määritellä luokkamuuttujiksi ja –funktioiksi Voimme uudelleenmääritellä operaattoreita, jotta luokkiamme olisi mukavempi käyttää On mahdollista (vaikkei välttämättä suositeltavaa) antaa luokan ulkopuoliselle funktiolle tai luokalle vapaa pääsy luokan jäseniin Virhetilanteet ja poikkeukset Virheiden käsittely on vaikeaa ja tarkkaa työtä. Oliopohjaisuus monimutkaistaa entisestään virheiden käsittelyä Opimme virheiden käsittelyyn liittyviä ongelmatilanteita ja virheistä toipumis strategioita


Lataa ppt "Sami Jantunen LTY/Tietotekniikan osasto"

Samankaltaiset esitykset


Iklan oleh Google