Mallit standardi mallikirjasto parametroitu tyyppi



Samankaltaiset tiedostot
815338A Ohjelmointikielten periaatteet Harjoitus 5 Vastaukset

12 Mallit (Templates)

Operaattoreiden uudelleenmäärittely

Taulukot. Jukka Harju, Jukka Juslin

Operaattoreiden ylikuormitus. Operaattoreiden kuormitus. Operaattoreiden kuormitus. Operaattoreista. Kuormituksesta

Ohjelman virheet ja poikkeusten käsittely

Luokat. Luokat ja olio-ohjelmointi

Luokassa määriteltävät jäsenet ovat pääasiassa tietojäseniä tai aliohjelmajäseniä. Luokan määrittelyyn liittyvät varatut sanat:

Virtuaalifunktiot ja polymorfismi

Osoittimet. Mikä on osoitin?

815338A Ohjelmointikielten periaatteet Harjoitus 3 vastaukset

Osoitin ja viittaus C++:ssa

Harjoitustyö: virtuaalikone

C-kielessä taulukko on joukko peräkkäisiä muistipaikkoja, jotka kaikki pystyvät tallettamaan samaa tyyppiä olevaa tietoa.

T Olio-ohjelmointi Osa 5: Periytyminen ja polymorfismi Jukka Jauhiainen OAMK Tekniikan yksikkö 2010

Olion elinikä. Olion luominen. Olion tuhoutuminen. Olion tuhoutuminen. Kissa rontti = null; rontti = new Kissa();

Opintojakso TT00AA11 Ohjelmoinnin jatko (Java): 3 op Taulukot & Periytyminen

Ohjelmoinnin perusteet Y Python

11. oppitunti III. Viittaukset. Osa. Mikä on viittaus?

Periytyminen. Luokat ja olio-ohjelmointi

13 Operaattoreiden ylimäärittelyjä

Funktiomallit Funktiomallin määrittely

C++11 Syntaksi. Jari-Pekka Voutilainen Jari-Pekka Voutilainen: C++11 Syntaksi

Sisältö. 22. Taulukot. Yleistä. Yleistä

Listarakenne (ArrayList-luokka)

Esimerkkiprojekti. Mallivastauksen löydät Wroxin www-sivuilta. Kenttä Tyyppi Max.pituus Rajoitukset/Kommentit

Sisältö. 2. Taulukot. Yleistä. Yleistä

Luokan operaatiot. Osoittimet ja viittaukset luokan olioihin

ITKP102 Ohjelmointi 1 (6 op)

Ohjelmoinnin perusteet Y Python

Yleistä. Nyt käsitellään vain taulukko (array), joka on saman tyyppisten muuttujien eli alkioiden (element) kokoelma.

Ohjelmassa muuttujalla on nimi ja arvo. Kääntäjä ja linkkeri varaavat muistilohkon, jonne muuttujan arvo talletetaan.

15. Ohjelmoinnin tekniikkaa 15.1

812347A Olio-ohjelmointi, 2015 syksy 2. vsk. X Poikkeusten käsittelystä

Luku 6. Dynaaminen ohjelmointi. 6.1 Funktion muisti

Olio-ohjelmointi Syntaksikokoelma

7. Näytölle tulostaminen 7.1

Ohjelmointi 1 Taulukot ja merkkijonot

Ohjelmoinnin peruskurssi Y1

2. Lisää Java-ohjelmoinnin alkeita. Muuttuja ja viittausmuuttuja (1/4) Muuttuja ja viittausmuuttuja (2/4)

Oliosuunnitteluesimerkki: Yrityksen palkanlaskentajärjestelmä

Ohjelmointi 2. Jussi Pohjolainen. TAMK» Tieto- ja viestintäteknologia , Jussi Pohjolainen TAMPEREEN AMMATTIKORKEAKOULU

Ohjelmoinnin perusteet Y Python

Ohjelmoinnin perusteet Y Python

15. Ohjelmoinnin tekniikkaa 15.1

Olio-ohjelmointi Javalla

Tietueet. Tietueiden määrittely

Ohjelmoinnin perusteet Y Python

Geneeriset luokat. C++ - perusteet Java-osaajille luento 6/7: Template, tyyppi-informaatio, nimiavaruudet. Geneerisen luokan käyttö.

Muuttujien roolit Kiintoarvo cin >> r;

JAVA-PERUSTEET. JAVA-OHJELMOINTI 3op A JAVAN PERUSTEET LYHYT KERTAUS JAVAN OMINAISUUKSISTA JAVAN OMINAISUUKSIA. Java vs. C++?

Java-kielen perusteet

Metodit. Metodien määrittely. Metodin parametrit ja paluuarvo. Metodien suorittaminen eli kutsuminen. Metodien kuormittaminen

Sisällys. 1. Omat operaatiot. Yleistä operaatioista. Yleistä operaatioista

ITKP102 Ohjelmointi 1 (6 op)

1. Omat operaatiot 1.1

4. Luokan testaus ja käyttö olion kautta 4.1

T Olio-ohjelmointi Osa 3: Luokka, muodostin ja hajotin, this-osoitin Jukka Jauhiainen OAMK Tekniikan yksikkö 2010

ITKP102 Ohjelmointi 1 (6 op)

ITKP102 Ohjelmointi 1 (6 op)

7. Oliot ja viitteet 7.1

14. Poikkeukset 14.1

Sisällys. Yleistä attribuuteista. Näkyvyys luokan sisällä. Tiedonkätkentä. Aksessorit. 4.2

INSIDE C++ Ohjelmoijan käsikirja. Ivor Horton WROX PRESS

Java kahdessa tunnissa. Jyry Suvilehto

Sisällys. Yleistä attribuuteista. Näkyvyys luokan sisällä ja ulkopuolelta. Attribuuttien arvojen käsittely aksessoreilla. 4.2

Sisällys. 11. Javan toistorakenteet. Laskurimuuttujat. Yleistä

Osa III. Olioiden luominen vapaalle muistialueelle

Sisällys. 3. Muuttujat ja operaatiot. Muuttujat ja operaatiot. Muuttujat. Operaatiot. Imperatiivinen laskenta. Muuttujat. Esimerkkejä: Operaattorit.

Java-kielen perusteet

Sisällys. 7. Oliot ja viitteet. Olion luominen. Olio Java-kielessä

C++ rautaisannos. Kolme tapaa sanoa, että tulostukseen käytetään standardikirjaston iostreamosassa määriteltyä, nimiavaruuden std oliota cout:

Operaattorin ylikuormitus ja käyttäjän muunnokset

Kääreluokat (oppikirjan luku 9.4) (Wrapper-classes)

3. Muuttujat ja operaatiot 3.1

Omat tietotyypit. Mikä on olio?

14. oppitunti. Operaattorin ylikuormitus. Osa. Operaattorin ylikuormittaminen

5.6. C-kielen perusteet, osa 6/8, Taulukko , pva, kuvat jma

Taulukot. Taulukon määrittely ja käyttö. Taulukko metodin parametrina. Taulukon sisällön kopiointi toiseen taulukkoon. Taulukon lajittelu

Tietotyypit ja operaattorit

Osa VII. Mitä mallit ovat ja kuinka niitä käytetään Miksi mallit tarjoavat paremman vaihtoehdon makroille Kuinka luokkamalleja luodaan

Ohjelmoinnin jatkokurssi, kurssikoe

Ohjelmoinnin perusteet Y Python

Rakenteiset tietotyypit Moniulotteiset taulukot

Johdatus Ohjelmointiin

C++11 lambdat: [](){} Matti Rintala

Sisällys. 14. Poikkeukset. Johdanto. Johdanto

Kääntäjän virheilmoituksia

Java-kielen perusteet

Sisällys. 14. Poikkeukset. Johdanto. Johdanto

VIII. Osa. Liitteet. Liitteet Suoritusjärjestys Varatut sanat Binääri- ja heksamuoto

Loppukurssin järjestelyt C:n edistyneet piirteet

Standardi mallikirjasto

Sisällys. 6. Metodit. Oliot viestivät metodeja kutsuen. Oliot viestivät metodeja kutsuen

Demo 6 vastauksia. 1. tehtävä. #ifndef #define D6T1 H D6T1 H. #include <iostream> using std::ostream; using std::cout; using std::endl;

Osio2: Taulukot Jukka Juslin

812347A Olio-ohjelmointi, 2015 syksy 2. vsk. V Geneerisyys

Sisällys. 12. Näppäimistöltä lukeminen. Yleistä. Yleistä

Osa III. Edelliset kolme lukua ovat käsitelleet viittausten ja osoittimien käyttöä. Tämän luvun aiheita ovat:

Ohjelmointitaito (ict1td002, 12 op) Kevät Java-ohjelmoinnin alkeita. Tietokoneohjelma. Raine Kauppinen

Transkriptio:

