1.09k likes | 1.19k Views
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
E N D
Olio-ohjelmoinnin perusteetluento 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 • Syitä poikkeuksiin • Virhetilanteisiin reagointi • Virhehierarkiat • Poikkeuksien heitto ja sieppaaminen • Poikkeukset ja oliot • Yhteenveto
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?
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 mite 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 • 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 { ... }; }
std nimiavaruus • Kaikki C++ standardi määrittelee omaan käyttöönsä std-nimiavaruuden • Käytössä kaikissa nykyaikaisissa kääntäjissä • std-nimiavaruus sisältää lähestulkoon kaikki C++ ja C-kielissä määritellyt rakenteet. Esim: • std::prinf • std::cout • Varattu pelkästään sisäiseen käyttöön. • Et saa lisätä omia rakenteita std-nimiavaruuteen
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äskyjän jälkeen • Vältä using-lauseen käyttöä otsikkotiedostossa • Käytä using-lausetta mahdollisimman lähellä sitä aluetta, jossa sen on tarkoitus olla voimassa • Paljon käytetyissä rakenteissa on usein kuitenkin selkeämpää kirjoittaa using heti siihen liittyvän otsikkotiedoston #include-käskyn jälkeen.
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;
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! // static members in classes #include <iostream.h> class CDummy { public: static int n; CDummy () { n++; }; ~CDummy () { n--; }; }; int CDummy::n=0; int main () { CDummy a; CDummy b[5]; CDummy * c = new CDummy; cout << a.n << endl; delete c; cout << CDummy::n << endl; return 0; } • Mikä on oheisen koodin lopputulos? Vastaus: 7 6
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 Pattern class Singleton {public:static Singleton *get_instance();protected: Singleton(); Singleton( const Singleton& s);private:static Singleton *instance;};Singleton::instance = 0; Singleton *Singleton::get_instance() {if ( instance == 0 ) { instance = new Singleton; }return instance;} «Singleton» theInstance getInstance Company theCompany Company «private» getInstance if (theCompany==null) theCompany= new Company(); return theCompany;
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); kutsuttava olio funktion nimi argumentti
Ongelma! • Miksi seuraava ei onnistu?:char cMjono[] = {“Hello”};Mjono mjono(“World!”);Mjono mJonoKaksi = cMjono + mMjono; //Virhe
Syy ongelmaan • cMjono on C-tyyppinen merkkijono • sillä ei voi olla jäsenfunktioita • se ei voi suoriutua komennosta: cMjono.operator+(mMjono) Mjono mJonoKaksi = cMjono + mMjono; //Virhe kutsuttava olio funktion nimi argumentti
Määritetään funktio:const Mjono operator +( const Mjono& teksti1, const Mjono& teksti2); Ratkaisu • Loogisesti ajateltuna edellä mainittu kahden merkkijonon yhteen liittäminen tulisi onnistua!!! Mikä olisi ongelmaan ratkaisu?
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: • //rakentajat • Mjono(); • Mjono(const char *const); • Mjono(const Mjono &) • ~Mjono; • Mjono operator+(const Mjono&); • friend Mjono operator+(const Mjono&, const Mjono&); • . • . • .