1 / 96

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

Olio-ohjelmoinnin perusteet luento 6: C++ tekniikoita, virhetilanteet ja poikkeukset. Jani Rönkkönen LTY/Tietotekniikan osasto Kalvot on muokattu Sami Jantusen luentokalvoista viime vuodelta. Sisältö. Operaattoreiden suoritusjärjestys C++ tekniikoita Nimiavaruus Luokkamuuttujat

Download Presentation

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

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

E N D

Presentation Transcript


  1. Olio-ohjelmoinnin perusteetluento 6: C++ tekniikoita, virhetilanteet ja poikkeukset Jani Rönkkönen LTY/Tietotekniikan osasto Kalvot on muokattu Sami Jantusen luentokalvoista viime vuodelta.

  2. Sisältö • Operaattoreiden suoritusjärjestys • C++ tekniikoita • Nimiavaruus • Luokkamuuttujat • Luokkafunktiot • Virhetilanteet ja poikkeukset • Syitä poikkeuksiin • Virhetilanteisiin reagointi • Virhehierarkiat • Poikkeuksien heitto ja sieppaaminen • Poikkeukset ja oliot • Yhteenveto

  3. Operaattoreiden Suoritusjärjestys Assosiatiivisyys

  4. Operaattoreiden Suoritusjärjestys Assosiatiivisyys

  5. Operaattoreiden Suoritusjärjestys Assosiatiivisyys

  6. Operaattoreiden suoritusjärjestys • Matemaattisissa kaavoissa suoritusjärjestys noudattaa matematiikan sääntöjä • Kannattaa käyttää sulkuja varmistamaan oikea järjestys jos epäselvyyttä • Assosiatiivisyys (operaattoreiden suoritus suunta) oletuksena vasemmalta oikealle, mutta joissakin operaatioissa oikealta vasemmalle • Sama suoritusjärjestys koskee myös operaattoreita, joissa toiminta on uudelleenmääritelty

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

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

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

  10. 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); • ... • }

  11. 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 selkeyttää koodia

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

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

  14. 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::printf • std::cout • Varattu pelkästään sisäiseen käyttöön. • Et saa lisätä omia rakenteita std-nimiavaruuteen

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

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

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

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

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

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

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

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

  23. Missä mennään? • Operaattoreiden suoritusjärjestys • C++ tekniikoita • Nimiavaruus • Luokkamuuttujat • Luokkafunktiot • Virhetilanteet ja poikkeukset • Syitä poikkeuksiin • Virhetilanteisiin reagointi • Virhehierarkiat • Poikkeuksien heitto ja sieppaaminen • Poikkeukset ja oliot • Yhteenveto

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

  25. 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?

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

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

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

  29. Luokkamuuttuja • Luokkamuuttuja on luonteeltaan hyvin lähellä normaalia globaalia muuttujaa • Se on olemassa, vaikka luokasta ei olisi vielä luotu ainuttakaan oliota. • Itse asiassa luokkamuuttujat luodaan jo ennen main ohjelman aloitusta, kuten globaalit muuttujatkin • 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;

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

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

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

  33. 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?

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

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

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

  37. Vaaroja • Emme tiedä etukäteen missä järjestyksessä static (tai globaalit) oliot luodaan • Jos tälläisen olion muodostajassa käytetään toisen vastaavan olion metodia, voi käydä niin, että oliota, jonka metodia yritettiin kutsua ei ole vielä luotu -> ohjelma kaatuu • Ongelma voidaan kiertää käyttämällä singleton ideaa hyväksi siirtämällä static olio funktion sisään, jossa se ensimmäisellä kutsukerralla luodaan dynaamisesti • Ongelmana on, että nyt olion tuhoajaa ei kutsuta koskaan

  38. Esimerkki x.cpp #include ”class1.h” class1 x; //global class1.cpp #include ”class1.h” class1::class1() { … y.method(); … } y.cpp #include ”class2.h” class2 y; //global 50% todennäköisyys että y on luotu ennen x:ää, jolloin ei ongelmia mutta 50% tapauksista ohjelma kaatuu

  39. Esimerkki class1.cpp #include ”class1.h” class1::class1() { … y().method(); … } x.cpp #include ”class1.h” class1 x; //global y.cpp #include ”class2.h” class2& y() { static class2* ans = new class2(); return *ans; } Funktion kutsu olion sijasta Static rivi suoritetaan vain kerran, ennen main ohjelman alkua, sen jälkeen y () vain palauttaa viitteen static olioon

  40. Missä mennään? • Operaattoreiden suoritusjärjestys • C++ tekniikoita • Nimiavaruus • Luokkamuuttujat • Luokkafunktiot • Virhetilanteet ja poikkeukset • Syitä poikkeuksiin • Virhetilanteisiin reagointi • Virhehierarkiat • Poikkeuksien heitto ja sieppaaminen • Poikkeukset ja oliot • Yhteenveto

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

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

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

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

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

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

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

  48. 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!

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

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

More Related