Mallit 18 Mallit Malli on tehokas mekanismi uusien luokkien generoimiseksi automaattisesti. Standardikirjaston suuri osa, standardi mallikirjasto, rakentuu kokonaan mallien määrittelymahdollisuuden ympärille, joten malleihin liittyvät tekniikat ovat tärkeitä. Tämän luvun lopussa olemme käsitelleet seuraavat aiheet: Mikä on malliluokka ja miten sellainen määritellään Mikä on mallin ilmentymä ja miten sellainen luodaan Miten mallin jäsenfunktioille määritellään mallifunktio mallin määrittelyn rungon ulkopuolella Miten tyyppiparametri eroaa ei-tyyppiparametristä Miten mallin staattiset jäsenet alustetaan Mikä on osittain erikoistunut funktio ja miten sellainen määritellään Miten malli sijoitetaan toisen mallin sisälle Malliluokka Malliluokat (malli) perustuvat samaan ideaan kuin mallifunktiot, joita käsittelimme luvussa 9. Malli on parametroitu tyyppi - eli resepti, jonka mukaan luodaan luokkia käyttämällä yhtä tai useampaa parametriä, jotka ovat yleensä tyyppejä (mutta eivät aina). Kun esittelet muuttujan käyttämällä mallia, kääntäjä muodostaa mallin mukaan luokan määrittelyn käyttämiesi malliparametrien mukaan. Mallia voidaan käyttää tällä tavalla vaikka kuinka monen erilaisen luokan muodostamiseen. 707

C++ Ohjelmoijan käsikirja Malliluokka Tyyppiä T oleva oliotaulukko Joka kerta, kun mallille välitetään uudentyyppiset parametrit, luodaan uusi luokan määrittely. Luokan määrittely Tyyppiä int olevat oliotaulukot. Luokan määrittely Tyyppiä string olevat oliotaulukot. Luokan määrittely Tyyppiä Laatikko* olevat oliotaulukot. Nämä luokat ovat malliluokan ilmentymiä. Mallilla on nimi, aivan samaan tapaan kuin tavallisellakin luokalla, ja joukko parametrejä. Mallin nimen täytyy olla nimiavaruudessa yksikäsitteinen, joten toisella mallilla tai luokalla ei voi olla samaa nimeä kyseisessä nimiavaruudessa. Luokan määrittely muodostetaan mallista välittämällä malliparametreille tiedot. Jokaista luokkaa, jonka kääntäjä on mallista muodostanut, kutsutaan mallin ilmentymäksi. Kuten huomaat, muuttujan esittely mallin avulla luo mallin ilmentymän, mutta voit myös eksplisiittisesti esitellä mallin ilmentymän ilman, että samalla esiteltäisiin muuttuja. Mallista muodostettuja luokkia ei monisteta, eli kun tietty mallin ilmentymä on kertaalleen muodostettu, sitä käytetään mahdollisissa myöhemmissä samantyyppisten muuttujien esittelyissä. 708 Mallien käyttökohteet Vaikka malleilla on monia käyttökohteita, niiden ehkä kaikkein tärkein käyttökohde on säiliöluokkien määrittely. Säiliöluokka on luokka, joka voi sisältää tietyn tyyppisten olioiden joukon, joka on järjestetty tietyllä tavalla. Se voi olla yksinkertaisesti taulukko, pino tai olioiden linkitetty lista; idea on siinä, että käytettävä talletusmuoto ei ole riippuvainen talletettavien olioiden tyypistä. Malliluokka sisältää juuri tarvittavat apuvälineet minkä tahansa tyypin tallettavan säiliön määrittelylle. Malliparametriä voidaan käyttää määrittelemään oliotyypit, jotka säiliö tallettaa. Standardin C++:n mukana tuleva standardi mallikirjasto sisältää useita malleja, jotka määrittelevät erilaisia säiliöitä. Käsittelemme niiden käyttöä luvussa 20. Tarkastellaan yhtä tilannetta, jossa malli on hyödyllinen. Oletetaan, että olet tyytymätön siihen, että C++:n taulukot eivät tarkista, onko indeksin arvo sallittu. Tällöinhän voit vahingossa kirjoittaa muistiin taulukon rajojen yli. Yhtenä ratkaisutapana olisi tietysti kirjoittaa oma Taulukko-luokka, joka tarkistaisi, että indeksin arvo on sallittu. Kaikki mitä tarvitsisi tehdä, olisi

määritellä luokalle operator[]()-funktio, joka tarkistaisi indeksin arvon ja esimerkiksi muodostaisi poikkeuksen, jos indeksi ei olisi sallittu. Mutta minkä tyyppinen Taulukko-luokan tulisi olla? Yhdessä tilanteessa tarvitsisit doubletyyppistä taulukkoa ja toisessa tilanteessa tarvitsisit string-tyyppisten olioiden taulukkoa - tai itse asiassa minkä tahansa tyyppistä taulukkoa. Sinulla tulisi olla erilliset luokat jokaiselle tyypille, vaikka luokat muuten olisivat varsin samanlaiset. Useiden varsin samanlaisten luokkien kirjoittaminen kuulostaa varsin tylsältä ja tarpeettomalta - ja niinhän se onkin! Tämän lisäksi jokaisella luokalla tulisi olla oma nimi, joten lopulta sinulla saattaisi olla esimerkiksi luokat DoubleTaulukko, StringTaulukko, LaatikkoTaulukko jne. Malli pelastaa nyt meidät, koska sitä voidaan käyttää muodostamaan Taulukko-luokka, joka sopii kaikille tarvitsemillesi tyypeille. Kun olet määritellyt Taulukko-mallin, uudentyyppiset Taulukkoluokat muodostetaan varsin automaattisesti. Mallit Mallien määrittely Kun ensimmäisen kerran näet mallin määrittelyn, se näyttää monimutkaisemmalta kuin se oikeastaan on, johtuen lähinnä määrittelyssä käytetystä merkintätavasta ja koska parametrit on ripoteltu ympäri määrittelyä. Mallien määrittelyt ovat varsin samanlaiset kuin tavallistenkin luokkien määrittelyt, mutta niin kuin useiden muidenkin asioiden kohdalla, yksityiskohdat ratkaisevat. Malli määritellään avainsanan template avulla ja malliparametrit sijoitetaan template-avainsanan perässä oleviin hakasulkeisiin. Tämän jälkeen kirjoitetaan luokan määrittely alkaen classavainsanalla, jonka perään kirjoitetaan luokan nimi ja määrittelyn runko aaltosulkeiden sisään. Aivan samaan tapaan kuin tavallistenkin luokkien kohdalla, koko määrittely päättyy puolipisteeseen. Eli mallin määrittelyn yleinen muoto on: template <malliparametrien lista> class LuokanNimi // Mallin määrittely... ; Tässä käsitteellisessä määrittelyssä, LuokanNimi on mallin nimi. Mallin rungon koodi kirjoitetaan muuten aivan samaan tapaan kuin tavallisen luokankin runko, mutta joidenkin jäsenten esittelyt ja määrittelyt on tehty malliparametrien pohjalta. Malliparametrit sijaitsevat kulmasulkeiden sisällä pilkuilla eroteltuina. Kun luot luokan tästä mallista, listan jokainen parametri tulee määritellä. Malliparametrit Malliparametrien lista voi sisältää kahdenlaisia parametrejä - tyyppiparametrejä ja eityyppiparametrejä - ja listassa voi olla niin monta parametriä kuin tarvitaan. Tyyppiparametrin arvona on tyyppi, kuten int, string tai Laatikko. Ei-tyyppiparametrin arvo on joko tietyn tyyppinen arvo, kuten 200, tai tietyn tyyppinen muuttuja, kuten iarvo. Tyyppiparametrit ovat huomattavasti yleisempiä kuin ei-tyyppiparametrit, joten emme käsittele ei-tyyppiparametrejä kuin vasta luvun lopussa. 709

C++ Ohjelmoijan käsikirja Tyyppiparametri Ei-tyyppiparametri Taulukko-mallilla on yksi tyyppiparametri T. Tunnistat sen tyyppiparametriksi, koska sen edessä on avainsana typename. Se tyyppi, joka tälle parametrille välitetään mallin käytön yhteydessä - int, double*, string jne - määrää luokan olioon talletettavien alkioiden tyypin. Mallin rungon määrittely on varsin samanlainen kuin luokan määrittelynkin. Jäsenmuuttujat voidaan esitellä julkisiksi, suojatuiksi tai yksityisiksi ja mallilla on tyypillisesti muodostinfunktio ja tuhoajatemplate<class T, Tyyppi par,... >class LuokanNimi... ; Voit käyttää tässä typename- avainsanaa class- avainsanan sijaan. Parametri voi olla mikä tahansa tyyppi. Esimerkiksi int, double*, Laatikko&, string** jne. Parametri voi olla mikä tahansa tyypin Tyyppi arvo. Jos Tyyppi on int, parametri voi olla esimerkiksi 20 tai 100. Tyyppi voi olla ainoastaan kokonaisluku tai lueteltu tyyppi, osoitin tai viittaus olioon tai funktioon tai osoitin luokan jäseneen. Tyyppiparametrit kirjoitetaan yleensä käyttämällä class-avainsanaa, jonka perässä on parametrin nimi (class T yllä olevassa kaaviossa), mutta voit yhtä hyvin käyttää typenameavainsanaa class-avainsanan sijaan. Eli typename T kävisi yhtä hyvin. Tyyppiparametrinä käytetään usein merkkiä T (tai T1, T2 ja niin edelleen, jos mallilla on useita tyyppiparametrejä), mutta voit käyttää mitä nimeä tahansa. Vaikka class-avainsana näyttääkin viittaavan siihen, että tyyppiparametrin arvon tulee olla luokka, voit välittää minkä tahansa tyypin. Eli voit käyttää typenameavainsanaa, jos se näyttää tässä yhteydessä selkeämmältä. Katsotaan nyt miten mallit toimivat käytännössä. Näemme, miten yksinkertainen malli, jolla on yksi malliparametri, toimii käytännössä. Yksinkertainen malli Käytetään edellä ollutta esimerkkiä ja määritellään malli taulukoille, jotka tarkistavat indeksien arvot. Taulukko-mallimme sisältää ainoastaan yhden tyyppiparametrin, joten määrittelyn hahmotelma on seuraava: template <typename T> class Taulukko // Mallin määrittely... ; 710

