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

Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

  • 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

Page 1: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

Olio-ohjelmoinnin perusteetluento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

Sami JantunenLTY/Tietotekniikan osasto

Page 2: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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

Page 3: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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?

Page 4: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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

Page 5: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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ä

Page 6: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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);

...}

Page 7: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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

Page 8: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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

Page 9: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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

Page 10: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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

Page 11: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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>

Page 12: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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;}

Page 13: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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

Page 14: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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;}

Page 15: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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.

Page 16: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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;

Page 17: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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

Page 18: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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; . . .}

Page 19: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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

Page 20: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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ä?

Page 21: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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?

Page 22: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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

Page 23: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

Hmmm…

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

Page 24: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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

Page 25: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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;

Page 26: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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

Page 27: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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

Page 28: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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

Page 29: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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?

Page 30: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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

Page 31: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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

Page 32: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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)!

Page 33: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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

Page 34: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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

Page 35: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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.

Page 36: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

Uudelleenmääriteltävät operaattorit C++ kielessä

+ - * / % ^ & | ~ ! = < > += -= *= /= %= ^= &= |= << >> >>= <<= == != <= >= && || ++ -- ->* , -> [] () new delete

Page 37: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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;

Page 38: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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 ->

Page 39: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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

Page 40: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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;

Page 41: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

Operaattorin uudelleenmäärittely jäsenfunktiona Esitetty yhteenlasku

voidaan esittää funktiokutsuna muodossa: kokoNimi = etuNimi.+(sukuNimi);

funktion nimi

argumenttikutsuttavaolio

Page 42: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

Ongelma!

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

Page 43: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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)

Page 44: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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);

Page 45: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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

Page 46: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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

Page 47: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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

Page 48: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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

Page 49: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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

Page 50: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

‘+’-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&);...

Page 51: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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

Page 52: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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

Page 53: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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

};

Page 54: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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

Page 55: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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

Page 56: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

Poikkeukset (exception)

C++ tarjoaa virheiden käsittelyyn erityisen mekanismin, poikkeukset (exception)

Poikkeusten toiminta perustuu luokkahierarkioihin

Page 57: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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

Page 58: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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

Page 59: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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ä:

Page 60: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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

Page 61: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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

Page 62: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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!

Page 63: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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.

Page 64: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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

Page 65: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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

Page 66: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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

Page 67: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

Virhetilanteisiin reagointi

Jatkaminen (continuation)

Jätetään havaittu virhe huomiotta

Harvinainen toimintamalli Esimerkki:

hiirikohdistimen paikkatieto katoaa (ei vaikuta juurikaan ohjelmiston toimintaan).

Page 68: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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

Page 69: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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

Page 70: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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

Page 71: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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

Page 72: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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

Page 73: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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

Page 74: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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);};

Page 75: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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_;

};

Page 76: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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.

Page 77: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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!

Page 78: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

Esimerkki poikkeuskäsittelijästä

Tarkastellaan keskiarvon laskentaa esimerkkinä

Virhemahdollisuudet: lukujen lukumäärä saattaa olla nolla lukujen summa saattaa kasvaa liian

suureksi

Page 79: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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;

}

Page 80: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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;

}

Page 81: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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){}

Page 82: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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;

}

Page 83: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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

Page 84: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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

Page 85: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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){}

Page 86: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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;}

Page 87: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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

Page 88: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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

Page 89: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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;

}

Page 90: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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ä

Page 91: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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 ->

Page 92: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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

Page 93: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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

Page 94: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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;

}

Page 95: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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

Page 96: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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

Page 97: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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

Page 98: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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ää

Page 99: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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

Page 100: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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

Page 101: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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

Page 102: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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.

Page 103: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

Poikkeusneutraalius (exception neutrality)

Takuu: komponentit vuotavat poikkeukset ulos muuttumattomina

Luokka saa ottaa poikkeuksia väliaikaisesti kiinni

Page 104: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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)

Page 105: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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

Page 106: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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ä

Page 107: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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!

Page 108: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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!

Page 109: Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset

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