Upload
opa
View
51
Download
5
Embed Size (px)
DESCRIPTION
Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset. Sami Jantunen LTY/Tietotekniikan osasto. Sisältö. C++ tekniikoita Nimiavaruus Luokkamuuttujat Luokkafunktiot Operaattorien uudelleenmäärittely Ystäväfunktiot Ystäväluokat Virhetilanteet ja poikkeukset - PowerPoint PPT Presentation
Citation preview
Olio-ohjelmoinnin perusteetluento 6: C++ tekniikoita, virhetilanteet ja poikkeukset
Sami JantunenLTY/Tietotekniikan osasto
Sisältö C++ tekniikoita
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
Ongelma
myProgram.cpp
#include "vendor1.h" #include "vendor2.h"
void main(){
…}
// vendor1.h
... various stuff ... class String { ... };
// vendor2.h
... various stuff ... class String { ... };
Löydätkö esimerkistä ongelman?
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
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ä
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);
...}
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 mitä alkiota sen sisällä hän haluaa käyttää
Paivays::tulosta() Kirja::tulosta()
Ylimääräistä kirjoittelua, mutta toisaalta selkiyttää koodia
NimiavaruudetHyö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
Korjataan ongelma
// vendor2.h
... various stuff ... namespace Vendor2 {
class String { ... }; }
// vendor1.h
... various stuff ... namespace Vendor1 {
class String { ... };}
Enää ei ole käytössä kahta String-luokka String-luokkien sijasta meillä on käytössä
luokat:Vendor1::StringVendor2::String
std nimiavaruus 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
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>
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;}
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
usingEsimerkki
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;}
usingOhjeita Käytä using-lausetta mielellään vasta
kaikkien #include-käskyjen 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.
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-lauseetusing Paivays::PVM;using Loki::varoitus;using Loki::virhe;
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
Nimiavaruuden synonyymiEsimerkki#include “prjlib/string.h”#include <string>
int main(){ #ifdef PRJLIB_OPTIMOINNIT_KAYTOSSA namespace Str = ComAcmeFastPrjlib; #else namespace Str = std; #endif
Str::string esimerkkijono; . . .}
Missä mennään? C++ tekniikoita
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
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ä?
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?
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
Hmmm…
Huomaamme, että joskus olisi tarvetta sellaisille jäsenille, jotka on yhteisiä kaikille luokan olioille! toisaalta kaikille olioille toisaalta ei millekkään niistä
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
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;
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
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
Ja ratkaistaan ongelma!
Mikä on oheisen koodin lopputulos?
// 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;
}
Vastaus: 7
6
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?
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
Singleton Patternclassclass Singleton { Singleton {publicpublic:: staticstatic Singleton *get_instance(); Singleton *get_instance();protectedprotected:: Singleton(); Singleton();
Singleton( Singleton( constconst Singleton& s); Singleton& s);privateprivate:: staticstatic Singleton *instance; Singleton *instance;};};Singleton::instance = 0;Singleton::instance = 0;
Singleton *Singleton::get_instance() {Singleton *Singleton::get_instance() { ifif ( instance == 0 ) { ( instance == 0 ) { instance = instance = newnew Singleton; Singleton; } } returnreturn instance; instance;}}
Company
theCompany
Company «private»getInstance
if (theCompany==null) theCompany= new Company();
return theCompany;
«Singleton»
theInstance
getInstance
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)!
Missä mennään? C++ tekniikoita
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
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
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.
Uudelleenmääriteltävät operaattorit C++ kielessä
+ - * / % ^ & | ~ ! = < > += -= *= /= %= ^= &= |= << >> >>= <<= == != <= >= && || ++ -- ->* , -> [] () new delete
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;
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 ->
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
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;
Operaattorin uudelleenmäärittely jäsenfunktiona Esitetty yhteenlasku
voidaan esittää funktiokutsuna muodossa: kokoNimi = etuNimi.+(sukuNimi);
funktion nimi
argumenttikutsuttavaolio
Ongelma!
Miksi seuraava ei onnistu?:char cMjono[] = {“Hello”};Mjono mjono(“World!”);Mjono mJonoKaksi = cMjono + mMjono; //Virhe
Syy ongelmaan
Mjono mJonoKaksi = cMjono + mMjono; //Virhe
funktion nimi
argumenttikutsuttavaolio
cMjono on C-tyyppinen merkkijono sillä ei voi olla jäsenfunktioita se ei voi suoriutua komennosta: cMjono.operator+(mMjono)
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);
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
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
Missä mennään? C++ tekniikoita
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
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
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
‘+’-operaattorin uudelleenmäärittely ystäväfunktiona
class Mjono{
public: //rakentajatMjono();Mjono(const char *const);Mjono(const Mjono &)~Mjono;
Mjono operator+(const Mjono&);
friend Mjono operator+(const Mjono&, const Mjono&);...
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
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
C++ ystäväluokkaEsimerkki
class KirjastonKirja{
public: //rakentajat...private:void asetaPalautusPvm(const Paivays& uusiPvm);Paivays palautusPvm_;...friend class LainausJarjestelma;//Lainausjärjestelman oliot kutsuvat funktiota asetaPalautusPvm
};
Missä mennään? C++ tekniikoita
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
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
Poikkeukset (exception)
C++ tarjoaa virheiden käsittelyyn erityisen mekanismin, poikkeukset (exception)
Poikkeusten toiminta perustuu luokkahierarkioihin
Syitä virheisiin Määrittelyvirheet
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
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
Laitteistovirheet
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
Näkyvät ohjelmistolle sen ympäristön käyttäytymisenä eri tavoin kuin on oletettu. Esimerkkejä:
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 huomioimatta luottaen niiden olevan erittäin harvinaisia
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
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!
Varautuva ohjelmointi (defensive programming)
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
Ohjelmointityyli, jota voisi verrata autolla ajossa ennakoivaan ajotapaan.
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
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
Virhetilanteisiin reagointi
Suorituksen hallittu lopetus (abort, exit)
Keskeytystä lievempi tapa Yritetään:
1. siivota ohjelmiston tila vapauttamalla kaikki sen varaamat resurssit
2. kirjata virhetilanne pysyvään talletuspaikkaan
3. ilmoittamaan virheestä käyttäjälle ennen suorituksen lopettamista
Virhetilanteisiin reagointi
Jatkaminen (continuation)
Jätetään havaittu virhe huomiotta
Harvinainen toimintamalli Esimerkki:
hiirikohdistimen paikkatieto katoaa (ei vaikuta juurikaan ohjelmiston toimintaan).
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:1. luoda kopio muutettavasta tiedosta ennen operaatiota2. tehdä muutokset kopioon3. operaation onnistuessa vaihtaa kopion tiedot
alkuperäisen tiedon tilalle4. operaation epäonnistuessa tuhota kopio
Virhetilanteisiin reagointi
Toipuminen(recovery)
Ohjelman osan paikallinen toteutus hallitusta lopetuksesta
Osanen ei pysty itse käsittelemään havaittua virhettä. Sen sijaan osanen:
1. pyrkii vapauttamaan kaikki varaamansa resurssit
2. tiedottamaan ohjelmistossa toisaalle (usein loogisesti ylemmälle tasolle) jonka toivotaan pystyvän käsittelemään havaittu virhe paremmin
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
Virheestä toipuminenEsimerkki: 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
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ä
E xcep tion
inv a lid_argum en t
bad_excep tion
ou t_o f_range
run tim e_errorlog ic_error
bad_ type id bad_ca s t
bad_a llo c
dom a in_error
leng th_error range_error
underflow _error
ov erflow _error
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
E xcep tion
inv a lid_argum en t
bad_excep tion
ou t_o f_range
run tim e_errorlog ic_error
bad_ type id bad_ca s t
bad_a llo c
dom a in_error
leng th_error range_error
underflow _error
ov erflow _error
Virhetyypit C++ luokkinaclass exception{
public: exception() throw(); //throw() selitetään myöhemminexception (const exception& e) throw();exception& operator =(const exception& e) throw();virtual ~exception() throw();virtual const char* what() const throw();...
};
class runtime_error : public exception{public:
runtime_error(const std::string& msg);};
class overflow_error : public runtime_error {public:
overflow_error(const std::string& msg);};
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_;
};
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.
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!
Esimerkki poikkeuskäsittelijästä
Tarkastellaan keskiarvon laskentaa esimerkkinä
Virhemahdollisuudet: lukujen lukumäärä saattaa olla nolla lukujen summa saattaa kasvaa liian
suureksi
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;
}
Keskiarvoesimerkki:
Keskiarvon laskudouble 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;
}
Keskiarvoesimerkki:
Katsotaan tarkemmin
keskiarvolaskuri
laskeKeskiarvo
summaaLuvut
throw std::overflow_error(“Summa liian suuri”);throw std::overflow_error(“Summa liian pieni”);
throw std::range_error(“Lukumäärä keskiarvossa 0”);
try{}catch (std::ragne_error& virhe){}catch (std::overflow_error& virhe){}
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 seurauksenacerr << “ajonaikainen virhe: “ << virhe.what() << endl;
}cout << “Loppu” << endl;
}
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
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
Keskiarvoesimerkin parantaminen
Sisäkkäiset valvontalohkot
keskiarvolaskuri
laskeKeskiarvo
summaaLuvut
throw std::overflow_error(“Summa liian suuri”);throw std::overflow_error(“Summa liian pieni”);
throw std::range_error(“Lukumäärä keskiarvossa 0”);
Pääohjelma
try{}catch (std::range_error& virhe){}catch (std::overflow_error& virhe){}
try{}catch (std::bac_alloc& virhe){}catch (std::exception& virhe){}
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_FAILURE;
}
return EXIT_SUCCESS;}
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
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
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 satudelete taulup; taulup = 0;
}
Muistivuotojen välttäminenauto_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ä
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 ->
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
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
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;
}
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
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 (++, --, indeksointi 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
automaattiosoittimetLopuksi 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
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ää
…
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
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
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
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.
Poikkeusneutraalius (exception neutrality)
Takuu: komponentit vuotavat poikkeukset ulos muuttumattomina
Luokka saa ottaa poikkeuksia väliaikaisesti kiinni
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)
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
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ä
Funktion valvontalohkoEsimerkki
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
}
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!
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 tilanteestaOhjelman suoritus keskeytetään
Älä koskaan vuoda purkajasta poikkeuksia ulos!
Mitä opimme tänään? C++ tekniikoita
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