funktio. Voit käyttää T:tä muuttujien esittelyissä, jäsenfunktion parametrien ja paluuarvojen tyyppinä - joko sellaisenaan tai muodossa T* (osoitin tyyppiin T). Lisäksi voit käyttää mallin nimeä - tässä tapauksessa Taulukko - tyypin nimenä muodostinfunktion ja tuhoajafunktion esittelyssä. Tarvitsemme malliin vielä muodostinfunktion, kopiomuodostimen (koska tulemme varaamaan taulukon tarvitseman muistin dynaamisesti), kopioivan sijoitusoperaattorin (koska kääntäjä muodostaa sellaisen, jos emme sitä itse tee), uudelleenmääritellyn indeksointioperaattorin ja tuhoajafunktion. Nyt voimme kirjoittaa mallin alkuperäisen määrittelyn muotoon: template <typename T> class Taulukko private: T* alkiot; // T-tyyppinen taulukko size_t koko; // Taulukon alkioiden lukumäärä public: explicit Taulukko<T>(size_t taulkoko); // Muodostinfunktio Taulukko<T>(const Taulukko<T>& ataul); // Kopiomuodostin ~Taulukko<T>(); // Tuhoajafunktio T& operator[](long indeksi); // Indeksointioperaattori const T& operator[](long indeksi) const; // Indeksointioperaattori (const) Taulukko<T>& operator=(const Taulukko<T>& rhs); // Sijoitusoperaattori ; Mallit Mallin runko näyttää lähes samanlaiselta kuin tavallisen luokankin määrittely. Erona on se, että T esiintyy monessa paikassa. Esimerkiksi jäsenmuuttuja alkiot on tyyppiä osoitin tyyppiin T (sama kuin T-tyyppinen taulukko ). Kun mallista muodostetaan luokan määrittely, T korvataan todellisella tyypillä. Jos muodostamme mallista double-tyyppisen ilmentymän, alkiot on tyyppiä double-tyyppinen taulukko. Käytämme tyyppiä size_t jäsenelle koko, joka tallettaa taulukon alkioiden lukumäärän. Tämä on standardi kokonaislukutyyppi, joka on määritelty standardiotsikkotiedostossa cstddef ja vastaa sizeof()-operaattorin palauttaman paluuarvon tyyppiä. Se on suositeltava tyyppi taulukon koon määrittelyssä. Huomaa, miten ensimmäinen muodostinfunktio on esitelty explicit-määreellä. Koska tälle funktiolle välitetään vain yksi parametri, voimme kirjoittaa muodostinfunktion kutsun kahdella tavalla: Taulukko<int>yksi(5); Taulukko<int>kaksi = 5; // eksplisiittinen muoto //sijoitustyyppinen muoto Kun esittelemme muodostinfunktion explicit-määreellä, kiellämme jälkimmäisen muodon käytön. Kiellämme myöskin kokonaislukujen välittämisen funktiolle, joka vaatii taulukon. Indeksointioperaattori on uudelleenmääritelty const-tyyppiseksi. Ei-const-tyyppistä versiota käytetään ei-const-tyyppisten taulukoiden kohdalla ja se palauttaa ei-const-tyyppisen viittauksen taulukon alkioon. Eli tämä versio voi olla sijoitusoperaattorin vasemmalla puolella. consttyyppistä versiota käytetään const-tyyppisten olioiden kohdalla ja se palauttaa const-tyyppisen viittauksen taulukon alkioon. Tämä ei luonnollisestikaan voi olla sijoitusoperaattorin vasemmalla puolella. 711

C++ Ohjelmoijan käsikirja Kopioivan sijoitusoperaattorin kohdalla käytämme tyyppiä Taulukko<T>&. Kun luokka muodostetaan mallista - kun T on esimerkiksi tyyppi double - se on viittaus tämän kyseisen luokan nimeen, joka on Taulukko<double>. Yleisesti sanottuna mallin ilmentymän nimi muodostuu mallin nimestä, jonka perässä on kulmasulkeiden sisällä varsinainen tyyppi. Kulmasulkeiden sisällä olevien parametrien jälkeen tulevaa mallin nimeä kutsutaan mallin ID:ksi. Sinun ei tarvitse käyttää täydellistä mallin ID:tä mallin määrittelyssä. Mallin rungossa pelkkä Taulukko tarkoittaa samaa kuin Taulukko<T> ja Taulukko& tulkitaan samaksi kuin Taulukko<T>&, joten voimme yksinkertaistaa mallin määrittelyn: template <typename T> class Taulukko private: T* alkiot; // T-tyyppinen taulukko size_t koko; // Taulukon alkioiden lukumäärä public: explicit Taulukko(size_t taulkoko); Taulukko(const Taulukko& ataul); ~Taulukko(); T& operator[](long indeksi); const T& operator[](long indeksi) const; Taulukko& operator=(const Taulukko& rhs); ; // Muodostinfunktio // Kopiomuodostin // Tuhoajafunktio // Indeksointioperaattori // Indeksointioperaattori (const) // Sijoitusoperaattori Jos sinun täytyy viitata malliin mallin rungon ulkopuolelta, sinun tulee käyttää mallin ID:tä. Törmäämme tällaiseen tilanteeseen, kun määrittelemme mallin jäsenfunktioita myöhemmin tässä luvussa. 712 Sijoitusoperaattorin avulla voit sijoittaa taulukon toiseen taulukkoon - tätähän et voi tehdä C++:n tavallisten taulukoiden kohdalla. Jos jostain syystä haluat estää tämän toiminnallisuuden, sinun tulee kuitenkin esitellä operator=()-funktio mallin jäseneksi. Jos et esittele, julkinen oletussijoitusoperaattori luodaan mallin ilmentymälle tarvittaessa. Sijoitusoperaattorin käytön estät esittelemällä sen luokan yksityiseksi jäseneksi - tällöin siihen ei päästä käsiksi. Tallöin funktiolle ei tietenkään tarvita minkäänlaista toteutusta, koska C++ ei vaadi toteuttamaan jäsenfunktiota, ellei sitä käytetä - ja tätähän ei käytetä. Mallin jäsenmuuttujien määrittely Voit sijoittaa mallin jäsenfunktioiden määrittelyt mallin runkoon. Tällöin ne ovat avoimia (inline) kaikissa mallin ilmentymissä, aivan samaan tapaan kuin tavallistenkin luokkien kohdalla. Haluat varmastikin joskus määritellä jäsenfunktiot mallin rungon ulkopuolella, varsinkin, jos ne sisältävät runsaasti koodia. Kun näin teet, syntaksi on hieman erilainen ja näyttää aluksi varsin pelottavalta, joten katsotaan sitä nyt. Johtolankana on, että mallin jäsenfunktioiden määrittelyt ovat mallifunktioita. Mallifunktion, joka määrittelee jäsenfunktion, parametriluettelon tulee olla sama kuin malliluokan. Jos tämä kuulostaa sekavalta, yksityiskohtien käsittely helpottaa ymmärtämistä. Voimme nyt kirjoittaa Taulukko-mallimme jäsenfunktiot. Aloitamme muodostinfunktiosta.

Kun määrittelet sen mallin määrittelyn ulkopuolella, muodostinfunktion nimen yhteydessä tulee kirjoittaa mallin nimi, samaan tapaan kuin tavallisen luokan jäsenfunktion kohdalla. Tämä ei kuitenkaan ole funktion määrittely, vaan mallifunktion määrittely, joten se täytyy kertoa selvästi. Seuraavassa on muodostinfunktion määrittely: template <typename T> // Malli, jonka parametrinä on T Taulukko<T>::Taulukko(size_t taulkoko) : koko(taulkoko) // Taulukko<T> määrittelee mallin alkiot = new T[koko]; Mallit Ensimmäinen rivi kertoo, että kyseessä on malli, ja määrittelee myöskin malliparametriksi T:n. Mallifunktion esittelyn jakaminen kahdelle riville on ainoastaan lukemisen helpottamiseksi, eikä jakamista tarvitse tehdä, jos koko rakennelma mahtuu yhdelle riville. Muodostinfunktion nimen, Taulukko<T>, määrittelyssä malliparametri on välttämätön, koska se sitoo funktion määrittelyn malliluokkaan. Huomaa, että tässä ei käytetä typedef-avainsanaa - sitä käytetään ainoastaan malliparametrien listassa. Parametrilista ei ole välttämätön muodostinfunktion nimen perässä. Kun muodostinfunktio muodostetaan mallifunktion ilmentymäksi, tyypin nimi, esimerkiksi double, korvaa T:n. Eli muodostinfunktion nimi luokalle Taulukko<double> on tällöin Taulukko<double>::Taulukko(). Muodostinfunktiossa meidän tulee varata muistia vapaasta muistista alkiot-taulukolle, jossa on koko alkioita, jotka ovat tyyppiä T. Jos T on luokkatyyppi, luokassa T tulee olla julkinen oletusmuodostinfunktio. Jos näin ei ole, tämän muodostinfunktion ilmentymä ei käänny. Operaattori new muodostaa bad_alloc-poikkeuksen, jos muistia ei jostain syystä voida varata, joten Taulukko-luokan muodostinfunktiota tulee yleensä käyttää try-lohkossa. Tuhoajafunktion tulee vapauttaa alkiot-taulukon muisti, joten sen määrittely on seuraava: template <typename T> Taulukko<T>::~Taulukko() delete[] alkiot; Vapautamme tässä taulukolle varatun muistin, joten meidän tulee käyttää delete-operaattorin taulukkomuotoa. Kopiomuodostimen tulee luoda saman kokoinen uusi taulukko kuin parametrinä oleva taulukko, ja kopioida parametritaulukon sisältö uuteen taulukkoon. Kopiomuodostimen koodi näyttää seuraavalta: template <typename T> Taulukko<T>::Taulukko(const Taulukko& ataul) koko = ataul.koko; alkiot = new T[koko]; for(int i = 0 ; i < koko ; i++) alkiot[i] = ataul.alkiot[i]; 713

C++ Ohjelmoijan käsikirja Tässä oletetaan, että sijoitusoperaattori toimii luokalle T. Kuten muistat, sijoitusoperaattorin määrittely on erittäin tärkeää luokille, jotka varaavat muistia dynaamisesti. Jos luokassa T ei sitä määritellä, T:n oletuskopiomuodostinta käytetään, jolloin tulee epätoivottuja sivuvaikutuksia sellaisten luokkien yhteydessä, joita käsittelimme luvussa 13. Jos et katso mallin koodia ennen sen käyttöä, et ehkä huomaa riippuvuutta sijoitusoperaattorista. Funktio operator[]() on varsin suoraviivainen, mutta meidän tulee varmistaa, että vain sallittuja indeksin arvoja voidaan käyttää. Jos indeksin arvo ei ole sallittu, muodostamme poikkeuksen: template <typename T> T& Taulukko<T>::operator[](long indeksi) if(indeksi < 0 indeksi >= koko) throw out_of_range(indeksi < 0? "Negatiivinen indeksi" : "Indeksi liian suuri"); return alkiot[indeksi]; Voisimme määritellä myös oman poikkeusluokkamme, mutta on helpompi lainata out_of_rangeluokkaa, joka on jo valmiiksi määritelty standardikirjaston stdaxcept-otsikkotiedostossa. Poikkeus muodostetaan, jos esimerkiksi string-tyyppisen olion kohdalla indeksin arvo on sallittujen rajojen ulkopuolella. Muodostamme out_of_range-tyyppisen poikkeuksen, jos indeksi:n arvo ei ole välillä 0 ja koko-1. Muodostinfunktion parametri on string-olio, joka kuvaa virheen. Poikkeusolion jäsenfunktio what() palauttaa null-merkkiin päättyvän merkkijonon (tyyppi const char*). out_of_range-luokan muodostinfunktiolle välitämme yksinkertaisen viestin, mutta voit halutessasi lisätä viestiin tietoa indeksin arvosta ja taulukon koosta, jolloin ongelma on helpompi löytää koodista. Indeksointi-operaattorin const-versio on lähes samanlainen: template <typename T> const T& Taulukko<T>::operator[](long indeksi) const if(indeksi < 0 indeksi >= koko) throw out_of_range(indeksi < 0? "Negatiivinen indeksi" : "Indeksi liian suuri"); return alkiot[indeksi]; Viimeinen tarvitsemamme mallifunktio on sijoitusoperaattori. Sen tulee vapauttaa kohteena olevan olion varaama muisti ja sen jälkeen tehdä se, mitä kopiomuodostinkin teki - tietysti sen jälkeen, kun se on testannut, että oliot eivät ole samat: template <typename T> Taulukko<T>& Taulukko<T>::operator=(const Taulukko& rhs) if(&rhs == this) // Jos lhs == rhs return *this; // palautetaan lhs if(alkiot) delete[]alkiot; // Jos lhs-taulukko on olemassa // vapautetaan varattu muisti 714 koko = rhs.koko; alkiot = new T[rhs.koko]; for(int i = 0 ; i < koko ; i++) alkiot[i] = rhs.alkiot[i];

Vasemmanpuoleisen operandin vertaaminen oikeanpuoleiseen on tässä välttämätöntä, muutoin vapauttaisimme alkiot-jäsenen varaaman muistin, jonka jälkeen yrittäisimme kopioida sen, vaikka sitä ei enää ole olemassa. Eri operandien kohdalla vapautamme vasemman operandin varaaman muistin ennen kuin kopioimme oikeanpuoleisen operandin. Kaikki tässä kirjoittamamme määrittelyt ovat mallifunktioita varten ja ne ovat kiinteästi sidotut malliluokkaan. Ne eivät ole funktioiden määrittelyjä, ne ovat malleja, joita kääntäjä käyttää, kun niiden mukainen koodi tulee muodostaa. Eli niiden tulee olla saatavilla kaikissa mallia käyttävissä lähdetekstitiedostoissa. Tästä syystä sijoitamme mallin kaikkien jäsenfunktioiden määrittelyt otsikkotiedostoon, joka sisältää itse mallinkin. Vaikka voit määritellä mallin jäsenfunktiot erillisinä mallifunktioina, ne voivat silti olla avoimia. Pyydät kääntäjää käsittelemään ne avoimina, kun lisäät inline-avainsanan template<>:n perässä olevan määrittelyn alkuun: template <typename T> inline const T& Taulukko<T>::operator[](long indeksi) const if(indeksi < 0 indeksi >= koko) throw out_of_range(indeksi < 0? "Negatiivinen indeksi" : "Indeksi liian suuri"); return alkiot[indeksi]; Mallin ilmentymien luominen Kääntäjä luo mallin ilmentymän, kun ohjelmassa esitellään olio, jonka tyyppi on malli: Taulukko<int> tieto(40); Mallit Tämän kääntämiseksi tarvitaan kaksi asiaa - tyypin Taulukko<int> tulee olla esitelty, jotta tyyppi tunnistetaan ja muodostinfunktion tulee olla olemassa, koska sitä kutsutaan luomaan olio. Tämä lause luo mallimme ilmentymän, joka on luokka Taulukko<int>, sekä tämän luokan muodostinfunktion ilmentymän. Tässä oli kaikki, mikä on tarpeellista tieto-olion luomiselle, joten tässä oli kaikki, mitä kääntäjä tekee tässä vaiheessa. Ohjelmaasi lisättävän luokan määrittely muodostetaan sijoittamalla int T:n paikalle mallin määrittelyssä, mutta tässä on eräs vaikeus. Kääntäjä kääntää vain ne jäsenfunktiot, joita ohjelmasi käyttää, ei välttämättä koko luokkaa, joka voitaisiin luoda yksinkertaisella malliparametrin korvauksella. data-olion esittelyn kannalta se vastaa seuraavaa: class Taulukko<int> private: T* alkiot; // T-tyyppinen taulukko size_t koko; // Taulukon alkioiden lukumäärä 715

C++ Ohjelmoijan käsikirja ; public: Taulukko(size_t taulkoko); // Muodostinfunktio Kuten huomaat, muodostinfunktiota lukuun ottamatta luokan jäsenfunktiota ei tässä ole lainkaan. Kääntäjä ei luo minkään sellaisen ilmentymiä, joita ei tarvita olion luomiseen, eikä kääntäjä ota mukaan mitään mallin osia, joita ei tarvita ohjelmassasi. Tästä seuraa se, että mallin koodissa saattaa olla jopa virheitä ja ohjelmasi kääntyy, linkittyy ja sitä voidaan suorittaa normaalisti. Jos virheet ovat mallin osissa, joita ei ohjelmassa tarvita, niitä ei ehkä huomata, koska ne sisältävää koodia, jota ei oteta mukaan käännettävään koodiin. Ohjelmassasi on lähes varmasti muitakin lauseita kuin olion luominen, johon tarvitaan muodostinfunktiota - tuhoajafunktiota tarvitaan ainakin olion tuhoamiseen - joten luokan lopulliseen versioon kuuluu varmasti enemmän kuin mitä edellä näytettiin. Mallin ilmentymän muodostamista esittelystä kutsutaan mallin implisiittiseksi ilmentämiseksi, koska se tapahtuu olion pala-palalta muodostamisena. Tämä erottaa sen myös mallin eksplisiittisestä ilmentämisestä, jota käsittelemme hetken kuluttua ja joka toimii hieman eri tavalla. Kuten mainitsimme, data:n esittely saa aikaan myös muodostinfunktion, Taulukko<int>::Taulukko(), kutsun, joten kääntäjä luo mallifunktion avulla luokan muodostinfunktion määrittelyn: Taulukko<int>::Taulukko(long taulkoko) : koko(taulkoko) alkiot = new int[koko]; Joka kerta, kun käytät malliluokkaa eri tyyppisen muuttujan luomiseen, uusi luokka määritellään ja lisätään ohjelmaasi. Koska luokan olion luominen vaatii muodostinfunktion kutsumisen, saman tyyppinen muodostinfunktio muodostetaan myöskin samalla. Luonnollisestikaan sellaisen olion luominen, jonka tyyppinen mallin ilmentymä on jo muodostettu, ei saa aikaan uuden ilmentymän luomista. Kääntäjä osaa tarvittaessa käyttää aiemmin muodostettua ilmentymää. Kun kutsut mallin tietyn ilmentymän jäsenfunktiota - esimerkiksi silloin, kun kutsut mallista luodun olion jäsenfunktiota - jokaisen käytettävän jäsenfunktion koodi muodostetaan. Jos mallilla on jäsenfunktioita, joita et ohjelmassasi käytä, niiden mallien ilmentymiä ei luoda lainkaan. Jokaisen funktion määrittelyn muodostaminen on mallin implisiittinen ilmentäminen, koska se tapahtuu itse funktion käytön ulkopuolella. Malli itse ei ole osa suoritettavaa koodia, sen tarkoituksena on vain mahdollistaa se, että kääntäjä voi automaattisesti muodostaa tarvittavan koodin. 716

Mallit template<typename T> class Taulukko // Mallin määrittely... ; taulukko<int> data(40); Kääntäjä muodostaa malliparametrien mukaisen luokan määrittelyn - mallin ilmentymän. Taulukko<string> viestit(20); Mallin parametrit liitetään luokan määrittelyyn. Jokaisen esittelyn kohdalla, jossa on yydet malliparametrit, uusi luokan määrittely luodaan. class Yaulukko<int> // Luokan määrittely... //...tarpeen mukaan ; Mallin kukin ilmentymä muodostetaan vain kerran. Jos tarvittava ilmentymä on jo olemassa, sitä käytetään. Taulukko<int> arvot(60); class Taulukko<string> // Luokan määrittely... //...tarpeen mukaan ; Huomaa, että malli muodostetaan implisiittisesti, kun oliotyyppi tulee muodostaa. Oliotyyppiin osoittavan osoittimen esittely ei saa aikaan mallin ilmentämistä. Esimerkiksi: Taulukko<string>*pOlio; Tämä esittelee osoitin tyyppiin Taulukko<string> -tyyppisen polio-olion. Taulukko<string>tyyppistä oliota ei tämän lauseen vaikutuksesta luoda, joten myöskään mallin ilmentymää ei luoda. Tämän vastakohtana on lause: Taulukko<string*> pviesti(10); Tämä saa aikaan mallin ilmentymän luomisen. Se esittelee Taulukko<string*>-tyyppisen olion luomisen, joten pviesti:n jokainen alkio voi tallettaa osoittimen string-olioon. Luokan muodostinfunktion määrittely mallista muodostetaan samalla. Kokeillaan nyt Taulukko-malliamme esimerkin avulla. Kokeile itse - Mallin käyttö Voimme sijoittaa mallin määrittelyn ja mallin jäsenfunktioiden määrittelyt otsikkotiedostoon Taulukko.h: // Taulukko-mallin määrittely #ifndef TAULUKKO_H #define TAULUKKO_H #include <iostream> #include <stdexcept> using namespace std; // Poikkeus-luokat 717

C++ Ohjelmoijan käsikirja template <typename T> class Taulukko private: T* alkiot; // T-tyyppinen taulukko size_t koko; // Taulukon alkioiden lukumäärä public: explicit Taulukko(size_t taulkoko); // Muodostinfunktio Taulukko(const Taulukko& ataul); // Kopiomuodostin ~Taulukko(); // Tuhoajafunktio T& operator[](long indeksi); // Indeksointioperaattori const T& operator[](long indeksi) const; // Indeksointioperaattori (const) Taulukko& operator=(const Taulukko& rhs); // Sijoitusoperaattori ; // Muodostinfunktio template <typename T> // Malli, jonka parametrinä on T Taulukko<T>::Taulukko(size_t taulkoko) : koko(taulkoko) // Taulukko<T> määrittelee mallin alkiot = new T[koko]; // Kopiomuodostin template <typename T> Taulukko<T>::Taulukko(const Taulukko& ataul) koko = ataul.koko; alkiot = new T[koko]; for(int i = 0 ; i < koko ; i++) alkiot[i] = ataul.alkiot[i]; // Tuhoajafunktio template <typename T> Taulukko<T>::~Taulukko() delete[] alkiot; // Indeksointioperaattori template <typename T> T& Taulukko<T>::operator[](long indeksi) if(indeksi < 0 indeksi >= koko) throw out_of_range(indeksi < 0? "Negatiivinen indeksi" : "Indeksi liian suuri"); return alkiot[indeksi]; // Indeksointioperaattori (const) template <typename T> const T& Taulukko<T>::operator[](long indeksi) const if(indeksi < 0 indeksi >= koko) throw out_of_range(indeksi < 0? "Negatiivinen indeksi" : "Indeksi liian suuri"); 718 return alkiot[indeksi];

Mallit // Sijoitusoperaattori template <typename T> Taulukko<T>& Taulukko<T>::operator=(const Taulukko& rhs) if(&rhs == this) // Jos lhs == rhs return *this; // palautetaan lhs if(alkiot) delete[]alkiot; // Jos lhs-taulukko on olemassa // vapautetaan varattu muisti koko = rhs.koko; alkiot = new T[rhs.koko]; for(int i = 0 ; i < koko ; i++) alkiot[i] = rhs.alkiot[i]; #endif Tarvitsemme mallin käyttämiseen ohjelman, joka esittelee joitakin taulukoita mallin avulla. Kokeilemme myös joitakin kiellettyjä indeksien arvoja: // Esimerkki 18.1 Mallin käyttö #include "Laatikko.h" #include "Taulukko.h" #include <iostream> #include <iomanip> using namespace std; int main() const int doublelkm = 50; Taulukko<double> arvot(doublelkm); try for(int i = 0 ; i < doublelkm ; i++) arvot[i] = i + 1; // Luodaan mallin ilmentymä // Luodaan jäsenfunktion ilmentymä cout << endl << "Alkioparien summat:"; int rivit = 0; for(int i = doublelkm - 1 ; i >= 0 ; i--) cout << (rivit++ % 5 == 0? "\n" : "") << setw(5) << arvot[i] + arvot[i - 1]; catch(const out_of_range& ex) cout << endl <<"out_of_range-poikkeus muodostettu! " << ex.what(); try const int ltklkm = 10; Taulukko<Laatikko> laatikot(ltklkm); // Luodaan mallin ilmentymä for(int i = 0 ; i <= ltklkm ; i++) cout << endl << "Laatikon tilavuus on " << laatikot[i].tilavuus(); // Luodaan jäsenen ilmentymä 719

C++ Ohjelmoijan käsikirja catch(const out_of_range& ex) cout << endl << "out_of_range-poikkeus muodostettu! " << ex.what(); cout << endl; return 0; Otsikkotiedosto Laatikko.h sisältää luvun 16 Laatikko-luokan määrittelyn ja tarvitset ohjelmassa myös Laatikko-luokan jäsenfunktioiden määrittelyt sisältävää Laatikko.cpp-tiedostoa. Esimerkin tulostus näyttää seuraavalta: Alkioparien summat: 99 97 95 93 91 89 87 85 83 81 79 77 75 73 71 69 67 65 63 61 59 57 55 53 51 49 47 45 43 41 39 37 35 33 31 29 27 25 23 21 19 17 15 13 11 9 7 5 3 out_of_range-poikkeus muodostettu! Negatiivinen indeksi Laatikon tilavuus on 1 Laatikon tilavuus on 1 Laatikon tilavuus on 1 Laatikon tilavuus on 1 Laatikon tilavuus on 1 Laatikon tilavuus on 1 Laatikon tilavuus on 1 Laatikon tilavuus on 1 Laatikon tilavuus on 1 Laatikon tilavuus on 1 out_of_range-poikkeus muodostettu! Indeksi liian suuri Kuinka se toimii main()-funktion alussa luomme Taulukko<double>-tyyppisen olion käyttämällä mallia malliparametrillä double: Taulukko<double> arvot(doublelkm); // Luodaan mallin ilmentymä Tämä lause esittelee arvot Taulukko<double>-tyyppiseksi ja alkioiden lukumäärä ilmoitetaan muuttujalla doublelkm. Kun kääntäjä käsittelee tämän lauseen, se luo mallista luokan Taulukko<double> määrittelyn. Jotta olio arvot voidaan luoda, tarvitaan muodostinfunktion kutsu (Taulukko<double>::Taulukko(doubleLkm)), joten kääntäjä muodostaa muodostinfunktion muodostinfunktion mallista. 720

Alustamme arvot-taulukon alkiot 1 - doublelkm try-lohkossa olevassa for-silmukassa: Mallit for(int i = 0 ; i < doublelkm ; i++) arvot[i] = i + 1; // Luodaan jäsenfunktion ilmentymä Lauseke arvot[i] saa aikaan indeksointioperaattorin ilmentymän luomisen. Sitä kutsutaan implisiittisesti muodossa arvot.operator[](i). Koska arvot ei ole const-tyyppinen, ei-constversiota käytetään. Käytämme try-lohkossa toista for-silmukkaa, joka tulostaa peräkkäisten alkioiden summan, alkaen taulukon lopusta: for(int i = doublelkm - 1 ; i >= 0 ; i--) cout << (rivit++ % 5 == 0? "\n" : "") << setw(5) << arvot[i] + arvot[i - 1]; Tämä kutsuu myöskin indeksointioperaattoria, mutta mallifunktion ilmentymä on jo muodostettu, joten uutta ilmentymää ei muodosteta. Lausekkeella arvot[i - 1] on selvästikin kielletty indeksin arvo, kun i on 0, joten tällöin muodostetaan operator[]()-funktiossa poikkeus. Seuraava käsittelijä käsittelee poikkeuksen: catch(const out_of_range& ex) cout << endl <<"out_of_range-poikkeus muodostettu! " << ex.what(); out_of_range-poikkeuksen what()-funktio palauttaa null-merkkiin päättyvän merkkijonon, joka vastaa muodostinfunktiolle poikkeusolion luonnissa välitettyä string-oliota. Tulostuksesta huomaat, että uudelleenmääritelty operaattorifunktio muodosti Negatiivinen indeksi -poikkeuksen. Kun indeksointioperaattori muodostaa poikkeuksen, ohjelman suoritus siirtyy välittömästi poikkeuksen käsittelijään, joten kiellettyä alkion viittausta ei käytetä, eikä mitään talleteta kielletyn indeksin viittamaan paikkaan. Silmukan suoritus myöskin päättyy tässä kohtaa. Seuraavassa try-lohkossa määrittelemme olion, joka voi tallettaa Laatikko-olioiden taulukon: Taulukko<Laatikko> laatikot(ltklkm); // Luodaan mallin ilmentymä Nyt kääntäjä muodostaa mallin ilmentymän Taulukko<Laatikko>, joka tallettaa Laatikko-olioiden taulukon. Ilmentymä muodostetaan, koska mallin ilmentymää ei ole vielä muodostettu Laatikkoolioille. Tämä lause kutsuu myöskin tämän luokan muodostinfunktiota, joten muodostinfunktion mallin ilmentymä luodaan. Taulukko<Laatikko>-luokan muodostinfunktio kutsuu Laatikkoluokan oletusmuodostinfunktiota, kun luokan alkiot-jäsen luodaan vapaasta muistista. Kaikkien alkiot-talukon Laatikko-olioiden kokona on oletusarvoisesti 1 * 1 * 1. Tulostamme kunkin Laatikko-olion tilavuuden for-silmukassa: for(int i = 0 ; i <= ltklkm ; i++) cout << endl << "Laatikon tilavuus on " << laatikot[i].tilavuus(); // Luodaan jäsenen ilmentymä 721

C++ Ohjelmoijan käsikirja Lauseke laatikot[i] kutsuu uudelleenmääriteltyä indeksointioperaattoria, joten kääntäjä käyttää jälleen mallifunktiota funktion määrittelyn muodostamiseen. Kun i:n arvo on ltklkm, indeksointioperaattori muodostaa poikkeuksen, koska ltklkm viittaa laatikot-taulukon loppukohdan yli. try-lohkon perässä oleva catch-lohko käsittelee poikkeuksen: catch(const out_of_range& ex) cout << endl << "out_of_range-poikkeus muodostettu! " << ex.what(); Koska poistumme try-lohkosta, kaikki paikallisesti esitellyt oliot tuhotaan, mukaan lukien laatikot-olio. arvot-olio on tässä kohdassa vielä olemassa, koska sitä ei luotu edellisessä trylohkossa ja se on yhä näkyvyysalueella. Mallien vienti Mallin jäsenfunktioiden koodin sijoittamisella otsikkotiedostoon on se huono puoli, että kääntäjän täytyy käsitellä sama koodi jokaisen lähdetekstitiedoston kohdalla, johon otsikkotiedosto on sisällytetty. Tämä voi aiheuttaa huomattavan lisätyön laajoissa ohjelmissa, joissa käytetään runsaasti malleja. Vaihtoehtoisena ratkaisuna on sijoittaa jäsenfunktioiden mallit erilliseen lähdetekstitiedostoon ja antaa se kaikkien mallia tarvitsevien lähdetekstitiedostojen saataville. Kaikki, mitä sinun tarvitsee tehdä, on viedä mallifunktioiden määrittelyt export-avainsanalla: // TaulukkoMalli.cpp-tiedosto #include <iostream> #include <stdexcept> #include "Array.h" using namespace std; // Poikkeus-luokat // Muodostinfunktio export template <typename T> // Malli, jonka malliparametri on T Taulukko<T>::Taulukko(size_t taulkoko) : koko(taulkoko) // Taulukko<T> määrittelee mallin alkiot = new T[koko]; // Kopiomuodostin export template <typename T> Taulukko<T>::Taulukko(const Taulukko& ataul) koko = ataul.koko; alkiot = new T[koko]; for(int i = 0 ; i < koko ; i++) alkiot[i] = ataul.alkiot[i]; // Ja kaikki muut jäsenfunktioiden mallit kuten ennenkin, mutta export-avainsanalla Tämä tiedosto voidaan nyt kääntää erikseen, ja muodostettua objektitiedostoa voidaan käyttää kaikissa mallia käyttävissä ohjelmissa. 722

Otsikkotiedosto Taulukko.h sisältää mallin kuten ennenkin, mutta ainoastaan mallifunktioiden esittelyt tarvitaan: // Taulukko-mallin määrittely #ifndef TAULUKKO_H #define TAULUKKO_H // Mallin määrittely kuten ennenkin template <typename T> class Taulukko private: T* alkiot; // T-tyyppinen taulukko size_t koko; // Taulukon alkioiden lukumäärä Mallit public: explicit Taulukko(size_t taulkoko); Taulukko(const Taulukko& ataul); ~Taulukko(); T& operator[](long indeksi); const T& operator[](long indeksi) const; Taulukko& operator=(const Taulukko& rhs); ; // Muodostinfunktio // Kopiomuodostin // Tuhoajafunktio // Indeksointioperaattori // Indeksointioperaattori (const) // Sijoitusoperaattori #endif Nyt kaikkien ohjelmien, jotka haluavat käyttää mallia, tulee sisällyttää lyhyempi versio Taulukko.h-otsikkotiedostosta sitä tarvitsevaan lähdetekstitiedostoon. Lisäksi tarvitaan TaulukkoMalli.cpp-tiedosto tai siitä muodostettu objektitiedosto linkityksen yhteydessä. Ainoastaan sellaiset mallifunktiot voidaan viedä, jotka eivät ole avoimia. Jos käytät exportavainsanaa avoimen mallifunktion yhteydessä, sitä ei huomioida. Jos viet mallin määrittelyn, määrittelyä ei saa toistaa missään muualla ohjelmassa. Ainoastaan vietyjen mallien esittely voi olla muissa lähdetekstitiedostoissa. Et voi viedä mallia, joka on määritelty nimettömässä nimiavaruudessa. Mallin staattiset jäsenet Mallilla voi olla staattisia jäseniä, aivan samaan tapaan kuin tavallisellakin luokalla voi olla. Mallin staattiset jäsenfunktiot ovat varsin suoraviivaisia. Jokainen mallin ilmentymä ilmentää staattisen jäsenfunktion tarvittaessa. Tällaisella jäsenfunktiolla ei ole this-osoitinta eikä näin ollen voi viitata luokan ei-staattisiin jäseniin. Staattisten jäsenfunktioiden määrittelemisellä mallille on samat säännöt kuin luokankin kohdalla ja mallin staattiset jäsenfunktiot käyttäytyvät mallin kunkin ilmentymän kohdalla samaan tapaan kuin tavallisen luokankin kohdalla. Staattinen jäsenmuuttuja on hieman kiinnostavampi, koska se tulee alustaa mallin määrittelyn ulkopuolella. Oletetaan, että Taulukko-mallimme sisältää staattisen jäsenmuuttujan. Jäsenen esittely ja sen alustava malli näyttävät seuraavalta: template <typename T> class Taulukko private: static T arvo; // Staattinen jäsenmuuttuja T* alkiot; // T-tyyppinen taulukko size_t koko; // Taulukon alkioiden lukumäärä 723

C++ Ohjelmoijan käsikirja public: explicit Taulukko(size_t taulkoko); Taulukko(const Taulukko& ataul); ~Taulukko(); T& operator[](long indeksi); const T& operator[](long indeksi) const; Taulukko& operator=(const Taulukko& rhs); ; template < typename T > T Taulukko<T>::arvo; // Muodostinfunktio // Kopiomuodostin // Tuhoajafunktio // Indeksointioperaattori // Indeksointioperaattori (const) // Sijoitusoperaattori // Alustetaan staattinen // jäsenmuuttuja Alustus suoritetaan mallin avulla. Staattinen jäsenmuuttuja on aina riippuvainen sen mallin malliparametreistä, jonka jäsenenä se on, joten meidän tulee alustaa arvo mallilla, jonka malliparametrinä on T. Staattisen muuttujan nimen yhteyteen tulee kirjoittaa myös tyypin nimi Taulukko<T>, jotta se tunnistetaan mallin ilmentymään. Et voi tässä käyttää pelkkää Taulukkotyyppiä, koska sen määrittely on mallin rungon ulkopuolella ja mallin ID on Taulukko<T>. 724 Mallin ei-tyyppiparametrit Ei-tyyppiparametri näyttää samalta kuin funktion parametri - tyypin nimen perässä on parametrin nimi. Parametrissä välitetään täten tietyntyyppinen arvo. Et voi kuitenkaan käyttää mitä tahansa tyyppiä mallin ei-tyyppiparametrinä. Ei-tyyppiparametrit on tarkoitettu käytettäviksi määrittelemään arvoja, jotka saattavat olla hyödyllisiä säiliön määrittelyssä, kuten taulukon koko tai indeksin ylä- ja alarajat. Ei-tyyppiparametri voi olla vain kokonaislukutyyppinen, kuten int tai long, lueteltu tyyppi, osoitin tai viittaus olioon, kuten string* tai Laatikko&, osoitin tai viittaus funktioon tai osoitin luokan jäseneen. Tästä voit päätellä, että ei-tyyppiparametri ei voi olla liukulukutyyppinen tai luokan olio, eli tyypit double, Laatikko ja string eivät ole sallittuja, ei myöskään string**. Pidä mielessä, että ei-tyyppiparametrin päätarkoitus on säiliön koon ja arvoalueen rajojen määrittely. Ei-tyyppiparametrissä välitettävä arvo voi tietystikin olla myös luokan olio, jos parametrin tyyppi on viittaus. Jos parametrin tyyppi on esimerkiksi Laatikko&, voit käyttää Laatikko-tyyppistä oliota parametrin arvona. Ei-typpiparametri kirjoitetaan kuten funktion parametrikin, tyypin nimen perään kirjoitetaan parametrin nimi: template <typename T, size_t koko> class LuokanNimi // Määrittelyt T:n ja koon avulla... Tällä mallilla on tyyppiparametri T ja ei-tyyppiparametri koko. Määrittely esitetään näiden kahden parametrin ja mallin nimen avulla. Jos haluat, tyyppiparametrin nimi voi olla myös eityyppiparametrin tyyppi: template <typename T, size_t koko, T arvo> class LuokanNimi // T on tyyppiparametrin nimi // T on myös ei-tyyppiparametrin tyyppi

Mallit // Määrittely T:n koon ja arvon avulla... ; Tällä mallilla on T-tyyppinen ei-tyyppiparametri arvo. Parametrin T tulee esiintyä parametrilistassa ennen sen käyttöä, eli arvo ei tässä voi olla parametrin T edellä. Huomaa, että kun samaa symbolia käytetään sekä tyyppiparametrinä että ei-tyyppiparametrin tyyppinä, rajoitetaan samalla typename-parametrin arvot ei-tyyppiparametrille sallittujen tyyppien mukaan (toisin sanoen T:n tulee olla kokonaislukutyyppinen). Havainnollistaaksemme, miten voit käyttää ei-tyyppiparametrejä, oletetaan, että määrittelet taulukon mallin seuraavasti: template <typename T, in taulkoko, T arvo> class Taulukko // Määrittely T:n koon ja arvon avulla... Nyt voit käyttää muodostinfunktiossa ei-tyyppiparametriä arvo alustamaan taulukon jokaisen alkion: template <typename T, int taulkoko, T arvo> Taulukko<T, koko, arvo>::taulukko(size_t taulkoko) : koko(taulkoko) alkiot = new T[taulKoko]; for(int i = 0 ; i < taulkoko, i++) alkiot[i] = arvo; Koska ei-tyyppiparametrin tyyppi voi olla vain kokonaislukutyyppi, osoitin tai viittaus, et voi luoda Taulukko-oliota, joka tallettaa double-arvoja, joten tämän mallin käyttökelpoisuus on hieman heikko. Ei-tyyppiparametrien esimerkki Parempana esimerkkinä voimme käyttää aikaisempaa Taulukko-malliamme lisäämällä siihen eityyppiparametrin, jonka avulla taulukon indeksointiin saadaan hieman joustavuuta: template <typename T, long alkuindeksi> class Taulukko private: T* alkiot; // T-tyyppinen taulukko size_t koko; // Taulukon alkioiden lukumäärä public: explicit Taulukko(size_t taulkoko); Taulukko(const Taulukko& ataul); ~Taulukko(); T& operator[](long indeksi); const T& operator[](long indeksi) const; Taulukko& operator=(const Taulukko& rhs); ; // Muodostinfunktio // Kopiomuodostin // Tuhoajafunktio // Indeksointioperaattori // Indeksointioperaattori (const) // Sijoitusoperaattori 725

C++ Ohjelmoijan käsikirja Tässä malliin on lisätty long-tyyppinen ei-tyyppiparametri alkuindeksi. Tämän ideana on, että voit määritellä mitä arvoja indeksi voi saada, esimerkiksi -10 - +10, jolloin esittelisit taulukon ei-tyyppiparametrin arvoksi -10 ja muodostinfunktion parametrin arvoksi 21, koska taulukossa tarvitaan tällöin 21 alkiota. Koska mallilla on nyt kaksi parametriä, jäsenfunktiot määrittelevillä mallifunktioilla tulee olla myöskin sama määrä parametrejä. Näin myös silloin, kun jokin funktio ei tarvitse eityyppiparametriä lainkaan. Parametrit ovat osa mallin tunnistetta, joten jotta funktio täsmää malliin, sillä tulee olla sama parametrilista. Edellä esitetyllä on joitakin vakavia haittapuolia. Uuden malliparametrin alkuindeksi lisäämisellä on se seuraus, että parametrien erilaiset arvot muodostavat eri mallin ilmentymät. Tämä tarkoittaa sitä, että double-tyyppinen taulukko, jonka indeksit alkavat arvosta 0, on eri tyyppi kuin double-tyyppinen taulukko, jonka indeksit alkavat arvosta 1. Jos käytät ohjelmassasi molempia, kaksi erillistä luokan määrittelyä muodostetaan mallista, mukaan lukien kaikki jäsenfunktiot. Tällä on vähintään kaksi epätoivottua seurausta - ensinnäkin, ohjelmasi sisältää huomattavasti enemmän käännettyä koodia kuin odotit. Toiseksi (mikä on vielä huonompi asia), et voi sekoittaa näiden kahden tyypin alkioita samaan lausekkeeseen. Indeksien arvovälin määrittely on huomattavasti parempi sijoittaa muodostinfunktion parametriksi kuin mallin eityyppiparametriksi: template <typename T> class Taulukko private: T* alkiot; // T-tyyppinen taulukko size_t koko; // Taulukon alkioiden lukumäärä long alku // Indeksin alkuarvo public: explicit Taulukko(size_t taulkoko, long alkuindeksi = 0); // Muodostinfunktio Taulukko(const Taulukko& ataul); // Kopiomuodostin ~Taulukko(); // Tuhoajafunktio T& operator[](long indeksi); // Indeksointioperaattori const T& operator[](long indeksi) const; // Indeksointioperaattori (const) Taulukko& operator=(const Taulukko& rhs); // Sijoitusoperaattori ; Uusi jäsen, alku, tallettaa indeksin alkuarvon, joka määritellään muodostinfunktion toisena parametrinä. Parametrin alkuindeksi oletusarvo on nolla, joten normaalia indeksointia käytetään oletusarvoisesti. Nyt onkin mielenkiintoista nähdä, miten jäsenfunktiot määritellään, kun meillä on eimalliparametri. Muodostetaan muutama mallifunktio, joita tarvitsemme Taulukko-mallille. Jäsenfunktioiden mallit Koska olemme lisänneet mallin määrittelyyn ei-tyyppiparametrin, muodostinfunktion malli ja muiden jäsenfunktioiden mallit tulee muuttaa myöskin. Muodostinfunktion malli muuttuu muotoon: 726

Mallit template <typename T, long alkuindeksi> Taulukko<T, alkuindeksi>::taulukko(size_t taulkoko) : koko(taulkoko) alkiot = new T[koko]; Mallin ID on nyt Taulukko<T, alkuindeksi>, joten sitä käytetään muodostinfunktion nimen määrittelyssä. Tämä on ainut muutos uuden malliparametrin lisäämisen lisäksi. Kopiomuodostimen kohdalla meidän tulee tehdä samantyyppisiä muutoksia: template <typename T, long alkuindeksi> Taulukko<T, alkuindeksi>::taulukko(const Taulukko& ataul) koko = ataul.koko; alkiot = new T[koko]; for(int i = 0 ; i < koko ; i++) alkiot[i] = ataul.alkiot[i]; Ulkoinen indeksointi ei tietystikään vaikuta mitenkään asioiden sisäiseen käsittelyyn. Tuhoajafunktioon tulee myöskin lisätä uusi malliparametri: template <typename T, long alkuindeksi> Taulukko<T, alkuindeksi>::~taulukko() delete[] alkiot; Ei-const-tyyppisen indeksointioperaattorin mallifunktion määrittely tulee muuttaa muotoon: template <typename T, long alkuindeksi> T& Taulukko<T, alkuindeksi>::operator[](long indeksi) if(indeksi < alkuindeksi indeksi > alkuindeksi + static_cast<long>(koko) - 1) throw out_of_range(indeksi < alkuindeksi? "Negatiivinen indeksi" : "Indeksi liian suuri"); return alkiot[indeksi - alkuindeksi]; Tässä on jo varsin merkitseviä muutoksia. Indeksin indeksi arvo tarkistetaan nyt siten, että se on ei-tyyppiparametrin ja taulukon alkioiden lukumäärän mukaan sallittu. Indeksin arvot voivat olla arvovälillä alkuindeksi - alkuindeksi+koko-1. Koska size_t on usein etumerkitön kokonaislukutyyppi, meidän tulee eksplisiittisesti muuntaa se long-tyyppiseksi, muutoin kaikki muut arvot muutetaan size_t-tyyppisiksi, joka saa aikaan väärän tuloksen, jos arvot ovat negatiivisia. Poikkeuksen valitseva lauseke on myös muuttunut. const-tyyppinen versio tulee muuttaa samaan tapaan: template <typename T, long alkuindeksi> const T& Taulukko<T, alkuindeksi>::operator[](long indeksi) const 727

C++ Ohjelmoijan käsikirja if(indeksi < alkuindeksi indeksi > alkuindeksi + static_cast<long>(koko) -1) throw out_of_range(indeksi < alkuindeksi? "Negatiivinen indeksi" : "Indeksi liian suuri"); return alkiot[indeksi - alkuindeksi]; Lopuksi meidän tulee muuttaa sijoitusoperaattorin mallia, mutta ainoastaan mallin parametrilista ja mallin ID tarvitsee muuttaa: template <typename T, long alkuindeksi> Taulukko<T, alkuindeksi>& Taulukko<T, alkuindeksi>::operator=(const Taulukko& rhs) if(&rhs == this) // Jos lhs == rhs return *this; // palautetaan lhs if(alkiot) olemassa delete[]alkiot; // Jos lhs-taulukko on // vapautetaan varattu muisti koko = rhs.koko; alkiot = new T[rhs.koko]; for(int i = 0 ; i < koko ; i++) alkiot[i] = rhs.alkiot[i]; Mallin ei-tyyppiparametrin käytölle on rajoituksia. Käytännössä sinun ei tule muuttaa parametrin arvoa mallin määrittelyssä. Toisin sanoen, ei-tyyppiparametriä ei voi käyttää sijoitusoperaattorin vasemmalla puolella, eikä siihen voi soveltaa lisäys- eikä vähennysoperaattoria - eli sitä pidetään vakiona. Luvussa 9 näimme, kuinka mallifunktion malliparametrit voitiin päätellä funktion parametreistä. Näin ei ole malliluokan kohdalla. Mallin kaikki parametrit tulee aina määritellä, ellei parametreillä ole oletusarvoja - tätä käsittelemme myöhemmin tässä luvussa. Ei-tyyppiparametrien arvot Ei-tyyppiparametrin, joka ei ole viittaus tai osoitin, arvon tulee olla käännösaikainen vakiolauseke. Tämä tarkoittaa sitä, että et voi käyttää parametrin arvona lauseketta, jossa on muita kuin const-tyyppisiä kokonaislukumuuttujia. Tämä on pienoinen haitta, mutta kääntäjä pystyy tällöin tarkistamaan arvon, mikä on taas hyvä asia. Esimerkiksi seuraavat lauseet eivät käänny: long alku = -10; Taulukko<double, alku> arvot(21); // Ei käänny! 728 Kääntäjä muodostaa virheilmoituksen, että toinen arvo ei ole sallittu. Näiden kahden lauseen oikea versio on: const long alku = -10; Taulukko<double, alku> arvot(21); Kun alku on nyt esitelty const-tyyppiseksi, kääntäjä voi luottaa sen arvoon ja molemmat malliparametrien arvot ovat sallitut.

Kääntäjä voi myös suorittaa parametrien arvojen standardimuunnokset, jos sellaisia tarvitaan oikean parametrityypin löytämiseksi. Jos meillä olisi esimerkiksi const size_t-tyyppiseksi esitelty ei-tyyppiparametri, kääntäjä muuntaa kokonaislukuliteraalin, kuten 10, parametrin tyyppiseksi. Osoittimet ja taulukot ei-tyyppiparametreinä Ei-tyyppiparametrin, joka on osoitin, arvon tulee olla osoite, mutta se ei voi olla vanha osoite. Sen tulee olla sellaisen olion osoite, jolla on ulkoinen linkitys, joten et voi esimerkiksi käyttää taulukon alkioiden osoitteita tai luokan ei-staattisten jäsenten osoitteita. Tämä tarkoittaa myös sitä, että jos ei-tyyppiparametrisi on const char* -tyyppinen, et voi käyttää merkkijonoliteraalia parametrin arvona. Jos haluat käyttää merkkijonoliteraalia parametrin arvona, sinun tulee alustaa osoitinmuuttuja merkkijonoliteraalin osoitteella ja välittää tämä osoitin parametrinä. Koska osoitin on sallittu mallin ei-tyyppiparametri, voit määritellä taulukon parametriksi, mutta taulukko ja osoitin eivät aina ole vaihtoehtoisia välitettäessä mallille parametri. Jos esimerkiksi määrittelet mallin seuraavasti: template <long* pnumero> class OmaLuokka // Mallin määrittely... Mallit Voit nyt muodostaa tämän mallin ilmentymiä seuraavalla koodilla: long data[10]; long* pdata = data; // Globaali // Globaali OmaLuokka<pData> arvot; OmaLuokka<data> arvot; Joko taulukon nimeä tai osoitinta sopivaan tyyppiin voidaan käyttää osoitintyyppisen parametrin arvona. Tämä ei kuitenkaan onnistu toisin päin. Oletetaan, että olisimme määritelleet mallin seuraavasti: template <long numero[10]> class ToinenLuokka // Mallin määrittely... Parametri on tässä 10-alkioinen taulukko, ja parametrin arvon tulee olla samaa tyyppiä. Tässä tapauksessa voimme kirjoittaa esittelyn käyttämällä edellä esiteltyä data-taulukkoa: ToinenLuokka<data> numerot; // OK Et voi kuitenkaan käyttää osoitinta, joten seuraava lause ei käänny: ToinenLuokka<pData> numerot; // Ei sallittu! Huolimatta Taulukko-mallimme heikkouksista, tarkastellaan ei-tyyppiparametrejä esimerkin avulla. 729

C++ Ohjelmoijan käsikirja Kokeile itse - Ei-tyyppiparametrien käyttö Tee edellä käsittelemämme muutokset Taulukko-mallin sisältävään otsikkotiedostoon. Voimme nyt kokeilla uusia ominaisuuksia seuraavan esimerkin avulla: // Esimerkki 18.2 Mallin ei-tyyppiparametrien käyttö #include "Laatikko.h" #include "Taulukko.h" #include <iostream> #include <iomanip> using namespace std; int main() try const int koko = 21; const int alku = -10; const int loppu = alku + koko - 1; Taulukko<double, alku> arvot(koko); for(int i = alku; i <= loppu ; i++) arvot[i] = i - alku + 1; // Taulukon alkioiden lukumäärä // Ensimmäisen alkion indeksi // Viimeisen alkion indeksi // Esitellään taulukko double-arvoille // Alustetaan alkiot cout << endl<< "Alkioparien summat: "; int rivit = 0; for( int i = loppu ; i >= alku ; i--) cout << (rivit++ % 5 == 0? "\n" : "") << setw(5) << arvot[i] + arvot[i - 1]; catch(const out_of_range& ex) cout << endl << "out_of_range-poikkeus muodostettu! " << ex.what(); catch(const exception& ex) cout << endl << ex.what(); try const int alku = 0; const int koko = 11; Taulukko<Laatikko, alku - 5> laatikot(koko); for(int i = alku - 5 ; i <= alku + koko - 5 ; i++) cout << endl << "Laatikon tilavuus on " << laatikot[i].tilavuus(); catch(const exception& ex) cout << endl << typeid(ex).name() << "-poikkeus muodostettu! "<< ex.what(); 730 cout << endl; return 0;