466
Matti Rintala ja Jyke Jokinen Olioiden ohjelmointi C++:lla 13. maaliskuuta 2013

Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

  • Upload
    others

  • View
    7

  • Download
    0

Embed Size (px)

Citation preview

Page 1: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

Matti Rintala ja Jyke Jokinen

Olioiden ohjelmointiC++:lla

13. maaliskuuta 2013

Page 2: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

c© 2012 Matti Rintala ja Jyke Jokinen

(Subversion-revisio 1938 )

Tämän teoksen käyttöoikeutta koskee Creative Commons Nimeä-Ei muutoksia-Epäkaupallinen 1.0 Suomi-lisenssi.

• Nimeä — Teoksen tekijä on ilmoitettava siten kuin tekijä tai teoksen lisensoija on sen määrännyt(mutta ei siten että ilmoitus viittaisi lisenssinantajan tukevan lisenssinsaajaa tai Teoksenkäyttötapaa).

• Ei muutettuja teoksia — Teosta ei saa muuttaa, muunnella tai käyttää toisen teoksenpohjana.

• Epäkaupallinen — Lisenssi ei salli teoksen käyttöä ansiotarkoituksessa.

Lisenssi on nähtävillä kokonaisuudessaan osoitteessahttp://creativecommons.org/licenses/by-nd-nc/1.0/fi/

Tämä kirja on taitettu LATEX-ohjelmistolla (http://www.tug.org/). Kaavioiden ja kuvien piirtoon on

käytetty XFIG-ohjelmistoa (http://www.xfig.org/) ja ohjelmalistaukset on käsitelty LGrind-muotoili-

jalla.

Page 3: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

Sisalto 3

Sisalto

Esipuhe: Sirkkelin kayttoohje . . . . . . . . . . . . . . . . . . . 18

Alkusanat neljanteen, uudistettuun painokseen . . . . . . . . 20

1 Kohti olioita . . . . . . . . . . . . . . . . . . . . . . . . . . . 261.1 Ohjelmistojen tekijoiden ongelmakentta . . . . . . . . 261.2 Laajojen ohjelmistojen teon vaikeus . . . . . . . . . . . 271.3 Suurten ohjelmistokokonaisuuksien hallinta . . . . . . 28

1.3.1 Ennen kehittyneita ohjelmointikielia . . . . . . 291.3.2 Tietorakenteet . . . . . . . . . . . . . . . . . . . 301.3.3 Moduulit . . . . . . . . . . . . . . . . . . . . . . 311.3.4 Oliot . . . . . . . . . . . . . . . . . . . . . . . . . 341.3.5 Komponentit . . . . . . . . . . . . . . . . . . . . 38

1.4 C++: Moduulit kaannosyksikoilla . . . . . . . . . . . . . 391.5 C++: Nimiavaruudet . . . . . . . . . . . . . . . . . . . . . 41

1.5.1 Nimiavaruuksien maaritteleminen . . . . . . . 421.5.2 Nakyvyystarkenninoperaattori . . . . . . . . . . 421.5.3 Nimiavaruuksien hyodyt . . . . . . . . . . . . . 441.5.4 std-nimiavaruus . . . . . . . . . . . . . . . . . . 451.5.5 Standardin uudet otsikkotiedostot . . . . . . . . 451.5.6 Nimiavaruuden synonyymi . . . . . . . . . . . 461.5.7 Lyhyiden nimien kaytto (using) . . . . . . . . . 471.5.8 Nimeamaton nimiavaruus . . . . . . . . . . . . 49

1.6 Moduulituki muissa ohjelmointikielissa . . . . . . . . 511.6.1 Modula-3 . . . . . . . . . . . . . . . . . . . . . . 511.6.2 Java . . . . . . . . . . . . . . . . . . . . . . . . . 51

Page 4: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

Sisalto 4

2 Luokat ja oliot . . . . . . . . . . . . . . . . . . . . . . . . . . 542.1 Olioiden ominaisuuksia . . . . . . . . . . . . . . . . . . 54

2.1.1 Oliolla on tila . . . . . . . . . . . . . . . . . . . 552.1.2 Olio kuuluu luokkaan . . . . . . . . . . . . . . . 562.1.3 Oliolla on identiteetti . . . . . . . . . . . . . . . 57

2.2 Luokan dualismi . . . . . . . . . . . . . . . . . . . . . . 582.3 C++: Luokat ja oliot . . . . . . . . . . . . . . . . . . . . . 60

2.3.1 Luokan esittely . . . . . . . . . . . . . . . . . . . 602.3.2 Jasenmuuttujat . . . . . . . . . . . . . . . . . . . 612.3.3 Jasenfunktiot . . . . . . . . . . . . . . . . . . . . 642.3.4 Luokkien ja olioiden kaytto C++:ssa . . . . . . . . 66

3 Olioiden elinkaari . . . . . . . . . . . . . . . . . . . . . . . . 693.1 Olion syntyma . . . . . . . . . . . . . . . . . . . . . . . 703.2 Olion kuolema . . . . . . . . . . . . . . . . . . . . . . . 713.3 Olion elinkaaren maaraytyminen . . . . . . . . . . . . 71

3.3.1 Modula-3 . . . . . . . . . . . . . . . . . . . . . . 723.3.2 Smalltalk . . . . . . . . . . . . . . . . . . . . . . . 743.3.3 Java . . . . . . . . . . . . . . . . . . . . . . . . . 743.3.4 C++ . . . . . . . . . . . . . . . . . . . . . . . . . . 76

3.4 C++: Rakentajat ja purkajat . . . . . . . . . . . . . . . . . 793.4.1 Rakentajat . . . . . . . . . . . . . . . . . . . . . 803.4.2 Purkajat . . . . . . . . . . . . . . . . . . . . . . . 83

3.5 C++: Dynaaminen luominen ja tuhoaminen . . . . . . . 853.5.1 new . . . . . . . . . . . . . . . . . . . . . . . . . . 863.5.2 delete . . . . . . . . . . . . . . . . . . . . . . . . 893.5.3 Dynaamisesti luodut taulukot . . . . . . . . . . 903.5.4 Virheiden valttaminen dynaamisessa luomisessa 90

4 Olioiden rajapinnat . . . . . . . . . . . . . . . . . . . . . . . 954.1 Rajapinnan suunnittelu . . . . . . . . . . . . . . . . . . 96

4.1.1 Hyvan rajapinnan tunnusmerkkeja . . . . . . . 974.1.2 Erilaisia rajapintoja ohjelmoinnissa . . . . . . . 984.1.3 Rajapintadokumentaation tuottaminen ja yllapito 99

4.2 C++: Nakyvyysmaareet . . . . . . . . . . . . . . . . . . . 1014.2.1 public . . . . . . . . . . . . . . . . . . . . . . . . 1024.2.2 private . . . . . . . . . . . . . . . . . . . . . . . 103

4.3 C++: const ja vakio-oliot . . . . . . . . . . . . . . . . . . 1054.3.1 Perustyyppiset vakiot . . . . . . . . . . . . . . . 105

Page 5: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

Sisalto 5

4.3.2 Vakio-oliot . . . . . . . . . . . . . . . . . . . . . 1064.3.3 Vakioviitteet ja -osoittimet . . . . . . . . . . . . 109

4.4 C++: Luokan ennakkoesittely . . . . . . . . . . . . . . . . 1124.4.1 Ennakkoesittely kapseloinnissa . . . . . . . . . 113

5 Oliosuunnittelu . . . . . . . . . . . . . . . . . . . . . . . . . 1165.1 Oliosuunnittelua ohjaavat ominaisuudet . . . . . . . . 117

5.1.1 Mita suunnittelu on? . . . . . . . . . . . . . . . 1175.1.2 Abstraktio ja tiedon katkenta . . . . . . . . . . . 1185.1.3 Osien valiset yhteydet ja lokaalisuusperiaate . . 1185.1.4 Laatu . . . . . . . . . . . . . . . . . . . . . . . . 120

5.2 Oliosuunnittelun aloittaminen . . . . . . . . . . . . . . 1215.2.1 Luokan vastuualue . . . . . . . . . . . . . . . . 1225.2.2 Kuinka loytaa luokkia? . . . . . . . . . . . . . . 1235.2.3 CRC-kortti . . . . . . . . . . . . . . . . . . . . . 1245.2.4 Luokka, attribuutti vai operaatio? . . . . . . . . 126

5.3 Oliosuunnitelman graafinen kuvaus . . . . . . . . . . . 1265.3.1 UML:n historiaa . . . . . . . . . . . . . . . . . . 1275.3.2 Luokat, oliot ja rajapinnat . . . . . . . . . . . . . 1285.3.3 Luokkien valiset yhteydet . . . . . . . . . . . . 1305.3.4 Ajoaikaisen kayttaytymisen kuvaamistapoja . . 1385.3.5 Ohjelmiston rakennekuvaukset . . . . . . . . . 140

5.4 Saatteeksi suunnitteluun . . . . . . . . . . . . . . . . . 140

6 Periytyminen . . . . . . . . . . . . . . . . . . . . . . . . . . . 1426.1 Periytyminen, luokkahierarkiat, polymorfismi . . . . . 1436.2 Periytyminen ja uudelleenkaytto . . . . . . . . . . . . . 1476.3 C++: Periytymisen perusteet . . . . . . . . . . . . . . . . 149

6.3.1 Periytyminen ja nakyvyys . . . . . . . . . . . . . 1506.3.2 Periytyminen ja rakentajat . . . . . . . . . . . . 1526.3.3 Periytyminen ja purkajat . . . . . . . . . . . . . 1546.3.4 Aliluokan olion ja kantaluokan suhde . . . . . . 155

6.4 C++: Periytymisen kaytto laajentamiseen . . . . . . . . . 1566.5 C++: Virtuaalifunktiot ja dynaaminen sitominen . . . . . 159

6.5.1 Virtuaalifunktiot . . . . . . . . . . . . . . . . . . 1596.5.2 Dynaaminen sitominen . . . . . . . . . . . . . . 1606.5.3 Olion tyypin ajoaikainen tarkastaminen . . . . 1646.5.4 Ei-virtuaalifunktiot ja peittaminen . . . . . . . . 1676.5.5 Virtuaalipurkajat . . . . . . . . . . . . . . . . . . 168

Page 6: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

Sisalto 6

6.5.6 Virtuaalifunktioiden hinta . . . . . . . . . . . . 1696.5.7 Virtuaalifunktiot rakentajissa ja purkajissa . . . 170

6.6 Abstraktit kantaluokat . . . . . . . . . . . . . . . . . . . 1726.7 Moniperiytyminen . . . . . . . . . . . . . . . . . . . . . 175

6.7.1 Moniperiytymisen idea . . . . . . . . . . . . . . 1756.7.2 Moniperiytyminen eri oliokielissa . . . . . . . . 1766.7.3 Moniperiytymisen kayttokohteita . . . . . . . . 1776.7.4 Moniperiytymisen vaaroja . . . . . . . . . . . . 1786.7.5 Vaihtoehtoja moniperiytymiselle . . . . . . . . . 179

6.8 C++: Moniperiytyminen . . . . . . . . . . . . . . . . . . 1806.8.1 Moniperiytyminen ja moniselitteisyys . . . . . 1816.8.2 Toistuva moniperiytyminen . . . . . . . . . . . 186

6.9 Periytyminen ja rajapintaluokat . . . . . . . . . . . . . 1906.9.1 Rajapintaluokkien kaytto . . . . . . . . . . . . . 1916.9.2 C++: Rajapintaluokat ja moniperiytyminen . . . . 1936.9.3 Ongelmia rajapintaluokkien kaytossa . . . . . . 195

6.10 Periytyminen vai kooste? . . . . . . . . . . . . . . . . . 1986.11 Sovelluskehykset . . . . . . . . . . . . . . . . . . . . . 199

7 Lisaa olioiden elinkaaresta . . . . . . . . . . . . . . . . . . 2017.1 Olioiden kopiointi . . . . . . . . . . . . . . . . . . . . . 202

7.1.1 Erilaiset kopiointitavat . . . . . . . . . . . . . . 2037.1.2 C++: Kopiorakentaja . . . . . . . . . . . . . . . . . 2067.1.3 Kopiointi ja viipaloituminen . . . . . . . . . . . 210

7.2 Olioiden sijoittaminen . . . . . . . . . . . . . . . . . . 2157.2.1 Sijoituksen ja kopioinnin erot . . . . . . . . . . 2157.2.2 C++: Sijoitusoperaattori . . . . . . . . . . . . . . . 2167.2.3 Sijoitus ja viipaloituminen . . . . . . . . . . . . 221

7.3 Oliot arvoparametreina ja paluuarvoina . . . . . . . . . 2247.4 Tyyppimuunnokset . . . . . . . . . . . . . . . . . . . . 227

7.4.1 C++:n tyyppimuunnosoperaattorit . . . . . . . . 2287.4.2 Ohjelmoijan maarittelemat tyyppimuunnokset 232

7.5 Rakentajat ja struct . . . . . . . . . . . . . . . . . . . . 237

8 Lisaa rajapinnoista . . . . . . . . . . . . . . . . . . . . . . . 2408.1 Sopimus rajapinnasta . . . . . . . . . . . . . . . . . . . 240

8.1.1 Palveluiden esi- ja jalkiehdot . . . . . . . . . . . 2418.1.2 Luokkainvariantti . . . . . . . . . . . . . . . . . 2438.1.3 Sopimussuunnittelun kaytto . . . . . . . . . . . 244

Page 7: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

Sisalto 7

8.1.4 C++: luokan sopimusten tarkastus . . . . . . . . . 2448.2 Luokan rajapinta ja olion rajapinta . . . . . . . . . . . . 248

8.2.1 Metaluokat ja luokkaoliot . . . . . . . . . . . . . 2498.2.2 C++: Luokkamuuttujat . . . . . . . . . . . . . . . 2508.2.3 C++: Luokkafunktiot . . . . . . . . . . . . . . . . 252

8.3 Tyypit osana rajapintaa . . . . . . . . . . . . . . . . . . 2538.4 Ohjelmakomponentin sisaiset rajapinnat . . . . . . . . 258

8.4.1 C++: Ystavafunktiot . . . . . . . . . . . . . . . . . 2598.4.2 C++: Ystavaluokat . . . . . . . . . . . . . . . . . . 260

9 Geneerisyys . . . . . . . . . . . . . . . . . . . . . . . . . . . 2639.1 Yleiskayttoisyys, pysyvyys ja vaihtelevuus . . . . . . . 2649.2 Suunnittelun geneerisyys: suunnittelumallit . . . . . . 267

9.2.1 Suunnittelumallien edut ja haitat . . . . . . . . 2679.2.2 Suunnittelumallin rakenne . . . . . . . . . . . . 268

9.3 Valikoituja esimerkkeja suunnittelumalleista . . . . . . 2719.3.1 Kokoelma (Composite) . . . . . . . . . . . . . . . 2719.3.2 Iteraattori (Iterator) . . . . . . . . . . . . . . . . 2739.3.3 Silta (Bridge) . . . . . . . . . . . . . . . . . . . . 2749.3.4 C++: Esimerkki suunnittelumallin toteutuksesta 276

9.4 Geneerisyys ja periytymisen rajoitukset . . . . . . . . . 2779.5 C++: Toteutuksen geneerisyys: mallit (template) . . . . . 283

9.5.1 Mallit ja tyyppiparametrit . . . . . . . . . . . . 2849.5.2 Funktiomallit . . . . . . . . . . . . . . . . . . . . 2869.5.3 Luokkamallit . . . . . . . . . . . . . . . . . . . . 2879.5.4 Tyyppiparametreille asetetut vaatimukset . . . 2909.5.5 Erilaiset mallien parametrit . . . . . . . . . . . . 2929.5.6 Mallien erikoistus . . . . . . . . . . . . . . . . . 2959.5.7 Mallien ongelmia ja ratkaisuja . . . . . . . . . . 297

10 Geneerinen ohjelmointi: STL ja metaohjelmointi . . . . . . 30210.1 STL:n perusperiaatteita . . . . . . . . . . . . . . . . . . 303

10.1.1 STL:n rakenne . . . . . . . . . . . . . . . . . . . 30410.1.2 Algoritmien geneerisyys . . . . . . . . . . . . . 30510.1.3 Tietorakenteiden jaotteluperusteet . . . . . . . . 30610.1.4 Tehokkuuskategoriat . . . . . . . . . . . . . . . 308

10.2 STL:n sailiot . . . . . . . . . . . . . . . . . . . . . . . . 31010.2.1 Sarjat (“perakkaissailiot”) . . . . . . . . . . . . . 31210.2.2 Assosiatiiviset sailiot . . . . . . . . . . . . . . . 317

Page 8: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

Sisalto 8

10.2.3 Muita sailioita . . . . . . . . . . . . . . . . . . . 32310.3 Iteraattorit . . . . . . . . . . . . . . . . . . . . . . . . . 326

10.3.1 Iteraattoreiden kayttokohteet . . . . . . . . . . . 32710.3.2 Iteraattorikategoriat . . . . . . . . . . . . . . . . 32910.3.3 Iteraattorit ja sailiot . . . . . . . . . . . . . . . . 33210.3.4 Iteraattoreiden kelvollisuus . . . . . . . . . . . . 33410.3.5 Iteraattorisovittimet . . . . . . . . . . . . . . . . 336

10.4 STL:n algoritmit . . . . . . . . . . . . . . . . . . . . . . 33710.5 Funktio-oliot . . . . . . . . . . . . . . . . . . . . . . . . 340

10.5.1 Toiminnallisuuden valittaminen algoritmille . . 34110.5.2 Funktio-olioiden periaate . . . . . . . . . . . . . 34310.5.3 STL:n valmiit funktio-oliot . . . . . . . . . . . . 347

10.6 C++: Template-metaohjelmointi . . . . . . . . . . . . . . 34810.6.1 Metaohjelmoinnin kasite . . . . . . . . . . . . . 34910.6.2 Metaohjelmointi ja geneerisyys . . . . . . . . . 35110.6.3 Metafunktiot . . . . . . . . . . . . . . . . . . . . 35210.6.4 Esimerkki metafunktioista: numeric_limits . . 35710.6.5 Esimerkki metaohjelmoinnista: tyypin valinta . 36010.6.6 Esimerkki metaohjelmoinnista: optimointi . . . 362

11 Virhetilanteet ja poikkeukset . . . . . . . . . . . . . . . . . 36611.1 Mika virhe on? . . . . . . . . . . . . . . . . . . . . . . . 36711.2 Mita tehda virhetilanteessa? . . . . . . . . . . . . . . . 36911.3 Virhehierarkiat . . . . . . . . . . . . . . . . . . . . . . . 37111.4 Poikkeusten heittaminen ja sieppaaminen . . . . . . . 374

11.4.1 Poikkeushierarkian hyvaksikaytto . . . . . . . . 37411.4.2 Poikkeukset, joita ei oteta kiinni . . . . . . . . . 37711.4.3 Sisakkaiset valvontalohkot . . . . . . . . . . . . 378

11.5 Poikkeukset ja olioiden tuhoaminen . . . . . . . . . . . 38011.5.1 Poikkeukset ja purkajat . . . . . . . . . . . . . . 38011.5.2 Poikkeukset ja dynaamisesti luodut oliot . . . . 380

11.6 Poikkeusmaareet . . . . . . . . . . . . . . . . . . . . . . 38211.7 Muistivuotojen valttaminen: auto_ptr . . . . . . . . . . 383

11.7.1 Automaattiosoittimet ja muistinhallinta . . . . 38411.7.2 Automaattiosoittimien sijoitus ja kopiointi . . . 38511.7.3 Automaattiosoittimien kayton rajoitukset . . . . 387

11.8 Olio-ohjelmointi ja poikkeusturvallisuus . . . . . . . . 38811.8.1 Poikkeustakuut . . . . . . . . . . . . . . . . . . . 38911.8.2 Poikkeukset ja rakentajat . . . . . . . . . . . . . 393

Page 9: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

Sisalto 9

11.8.3 Poikkeukset ja purkajat . . . . . . . . . . . . . . 39711.9 Esimerkki: poikkeusturvallinen sijoitus . . . . . . . . . 399

11.9.1 Ensimmainen versio . . . . . . . . . . . . . . . . 39911.9.2 Tavoitteena vahva takuu . . . . . . . . . . . . . 40111.9.3 Lisataan epasuoruutta . . . . . . . . . . . . . . . 40311.9.4 Tilan eriyttaminen (“pimpl”-idiomi) . . . . . . . 40511.9.5 Tilan vaihtaminen paikseen . . . . . . . . . . . 407

Liite A. C++: Ei-olio-ominaisuuksia . . . . . . . . . . . . . . . . 410A.1 Viitteet . . . . . . . . . . . . . . . . . . . . . . . . . . . 410A.2 inline . . . . . . . . . . . . . . . . . . . . . . . . . . . . 413A.3 vector . . . . . . . . . . . . . . . . . . . . . . . . . . . . 414A.4 string . . . . . . . . . . . . . . . . . . . . . . . . . . . . 416

Liite B. C++-tyyliopas . . . . . . . . . . . . . . . . . . . . . . . . . 420

Kirjallisuutta . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 437

Englanninkieliset termit . . . . . . . . . . . . . . . . . . . . . . 444

Page 10: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

LISTAUKSET 10

Listaukset

1.1 Paivaysmoduulin tietorakenne ja osa rajapintaa . . . 341.2 Paivaysmoduulin kayttoesimerkki . . . . . . . . . . . 351.3 Paivaysolioiden kaytto . . . . . . . . . . . . . . . . . . 361.4 Paivaysmoduulin esittely C-kielella, paivays.h . . . . 391.5 Nimiavaruudella toteutettu rajapinta . . . . . . . . . . 431.6 Rajapintafunktioiden toteutus erillisessa tiedostossa . 431.7 Nimiavaruuden rakenteiden kayttaminen . . . . . . . 441.8 C-kirjastofunktiot C++:n nimiavaruudessa . . . . . . . . 461.9 Nimiavaruuden synonyymi (aliasointi) . . . . . . . . 471.10 using-lauseen kaytto . . . . . . . . . . . . . . . . . . . 481.11 using-lauseen kaytto eri otsikkotiedostojen kanssa . . 491.12 Nimeamaton nimiavaruus . . . . . . . . . . . . . . . . 501.13 Moduulituki Modula-3-kielessa . . . . . . . . . . . . . 521.14 Moduulituki Java-kielessa . . . . . . . . . . . . . . . . 52

2.1 Esimerkki luokan esittelysta, pienipaivays.hh . . . . 622.2 Jasenfunktioiden toteutus, pienipaivays.cc . . . . . . 652.3 Esimerkki luokan kaytosta, ppkaytto.cc . . . . . . . . 68

3.1 Esimerkki olion elinkaaresta Modula-3:lla . . . . . . . 733.2 Esimerkki olion elinkaaresta Smalltalkilla . . . . . . . 753.3 Esimerkki olion elinkaaresta Javalla . . . . . . . . . . 763.4 Esimerkki olion elinkaaresta C++:lla . . . . . . . . . . . 783.5 Paivays-luokan rakentaja . . . . . . . . . . . . . . . . 803.6 Esimerkki rakentajasta olion ollessa jasenmuuttujana 823.7 Paivays-luokan purkaja . . . . . . . . . . . . . . . . . 843.8 Esimerkki olion dynaamisesta luomisesta new’lla . . . 883.9 Taulukko, joka omistaa sisaltamansa kokonaisluvut . 92

Page 11: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

LISTAUKSET 11

3.10 Taulukko, joka ei omista sisaltamiaan kokonaislukuja 93

4.1 Jasenfunktio, joka palauttaa viitteen jasenmuuttujaan 1044.2 Paasy toisen saman luokan olion private-osaan . . . 1054.3 Paivaysluokka const-sanoineen . . . . . . . . . . . . . 1074.4 Esimerkki virheesta, kun const-sana unohtuu . . . . . 1114.5 Esimerkki ennakkoesittelysta . . . . . . . . . . . . . . 1134.6 Ennakkoesittely kapseloinnissa . . . . . . . . . . . . . 114

5.1 Lainausjarjestelman esittely, lainausjarjestelma.hh . 134

6.1 Periytymisen syntaksi C++:lla . . . . . . . . . . . . . . . 1506.2 Periytyminen ja rakentajat . . . . . . . . . . . . . . . . 1546.3 Kirjan tiedot muistava luokka . . . . . . . . . . . . . . 1576.4 Kirjaston kirjan palvelut tarjoava aliluokka . . . . . . 1586.5 Luokan Kirja virtuaalifunktiot . . . . . . . . . . . . . 1616.6 Luokan KirjastonKirja virtuaalifunktiot . . . . . . . 1626.7 Dynaaminen sitominen C++:ssa . . . . . . . . . . . . . 1636.8 Olion tyypin ajoaikainen tarkastaminen . . . . . . . . 1656.9 Esimerkki typeid-operaattorin kaytosta . . . . . . . . 1676.10 Abstrakteja kantaluokkia ja puhtaita virtuaalifunktioita 1736.11 Puhdas virtuaalifunktio, jolla on myos toteutus . . . . 1746.12 Moniselitteisyyden yksi valttamistapa . . . . . . . . . 1826.13 Jasenfunktiokutsun moniselitteisyyden eliminointi . 1846.14 Moniselitteisyyden eliminointi valiluokilla . . . . . . 1856.15 Erilliset rajapinnat Javassa . . . . . . . . . . . . . . . . 1926.16 Rajapintaluokkien toteutus moniperiytymisella C++:ssa 1936.17 Rajapintaluokan purkaja esittelyyn upotettuna . . . . 195

7.1 Esimerkki kopiorakentajasta . . . . . . . . . . . . . . . 2077.2 Kopiorakentaja aliluokassa . . . . . . . . . . . . . . . 2087.3 Viipaloitumisen kiertaminen kloonaa-jasenfunktiolla 2147.4 Esimerkki sijoitusoperaattorista . . . . . . . . . . . . . 2177.5 Sijoitusoperaattori periytetyssa luokassa . . . . . . . . 2207.6 Viipaloitumismahdollisuudet sijoituksessa . . . . . . 2227.7 Viipaloitumisen estaminen ajoaikaisella tarkastuksella 2237.8 Olio arvoparametrina ja paluuarvona . . . . . . . . . 2257.9 Esimerkki const_cast-muunnoksesta . . . . . . . . . 2307.10 Tiedon esitystavan muuttaminen ja reinterpret_cast 233

Page 12: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

LISTAUKSET 12

7.11 Esimerkki rakentajasta tyyppimuunnoksena . . . . . 2347.12 Esimerkki muunnosjasenfunktiosta . . . . . . . . . . 2377.13 struct-tietorakenne, jossa on olioita . . . . . . . . . . 2377.14 struct, jolla on rakentaja . . . . . . . . . . . . . . . . 239

8.1 Varmistusrutiinin toteutus . . . . . . . . . . . . . . . . 2478.2 Esimerkki luokkainvariantin toteutuksesta jasenfunk-

tiona . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2488.3 Esimerkki luokkamuuttujien ja -funktioiden kaytosta 2548.4 Tyypillinen paivaysluokan esittely . . . . . . . . . . . 2558.5 Parannettu paivaysluokan esittely . . . . . . . . . . . 2578.6 Luokan maarittelema tyyppi paluutyyppina . . . . . . 2588.7 Laajemman rajapinnan salliminen ystavafunktioille . 2618.8 Ystavaluokat . . . . . . . . . . . . . . . . . . . . . . . 262

9.1 Virhetiedoterajapinnan esittely ja toteutus . . . . . . 2789.2 Toteutusten kantaluokka, virheikkunatoteutus.hh . . 2799.3 Toteutus virheikkunoista, virheikkunaversiot.hh . . 2799.4 Eri virheikkunatoteutusten valinta . . . . . . . . . . . 2799.5 Parametreista pienemman palauttava funktiomalli . . 2869.6 Tietotyypin “pari” maaritteleva luokkamalli . . . . . . 2889.7 Esimerkki luokkamallin Pari jasenfunktioista . . . . . 2899.8 Luokkamallin sisalla oleva jasenfunktiomalli . . . . . 2909.9 Parempi versio listauksen 9.5 funktiomallista . . . . . 2929.10 Mallin oletusparametrit . . . . . . . . . . . . . . . . . 2939.11 Malli, jolla on vakioparametri . . . . . . . . . . . . . . 2949.12 Mallin malliparametri . . . . . . . . . . . . . . . . . . 2959.13 Luokkamallin Pari erikoistus totuusarvoille . . . . . . 2969.14 Funktiomallin min erikoistus paivayksille . . . . . . . 2979.15 Luokkamallin Pari osittaiserikoistus . . . . . . . . . . 2979.16 Avainsanan export kaytto . . . . . . . . . . . . . . . . 2999.17 Tyypin maaraaminen avainsanalla typename . . . . . 300

10.1 Fibonaccin luvut vectorilla . . . . . . . . . . . . . . . 31410.2 Puskurin toteutus dequella . . . . . . . . . . . . . . . . 31610.3 Nimien rekisterointi setilla . . . . . . . . . . . . . . . 31910.4 Tehokkaampi versio listauksesta 10.3 . . . . . . . . . 32010.5 Nimirekisterointi multisetilla . . . . . . . . . . . . . . 32010.6 Nimirekisterointi mapilla . . . . . . . . . . . . . . . . . 321

Page 13: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

LISTAUKSET 13

10.7 multimapilla toteutettu puhelinluetteloluokka . . . . . 32310.8 Puhelinluettelon toteutus . . . . . . . . . . . . . . . . 32410.9 Sailion lapikayminen iteraattoreilla . . . . . . . . . . 33310.10 Esimerkki STL:n algoritmien kaytosta . . . . . . . . . 34010.11 Funktio-osoittimen valittaminen parametrina . . . . . 34210.12 Esimerkki funktio-olioluokasta . . . . . . . . . . . . . 34410.13 Funktio-olion kayttoesimerkkeja . . . . . . . . . . . . 34510.14 Funktio-olion kaytto STL:ssa . . . . . . . . . . . . . . 34610.15 C++:n funktio-olioiden less ja bind2nd kaytto . . . . . 34910.16 Esimerkki yksinkertaisesta metaohjelmoinnista . . . . 35310.17 Yksinkertainen trait-metafunktio . . . . . . . . . . . . 35410.18 Erikoistamalla tehty template-metafunktio . . . . . . 35510.19 Osittaiserikoistuksella tehty metafunktio . . . . . . . 35610.20 Esimerkki numeric_limits-metafunktion kaytosta . . 35910.21 Metafunktion IF toteutus . . . . . . . . . . . . . . . . 36110.22 Metafunktion IF kayttoesimerkki . . . . . . . . . . . . 36110.23 Esimerkki metaohjelmoinnista optimoinnissa . . . . . 364

11.1 Virhetyypit C++:n luokkina . . . . . . . . . . . . . . . . 37311.2 Esimerkki omasta virheluokasta . . . . . . . . . . . . 37411.3 Esimerkki C++:n poikkeuskasittelijasta . . . . . . . . . 37611.4 Virhekategorioiden kaytto poikkeuksissa . . . . . . . 37711.5 Sisakkaiset valvontalohkot . . . . . . . . . . . . . . . 37911.6 Esimerkki dynaamisen olion siivoamisesta . . . . . . 38111.7 Virheisiin varautuminen ja monta dynaamista oliota . 38211.8 Esimerkki automaattiosoittimen auto_ptr kaytosta . . 38511.9 Automaattiosoitin ja omistuksen siirto . . . . . . . . . 38611.10 Esimerkki luokasta, jossa on useita osaolioita . . . . . 39411.11 Olioiden dynaaminen luominen rakentajassa . . . . . 39511.12 Funktion valvontalohko rakentajassa . . . . . . . . . . 39711.13 Yksinkertainen luokka, jolla on sijoitusoperaattori . . 39911.14 Sijoitus, joka pyrkii tarjoamaan vahvan takuun (ei toi-

mi) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40211.15 Kirjaluokka epasuoruuksilla . . . . . . . . . . . . . . . 40311.16 Uuden kirjaluokan rakentaja, purkaja ja sijoitus . . . 40411.17 Kirja eriytetylla tilalla ja automaattiosoittimella . . . 40611.18 Eriytetyn tilan rakentajat, purkaja ja sijoitus . . . . . . 40611.19 Kirja, jossa on nothrow-vaihto . . . . . . . . . . . . . 40811.20 Yhdistelma tilan eriyttamisesta ja vaihdosta . . . . . 409

Page 14: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

LISTAUKSET 14

A.1 Osamerkkijonon “ja” etsinta (C++) . . . . . . . . . . . . 417A.2 Osamerkkijonon “ja” etsinta (C) . . . . . . . . . . . . . 418

Page 15: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

KUVAT 15

Kuvat

1.1 Tyypillinen assembly-ohjelman spagettirakenne . . . 301.2 Paivaykset tietorakenteina . . . . . . . . . . . . . . . . 311.3 Paivayksia kasittelevan moduulin rakenne . . . . . . 321.4 Rajapinta osana tietorakennetta (oliot) . . . . . . . . . 351.5 Sama otsikkotiedosto voi tulla kayttoon useita kertoja 401.6 Moduulirakenne C-kielella . . . . . . . . . . . . . . . . 41

2.1 Ongelmasta ohjelmaksi olioilla . . . . . . . . . . . . . 552.2 Paivays-luokka ja siita tehdyt oliot A ja B . . . . . . . 59

4.1 Vaarin tehty luokkien keskinainen esittely (ei toimi) . 1124.2 Oikein tehty luokkien keskinainen esittely . . . . . . 115

5.1 Erilaisia yhteysvaihtoehtoja kuuden moduulin valilla 1195.2 Moduulien valisia yksisuuntaisia riippuvuuksia . . . 1205.3 Esimerkki kayttotapauksesta . . . . . . . . . . . . . . 1245.4 Kuva keskeneraisen CRC-korttipelin yhdesta kortista . 1255.5 Luokka, olio ja rajapinta . . . . . . . . . . . . . . . . . 1295.6 Tarkennetun suunnitelman luokka ja nakyvyysmaareita 1305.7 UML:n yhteystyyppeja . . . . . . . . . . . . . . . . . . 1315.8 Luokka “Heippa” kayttaa Javan grafiikkaolioita . . . . 1325.9 UML-assosiaatioiden lukumaaramerkintoja . . . . . . 1325.10 Esimerkki luokkien valisista assosiaatioista . . . . . . 1335.11 UML:n koostesuhteita . . . . . . . . . . . . . . . . . . 1355.12 Osaston, sen laitosten ja tyontekijoiden valisia yh-

teyksia . . . . . . . . . . . . . . . . . . . . . . . . . . . 1365.13 Kirjasta periytetty luokka, joka toteuttaa kaksi rajapin-

taa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137

Page 16: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

KUVAT 16

5.14 Palautuspaivamaaran asettamisen tapahtumasekvenssi 1385.15 Kirjastokortin tiloja . . . . . . . . . . . . . . . . . . . . 139

6.1 Periytymiseen liittyvaa terminologiaa . . . . . . . . . 1446.2 Elioita mallintavan ohjelman periytymishierarkiaa . . 1456.3 Nakyvyytta kuvaava kantaluokka ja periytettyja luokkia 1486.4 Periytymishierarkia ja oliot [Koskimies, 2000] . . . . . 1496.5 Periytyminen ja nakyvyys . . . . . . . . . . . . . . . . 1516.6 Moniperiytyminen ja sen vaikutus . . . . . . . . . . . 1766.7 Toistuva moniperiytyminen . . . . . . . . . . . . . . . 1876.8 Toistuva moniperiytyminen ja olion rakenne . . . . . 1886.9 Rakentajat virtuaalisessa moniperiytymisessa . . . . . 1896.10 Luokat, jotka toteuttavat erilaisia rajapintoja . . . . . 1916.11 Ongelma yhdistettyjen rajapintojen kanssa . . . . . . 1976.12 Insinoori, viulisti ja isa . . . . . . . . . . . . . . . . . . 1996.13 Aliohjelmakirjasto ja sovelluskehys . . . . . . . . . . 200

7.1 Viitekopiointi . . . . . . . . . . . . . . . . . . . . . . . 2037.2 Matalakopiointi . . . . . . . . . . . . . . . . . . . . . . 2047.3 Syvakopiointi . . . . . . . . . . . . . . . . . . . . . . . 2057.4 Viipaloituminen olion kopioinnissa . . . . . . . . . . 2117.5 Viipaloituminen olioiden sijoituksessa . . . . . . . . . 2237.6 Oliot arvoparametreina ja paluuarvoina . . . . . . . . 226

8.1 Luokka- ja metaluokkahierarkiaa Smalltalkissa . . . . . 250

9.1 Pysyvyys- ja vaihtelevuusanalyysi ja yleiskayttoisyys 2669.2 Kokoelman toteuttava luokka . . . . . . . . . . . . . . 2699.3 Taulukko joka sisaltaa kokoelmia . . . . . . . . . . . . 2709.4 Suunnittelumallin UML-symboli ja sen kaytto . . . . 2719.5 Suunnittelumalli Kokoelma . . . . . . . . . . . . . . . 2729.6 Suunnittelumalli Iteraattori . . . . . . . . . . . . . . . 2749.7 Suunnittelumalli Silta . . . . . . . . . . . . . . . . . . 2769.8 Yleiskayttoisen taulukon toteutus periyttamalla . . . 281

10.1 Tietorakenteiden herattamia mielikuvia . . . . . . . . 30610.2 Erilaisia tehokkuuskategorioita . . . . . . . . . . . . . 31010.3 Sarjojen operaatioiden tehokkuuksia . . . . . . . . . . 31310.4 Iteraattorit ja sailiot . . . . . . . . . . . . . . . . . . . . 32810.5 Iteraattorikategoriat . . . . . . . . . . . . . . . . . . . . 330

Page 17: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

KUVAT 17

10.6 Funktio-olioiden idea . . . . . . . . . . . . . . . . . . 34510.7 Funktio-olio bind2nd(less<int>(), 3) . . . . . . . . . 34810.8 Metafunktion numeric_limits palvelut . . . . . . . . . 358

11.1 C++-standardin virhekategoriat . . . . . . . . . . . . . . 372

A.1 Esimerkkien viittaukset . . . . . . . . . . . . . . . . . 412A.2 C-taulukon ja vektorin vertailu . . . . . . . . . . . . . 416A.3 Merkkijonotaulukon ja C++:n stringin vertailu . . . . 417

Page 18: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

18

Esipuhe: Sirkkelinkayttoohje

Siirtyminen C-kielesta C++-kieleen kaynnistyi pikkuhiljaa jo 1980-lu-vun loppupuolella. Olin tavattoman innostunut tasta, koska nain senmahdollisuutena siirtaa tutkimuslaboratorioissa syntyneita oliokes-keisiin menetelmiin liittyvia asioita osaksi arkipaivaista ohjelmis-totyota. 1990-luvun alkupuoli olikin ohjelmistoteollisuudessa olio-menetelmien kayttoonoton aikaa, ja hyvin usein kayttoonotto tapah-tui juuri C++-kieleen siirtymisen kautta.

Yleinen ensivaikutelma C++-kielesta oli, etta se on oliopiirteil-la laajennettu C-kieli ja vain hieman C-kielta monimutkaisempi. Joensimmaiset kaytannon kokemukset kielesta kuitenkin osoittivat,etta tama oli nakoharhaa. Kaytannossa C++-kieleen lisattyjen olio-ominaisuuksien aiheuttamat implikaatiot ovat paljon laajempia kuinaluksi osattiin ennakoida. Tata todistavat esimerkiksi yritysten oh-jelmistokehitysta varten laatimat tyylioppaat: C-tyyliopas saattaa ollavain muutamien sivujen mittainen kokoelma saantoja, kun pisimmatC++-oppaat lahentelevat sataa sivua.

Kaytanto on osoittanut, etta C++-kieli sisaltaa ominaisuudet, joil-la olio-ohjelmoinnin lupaukset muuan muassa uudelleenkaytetta-vyyden ja yllapidettavyyden osalta pystytaan suurelta osin lunasta-maan. Toisaalta olio-ominaisuuksien taitamattomasta kaytosta saat-taa aiheutua isoja ongelmia. C++ on kuin tehokas sirkkeli ilman suoja-valineita: ammattilaisen kasissa se on tuottava ja varmatoiminen tyo-kalu, mutta noviisin kasissa vaarallinen ase. Oman kokemukseni mu-kaan tehosirkkeli-noviisi-yhdistelma ei ole taman paivan ohjelmisto-tuotannossa harvinainen yhdistelma, ja patevallakin ammattilaisella

Page 19: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

19

on viela paljon opittavaa.C++-kielta kasittelevia oppikirjoja on kasapain. Useimmat kirjat la-

hestyvat aihetta esittelemalla kielen ominaisuuksia, kertomatta mi-ten ja miksi niita kaytetaan. Tassa kirjassa keskitytaan kielen syntak-sin sijasta juuri naihin C++-sirkkelin kayttoon liittyviin miten ja mik-si -kysymyksiin. Se on antoisaa luettavaa niin noviisille kuin vahanvanhemmallekin konkarille. Harvoinpa kirjalle on olemassa nain sel-kea tilaus niin oppilaitoksissa kuin teollisuudessakin.

Tampereella 3.9.2000, prof. Ilkka Haikala

Page 20: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

20

Alkusanat neljanteen,uudistettuun painokseen

Oliokeskeisyyteen liittyvassa markkinahumussa termistaoliokeskeinen on muodostunut vahitellen termin hyvasynonyymi; lahes mika hyvansa idea tai tuote voidaannaamioida oliokeskeiseksi myyntitarkoituksissa. Oleellisenerottaminen epaoleellisesta tulee taman myota yha hanka-lammaksi.

– Ilkka Haikala [Haikala ja Marijarvi, 2002]

Object-oriented programming is an exceptionally bad ideawhich could only have originated in California.

– Edsger Wybe Dijkstra

Olio-ohjelmointi on nopeasti muuttunut “uudesta ja ihmeellises-ta” ohjelmointitavasta arkipaivaiseksi valtavirran ohjelmointimene-telmaksi. Samaan aikaan aiheeseen liittyvia kirjoja on julkaistu luke-mattomia maaria.

Useissa olio-ohjelmointia kasittelevissa kirjoissa on kuitenkin tyy-pillisesti yksi ongelma. Osa kirjoista on kirjoitettu hyvin yleisella ta-solla, ja ne keskittyvat oliosuunnitteluun ja olio-ohjelmoinnin teo-riaan paneutumatta siihen, miten nama asiat toteutetaan kaytannonolio-ohjelmointia tukevissa kielissa. Toiset kirjat ovat puolestaan la-hes yksinomaan jonkin oliokielen oppikirjoja, jolloin ne usein keskit-tyvat lahinna tietyn kielen syntaksin ja erikoisuuksien opettamiseen.

Page 21: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

21

Tama kirja pyrkii osumaan naiden kahden aaripaan puolivaliin.Siina pyritaan antamaan mahdollisimman monesta olio-ohjelmoin-nin aiheesta seka yleinen (“teoreettinen” olisi ehka liian mahtiponti-nen sana) etta kaytannonlaheinen kuva. Kirjan kaytannon puolessakeskitytaan etupaassa C++-kieleen, koska se on talla hetkella yleisinolio-ohjelmointia tukevista kielista. Myos joidenkin muiden oliokiel-ten ominaisuuksia kaydaan kuitenkin lapi silloin, kun ne oleellisestieroavat C++:n oliomallista.

Kirjan paamaaran johdosta tama teos ei ole ohjelmoinnin alkeis-opas. Se edellyttaa lukijaltaan “perinteisen” ei-olio-ohjelmoinnin pe-rustaitoja seka C- tai C++-kielen alkeiden tuntemista. Olio-ohjelmoin-nista lukijan ei kuitenkaan tarvitse tietaa mitaan ennen tama kirjanlukemista.

Tama teos ei myoskaan pyri olemaan itse C++-kielen oppikirja. Seesittelee suurimman osan C++:n olio-ohjelmointiin liittyvista piirteistaja kasittelee niiden kayttoa olio-ohjelmoinnissa, mutta naiden piirtei-den kaikkiin yksityiskohtiin ei ole mahdollista paneutua kirjan puit-teissa. Taman vuoksi C++:aa opettelevan kannattaa hankkia taman kir-jan rinnalle kasikirjaksi jokin C++-kielen oppikirja, josta voi etsia vas-tauksia itse kielen omituisuuksiin liittyviin kysymyksiin. Kirjaksi kel-paa esim. “The C++ Programming Language” [Stroustrup, 1997] (myossuomennettuna [Stroustrup, 2000]) tai jokin muu monista muistavaihtoehdoista.

Kirjan materiaali on saanut alkunsa tekijoiden Tampereen teknil-lisella korkeakoululla luennoimasta kurssista Olio-ohjelmointi sekalukuisista yrityksille pidetyista C++-kursseista. Kirjaan liittyvia uuti-sia ja lisatietoja loytyy WWW-sivulta http://www.cs.tut.fi/~oliot/kirja/. Sivun kautta voi lahettaa myos palautetta kirjan tekijoille.

Olemme kayttaneet kirjaa myos omassa opetuksessamme TTY:llaja yrityksissa. Naiden kurssien myota olemme tehneet kirjaa vartenopetuskalvosarjan, jonka toimitamme mielellamme halukkaille (yh-teystietomme loytyvat kirjan kotisivulta).

Otamme mielellamme vastaan kaikki kirjaa koskevat kommentit jakehitysehdotukset. Kumpikin kirjoittaja mielellaan syytaa toista kai-kista kirjaan viela mahdollisesti jaaneista virheista.

Page 22: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

22

Neljas, uudistettu painos

Opetus on kehittyva prosessi. Tampereen teknillisella yliopistol-la on ohjelmistotekniikan opetuksessa nykyisin kaksi kurssia olio-ohjelmoinnista (perus- ja jatkokurssi). Erityisesti jatkokurssin tarpei-den mukaisesti olemme laajentaneet edellisia painoksia mm. seu-raavissa aiheissa: C++:n nimiavaruudet, moniperiytyminen, viipa-loituminen kopioinnissa ja sijoituksessa, geneerisyys ja template-metaohjelmointi seka poikkeusturvallisuus. Edelleen painotus onyleisissa olio-ohjelmoinnin periaatteissa ja niiden soveltamisessa C++-ohjelmointikielella.

Edella mainitut laajemmat lisaykset tehtiin kirjan vuoden 2003kolmanteen, uudistettuun painokseen. Nyt vuonna 2005 julkaistiinkirjan neljas painos. Siina on kolmanteen painokseen verrattuna teh-ty joitakin korjauksia, lisayksia, paivityksia ja tyylillisia parannuksia.

Kirjan rakenne

Tama kirja on tarkoitettu luettavaksi jarjestyksessa alusta loppuun, jase esittelee olio-ohjelmoinnin ominaisuuksia sellaisessa jarjestykses-sa, joka on ainakin tekijoiden kursseilla tuntunut toimivalta. Kirjanlukujen rakenne on seuraava:

1. Kohti olioita. Luku toimii johdantona olioajatteluun. Siina kay-daan lapi ongelmia, jotka ilmenevat suurten ohjelmistojen teke-misessa, seka puhutaan modulaarisuudesta olio-ohjelmoinnin“esi-isana”. Modulaarisuuden yhteydessa esitellaan myos C++:nnimiavaruudet (namespace).

2. Luokat ja oliot. Tama luku kay lapi olio-ohjelmoinnin tarkeim-mat kasitteet, luokat ja oliot. Lisaksi luvussa opetetaan C++:noliomallin perusteet.

3. Olioiden elinkaari. Tassa luvussa kerrotaan olioiden luomiseenja tuhoamiseen liittyvista ongelmista ja esitellaan, miten namaongelmat on ratkaistu eri oliokielissa.

4. Olioiden rajapinnat. Luvussa kasitellaan rajapintojen suunnit-telun tarkeimpia periaatteita ja kerrotaan, millaisia erilaisia ra-japintoja olio voi C++:ssa tarjota kayttajilleen.

Page 23: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

23

5. Oliosuunnittelu. Luku kay lapi tarkeimpia oliosuunnittelunmenetelmia, muiden muassa UML:aa.

6. Periytyminen. Periytyminen on ehka tarkeimpia olio-ohjelmoin-nin tuomia uusia asioita. Tama luku kasittelee periytymisenteoriaa yleisesti seka sen toteutusta C++-kielessa. Tassa uudiste-tussa painoksessa luku kasittelee myos moniperiytymista ja sensuhdetta rajapintaluokkiin.

7. Lisaa olioiden elinkaaresta. Tassa luvussa kaydaan lapi lisaaolioiden elinkaareen liittyvia asioita, mm. kopioiminen, sijoitta-minen, viipaloituminen (slicing) ja tyyppimuunnokset.

8. Lisaa rajapinnoista. Vastaavasti tama luku sisaltaa lisatietoarajapintoihin liittyvista aiheista, kuten sopimussuunnittelusta,luokkatason rajapinnasta, rajapintatyypeista seka komponentinsisaisista rajapinnoista.

9. Geneerisyys. Luvussa kasitellaan yleiskayttoisyyden ja uudel-leenkaytettavyyden periaatteita. Suunnittelun geneerisyydestaesitellaan suunnittelumallit ja C++:n toteutuksen geneerisyydes-ta mallit (template). Lisaksi luvussa pohditaan periytymisen jageneerisyyden suhdetta.

10. Geneerinen ohjelmointi — STL ja metaohjelmointi. Tama lukuesittelee C++:n STL-kirjaston esimerkkina geneerisesta ohjelma-kirjastosta. Lisaksi luku antaa katsauksen template-metaohjel-mointiin esimerkkina mukautuvista geneerisista mekanismeis-ta.

11. Virhetilanteet ja poikkeukset. Viimeisessa luvussa kaydaanviela lapi poikkeukset (C++:n tapa virhetilanteiden kasittelyyn)siina laajuudessa, kuin ne liittyvat olio-ohjelmointiin. Luku ka-sittelee myos olioiden poikkeusturvallisuutta ja sen toteuttamis-ta C++:lla.

Lisaksi liitteessa A esitellaan lyhyesti sellaisia C++:n ei-olio-omi-naisuuksia, joita ei ole ollut C-kielessa, mutta joita kaytetaan tamankirjan esimerkeissa. Liitteessa A kaydaan lapi

• viitetyypit

Page 24: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

24

• inline-sanan kaytto tehokkuusoptimoinnissa

• vector-taulukot C:n taulukkojen korvaajina

• string-merkkijonot C:n char*-merkkijonojen korvaajina.

Liitteen tarkoituksena on etta lukija, joka ei naita ominaisuuksia tun-ne, saa niista liitteen avulla sellaisen kasityksen, etta kirjan koodie-simerkkien lukeminen onnistuu vaivatta. Kyseisia ominaisuuksia eikuitenkaan opeteta liitteessa perinpohjaisesti.

Liite B sisaltaa C++-tyylioppaan. Siihen on keratty tekijoiden mie-lesta tarkeita ohjelmointityyliin ja C++:n sudenkuoppien valttamiseenliittyvia ohjeita selityksineen. Tama tyyliopas on kaytossa mm. Tam-pereen teknillisen yliopiston Ohjelmistotekniikan laitoksella opetuk-sessa ja ohjelmointiprojekteissa.

Kirjan lopussa on myos kirjallisuusluettelo seka aakkosellinenluettelo englanninkielisista olio-ohjelmoinnin termeista ja niidensuomenkielisista vastineista tassa teoksessa (itse tekstissa jokaises-ta uudesta termista on pyritty kertomaan seka suomenkielinen ettaenglanninkielinen sana). Lisaksi kirjan loppuun on liitetty normaaliaakkosellinen hakemisto.

Kiitokset

Haluamme kiittaa Tampereen teknillisen yliopiston Ohjelmistotek-niikan laitoksen henkilokuntaa ja erityisesti sen professoreita ReinoKurki-Suonio ja Ilkka Haikala, jotka ovat luoneet opetuksellaan, esi-merkillaan ja olemuksellaan tyoympariston, jossa on todella ilo tyos-kennella.

Kirjan kasikirjoituksen kommentoinnista suurkiitokset professo-reille Ilkka Haikala, Hannu-Matti Jarvinen, Kai Koskimies, Reino Kur-ki-Suonio ja Markku Sakkinen seka kollegoillemme Kirsti Ala-Mutka,Joni Helin, Vespe Savikko ja Antti Virtanen.

Kiitokset kuuluvat myos olio-ohjelmointi- ja C++-kurssiemme opis-kelijoille, jotka ovat kommenteillaan ja kysymyksillaan vaikuttaneetopetuksemme ja materiaalimme sisaltoon.

Lopuksi haluamme viela kiittaa kaikkia perheenjaseniamme, su-kulaisiamme, ystaviamme ja tuttaviamme, joiden ansiosta (tai joistahuolimatta) olemme sailyneet edes jotenkin tervejarkisina kirjoitus-urakan aikana.

Page 25: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

25

Tampereella 15.4.2005

Matti Rintala Jyke Jokinen

Page 26: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

26

Luku 1

Kohti olioita

Trurl paatti lopulta vaientaa hanet kerta kaikkiaan raken-tamalla koneen, joka osaisi kirjoittaa runoja. Ensin Trurlkerasi kahdeksansataakaksikymmenta tonnia kybernetiik-kaa kasittelevaa kirjallisuutta ja kaksitoistatuhatta tonniaparasta runoutta, istahti aloilleen ja luki kaiken lapi. Ainakun hanesta alkoi tuntua, ettei han pystyisi nielaisemaanenaa ainuttakaan kaavakuvaa tai yhtaloa, han vaihtoi ru-nouteen, ja painvastoin. Jonkin ajan kuluttua hanelle alkoivaljeta, etta koneen rakentaminen olisi lastenleikkia ver-rattuna sen ohjelmoimiseen. Keskivertorunoilijan paassaolevan ohjelmanhan on kirjoittanut runoilijan oma kult-tuuri, ja taman kulttuurin puolestaan on ohjelmoinut sitaedeltanyt kulttuuri ja niin edelleen aina aikojen aamuunasti, jolloin ne tiedonsirut, jotka myohemmin osoittautuvattarkeiksi tulevaisuuden runoilijalle, pyorteilivat viela kos-moksen syovereiden alkukaaoksessa. Jotta siis voisi onnis-tuneesti ohjelmoida runokoneen, on ensin toistettava kokomaailmankaikkeuden kehitys alusta pitaen — tai ainakinmelkoinen osa siita.

– Trurlin elektrubaduuri [Lem, 1965]

1.1 Ohjelmistojen tekijoiden ongelmakentta

Tehokkaiden, luotettavien, halpojen, selkeiden, helppokayttoisten,

Page 27: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

1.2. Laajojen ohjelmistojen teon vaikeus 27

yllapidettavien, siirrettavien, pitkaikaisten — sanalla sanoen laaduk-kaiden ohjelmistojen tekijat ovat ammattilaisia, jotka joutuvat pai-vittaisessa tyossaan toimimaan taiteen ja tieteen rajamailla. Toisaal-ta ohjelmoijien on ymmarrettava alansa formaalit periaatteet (ohjel-mistojen suunnittelumenetelmista tietorakenteiden kayttoon ja oh-jelmointiin liittyvaan matematiikkaan). Toisaalta ohjelmoijat ovatmyos taiteilijoita, jotka muokkaavat olemassa olevista materiaaleis-ta ja valmiista rakennuspalikoista ohjelmiston kokonaisuuden. Ohjel-mien teon luova puoli on varsinkin alan palkkauksessa vahalle huo-miolle jatetty puoli. Silti kaikki tuntevat alalla toimivia “taikureita”,jotka hallitsevat bitteja ja niiden kokonaisuuksia muita paremmin.

1.2 Laajojen ohjelmistojen teon vaikeus

Jokainen tietokoneohjelmointia opiskellut on kirjoittanut ensimmai-sena ohjelmanaan korkeintaan muutaman kymmenen rivin ohjel-man, jonka avulla on saanut tuntuman ohjelmoinnin kaytannon puo-leen (ohjelman kirjoittaminen syntaksisesti oikein koneen ymmarta-maan muotoon, ohjelman suorittaminen tietokoneella ja mahdollises-ti jonkin nakyvan tuloksen saaminen aikaan, ohjelman toimintalogii-kan muuttaminen ja sovittaminen halutun ongelman ratkaisemisek-si jne.). Koska lyhyet ohjelmanpatkat saa useimmiten tekemaan ha-luamiaan asioita kohtuullisen lyhyella vaivannaolla (muutamasta mi-nuutista muutamaan paivaan), on yleinen harhaluulo, etta tasta voi-daan yleistaa suurempien ohjelmistojen valmistamisen olevan vainsaman prosessin toistaminen isommalle rivimaaralle ohjelmakoodia.

Kaytannossa jokaiselle ihmiselle tulee jossain vaiheessa vastaanraja tiedon hallinnassa, kun hallittava tietomaara kasvaa liian suu-reksi. Ohjelmien teossa tama raja tulee nakyviin ohjelman rivimaa-ran kasvaessa (muuttujista ei muista enaa heti kaikkien kayttotarkoi-tusta, ehto- ja silmukkalauseiden logiikka hamartyy, tietorakenteidenhallinta sekoaa, osoittimet eivat osoita oikeaan paikkaan jne.).

Ohjelmistotekniikan suurimpia ongelmia on suurten ohjelmisto-jen tekemisen vaikeus. Tama ns. ohjelmistokriisi (“software crisis”)on tavallaan tietotekniikan itsensa aiheuttama ongelma. Tietokone-laitteistojen mm. tallennus- ja prosessointitekniikan rajahdysmainenkehitys on mahdollistanut jatkuvasti aikaisempaa laajempien ja mut-kikkaampien ohjelmistojen suorittamisen, mutta naiden ohjelmisto-

Page 28: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

1.3. Suurten ohjelmistokokonaisuuksien hallinta 28

jen tekemiseen tarvittavat menetelmat ja tyokalut eivat ole kehitty-neet laheskaan yhta nopeasti. Ohjelmistotuotannon professori IlkkaHaikala on sanonut aiheesta:

“Ohjelmistokriisia ratkomaan on kehitetty mm. uusia tyo-kaluja ja tyomenetelmia. Tasta huolimatta ohjelmistotyontuottavuuden kasvu on tilastojen mukaan ollut vuosit-tain vain noin neljan prosentin luokkaa. Muutamien vuo-sien valein kaksinkertaistuvaan ohjelmistojen keskimaa-raiseen kokoon verrattuna kasvu on huolestuttavan pieni.”[Haikala ja Marijarvi, 2002]

Ongelma ei ole mikaan uusi ilmio eika ole kyse siita, etta se olisihavaittu vasta viime aikoina. Vuonna 1972 vastaanottaessaan TuringAward -palkintoa Edsger W. Dijkstra puhui aiheesta:

“As long as there were no machines, programming was noproblem at all; when we had a few weak computers, pro-gramming became a mild problem and now that we havegigantic computers, programming has become an equallygigantic problem. In this sense the electronic industry hasnot solved a single problem, it has only created them —it has created the problem of using its product.” [Dijkstra,1972]

Suurten ohjelmistokokonaisuuksien hallintaan on kehitetty useitaerilaisia menetelmia, mutta mikaan niista ei ole osoittautunut muitaselvasti paremmaksi viisasten kiveksi, joka ratkaisee kaikki ohjelmis-totyon ongelmat. Naista eri menetelmista eniten huomiota ovat viimeaikoina saaneet oliokeskeiset menetelmat, joissa nimen mukaisestikeskitytaan olioihin. Mita nama oliot sitten ovat ja miten ne vaikutta-vat ohjelmien suunnitteluun ja toteuttamiseen? Naihin kysymyksiinhaemme vastausta seuraavissa luvuissa.

1.3 Suurten ohjelmistokokonaisuuksien hallin-ta

Seitsemankymmentaluvulla ryhdyttiin kehittamaan menetelmia oh-jelmistokriisin ratkaisemiseksi — tama tyo jatkuu yha edelleen. Yksi

Page 29: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

1.3. Suurten ohjelmistokokonaisuuksien hallinta 29

varhaisimmista ratkaisuperiaatteista on tuttu kaikesta inhimillisestatoiminnasta: ongelman jakaminen yhden ihmisen hallittaviin pa-loihin ja yksinkertaistaminen abstrahoimalla. Tama periaate onnahtavissa, kun tarkastellaan ohjelmoijien tarkeimpien tyokalujen,ohjelmointikielten ja niiden periaatteiden kehitysta.

Mita abstrahointi tarkoittaa? Voimme aloittaa vaikka sivistyssana-kirjan selvityksella:

Abstraktio. Ajatustoiminta, jonka avulla jostakin kasit-teesta saadaan yleisempi kasite vahentamalla siita tietty-ja ominaisuuksia. Myos valikointi, jossa jokin ominaisuustai ominaisuusryhma erotetaan muista yhteyksistaan tar-kastelun kohteeksi.

Abstrahoida. Suorittaa abstraktio, erottaa mielessaanolennainen muusta yhteydesta.

Abstrakti. Abstrahoimalla saatu, puhtaasti ajatuksellinen,kasitteellinen. [Aikio ja Vornanen, 1992]

Kerataan siis yhteen toisiinsa liittyvia asioita ja nimitetaan nii-den kokonaisuutta jollain kuvaavalla “uudella” termilla tai kuvauk-sella. Ohjelmointi on taynna tallaisia rakenteita. Otetaan esimerkiksihyvin yleinen operaatio: tiedon tallentaminen massamuistilaitteelle.Sovellusohjelmoijan ei tarvitse tuntea kiintolevyn valmistajan kayt-tamaa laitteen ohjauksen protokollaa, koodausta, ajoitusta ja bittienjarjestysta, vaan han voi kayttaa erikseen maariteltyja korkeamman(abstraktio)tason operaatioita (esim. open, write ja close).

Seuraavissa aliluvuissa naemme, miten abstrahoinnilla saadaanselkeammaksi tietokoneohjelman rakenteen hallinta.

1.3.1 Ennen kehittyneita ohjelmointikielia: ei rakennetta

Ohjelmoinnin historian alkuhamarassa ohjelmoinnin tavan maara-si laitteisto. Prosessorien ymmartamia kaskykoodeja kirjoitettiin suo-raan joko niiden numerokoodeilla tai myohemmin symboleja naik-si koodeiksi muuntavan assembler-ohjelman avulla. Tallaisessa lait-teistonlaheisessa naperryksessa kaikista hiemankin suuremmista oh-jelmista tuli spagettiroykkioita, joissa tieto oli yksittaisia muistipaik-koja, ja tietoa kasittelevaa ohjelmakoodia oli ripoteltuna ympariin-sa. Kuvassa 1.1 seuraavalla sivulla on esimerkki ohjelmasta (koodi ja

Page 30: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

1.3. Suurten ohjelmistokokonaisuuksien hallinta 30

Data

kuukausi_2

päivä_1

vuosi_1

päivä_2kuukausi_1

Ohjelmakoodi

vuosi_2

KUVA 1.1: Tyypillinen assembly-ohjelman spagettirakenne

data), joka kasittelee kahta paivaysta: ohjelmoija on varannut kolmemuistipaikkaa yhdelle paivaykselle ja naita tietoja kasitellaan ohjel-makoodista suoraan muistipaikkojen osoitteilla. Rakennekuvasta na-kee hyvin, etta tallaisesta rakenteesta on esim. yllapitovaiheessa hy-vin vaikea selvittaa, mitka kaikki ohjelmakoodin kohdat viittaavat jo-honkin maarattyyn muistipaikkaan (esim. kuukausi 1).

1.3.2 Tietorakenteet: tiedon keraaminen yhteen

Kun ohjelmointikielten kehitys kuusikymmentaluvulla paasi laajem-massa mitassa alkamaan, ryhdyttiin tekemaan ohjelmoijien kokemus-ten pohjalta kielia, joissa tietoa pystyttiin kasittelemaan muistipaik-koja korkeammalla abstraktiotasolla — tietorakenteina.

Tietorakenteet ovat nimettyja kokonaisuuksia, jotka koostuvatmuista tietorakenteista tai ns. kielen perustyypeista. Naiden hierark-kisten rakenteiden avulla saatiin kerattya yhteen kuuluvat tiedot sa-man nimikkeen alle. Esim. paivaystieto voitaisiin haluta kasitella kol-mena kokonaislukuna, jotka kuvaavat vuotta, kuukautta ja paivaa.Naista voidaan muodostaa ohjelmointikielen tasolla yksi paivayksek-si nimetty tietorakenne. Vastaavasti ohjelman toiminnallisuutta voi-

Page 31: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

1.3. Suurten ohjelmistokokonaisuuksien hallinta 31

Datavuosi

päivä

kuukausi

Päiväys_1

vuosi

päivä

kuukausi

Päiväys_2

Proseduurit

KUVA 1.2: Paivaykset tietorakenteina

daan jakaa yhden toiminnon toteuttaviin paloihin, joita nimitetaanfunktioiksi tai proseduureiksi.

Kuvassa 1.2 on esimerkki kahdesta paivayksesta ja niiden tieto-rakenteista. Tassa mallissa paivayksia kasitellaan ohjelmakoodissaedelleen missa kohtaa tahansa aivan kuten spagettimallissakin. Tie-torakenteita ja funktioita tukevia ohjelmointikielia ovat mm. Pascal jaC.

1.3.3 Moduulit: myos ohjelmakoodi kerataan yhteen

Vuosi 2000 -ongelma oli tietotekniikassa paljon puhetta ja tyota syn-nyttanyt aihe. Siina ohjelmistoista oli tarkastettava, etta ne eivat oletakaikkien vuosilukujen olevan muotoa 19xy. Naiden kohtien loytami-nen on edellisten kaytantojen mukaan tehdyissa ohjelmistoissa hy-vin vaikeata, silla mika tahansa ohjelmiston osa voi suoraan kasitellapaivayksen sita kokonaislukua, joka kuvaa vuosilukua.

Jotta tallaiset toiminnalliset viittaukset tietorakenteisiin saataisiinhierarkkiseen hallintaan, paatettiin maaritella joukko maarattyyn tie-torakenteeseen liittyvia funktioita ja sopia, etta vain nailla funktioil-la saa kasitella kyseista tietorakennetta (esim. paivays, katso ku-va 1.3 seuraavalla sivulla). Tata funktiokokoelmaa nimitetaan raja-pinnaksi (interface) ja sen funktioita rajapintafunktioiksi.

Page 32: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

1.3. Suurten ohjelmistokokonaisuuksien hallinta 32

Datapäivä

kuukausi

vuosi

Päiväys_1

vuosi

päivä

kuukausi

Päiväys_2

Päiväys−moduuli

Proseduurit ja moduulit

Rajapinnan toteutus

Rajapinta

KUVA 1.3: Paivayksia kasittelevan moduulin rakenne

Ohjelmointikielten rakenteita, joissa kootaan tietorakenteet ja nii-ta kasitteleva ohjelmakoodi samaan kokonaisuuteen seka erikseenmaaritellaan tietorakenteiden kasittelyyn toiminnallinen rajapinta,nimitetaan moduuleiksi (module). Koska moduulit katkevat niidenrajapinnan toiminnallisuuden toteutuksen, moduuleja tulee tarkas-tella kahdesta nakokulmasta:

• Moduulin kayttaja. Moduulin kayttaja on ohjelmoija, joka tar-vitsee moduulin tarjoamaa palvelua oman koodinsa osana. Tas-sa ulkopuolisessa nakokulmassa meita kiinnostaa vain moduu-lin ulkoinen eli julkinen rajapinta: miten sita tulee kayttaaja saammeko sen avulla toteutettua haluamamme toiminnan?(Moduulilla voi olla myos ns. sisainen rajapinta, joka on tarkoi-tettu vain moduulin tekijan kayttoon.)

• Moduulin toteuttaja. Moduulin suunnittelijan ja toteuttajanvastuulla on maaritella ja dokumentoida moduulille rajapinta,joka on yksinkertainen, helppokayttoinen, selkea ja kayttokel-poinen. Vain moduulin toteuttajan taytyy miettia ja toteuttaarajapinnan takana oleva toiminnallisuus — tama osa on muil-ta ohjelmoijilta katkettyna siina mielessa, etta heidan ei valt-

Page 33: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

1.3. Suurten ohjelmistokokonaisuuksien hallinta 33

tamatta tarvitse tuntea toteutuksen yksityiskohtia. Tata nimite-taan tiedon katkennaksi. Siina kapseloidaan kayton kannaltaturha tieto moduulin sisalle (encapsulation). Kyseessa on ab-strahoinnin tarkeimpia tyokaluja ohjelmoinnissa.

Kaytannossa moduuliajattelu voi olla vain sopimus maaratyn ra-japinnan noudattamisesta (esim. Pascal ja C), tai ohjelmointikieli voijopa kieltaa (ja tarkastaa) rajapinnan takana olevien tietorakenteidenkasittelyn moduulin ohjelmakoodin ulkopuolelta (Ada, Modula).

Rajapinta-ajattelu pakottaa suunnittelemaan tarkemmin ennalta,miten maarattya tietorakennetta on tarkoitus kayttaa eli mita pal-veluita tietorakenteen lisaksi halutaan siihen liittyen tarjota. Tie-don katkennan ja rajapinta-ajattelun haittapuolina voidaankin pitaasuunnittelun vaikeutumista. Moduulin tekijalle on helppo sanoa paa-maaraksi yksinkertainen ja kayttokelpoinen (eli taydellinen?) rajapin-ta. Naiden vaatimusten toteuttamiseen sitten tarvitaankin todellistaohjelmointitaikuria. Kaytannon ohjelmistotyossa rajapintoja joudu-taan tarkastamaan ja tarkentamaan ohjelmiston kokonaissuunnitte-lun edetessa ja jatkossa ohjelmistoa yllapidettaessa. [Sethi, 1996]

Rajapinta edistaa merkittavasti ohjelmistojen yllapidettavyytta,koska ne rajapinnan “takana” tehdyt muutokset, jotka eivat vaiku-ta rajapinnalle maariteltyyn toiminnallisuuteen, eivat aiheuta mitaanmuutoksia muuhun ohjelmistoon. Nain saadaan abstrahoitua laajanohjelmiston kokonaisuutta rajapinnoilla toisistaan eroteltuihin mah-dollisimman itsenaisiin paloihin.

Vuosi 2000 -ongelman tapauksessa modulaarisessa ohjelmistossaon ensin tarkastettava, etta rajapinnan kautta ei paasta kasittelemaanvuosilukuja vaaralla tavalla. Jos vuosisatojen 1900 ja 2000 valiseenkasittelyyn liittyvat rakenteet ovat mahdollisia vain moduulin sisalla,niin riittaa, etta tarkastamme kaiken moduulin koodin ja varmistam-me sen toimivan myos vuoden 1999 jalkeen. On tarkeata huomata,etta jos rajapinnan maarittely “paljastaa” vuosiluvun tietorakenteenulkopuolelle esimerkiksi 0–99 valille maariteltyna kokonaislukuna,niin joudumme edelleen tarkastamaan myos kaiken moduulin ulko-puolella paivayksia kasittelevan ohjelmakoodin. Tallaista rajapintaavoidaan pitaa huonosti suunniteltuna vuosi 2000 -ongelman kannal-ta. Kyse on kuitenkin jalkiviisaudesta, jos rajapinnan suunnittelunaikaan ohjelmiston arvioitu elinkaari ei ole yltanyt vuoden 1999 ylit-se. Olisiko rajapinnan maarittelijan pitanyt ottaa huomioon ongelma-

Page 34: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

1.3. Suurten ohjelmistokokonaisuuksien hallinta 34

kentan ulkopuolisia asioita? Ja mita kaikkia niista? Rajapinta-ajatteluei ole inhimillisia virheita korjaava tai estava menetelma, mutta seon ihmisille luontaista hierarkkista ajattelua tukeva menetelma, jokaselkeyttaa suurten ohjelmistojen rakennetta.

Modulaarista ohjelmointia tukevia ohjelmointikielia ovat mm.Ada ja Modula. Listauksessa 1.1 on esimerkki paivays-moduulin tie-torakenteesta ja osasta rajapintaa Modula-3-kielella.

1.3.4 Oliot: tietorakenteet, rajapinnat ja toiminnot yhdes-sa

Kun tarkastelemme paivaysrajapinnan kayttoa (listaus 1.2 seuraaval-la sivulla), niin huomaamme heti, etta rajapintafunktioille on aina va-litettava parametrina se tietorakenne, johon operaatio halutaan koh-distaa. Tasta kaytannon tarpeesta on lahtoisin ajattelumalli, jossa eihaluta korostaa pelkastaan moduulin toiminnallista osaa (rajapinta-funktiot) vaan ajatellaan rajapintaa tietorakenteen “osana”. Tamanmallin mukaisia alkioita, jotka yhdistavat tietorakenteet ja rajapin-nan, nimitetaan olioiksi (kuva 1.4 seuraavalla sivulla). Uudessa ajat-telumallissa kohdistetaan rajapintakutsu maarattyyn olioon, ja tamanakyy myos olio-ohjelmointikielen tasolla (listaus 1.3 sivulla 36).

Oliomallin mukaisen ohjelmistojen suunnittelun ja toteutuksenkatsotaan sopivan paremmin ihmisten luontaiseen tapaan tarkastel-la ongelmia. Oli toteutettavana jarjestelmana lentokoneen toiminnanvalvonta tai sakkipeli, niin jarjestelman katsotaan koostuvan osista janaiden osien toisiinsa ja ulkomaailmaan kohdistamista toiminnoista.

1 INTERFACE paivays;23 TYPE4 PVM : RECORD5 paiva, kuukausi, vuosi : INTEGER;6 END;78 PROCEDURE Luo( paiva, kuukausi, vuosi : INTEGER ) : PVM;9 PROCEDURE PaljonkoEdella( eka, toka : PVM ) : INTEGER;

10 PROCEDURE Tulosta( kohde : PVM );

LISTAUS 1.1: Paivaysmoduulin tietorakenne ja osa rajapintaa

Page 35: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

1.3. Suurten ohjelmistokokonaisuuksien hallinta 35

1 IMPORT paivays;23 VAR vappu : paivays.PVM;45 BEGIN6 vappu := paivays.Luo( 1, 5, 2001 );7 paivays.Tulosta( vappu );8 END;

LISTAUS 1.2: Paivaysmoduulin kayttoesimerkki

päivä

kuukausi

vuosi

päivä

kuukausi

vuosi

joulu

vappuProseduurit ja moduulit

Oliot

KUVA 1.4: Rajapinta osana tietorakennetta (oliot)

Tama uusi ajattelumalli vaikuttaa useisiin ohjelmistojen tekemisenosa-alueisiin:

• Maarittely. Kaytettaessa olioita on maarittelyssa pidettava mie-lessa tama paamaara (paadytaan oliorakenteeseen).

• Suunnittelu. Oliosuunnittelussa on tunnettava olioiden ja luok-kien ominaisuuksia (mm. periytyminen ja geneerisyys), jotta josuunnitteluvaiheessa pystytaan hyodyntamaan uuden ajattelu-mallin kaikki (tarvittava) teho.

Page 36: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

1.3. Suurten ohjelmistokokonaisuuksien hallinta 36

1 PROCEDURE TulostaAikaisempi( paiva A, paiva B : PVM )2 BEGIN3 IF paiva A.PaljonkoEdella( paiva B ) > 0 THEN4 paiva A.Tulosta();5 ELSE6 paiva B.Tulosta();7 END;8 END;9

10 BEGIN11 vappu := PVM( 1, 5, 2000 );12 joulu := PVM( 24, 12, 1999 );13 TulostaAikaisempi( vappu, joulu );14 END.

LISTAUS 1.3: Paivaysolioiden kaytto

• Ohjelmointi. Erityiset olio-ohjelmointikielet helpottavat olioitasisaltavan suunnitelman toteuttamista huomattavasti. Tyokalui-na nama kielet ovat aikaisempia mutkikkaampia, koska ne tuo-vat lisaa uusia laajoja ominaisuuksia aikaisempiin (proseduraa-lisiin) kieliin verrattuna.

Perinteisessa ohjelmistosuunnittelussa on otettu lahtokohdaksiosat (data, tietorakenteet) tai toiminnot (funktiot ja rajapinnat) ja py-ritty rakentamaan koko ohjelmisto tasta yhdesta jaottelusta lahtien.Olio-ohjelmoinnin yksi tarkeimmista suunnitteluperiaatteista on jat-kuvasti yrittaa pitaa naita molempia nakokantoja mukana suunnitte-luprosessissa: jarjestelmasta tunnistetaan seka olioita etta niiden va-lisia toiminnallisuuksia.

Ongelmalahtoinen ja laitteistolaheinen nakokulma olioihin

Olion voidaan katsoa olevan osa sen ongelman ratkaisua, johon oh-jelmistolla pyritaan. Tama on ulkoinen tai ongelmalahtoinen nako-kulma oliohin. Ohjelmiston suunnittelija pyrkii tunnistamaan esim.sakkipelista pelin mallintamiseen tarvittavat oliot: erilaiset nappulat,lauta, peliajan mittaava kello jne.

Olioista koostuvan ohjelmiston toiminnallisuus on olioiden valis-ta kommunikointia. Oliot kutsuvat toistensa rajapintojen kautta tar-vitsemiaan toimintoja. Esimerkiksi sakkinappulaolio voi pyytaa sak-

Page 37: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

1.3. Suurten ohjelmistokokonaisuuksien hallinta 37

kilautaa mallintavalta oliolta tietoa siita, voiko se siirtya maarattyynruutuun. Kyselyn seurauksena lautaolio voi taas vuorostaan kysyamuilta nappuloilta niiden nykyisia paikkoja jne. Ohjelmiston ylim-man tason “logiikka” voidaan nain maaritella olioiden rajapintojenja olioiden valisen kommunikaation avulla — tassa suunnitteluvai-heessa ei tarvitse ottaa kantaa siihen, miten nama toiminnot tullaanyksityiskohtaisesti toteuttamaan.

Toisaalta jokaisen olion voi ajatella edustavan erillista tietokonet-ta, jolla on oma ohjelmakoodi (rajapinnan takainen toteutus) ja muis-ti (olion tietorakenteen arvot eli olion tila). Nama “minikoneet” kom-munikoivat lahettamalla toisilleen viesteja, joilla pyydetaan jonkintoiminnon suorittamista toisessa koneessa eli oliossa. Tama ajattelu-malli on sisainen tai laitteistonlaheinen nakokulma olioihin. Mallivoi olla hyodyllinen esimerkiksi hajautetuissa jarjestelmissa, joissaosa ohjelmiston olioista tulee lopullisessa toteutuksessa todellisuu-dessakin sijaitsemaan eri fyysisissa tietokoneissa. [Sethi, 1996]

Ohjelmiston staattiset ja dynaamiset osat

Oliomallia ei ole tarkoitettu syrjayttamaan modulaarisuutta vaan tay-dentamaan sita. Moduulien avulla ohjelmistoon voidaan tehda yleen-sa ylimmilla tasoilla olevaa jakoa osiin ja niiden valisiin rajapintoi-hin: kayttoliittyman nakymat, nayttolaitteiden rajapinnat, tietokantajne. Koska tama jaottelu osiin on ohjelmassa pysyva, sita nimitetaanstaattiseksi jaotteluksi.

Dynaamiset osat tarkoittavat tietorakenteiden ja rajapintojen ko-koelmia, joissa yhden mallin mukaisia olioita voi esiintya ohjelmanajoaikana useita. Esim. paivaysoliolla on aina sama toiminnallinenrajapinta ja sama tietorakenne (kolme kokonaislukua). Eri paivayk-silla voi olla erilaiset arvot tietorakenteessa. Saman mallin (paivays)eri ilmentymilla (oliot) sanotaan olevan erilainen tila (tietorakenteenarvot). Olioiden dynaamisuutta on myos se, etta niita voi syntya jatuhoutua ohjelman suorituksen aikana — kutakin moduulia taas onolemassa yksi kappale koko ohjelmiston ajossaolon ajan.

Olio on itsenainen kokonaisuus

Oliokeskeisessa ajattelumallissa olioiden katsotaan olevan itsenaisiakokonaisuuksia, joille on maaritelty tarkka vastuualue (responsibil-

Page 38: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

1.3. Suurten ohjelmistokokonaisuuksien hallinta 38

ity) ohjelmistossa [Budd, 2002]. Vastuualue on nimitys kaikelle sille,mita olion on maaratty toteuttavan. Esim. paivaysoliolla on maaritel-tyna vastuu tiedon taltioinnista (olio kuvaa yhta paivamaaraa) ja jul-kinen rajapinta (tarjotut palvelut). Vastuualue kattaa muutakin kuinohjelmointikielen muuttujia ja funktioita. Se voi esimerkiksi maari-tella etta jokin olio on vastuussa maarattyjen muiden olioiden luomi-sesta ja tuhoamisesta eli niiden elinkaaresta.

1.3.5 Komponentit: moduulit ja oliot yhdessa

On tarkeata huomata, etta moduulit ja oliot voivat muodostaa hierar-kioita ja kokonaisuuksia yhdessa. Ohjelmistossa voidaan esim. ylim-malla tasolla maaritella ajanlaskusta vastuussa oleva moduuli, jonkatarjoamista palveluista yksi on paivayksia kuvaavat oliot.

Hyvin suunniteltu rajapintojen, olioiden ja toteutusten (samallarajapinnalla voi olla esimerkiksi vaihtoehtoisia toteutuksia) kokoel-maa on viime aikoina ryhdytty nimittamaan komponentiksi (compo-nent). Ideana olisi tarjota ohjelmistojen valmistajille hieman saman-lainen tilanne kuin talla hetkella on mm. elektroniikkasuunnittelijoil-la — he voivat suunnitella ja valmistaa tuotteitaan hyvin laajan val-miin komponenttivalikoiman avulla. Tuote luodaan yhdistelemallaelektroniikkakomponentit uusiksi mutkikkaammiksi tuotteiksi. Vas-taavalla tavalla uudelleenkayttoa varten suunniteltuja ohjelmakom-ponentteja voitaisiin pitaa uusien ohjelmistojen pohjana.

Ohjelmistokomponentti on itsenainen kokonaisuus, jota sen tar-joamien julkisten rajapintojen avulla voidaan hyodyntaa osana suu-rempaa kokonaisuutta. Komponentti keraa yhteen maaratyn vastuu-alueen toteuttamiseen tarvittavat ohjelmiston moduulit ja oliot (olio-ohjelmana toteutus on usein kokoelma luokkia). Komponenttimarkki-noita on talla hetkella olemassa erityisesti graafisten kayttoliittymienalueella, mutta vasta tulevaisuus tulee nayttamaan, onko kyseessa oh-jelmistojen tekemista suuremmassa mittakaavassa muokkaava ajatte-lumalli.

Aivan viime vuosien ohjelmistokomponentit sisaltavat usein ra-japintadokumentaation ja lahdekoodin lisaksi myos valmiin binaa-rimuotoisen toteutuksen. Talloin komponentti otetaan sellaisenaan(valmiiksi konekoodiksi kaannettyna) kayttoon omaan ohjelmistoon.Yksi tallainen komponenttiarkkitehtuuri on JavaBeans [JavaBeans,2001].

Page 39: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

1.4. C++: Moduulit kaannosyksikoilla 39

1.4 C++: Moduulit kaannosyksikoilla

Seuraavassa luvussa esiteltava C++:n luokkarakenne sisaltaa olio-omi-naisuuksien lisaksi modulaarisuuden tarkeimmat piirteet: julkisenrajapinnan ja toteutuksen katkennan. Tassa ja seuraavassa aliluvussaesitellaan kaksi tapaa tehda staattisia moduulirakenteita C++:lla.

Staattisella moduulilla tarkoitetaan kaannosaikana luotua ja ko-ko ohjelman suoritusajan samana pysyvaa kokonaisuutta, jonka myoskaantaja ymmartaa erilliseksi yksikoksi. C ja C++ -kaantajat ovat ainakasitelleet yhta lahdekooditiedostoa kerrallaan yhtena kokonaisuute-na, jonka kaannoksessa pitaa olla nakyvilla kaikkien kaytettyjen ra-kenteiden esittelyt. [Kerninghan ja Ritchie, 1988]

Ohjelmoitavan moduulin julkisen rajapinnan esittely voidaanlaittaa ns. otsikkotiedostoon (header, listaus 1.4), joka taas voidaan#include-esikaantajakomennolla ottaa nakyville kaikkiin moduuliakayttaviin tiedostoihin.

Suurissa ohjelmissa #include-rakenteet tulevat mutkikkaiksi jamonitasoisiksi. Kaantaja voi talloin virheellisesti saada kasittelyynsaman otsikkotiedoston useaan kertaan saman kaannoksen aikana,mika on virhetilanne koska kieli sallii esittelyiden esiintyvan vainkerran samassa kaannoksessa. Esimerkiksi paivaysmoduulin esitte-lya tarvitaan useassa korkeamman tason moduulin otsikkotiedostos-sa, jotka kaannosyksikko ottaa nakyville #include-kaskylla, kuten ku-va 1.5 seuraavalla sivulla nayttaa. Esimerkkilistauksen 1.4 alussa na-kyvalla esikaantajan ehdollisella kaantamisella (#ifndef X #define X

1 #ifndef PAIVAYS H2 #define PAIVAYS H34 typedef struct paivays data 5 int p , k , v ;6 paivays PVM;78 paivays PVM paivays luo( int paiva, int kuukausi, int vuosi );9 void paivays tulosta( paivays PVM kohde );

...10 #endif /* PAIVAYS H */

LISTAUS 1.4: Paivaysmoduulin esittely C-kielella, paivays.h

Page 40: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

1.4. C++: Moduulit kaannosyksikoilla 40

. . . #endif) saadaan varmistettua, etta esittely nakyy kaantajalle vainyhden kerran.

Eri puolella ohjelmistoa (kooditiedostoja) esitellyt rakenteet toteu-tetaan yhdessa kaannosyksikossa, joka lopuksi linkitetaan mukaanvalmiiseen ohjelmaan. Kaannosyksikko on C-kielessa yksi tiedosto(joka mahdollisesti on ottanut kaannokseen mukaan toisia tiedosto-ja #include-rakenteella), josta kaantaja tekee objektitiedoston. Linki-tysvaihe pitaa huolen siita, etta esittelyosan nahneiden kaannosyksi-koiden kutsut moduulin funktioihin menevat oikeaan osaa ohjelmaalopullisessa suoritettavassa ohjelmabinaarissa (kuva 1.6 seuraavallasivulla). Tassa ratkaisussa on kaksi ongelmaa:

1. Kaikki eri moduulien kayttamat nimet (funktioiden ja muuttu-jien nimet) ovat oletuksena kaytettavissa kaikkialla ohjelmassa(kunhan ne esitellaan kaannosyksikossa). Kaannosyksikon pai-kalliseen kayttoon tarkoitetut nimet on ohjelmoijan itse merkit-tava maareella static.

2. On olemassa vain yksi ylimman tason (globaali) nakyvyysalue,jossa kaikki moduulien julkisten rajapintojen symbolit ovat na-kyvissa. Jos esim. usea moduuli esittelee funktion Tulosta, niin

/* pääohjelma */

#include "tietokanta.h"

#include "loki.h"

/* ... */

#include "paivays.h"

/* tietokanta.h */ /* loki.h */

#include "paivays.h"

paivays.h

KUVA 1.5: Sama otsikkotiedosto voi tulla kayttoon useita kertoja

Page 41: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

1.5. C++: Nimiavaruudet 41

#include "paivays.h"

void paivays_tulosta( paivays_PVM kohde )

/* ... */

Päiväysmoduulin toteutus

#include "paivays.h"

...

paivays_tulosta(vappu);

call _pv_tul proc: _pv_tul

#include#include

void paivays_tulosta( paivays_PVM kohde );

/* paivays.h */

call _pv_tul proc: _pv_tul

linkitys linkitys

käännös

Päiväyksen käyttö

käännös

Lopullinen ohjelmabinääri

Lähdekooditiedostot

Tuotettu konekoodi

call _label_xyzcall _label_xyzcall _label_xyz

KUVA 1.6: Moduulirakenne C-kielella

ohjelma ei kaanny, koska sama symboli on lopullisessa binaaris-sa maariteltyna useita kertoja. Taman nimiavaruuden “roskaan-tumisen” takia on moduulin toteuttajan pyrittava nimeamisellavalttamaan nimikonflikteja. Esimerkkissa tama on tehty liitta-malla moduulin nimiin etuliite “paivays ”.

1.5 C++: Nimiavaruudet

Ratkaisuksi suurten ohjelmistojen rajapintojen nimikonflikteihin jamodulaarisen rakenteen esittamiseen ISOC++ -standardi [ISO, 1998]tarjoaa nimiavaruudet (namespace). Nimiavaruuksien tarkoituksena

Page 42: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

1.5. C++: Nimiavaruudet 42

on tarjota kielen syntaksin tasolla oleva hierarkkinen nimeamiskay-tanto. Hierarkia auttaa jakamaan ohjelmistoa osiin seka kaytannonohjelmoinnissa estaa nimikonflikteja ohjelmiston eri moduulien va-lilla. Kuvaavat ja tunnetut nimet voivat esiintya suuressa ohjelmistos-sa useassa paikassa ja ne taytyy pystya erottamaan toisistaan. Esimer-kiksi aliohjelmat Paivays::tulosta() ja Kirjastonkirja::tulosta()suorittavat saman semanttisen operaation (tulostamisen), mutta etu-liite kertoo, minka moduulin maarittelemasta tulostusoperaatiosta onkyse.

1.5.1 Nimiavaruuksien maaritteleminen

Kaytannossa C++-ohjelmoija voi uudella avainsanalla namespace koo-ta yhteen toisiinsa liittyvia ohjelmakokonaisuuksia, jotka voivat ollamita tahansa C++-ohjelmakoodia. Kaikki esimerkkimme paivamaariakuvaavat ohjelmiston osat (tietorakenteet, tietotyypit, vakiot, oliot jafunktiot) voidaan kerata yhteen nimikkeen namespace Paivays alle,kuten listauksessa 1.5 seuraavalla sivulla on tehty.

Nimiavaruuden voi toisessa kaannosyksikossa (eli lahdekooditie-dostossa) “avata” uudelleen laajentamista varten, jolloin moduulinesittelemat rajapintafunktiot voidaan toteuttaa toisessa ohjelmatie-dostossa (listauksessa 1.6 seuraavalla sivulla). Talla tavoin moduu-lin esittely ja toteutus saadaan eroteltua toisistaan. Tiedon yksinker-taistamisen (abstrahointi) mukaisesti moduulin kayttajan pitaisi pys-tya hyodyntamaan moduulia ainoastaan sen esittelya (hh-tiedosto) jadokumentointia tutkimalla. “Tylsat” ja epaoleelliset toteutusyksityis-kohdat on piilotettu erilliseen kaannosyksikkoon, joka kuitenkin onosa samaa C++-nimiavaruutta.

1.5.2 Nakyvyystarkenninoperaattori

Kaikki nimiavaruuden sisalla olevat tunnistenimet ovat oletukse-na nakyvissa eli kaytettavissa vain kyseisen nimiavaruuden sisal-la (paivays.hh-tiedostossa esitelty tietue PVM on kaytossa listaukses-sa 1.6 seuraavalla sivulla). Ulkopuolelta nimiavaruuden sisalla ole-

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .ISOC++ -standardi sallisi tunnisteiden nimissa mm. skandinaavisia symboleja, jolloin esi-

merkkimme nimiavaruuden nimi olisi Paivays. Koska kaytannossa kaantajat eivat tallaistaominaisuutta tue, ohjelmaesimerkkiemme nimet ovat ilman suomen kielen kirjaimia a, a jao.

Page 43: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

1.5. C++: Nimiavaruudet 43

1 // Paivays-moduulin rajapinnan esittely (tiedosto: paivays.hh)2 #ifndef PAIVAYS HH3 #define PAIVAYS HH45 namespace Paivays 67 // Paivayksien tietorakenne:8 struct Pvm int p , k , v ; ;9

10 // Julkisen rajapinnan funktiot:1112 Pvm luo( int paiva, int kuukausi, int vuosi );13 // Palauttaa uuden alustetun paivayksen.1415 void tulosta( Pvm kohde );16 // Tulostetaan paivays jarjestelman oletuslaitteelle.

...17 18 #endif

LISTAUS 1.5: Nimiavaruudella toteutettu rajapinta

1 // Paivays-moduulin toteutus (tiedosto: paivays.cc)2 #include "paivays.hh" /* rajapinnan esittely */34 namespace Paivays 56 Pvm luo( int paiva, int kuukausi, int vuosi )7 8 Pvm paluuarvo;

...9 return paluuarvo;

10 1112 void tulosta( Pvm p )13

...14

...15

LISTAUS 1.6: Rajapintafunktioiden toteutus erillisessa tiedostossa

Page 44: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

1.5. C++: Nimiavaruudet 44

viin rakenteisiin voidaan viitata nakyvyystarkenninoperaattorin (::)avulla (listaus 1.7).

Tarkoituksena on, etta ohjelmoija kertoo kayttopaikassa mita ko-konaisuutta ja mita alkiota sen sisalla han kulloinkin tarkoittaa (on-ko kaytossa funktio Paivays::tulosta() vai Kirja::tulosta()). Ta-paa voidaan kritisoida ylimaaraisella kirjoitusvaivalla, mutta tassakannattaa huomioida myos mukaan tuleva dokumentaatio. Moduu-linimet kertovat heti, mita rakenteita kaytetaan, eika niita luultavastitarvitse enaa erikseen kommentoida ohjelmaan.

Nimiavaruuksia voi olla myos sisakkain, jolloin naky-vyystarkentimia ketjutetaan oikean alkion osoittamiseksi(Tietokanta::Yhteys::SQL). C++ ei aseta mitaan erityisia rajojataman ketjun pituudelle, mutta eivat pitkat ketjut enaa noudatanimiavaruuksien alkuperaista ajatusta selkeydesta hierarkian avulla.

1.5.3 Nimiavaruuksien hyodyt

Toteuttamalla moduulit nimiavaruuksien avulla saadaan C-kielenmalliin verrattuna seuraavat parannukset:

• Nimikonfliktien vaara vahenee merkittavasti, koska jokaisenmoduulin rajapintanimet ovat omassa nimetyssa nakyvyysalu-eessaan (tietysti todella laajoissa ohjelmistoissa on pidettavahuoli siita, etteivat nimiavaruuksien nimet taas vuorostaan olesamoja eri moduuleilla).

• Moduulin maarittelemien rakenteiden kaytto on kielen syntak-sin tasolla nakyvan rakenteen (nakyvyystarkennin ::) vuoksi

1 #include "paivays.hh"23 int main() 4 Paivays::Pvm vappu;5 vappu = Paivays::luo( 1,5,2001 );6 Paivays::tulosta( vappu );

...7

LISTAUS 1.7: Nimiavaruuden rakenteiden kayttaminen

Page 45: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

1.5. C++: Nimiavaruudet 45

selkeampaa (vertaa esim. Modula-3:n moduuliproseduurien kut-sutapa pisteoperaattorilla listauksessa 1.2 sivulla 35).

• Hierarkkisuudesta huolimatta moduulin sisalla on kaytettavis-sa lyhyet nimet. Koodista nahdaan syntaksin tasolla esimerkik-si, mitka funktiokutsut kohdistuvat saman moduulin sisalle jamitka muualle ohjelmistoon.

Nimiavaruudet ovat kehittyneet C++:aan vahitellen korjaamaan ha-vaittuja puutteita. Ne ovat melko uusi ominaisuus, joten on olemassapaljon C++ ohjelmakoodia, jossa niita ei kayteta, mutta nimiavaruuk-sien etujen takia niiden kayttoa suositellaan yleisesti.

1.5.4 std-nimiavaruus

ISOC++ -standardi maarittelee omaan kayttoonsa std-nimisen nimia-varuuden. Taman nimen alle on keratty kaikki kielen maarittelynesittelemien rakenteiden nimet (muutamaa poikkeusta, kuten funk-tiota exit, lukuun ottamatta). Esimerkiksi C:n tulostusrutiini printfja C++:n tulostusolio cout ovat ISOC++:n mukaisesti toteutetussa kaan-tajassa nimilla std::printf ja std::cout, jotta ne eivat aiheuttaisiongelmia ohjelman muiden nimien kanssa. Koska kirjastoon kuuluumyos C-kielesta tulleita funktioita, std-nimiavaruus sisaltaa satoja ni-mia. std-nimiavaruus on olemassa kaikissa nykyaikaisissa C++-kaanta-jissa, ja ainoastaan sen rakenteiden kayttaminen on sallittua — tahannimiavaruuteen ei saa itse lisata uusia ohjelmarakenteita.

1.5.5 Standardin uudet otsikkotiedostot

Samalla kun kielen maarittelemat rakenteet on siirretty std-nimia-varuuden sisaan, ovat myos otsikkotiedostojen nimet vaihtuneet. Ai-kana ennen nimiavaruuksia C++:ssa otettiin tulostusoperaatiot kayt-toon esiprosessorin kaskylla #include <iostream.h>. Nyt oikea (std-nimiavaruudessa oleva) tulostusoperaatioiden esittely saadaan kas-kylla #include <iostream>. Tama tiedostotyyppiin viittava .h-liit-teen puuttuminen on ominaisuus kaikissa standardin maarittelemis-sa otsikko-“tiedostoissa”. Sana “Tiedostot” on lainausmerkeissa, kos-ka standardi ei maaraa rakenteiden olevan missaan tiedostossa, vaanem. rivi ainoastaan kertoo kaantajalle, etta kyseiset esittelyt otetaan

Page 46: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

1.5. C++: Nimiavaruudet 46

kayttoon — kaantaja saa toteuttaa ominaisuuden vapaasti vaikka tie-tokantahakuna.

C-kielen kautta standardiin tulleiden otsikkotiedostojen nimistaon myos poistunut loppuliite “.h” ja lisaksi niiden eteen on lisat-ty kirjain “c” korostamaan C-kielesta peraisin olevia rakenteita. Esi-merkiksi merkkitaulukoiden kasittelyyn tarkoitetut funktiot (strcpyyms.) saadaan kayttoon kaskylla #include <cstring>. Naiden “van-hojen” funktioiden kaytosta yhdessa C++-tulostusoperaatioiden kanssanakyy esimerkki listauksessa 1.8.

1.5.6 Nimiavaruuden synonyymi

Nimiavaruuden nimi voi myos olla synonyymi (alias) toiselle jo ole-massa olevalle nimiavaruudelle. Talloin kaikki alias-nimeen tehdytviittaukset kayttaytyvat alkuperaisen nimen tavoin. Tata ominaisuut-ta voidaan hyodyntaa esimerkiksi silloin, jos samalle moduulille onolemassa useita vaihtoehtoisia toteutuksia. Moduulia kayttava ohjel-makoodi kirjoitetaan kayttamalla alias-nimea ja todellinen kayttoonotettava moduuli valitaan synonyymin maarittelyn yhteydessa (katsolistaus 1.9 seuraavalla sivulla).

Kun ollaan nimeamassa yleiskayttoisia ohjelmakoodikirjastoja, ontarkeata valita myos kokonaisuuden nimiavaruudelle nimi, joka eihelposti aiheuta nimikonfliktia muun ohjelmiston kanssa. Esimer-

1 #include <cstdlib> // paaohjelman paluuarvo EXIT SUCCESS2 #include <iostream> // C++:n tulostus3 #include <cstring> // C:n merkkitaulukkofunktiot45 int main()6 7 char const* const p = "Jyrki Jokinen";8 char puskuri[ 42 ];9 std::strcpy( puskuri, "Jyke " );

10 std::strcat( puskuri, std::strstr(p, "Jokinen") );1112 std::cout << puskuri << std::endl;13 return EXIT SUCCESS;14

LISTAUS 1.8: C-kirjastofunktiot C++:n nimiavaruudessa

Page 47: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

1.5. C++: Nimiavaruudet 47

1 #include "prjlib/string.hh"2 #include <string>34 int main() 5 #ifdef PRJLIB OPTIMOINNIT KAYTOSSA6 namespace Str = ComAcmeFastPrjlib;7 #else8 namespace Str = std;9 #endif

1011 Str::string esimerkkijono;

...12

LISTAUS 1.9: Nimiavaruuden synonyymi (aliasointi)

kiksi String lienee huono kirjaston nimi, silla usealla valmistajallaon varmasti kiinnostusta tehda samanlainen rakenne. Virallisen ni-men kannattaa olla pitka (OhjTutFiOpetusMerkkijono), ja ohjelmoijatvoivat omassa koodissaan kayttaa moduulia lyhyemmalla etuliitteel-la synonyymin avulla.

1.5.7 Lyhyiden nimien kaytto (using)

Etuliitteen “std::” toistaminen jatkuvasti esimerkiksi cout-tulostuk-sessa on varmasti raskaalta ja turhalta tuntuva rakenne.] Jos samas-sa ohjelmalohkossa kaytetaan useita kertoja samaa nimiavaruuden si-salla olevaa nimea, ohjelmoijien kirjoitusvaivaa helpottamaan on ole-massa using-lause, joka “nostaa” nimiavaruuden sisalla olevan nimenkaytettavaksi ohjelman nykyisen nakyvyysalueen sisalle. Peruskay-tossa kerrotaan yksittainen rakenne, jota halutaan kayttaa. Esimerkik-si using std::cout mahdollistaa tulostusolion nimen kayton ilmanetuliitetta (katso listaus 1.10 seuraavalla sivulla). Jotta nimiavaruuk-sien alkuperainen kayttotarkoitus sailyisi, using-lauseet tulisi laittaaaina mahdollisimman lahelle niiden tarvittua kayttokohtaa ohjelma-tiedostossa (funktion tai koodilohkon alkuun, jossa ne samalla toimi-vat dokumenttia kaytetyista ulkopuolisista rakenteista).

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .]Kaikki C++-kaantajat eivat vaadi std::-etuliitteen kayttamista, mutta kyseessa on kielen

standardin vaatimuksen vastainen toiminnallisuus.

Page 48: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

1.5. C++: Nimiavaruudet 48

1 #include "paivays.hh"2 #include <iostream>34 void kerroPaivays( Paivays::Pvm p )5 6 using std::cout; // kaytetaan maarattya nimea7 using std::endl;8 using namespace Paivays; // kaytetaan kaikkia nimiavaruuden nimia9 cout << "Tanaan on: ";

10 tulosta( p ); // Kutsuu rutiinia Paivays::Tulosta11 cout << endl;12

LISTAUS 1.10: using-lauseen kaytto

Turvallisuussyista using tulisi laittaa ohjelmakooditiedoston al-kuun vasta kaikkien #include-kaskyjen jalkeen, jottei se vahingos-sa sotke otsikkotiedostojen toimintaa vaaraan paikkaan nostetulla ni-mella. Sama sotkemisen valttaminen tarkoittaa myos sita, ettei using-lauseita kannata laittaa otsikkotiedostojen sisalle (etukateen ei tiede-ta missa yhteydessa tai jarjestyksessa tiedoston sisaltamia esittelyitakaytetaan). [Sutter, 2000]

Hyva perusperiaate on kayttaa using-lausetta mahdollisimman la-hella sita aluetta, jossa sen on tarkoitus olla voimassa (yleensa koo-dilohko tai funktio). Toisaalta paljon kaytetyissa rakenteissa on useindokumentaation kannalta selkeampaa kirjoittaa using heti siihen liit-tyvan otsikkotiedoston #include-kaskyn jalkeen. Tama suositus taason heti ristiriidassa edellisen kappaleen “sotkemissaannon” kanssa.Nimiavaruudet ovat C++:ssa sen verran uusi asia, etta parasta mahdol-lista suositusta selkeyden ja turvallisuuden kannalta tassa asiassa onvaikea antaa. Hyva kompromissi on luottaa kaantajan std-kirjastojenolevan oikein toimivia, jolloin niiden esittelyiden valiin voi kirjoittaahuoletta using-lauseita, mutta omissa osissa ja ostettujen kirjastojenkanssa kirjoittaa using-lauseet vasta kaikkien esittelyiden (otsikkotie-dostojen) jalkeen. Listaus 1.11 seuraavalla sivulla on esimerkki tasta“yhdistelmasaannosta”.

Kun normaali using-lause nostaa nakyville yhden nimen, sen eri-koistapaus using namespace nostaa nimiavaruuden sisalta kaikki ni-met nykyiselle tasolle ja siten kaytannossa poistaa nimiavaruudenkaytosta kokonaan. Erityisesti lausetta using namespace std tulisi ai-

Page 49: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

1.5. C++: Nimiavaruudet 49

1 // Omat rakenteet esitellaan std-kirjastoja ennen2 // (tama siksi etta saamme tarkastettua niiden sisaltavan kaikki3 // tarvittavat #include-kaskyt ts. ne ovat itsenaisesti kaantyvia yksikkoja)4 #include "paivays.hh"5 #include "swbus.hh"6 #include "tietokanta.hh"7 #include "loki.hh"8 // Kaikista yleisimmin tassa tiedostossa kaytetyt std-rakenteet esitellaan9 // heti niihin liittyvan otsikkotiedoston jalkeen

10 #include <iostream>11 using std::cout;12 using std::endl;13 #include <vector>14 using std::vector;15 #include <string>16 using std::string;17 // lopuksi omiin moduuleihin liittyvat using-lauseet18 using Paivays::PVM;19 using Loki::varoitus;20 using Loki::virhe;

LISTAUS 1.11: using-lauseen kaytto eri otsikkotiedostojen kanssa

na valttaa C++-ohjelmissa. Tata rakennetta kaytetaan kylla usein ope-tuksessa ja lyhyissa esimerkkiohjelmissa selkeyden saavuttamiseksi,mutta suurissa C++-ohjelmistoissa emme sita suosittele.

1.5.8 Nimeamaton nimiavaruus

Nimiavaruuksiin on maaritelty erikoistapaus nimeamaton nimiava-ruus (unnamed namespace), jolla rajoitetaan funktioiden ja muuttu-jien nimien nakyvyys yhteen kaannosyksikkoon (tama tehtiin aikai-semmin C- ja C++-kielissa static-avainsanan avulla). Nimeamattomal-la nimiavaruudella voidaan dokumentoida ne osat moduulin ohjel-makoodia, jotka ovat olemassa ainoastaan sen sisaista toteutusta var-ten. Samalla kaantaja myos pitaa huolen, ettei niita vahingossa pys-tyta kasittelemaan moduulin ulkopuolelta.

Listauksessa 1.12 seuraavalla sivulla maaritellaan yksi muuttu-ja ja funktio kaannosyksikon paikallisiksi nimeamattomalla nimiava-ruudella. Taman nimiavaruuden sisalla olevat rakenteet ovat samas-sa kaannosyksikossa (tiedostossa) suoraan kaytettavissa ilman na-kyvyystarkenninta (jota nimeamattomalla nimiavaruudella ei tietysti

Page 50: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

1.5. C++: Nimiavaruudet 50

edes ole), mutta ne eivat ole kaytettavissa kaannosyksikon ulkopuo-lella.

Nimeamaton nimiavaruus suojaa sen sisalla olevat nimet tavalli-sen nimiavaruuden tapaan. Vaikka useassa kaannosyksikossa on sa-moilla nimilla olevia rakenteita, niin ne eivat sotke toisiaan, kunhankaikki ovat nimeamattoman nimiavaruuden sisalla. Taman voi ajatel-la tapahtuvan siten, etta kaantaja tuottaa kulissien takana uniikin ni-men jokaiselle nimeamattomalle nimiavaruudelle. Jos nimeamaton-ta nimiavaruutta kayttaa tavallisen nimiavaruuden sisalla, on nimea-mattoman nimiavaruuden sisalto kaytettavissa ainoastaan taman ta-vallisen nimiavaruuden sisalla (esim. moduulin sisainen funktio taitietorakenne).

1 static unsigned long int laskuri; // Vanha tapa23 // ISOC++:n mukainen tapa4 // nimeamaton nimiavaruus:5 namespace 6 unsigned long int viiteLaskuri;7 void lisaaViiteLaskuria() 8 ++viiteLaskuri;

...9

10 1112 // Julkinen operaatio:13 Pvm luoPaivaysOlio() 14 lisaaViiteLaskuria();15 // yllaoleva rutiinin kutsu toimii samassa tiedostossa ilman16 // using-lausetta tai nakyvyystarkenninta.17 // rutiinia ei pysty kutsumaan tiedoston ulkopuolisesta koodista.

...18 19

LISTAUS 1.12: Nimeamaton nimiavaruus

Page 51: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

1.6. Moduulituki muissa ohjelmointikielissa 51

1.6 Moduulituki muissa ohjelmointikielissa

Modulaarisuus on jo vanha ja hyvaksi havaittu ohjelmointikieltenominaisuus, joten ei ole yllattavaa, etta sita tuetaan tavalla tai toisellamonissa nykyisissa ohjelmointikielissa. Seuraavassa esittelemme ly-hyesti Modula-3- ja Java-kielten tukea ohjelman jakamisessa moduu-leihin.

1.6.1 Modula-3 [Boszormenyi ja Weich, 1996]

Modula-3-kielen yksi tarkeimmista suunnitteluperusteista on ollutmodulaarisen ohjelmoinnin tukeminen. Moduulien julkinen rajapin-ta kirjoitetaan erilliseen tiedostoon, jossa on nakyvissa ainoastaanmoduulin tarjoamien palveluiden kayttamiseen tarvittavat tiedot. Ta-ma nakyy kuvasta 1.13 seuraavalla sivulla. Moduulin kayttajalle onrajapinnan esittelyssa tarpeeksi informaatiota Paivays-moduulin tar-joaman tietotyypin (T) kayttamiseen. Moduulin tekija on ainoa, jon-ka kirjoittamassa toteutuksessa “paljastetaan” paivaystietorakenteenkoko muoto.

Modula-3:n tapa esittaa “vajaita” tyyppimaarittelyja on hyvin pit-kalle vietya moduulin kayttajan kannalta turhan tiedon katkemista.Ohjelmamoduuleja kasittelevan kaantajaohjelman on tietysti tunnet-tava tietotyypin Paivays.T rakenne ja sen viema muistinvaraus, mut-ta tama kaannostekninen yksityiskohta on piilotettu ohjelmoijalta.

1.6.2 Java [Arnold ja Gosling, 1996]

Javassa ohjelman nimet jaetaan kokonaisuuksiin tavalla, joka on hy-vin lahella C++:n nimiavaruuksia. Tasta on esimerkki listauksessa 1.14seuraavalla sivulla. Java-kooditiedoston alussa voidaan avainsanallapackage kertoa, minka nimiseen pakkaukseen tiedostossa esiteltavatnimet kuuluvat. Javassa pakkausten nimet muodostavat hierarkianpisteella erotelluilla osanimilla. Nimet otetaan kayttoon omassa oh-jelmakoodissa import-lauseella. Oletuksena kayttoon tulee vain yksinimi, mutta jos haluaan kayttaa kaikkia pakkauksen julkisia nimia,kaytetaan merkintaa tahti (.*).

Javassa on myos rakenne interface, mutta se ei ole moduulienrajapintaesittely. Kyseisella rakenteella esitellaan lupaus maaratyn-

Page 52: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

1.6. Moduulituki muissa ohjelmointikielissa 52

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . esittely . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .1 INTERFACE Paivays;23 TYPE T <: REFANY; (* Kerrotaan vain, etta T on viite dataan, jonka4 rakennetta moduulin kayttajan ei tarvitse tietaa *)56 PROCEDURE Luo( paiva, kuukausi, vuosi : INTEGER ) : T;7 PROCEDURE Tulosta( kohde : T );8 . . .9 END Paivays.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . kaytto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .1 MODULE Kaytto;23 IMPORT Paivays;45 VAR vappu : Paivays.T;6 BEGIN7 vappu := Paivays.Luo( 1, 5, 2001 );8 Paivays.Tulosta( vappu );

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . maarittely . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .1 MODULE Paivays;23 REVEAL T = BRANDED REF RECORD4 paiva, kuukausi, vuosi : INTEGER;5 END;6 . . .

LISTAUS 1.13: Moduulituki Modula-3-kielessa

1 package com.jyke.oliokirja.heippa;23 import java.applet.Applet; // kaytetaan luokkaa Applet4 import java.awt.*; // awt-moduulista kayttoon kaikki56 public class Heippa extends Applet 7 public void paint(Graphics g) // java.awt.Graphics8 9 g.drawString("Heippa!", 0, 0);

10 11

LISTAUS 1.14: Moduulituki Java-kielessa

Page 53: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

1.6. Moduulituki muissa ohjelmointikielissa 53

laisesta rajapinnasta, jonka jokin luokka voi toteuttaa (katso aliluku6.9). Moduulit ja niiden rajapinnat ovat tata laajempi kasite.

Page 54: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

54

Luku 2

Luokat ja oliot

Lucius: “Ay me! This object kills me.”– The Tragedy of Titus Andronicus, Act 3, Scene 1 [Shakespeare,

1593]

Ohjelmointikielten oliokasite on peraisin Simula-67 -ohjelmointikie-lesta, joka kehitettiin erilaisten simulointimallien toteutusta varten[Sethi, 1996]. Kun ongelmana on esimerkiksi simuloida pankissaasioivien asiakkaiden keskimaaraista odotusaikaa, Simula-ohjelmas-sa kuvataan simuloitavan ongelman kannalta oleellisia rakenteita oh-jelmointikielen rakenteina, joita nimitettiin olioiksi: asiakas, jonoasiakkaita, toimihenkilo. Simulointiohjelma “pyoritteli” naita olioitamuistissaan: asiakasolio tulee pankkiin, menee jonoon, odottaa vuo-roaan, menee palveltavaksi, kertoo asiansa A, jonka palveluun meneeaikaa T , kiittaa ja poistuu simulaatiosta. Vaikka todelliset asiakkaatovat yksiloita, ohjelman mallintamat abstraktit asiakasoliot sisalta-vat paljon yhteisia piirteita. Kuvaamalla nama yhteiset piirteet ohjel-massa vain kerran saadaan saastetyksi aikaa ja vaivaa. Samat piirteetomaavat oliot kuuluvat samaan luokkaan ja niilla kaikilla on luokanmaarittelemat ominaisuudet ja toiminnallisuus.

2.1 Olioiden ominaisuuksia

Olioita voidaan tarkastella usealta nakokannalta. Kuten moduulejavoidaan olioitakin tarkastella ulkoapain (olion kayttaja) tai sisalta ka-

Page 55: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

2.1. Olioiden ominaisuuksia 55

sin (olion suunnittelija ja toteuttaja). Olion julkinen rajapinta kertookayttajalle, mita tehtavia se lupaa toteuttaa vastuualueellaan.

Olioiden kayttotarkoitus on kaksijakoinen: niilla pyritaan mallin-tamaan ongelmaa (maarittelyssa), ja toisaalta ne pyrkivat tarjoamaankaytannollisen tavan ohjelmiston toteuttamiseen (suunnittelu ja to-teutus). Naiden “todellisten” ja ohjelmallisten olioiden ero on naky-vissa kuvassa 2.1. Olio-ohjelmien tekeminen ei kuitenkaan ole mis-saan nimessa taman kuvan nuolten mukainen suoraviivainen proses-si, jossa toteutetaan vain todelliset oliot jollain olio-ohjelmointikielel-la. Aina kaikki maarittelyssa loydetyt “todelliset” oliot eivat ole olioi-ta toteutetussa ohjelmassa, ja ohjelmiston toteutuksessa usein tarvi-taan olioita, joita maarittelyssa ei ole olemassa (toteutusta tukevatoliot).

2.1.1 Oliolla on tila

Jokaisella oliolla on aina olemassa tila, jonka sisaltama informaatioon oleellinen ohjelmiston toiminnan kannalta. Kirjastonkirja-olio voiesimerkiksi sisaltaa tiedon kirjan nimesta, ISBN-numerosta ja han-

Ongelma

Todelliset oliot

metodi()

Virkailija

jonon_perään()

Jono

pois_jonosta()

metodi()

Matti

metodi()

Jyke

Ohjelmiston oliot Asiakas

palvelu()

Asia

Keskimääräinen jonotusaika

Nimi

palvelu()

Asia

Keskimääräinen jonotusaika

Nimi

Satunnainen Asiakas Vakituinen Asiakas

palvelu()

Asia

Keskimääräinen jonotusaika

Nimi

Luokat ja niiden hierarkiat

Määrittely

Suunnittelu ja toteutus

KUVA 2.1: Ongelmasta ohjelmaksi olioilla

Page 56: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

2.1. Olioiden ominaisuuksia 56

kintapaivamaaran. Suunnittelussa olion tila koostuu attribuuteista,joilla kerrotaan mita informaatiota olion vastuualueen toteutuksessatarvitaan. Toteutuksessa nama attribuutit toteutetaan ohjelmointikie-len tarjoamilla tavoilla, joita voivat olla muuttujat, tietueet tai oliot —olion sisaisessa toteutuksessa on hyvin tavallista kayttaa toisia (mah-dollisesti muualta hankittuja kirjastoituja) olioita hyvaksi. Kirjaston-kirja-olio voi toteutuksessa siis sisaltaa merkkijonon (kirjan nimi),numerotietueen (ISBN) ja paivamaara-olion.

Olion tila muuttuu ohjelman suorituksen aikana ja usein tama tilakapseloidaan piiloon olion sisalle siten, etta sita voidaan tarkastella jamuuttaa ainoastaan olion julkisen rajapinnan tarjoamien palveluidenkautta. Yleensa pidetaan suunnittelu- tai ohjelmointivirheena tilan-netta, jossa ohjelmassa olevilla olioilla ei ole maariteltya tilaa jonainsuoritusajankohtana.

2.1.2 Olio kuuluu luokkaan

Kirjastonkirja-olioiden tilaan kuuluu nimi (merkkijono, max 256merkkia), ISBN (maaramuotoinen tietue numeroita) ja hankintapai-va (paivays-olio). Jokaisella kirjalla on tietysti omat tietonsa, muttaattribuuttien tietotyypit ovat kaikilla Kirjastonkirjoilla samat ja namavoidaan maaritella keskitetysti yhdessa paikassa (luokka). Vastaavastipalvelut (julkinen rajapinta) on jokaisella Kirjastonkirjalla sama ja ne-kin kannattaa maaritella vain kerran. Luokka voidaan ajatella raken-teeksi, joka edustaa kaikkia samalla tavoin rakentuneita (attribuutit,palvelut ja kayttaytyminen) olioita. Luokka myos maaraa miten ky-seisen tyyppisia olioita luodaan ja tuhotaan ohjelmassa.

Jos kerran olio on mallinnuksen ja toteutuksen perusyksikko, niinmiksi puhua mistaan luokista? Tasmalleen samasta syysta kuin aikai-semmin modulaarisuuden puolustuspuheessa perusideana on asioi-den yksinkertaistaminen — abstrahointi. Ohjelmointikielen luokkakertoo “yleisen rakenteen” yksittaisen olion tilalle ja maarittelee raja-pinnat olion kayttamiselle. Useampaa oliota edustava luokka on olio-ohjelmien suunnittelussa ehka eniten kaytetty rakenne. Ohjelmisto-jen rakennetta kuvaavat piirrokset ovat usein luokkakaavioita ja pu-huvat luokkien ominaisuuksista, eivat yksittaisista olioista.

Kun ohjelmassa luodaan uusi olio, puhutaan olion instantioinnis-ta, jolloin olion luokka maaraa muodostuvan uuden olion rakenteen(olion kuluttaman muistin maaran seka olion tilan sisaisen raken-

Page 57: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

2.1. Olioiden ominaisuuksia 57

teen) ja uuden olion alkutilan alustustoimien avulla (joita kasitellaanluvussa 3).

Olioita voidaan verrata ohjelmointikielen muuttujiin, joille yhtei-set asiat kertoo muuttujan tietotyyppi. Olioilla tyyppia vastaa olionluokka. Luokka on olio-ohjelmien perusyksikko, jonka varaan laajem-mat ja vahvemmat ominaisuudet rakentuvat.

Olio-ohjelmien uudelleenkaytettavyytta lisaa ominaisuus, jossaolemassa olevaan luokkaan voidaan lisata tai muuttaa ominaisuuk-sia (periytyminen, luku 6). Luokat voivat muodostaa keskinaisia hie-rarkioita, joissa samaan “kokoelmaan” kuuluvien luokkien oliot voi-vat toimia yhtenaisesti ja tarvittaessa hieman toisistaan poikkeaval-la tavalla (periytyminen ja polymorfismi, aliluku 6.1). Luokista it-sestaan voidaan tehda yleiskayttoisia malleja (“metaluokkia”), jois-ta muodostetaan malliin sopivia luokkainstansseja (geneerisyys, lu-ku 9). Useimmat olio-ohjelmointikielet tuottavat ohjelmia, joissa jo-kainen olio tietaa mihin luokkaan se kuuluu. Lahes kaikki olio-ohjel-mointikielet tarjoavat myos mekanismin, jolla ajoaikana voidaan tar-kastella, kuuluuko olio maarattyyn luokkaan (C++:n RTTI on kasiteltyaliluvussa 6.5.3).

Luokka on kasite, joka esiintyy ainoastaan ohjelman suunnitte-lu- ja toteutusvaiheessa. Ohjelmiston suorituksen aikana on olemas-sa vain olioita. Tama nakokulma hamartyy helposti, koska varsinusein olio-ohjelman suunnitteluvaiheessa on nakyvissa vain ohjel-man luokkarakenne. Luokka on kuvaustapa, jolla voidaan puhua sa-maan “ryhmaan” eli luokkaan kuuluvien olioiden yhteisista ominai-suuksista, ja lopullisessa ohjelmistossa on aina toimimassa luokanmallin toteuttavia olioita.

2.1.3 Oliolla on identiteetti

Koska olion sisainen tila on muista olioista riippumaton, on taysinmahdollista, etta jonain ajanhetkena kahdella tai useammalla samanluokan oliolla on tasmalleen sama tila. Ajatellaan luokka, joka kuvaapaivamaaria. Voimme luoda taman mallin mukaisesti oliot A ja B,jotka molemmat kuvaavat samaa paivamaaraa (vaikkapa 22.4.2069).Kuinka voimme erottaa nama oliot toisistaan, kun molempien pai-vamaaraattribuutilla on sama arvo? Kysymys voi kuulostaa oudolta,

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .Poikkeuksena ovat erityistapaukset, joissa halutaan kerata oliojoukon ominaisuuksia yh-

teen “luokkaolioksi” (aliluku 8.2.1)

Page 58: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

2.2. Luokan dualismi 58

koska teksti itsessaan sisaltaa tiedon erottelusta (“oliot A ja B”). Oleel-lista on huomata, etta tassa on jokin olioiden ulkopuolinen tapa (ni-meaminen), jolla ne erotetaan toisistaan.

Ohjelmiston toteuttamiseksi on valttamatonta, etta jokainen ole-massa oleva olio voidaan yksikasitteisesti tunnistaa ja kasitella. Tatatarkoitusta varten olio-ohjelmointikielet tarjoavat identiteetin (iden-tity) kasitteen.

Yksinkertaisimmillaan identiteetti voi olla muuttujanimi, jokakertoo mista oliosta puhutaan (“muuttujassa A tai B”). Identiteetti voiolla myos esim. muistiosoite, joka kertoo missa olio sijaitsee muistis-sa. Oikein toimiva jarjestelma ei talleta olioita muistiin paallekkain,joten muistiosoite erottelee kaikki oliot toisistaan eli antaa niille yk-silollisen identiteetin.

Mutkikkaammissa nykyaikaisissa jarjestelmissa voi olioita ollataltioituna tietokannoissa tai niita voidaan kasitella hajautetusti tie-toverkossa, jolloin yksikasitteinen identiteetti (jarjestelman- tai jopamaailmanlaajuisena) on valttamaton olioiden kaytossa.

Identitettia voidaan kayttaa tarkistamaan tarvittaessa, onko ky-seessa sama olio ilman etta olion tila vaikuttaa tulokseen (tai etta si-ta edes tarvitsee tarkistaa). Esimerkissa voidaan sanoa olioiden A jaB olevan yhta suuria (edustavan samaa paivamaaraa) mutta ne eivatole sama olio, koska identiteetit ovat erilaiset (“A ei ole B”). Olioideneri ominaisuuksia (tila, identiteetti ja luokka) on havainnollistettu ku-vassa 2.2 seuraavalla sivulla.

2.2 Luokan dualismi

Olio-ohjelmointikielen luokalla voidaan katsoa olevan kaksi esiin-tymismuotoa tai katselukulmaa: moduuli ja tyyppi. Luokka sisaltaaniiden molempien ominaisuuksia ja pystyy esiintymaan ohjelmassakumman tahansa normaalissa kayttotarkoituksessa.

• Luokka moduulina. Luokka toteuttaa rajapintojen ja tiedon kat-kennan periaatteet: luokan suunnittelija maarittelee julkiseenrajapintaan ne operaatiot, joilla luokasta tehtya oliota voidaankasitella. Kaikki pelkastaan luokan toteutukseen liittyvat omi-naisuudet (tiladata ja sisaiset funktiot) pystytaan katkemaanluokan kayttajalta.

Page 59: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

2.2. Luokan dualismi 59

Paivays

paiva

kuukausi

vuosi

(a) Luokka

22

2069

4

22

2069

4

A

B

(b) oliot

KUVA 2.2: Paivays-luokka ja siita tehdyt oliot A ja B

• Luokka tietotyyppina. Luokka esiintyy olio-kielessa “ykkosluo-kan kansalaisena” kielen perustietotyyppien (esim. liukuluvut)kanssa. Kaikki muuttujille tutut operaatiot (esim. laskutoimi-tukset, sijoitukset ja arvojen valitys parametreina) voidaan maa-ritella myos luokasta tehtyjen olioiden yhteydessa.

Tama kahtiajako nahdaan myos silloin, jos ajattelemme jo ennenoliokasitetta olleita tietotyyppeja “olioina”. Esimerkiksi useimmissaohjelmointikielissa kaytettavissa oleva liukulukutyyppi sisaltaa namakaksi nakokulmaa:

• Liukuluku moduulina. Automaattisesti oletamme, etta liukulu-vuilla on olemassa laskuoperaatioita (ainakin yhteen-, vahen-nys-, kerto- ja jakolasku), lukuja voidaan alustaa maarattyyn ar-voon ja vertaamalla liukulukuja saadaan niiden valille suuruus-jarjestys. Oliomaailmassa nama operaatiot ovat liukulukuluok-kaan kuuluville olioille maaritellyn julkisen rajapinnan palve-luita. Liukulukua kayttavan ohjelmoijan ei tarvitse tuntea kay-tetyn laiteympariston liukuluvun esitysmuotoa konekoodin ta-solla (esim. IEEE-754). Tama toteutusyksityiskohta on katkettyliukulukumoduulin sisalle.

Page 60: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

2.3. C++: Luokat ja oliot 60

• Liukuluku tietotyyppina. Voimme esimerkiksi tehda liukutyyp-pia olevia muuttujia, sijoittaa niita toisiin muuttujiin ja valittaamuuttujien arvoja parametreina. Tama olioiden kaytto muuttu-jina on tarkein ero staattista rakennetta edustavien moduulienja luokista tehtyjen olioiden valilla.

2.3 C++: Luokat ja oliot

Jo aiemmin on mainittu, etta yksi tapa ajatella olioita ja luokkia onjakaa niiden sisalto kahteen osaan — toimintoihin ja tietorakenteisiin— ja ajatella olioita ikaan kuin tietorakenteina, joihin on “liitetty”niihin kohdistuvat toiminnot.

Tama ajattelutapa on ilmeisesti ollut lahtokohtana, kun C++-kiel-ta on kehitetty C-kielesta [Stroustrup, 1994]. C++:n luokat muistuttavatjoiltain osin erittain paljon C:n struct-tietotyyppeja (jopa niin paljon,etta C++:ssa itse kielen kannalta avainsanat struct ja class ovat la-hestulkoon vaihtokelpoisia). Aivan kuten struct-tietotyypitkin, myosluokat taytyy C++:ssa esitella jokaisessa kaannosyksikossa ennen kuinniita voi kayttaa. Tietotyypeista poiketen luokilla on kuitenkin myossisainen toteutuksensa — rajapinnan palvelut toteuttava ohjelmakoo-di tai toisin sanottuna rajapintafunktioiden toteutus —, joka kirjoite-taan tyypillisesti erikseen omaan tiedostoonsa (tai tiedostoihinsa).

2.3.1 Luokan esittely

Luokan kayttajalle riittaa yleensa luokan esittely. Luokan esittely ker-too luokasta kaiken luokan kayttoon tarvittavan. Ohjelmoijalle se ker-too luokan kayttoon tarvittavan rajapinnan. Kaantajaa varten luokanesittely sisaltaa tietoa luokan periytymissuhteista seka tietoa luokansisallosta muistinvarausta yms. varten. Kuten esittelyt yleensa, myosluokan esittelyt kirjoitetaan tyypillisesti otsikkotiedostoon, josta luo-kan kayttaja sitten lukee ne #include-komennolla omaan kooditiedos-toonsa.

Luokan esittely alkaa avainsanalla class, jonka jalkeen tulee esi-teltavan luokan nimi. Taman jalkeen kerrotaan aaltosuluissa, mitaluokka sisaltaa. Luokan sisalto voi koostua seuraavista asioista:

Page 61: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

2.3. C++: Luokat ja oliot 61

• Jasenmuuttujat ovat luokan olion sisalla olevia muuttujia. Ja-senmuuttujia kaytetaan olion sisaisen tilan muistamiseen. Ja-senmuuttujista kerrotaan aliluvussa 2.3.2.

• Jasenfunktiot ovat funktioita, joihin kirjoitetaan luokan toimin-nallisuus. Luokan rajapinta koostuu jasenfunktioista. Rajapin-nan lisaksi luokalla voi olla myos jasenfunktioita, jotka eivatnay ulospain. Jasenfunktioita kasitellaan aliluvussa 2.3.3.

• Sisaiset tyypit ovat luokan rajapintaan tai sisaiseen toteutuk-seen liittyvia tyyppimaarittelyja. Niista kerrotaan tarkemminaliluvussa 8.3.

• Luokkamuuttujat ovat luokan kaikille olioille yhteisia muuttu-jia. Niita voidaan kayttaa sellaisen tiedon tallettamiseen, jokaei ole oliokohtaista, vaan koskee kaikkia luokan olioita yhdessa.Luokkamuuttujat kasitellaan tarkemmin aliluvussa 8.2.2.

• Luokkafunktiot ovat luokkamuuttujien vastine funktioidenpuolella. Ne ovat funktioita, joiden toiminnallisuus ei koskeyksittaista oliota, vaan koko luokkaa kokonaisuutena. Yleen-sa luokkafunktioita kaytetaan luokkamuuttujien kasittelyyn.Luokkafunktioista kerrotaan aliluvussa 8.2.3.

Listaus 2.1 seuraavalla sivulla sisaltaa esimerkkina yksinkertaisenluokan PieniPaivays esittelyn, joka on kirjoitettu tiedostoon pieni-paivays.hh. Huomaa, etta tama luokkaesimerkki on tarkoituksella yk-sinkertaistettu, ja siita puuttuu useita C++:n luokille tarpeellisia asioita(kertaustehtava: etsi luokkaesimerkin puutteet ,).

On syyta huomata, etta tassa teoksessa kaytetty termi “luokanesittely” ei kirjaimellisesti vastaa englanninkielista C++-terminologi-aa, jossa samasta asiasta kaytetaan nimitysta “class definition” (kirjai-mellisesti “luokan maarittely”). Sana “esittely” kuvaa tilannetta kui-tenkin ehka paremmin, koska esittelyn yhteydessa ei kerrota luokanrajapintafunktioiden toteutusta.

2.3.2 Jasenmuuttujat

Jasenmuuttujista (data member) kaytetaan myos nimityksia “attri-buutti”, “kentta”, “instanssimuuttuja” ja “tietojasen”. Ne ovat olioonkiinteasti liittyvia muuttujia, jotka muodostavat olion sisaisen tilan.

Page 62: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

2.3. C++: Luokat ja oliot 62

1 #ifndef PIENIPAIVAYS HH2 #define PIENIPAIVAYS HH34 class PieniPaivays5 6 public:7 void asetaPaivays(unsigned int p, unsigned int k, unsigned int v);8 void sijoitaPaivays(PieniPaivays& p);9 void asetaPaiva(unsigned int paiva);

10 void asetaKk(unsigned int kuukausi);11 void asetaVuosi(unsigned int vuosi);1213 unsigned int annaPaiva();14 unsigned int annaKk();15 unsigned int annaVuosi();1617 void etene(int n);18 int paljonkoEdella(PieniPaivays& p);1920 private:21 unsigned int paiva ;22 unsigned int kuukausi ;23 unsigned int vuosi ;24 ;2526 #endif

LISTAUS 2.1: Esimerkki luokan esittelysta, pienipaivays.hh

Tyypiltaan jasenmuuttujat voivat olla mita tahansa — kokonaisluku-ja, osoittimia, viitteita tai vaikkapa toisia olioita.

Jasenmuuttujat taytyy C++:ssa esitella luokan esittelyn sisalla, janiiden esittely muistuttaa suuresti tavallisten muuttujien esittelya.Listauksen 2.1 riveilla 21–23 on esitelty paivaysluokan tarvitsemat ja-senmuuttujat, joihin paivaysolion sisaltama paivaystieto talletetaan.Jasenmuuttujien nimeamisessa ei C++:n kannalta ole mitaan erityisiasaantoja, mutta useissa ohjelmointityyleissa (katso liite B) jasenmuut-tujat nimetaan niin, etta ne on koodissa helppo erottaa esimerkiksijasenfunktioiden parametreista.

Tassa teoksessa kaytetaan C++:ssa kohtalaisen yleista nimeamista-paa, jossa jasenmuuttujien nimien peraan lisataan alaviiva. Vastaavaenglanninkielisessa ohjelmoinnissa yleinen kaytanto on lisata jasen-muuttujien eteen sana “my”, siis myDay, myMonth, myYear ja niin edel-

Page 63: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

2.3. C++: Luokat ja oliot 63

leen. Sen sijaan joissain teoksissa nakyva tapa lisata jasenmuuttujiennimien eteen alaviiva ( paiva) ei ole suotava, koska C++-standardi va-raa tietyt alaviivoilla alkavat nimet kaantajan sisaiseen kayttoon.

Olio-ohjelmoinnissa on varsin tavallista, etta olion jasenmuuttu-jat ovat tyypiltaan toisia olioita. Paivaystyyppista oliota voisi esimer-kiksi kayttaa jasenmuuttujana luokassa, joka kuvaa kirjaston tietojar-jestelmassa yhden kirjan tietoja, joihin kuuluu myos palautuspaiva.Osa tallaisen luokan esittelysta voisi olla seuraavanlainen:

class Kirja

...private:string nimi ; // C++:n merkkijonoluokka, ks. liitteen A aliluku A.4string tekijannimi ;PieniPaivays palautuspvm ;...

;

Tallainen hierarkkinen rakenne, jossa olio sisaltaa jasenmuuttuja-naan olion, jolla taas on sisallaan omat jasenmuuttujansa, on hyvintyypillinen olio-ohjelmoinnissa. Sen avulla olion sisaltama data voi-daan abstrahoida ja kapseloida selkeiksi kokonaisuuksiksi, ja olioi-den rajapintojen avulla myos tiedon kasittely voidaan jakaa selkeisiinosiin luokkien avulla.

Koska jasenmuuttujat liittyvat kiinteasti olioon, niiden elinkaarion tasmalleen sama kuin olionkin. Kun olio syntyy, syntyvat myoskaikki sen jasenmuuttujat. Samoin jasenmuuttujat tuhoutuvat ainasamalla kuin itse oliokin. Kaikki olion sisainen tieto ei kuitenkaanole luonteeltaan sellaista, etta se olisi saatavilla heti olion syntymanyhteydessa tai etta se kestaisi olion tuhoutumiseen saakka. Esimer-kiksi listaoliolla olisi jarkevaa olla jasenmuuttujissaan tallessa listansisaltamat alkiot. Tama ei kuitenkaan ole suoraan mahdollista, kos-ka listan alkiot eivat ole tiedossa listaolion syntyessa ja vastaavastiniita voidaan poistaa ja lisata listaolioon koska tahansa. Tallaisissatapauksissa on C++:ssa tapana laittaa olioon jasenmuuttujaksi osoitintai osoittimia, joiden paahan voi sitten myohemmin sijoittaa tietoa,joka ei ole olion “omistuksessa” koko olion elinaikaa. Yleensa tallai-sissa tapauksissa tarvitaan myos olioiden tai datan dynaamista luo-

Page 64: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

2.3. C++: Luokat ja oliot 64

mista, josta kerrotaan enemman aliluvussa 3.3.4. Olioiden elinkaartakasitellaan tarkemmin luvuissa 3 ja 7.

2.3.3 Jasenfunktiot

Jasenfunktioita (member function) kutsutaan myos monilla muilla ni-milla olio-ohjelmoinnissa. Yleisimpia ovat “metodi”, “rutiini”, “pal-velu” tai “operaatio”. Kaikki nama nimitykset kuvaavat saman asianeri puolia. Jasenfunktiot ovat funktioita, jotka toteuttavat olion tar-joamat palvelut ja joiden avulla eri oliot kommunikoivat keskenaan.Kaikki luokan toiminnallisuus kirjoitetaan jasenfunktioihin (ja luok-kafunktioihin, joista enemman aliluvussa 8.2.3).

Kuten tavalliset funktiotkin, myos jasenfunktiot taytyy esitella en-nen kuin niita voi kayttaa. Rivit 7–18 listauksessa 2.1 sivulla 62 sisal-tavat luokan PieniPaivays jasenfunktioiden esittelyt luokan esittelynsisassa. Jasenfunktioiden esittelyssa syntaksi on lahes tasmalleen sa-ma kuin tavallistenkin funktioiden esittelyssa.

Jasenfunktion esittelyssa kerrotaan vain, milta jasenfunktio nayt-taa ulospain eli miten sita voi kutsua. Tavallisten funktioiden tapaanjasenfunktioiden varsinainen toteutus (koodi) kirjoitetaan erilliseenkooditiedostoon. Yleinen kaytanto on, etta jokaista luokkaa kohti kir-joitetaan yksi jasenfunktioiden toteutukset sisaltava kooditiedosto,mutta C++ ei mitenkaan rajoita tata. Joskus saattaa olla tarpeen hajot-taa luokan toteutus useisiin tiedostoihin, joskus taas on jarkevaa kir-joittaa muutaman toisiinsa tiiviisti liittyvan luokan toteutus samaankooditiedostoon — talloin usein myos luokkien esittelyt kirjoitetaansamaan otsikkotiedostoon.

Listaus 2.2 seuraavalla sivulla sisaltaa osan PieniPaivays-luo-kan jasenfunktioiden toteutuksista, jotka ovat tiedostossa pienipai-vays.cc. Tiedoston alussa luetaan ensin sisaan luokkaesittely tiedos-tosta pienipaivays.hh. Tama on aina tehtava kooditiedostoissa en-nen jasenfunktioiden maarittelyja, jotta kaantaja saa luettua esittelys-ta tarvitsemansa tiedot luokasta.

Jasenfunktioiden maarittely on syntaksiltaan hyvin samanlainenkuin tavallisen C++:n funktion maarittely. Nakyvin ero on, etta maarit-telyn alussa kaytetaan nakyvyystarkenninta ::, eli jasenfunktion ni-mi esiintyy aina muodossa Luokannimi::jfunktionnimi. Tama on tar-peen, jotta kaantaja tietaa, minka luokan jasenfunktiota ollaan maa-

Page 65: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

2.3. C++: Luokat ja oliot 65

1 #include "pienipaivays.hh"23 void PieniPaivays::asetaPaivays(unsigned int p, unsigned int k,4 unsigned int v)5 6 asetaPaiva(p);7 asetaKk(k);8 asetaVuosi(v);9

1011 void PieniPaivays::asetaPaiva(unsigned int paiva)12 13 paiva = paiva;14 1516 unsigned int PieniPaivays::annaPaiva()17 18 return paiva ;19

...LISTAUS 2.2: Jasenfunktioiden toteutus, pienipaivays.cc

rittelemassa — useissa luokissa kun voi olla samannimisia jasenfunk-tioita.

Toinen ero tavallisiin funktioihin on, etta jasenfunktion koodis-sa voi viitata “oman olion” jasenmuuttujiin ja toisiin jasenfunktioi-hin suoraan. Tama on mahdollista, koska jasenfunktioita kutsutaanaina jonkin olion kautta, joten jasenfunktio “tietaa” kutsun yhteydes-sa, minka olion palvelua ollaan suorittamassa. Niinpa listauksen 2.2rivilla 6 kutsutaan oman olion asetaPaiva-jasenfunktiota, joka puo-lestaan sijoittaa parametrina tulleen paivayksen talteen oman olionjasenmuuttujaan paivays rivilla 13. Vastaavasti rivilla 18 jasenfunk-tio annaPaiva palauttaa paluuarvonaan oman olion jasenmuuttujanpaiva arvon. Suora paasy olion jasenmuuttujiin ja jasenfunktioi-hin on luontevaa, onhan jasenfunktion toteutuksen tarkoitus nime-nomaan tarjota olion kayttajalle jokin palvelu operoimalla olion sisal-tamalla tiedolla ja mahdollisesti kayttamalla hyvakseen muita oliontarjoamia palveluita.

Joskus jasenfunktioiden koodissa tulee tarve erikseen viitataolioon itseensa. Tyypillinen esimerkki tasta on tilanne, jossa jasen-

Page 66: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

2.3. C++: Luokat ja oliot 66

funktio joutuu antamaan olion itsensa parametrina jollekin toisellefunktiolle. Tallaisia tilanteita varten jasenfunktioiden koodissa voikaytaa erityismerkintaa this. Luokan X jasenfunktion koodissa thisintyyppi on “osoitin X-olioon”, eli se kayttaytyy aivan kuin se olisi esi-telty lauseella “X* this;”. Osoittimen arvo on automaattisesti sellai-nen, etta se osoittaa olioon, jonka jasenfunktion koodia ollaan suorit-tamassa, siis “olioon itseensa”.

Jos esimerkiksi ohjelmassa on funktio rekisteroiPvm, joka ot-taa parametrikseen osoittimen paivaysolioon, voi paivaysolion jo-kin jasenfunktio rekisteroida olion itsensa tietokantaan lauseel-la rekisteroiPvm(this);. Jos funktio ottaisikin osoittimen sijaanparametrikseen viitteen] paivaysolioon, olisi syntaksi vastaavastirekisteroiPvm(*this);.

Joissain oliokielissa jasenfunktioiden koodissa ei voi lainkaankayttaa jasenmuuttujia tai jasenfunktioita suoraan, vaan niihin tay-tyy aina viitata erikseen olion itsensa kautta, esimerkiksi syntaksillathis->jmuuttuja tai self.muuttuja. Vaikka C++:ssakin tama olisi mah-dollista this-merkinnan avulla, ei se ole tapana.

2.3.4 Luokkien ja olioiden kaytto C++:ssa

Kun kooditiedostossa luokan esittely on luettu sisaan, voidaan luo-kasta luoda koodissa olioita samalla tavalla kuin normaaleja muut-tujia luodaan C++:ssa. Muutenkin luokka kayttaytyy kuten mika ta-hansa muukin kayttajan itsensa maarittelema tyyppi, kuten esim.struct-tietorakenne. Tassa suhteessa C++ heijastaa suoraan aliluvus-sa 2.2 mainittua “luokka tietotyyppina” -periaatetta.

Listauksessa 2.3 sivulla 68 on esimerkki yksinkertaisesta paa-ohjelmasta, jossa kaytetaan aiemmin maariteltya PieniPaivays-luok-kaa. Jalleen tiedoston alussa luetaan sisaan luokan esittely otsik-kotiedostosta. Rivilla 14 luodaan luokasta PieniPaivays paivays-olio aivan kuten seuraavalla rivilla luodaan tyypista int kokonais-lukumuuttuja. Taman jalkeen jasenfunktioita kutsutaan syntaksillaolionNimi.jfunktio(parametrit), kuten tapahtuu rivilla 17. Jos olionsijaan kaytetaan osoitinta olioon, jasenfunktioita kutsutaan syntaksil-la osoitin->jfunktio(parametrit). Tama nakyy rivilla 30. Jos taaskaytetaan viitetta olioon, nayttaa jasenfunktion kutsu samalta kuin

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .]C++:n viitetyypin kaytto esitellaan lyhyesti liitteen A aliluvussa A.1.

Page 67: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

2.3. C++: Luokat ja oliot 67

suoraan oliota kaytettaessa. Tasta on esimerkkina viitteen pvm kauttatapahtuva kutsu rivilla 40.

Olioita voi myos valittaa parametreina toisiin funktioihin (ja ja-senfunktioihin). Listauksen rivi 27 nayttaa funktion, joka ottaa para-metrinaan osoittimen olioon. Vastaavasti rivilla 37 on funktio, jokaottaa parametrina viitteen olioon. Olioiden valittaminen “normaalei-na” arvoparametreina on myos mahdollista, mutta se vaatii kopiora-kentajan kasitteen tuntemista, jota kasitellaan myohemmin aliluvus-sa 7.1.2. Huomaa, etta kun funktioon valittaa osoittimen tai viitteenolioon, niin funktiossa osoittimen tai viitteen lapi tehtavat jasenfunk-tiokutsut kohdistuvat alkuperaiseen olioon, aivan kuten normaalisti-kin osoitin- ja viiteparametreja kaytettaessa.

Page 68: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

2.3. C++: Luokat ja oliot 68

1 #include "pienipaivays.hh"23 #include <iostream>4 using std::cout;5 using std::endl;67 // Funktio, joka tarkistaa, onko vuoden sisalla karkauspaivaa8 bool onkoKarkauspaivaa(PieniPaivays* pvm p);9 // Funktio, joka kertoo montako paivaa on saman vuoden jouluun

10 int kauankoJouluun(PieniPaivays& pvm);1112 int main()13 14 PieniPaivays kesapaiva;15 int paiviaJouluun = 0;1617 kesapaiva.asetaPaivays(21,7,1999);18 if (onkoKarkauspaivaa(&kesapaiva))19 20 cout << "Karkauspaiva on alle vuoden paassa." << endl;21 22 paiviaJouluun = kauankoJouluun(kesapaiva);23 cout << "Jouluun on " << paiviaJouluun << " paivaa." << endl;24 2526 // Karkauspaivafunktion (typera) toteutus

27 bool onkoKarkauspaivaa(PieniPaivays* pvm p)28 29 unsigned int vanhaPaiva = pvm p->annaPaiva(); // Paiva talteen30 pvm p->etene(365); // Siirry 365 paivaa eteenpain31 // Karkauspaiva on tullut vastaan, jos 365 paivaa ei ollut koko vuosi32 bool oliKarkauspaiva = (pvm p->annaPaiva() != vanhaPaiva);33 pvm p->etene(-365); // Palauta vanha paiva menemalla takaisin34 return oliKarkauspaiva;35 36 // Jouluunlaskun toteutus

37 int kauankoJouluun(PieniPaivays& pvm)38 39 PieniPaivays joulu;40 joulu.asetaPaivays(24, 12, pvm.annaVuosi()); // Saman vuoden joulu41 return joulu.paljonkoEdella(pvm); // Paljonko joulu on edella?42

LISTAUS 2.3: Esimerkki luokan kaytosta, ppkaytto.cc

Page 69: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

69

Luku 3

Olioiden elinkaari

Bernie: “But I did okay, didn’t I? — I mean I got, what,fifteen thousand years. That’s pretty good. Isn’t it? I lived apretty long time.”

Death %: “You lived what anybody gets, Bernie. — You gota lifetime. — No more. — No less. — You got a lifetime.”

– Brief Lives [Gaiman ja muut, 1994]

“Normaalissa” ei-olio-ohjelmoinnissa muuttujien “elinkaaresta” eiyleensa ole tarpeen erikseen puhua — muuttujia luodaan kun niitatarvitaan, ja kaytetaan niin kauan kuin kaytetaan. Ainoat muistetta-vat asiat ovat muuttujien alustus tarvittaessa seka dynaamisesti vara-tun muistin vapauttaminen. Paikallisten muuttujien kaytto on vielahelpompaa, koska niita ei tarvitse koskaan muistaa vapauttaa.

Olio-ohjelmoinnissa olioiden kayttaminen sen sijaan vaatii enem-man harkintaa. Oliot voivat olla monimutkaisiakin kokonaisuuksiaja niiden “syntyma ja kuolema” saattavat vaatia kaikenlaisia toimen-piteita. Olio-ohjelmoinnin vastuualueajattelun mukaista olisi se, ettaolio itse vastaisi tallaisista toimenpiteista, jotta ne eivat jaisi kayttajanharteille.

Tassa luvussa perehdytaan tarkemmin olioiden elinkaareen liitty-viin kysymyksiin ja katsotaan, miten siihen liittyvat ongelmat on rat-kaistu eri oliokielissa. Erityisesti tutkitaan, millaista tukea C++ antaaolioiden elinkaaren hallintaan.

Page 70: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

3.1. Olion syntyma 70

3.1 Olion syntyma

Olioiden luominen saattaa vaatia monimutkaisiakin toimenpiteita,ennen kuin olio on kayttovalmis. Tyypillisia tallaisia toimenpiteitaovat

• muistin varaaminen (merkkijono-olio varaa syntyessaan tietynminimimaaran muistia)

• toisten olioiden luominen (kirjasto-olio luo syntyessaan tarvit-tavat kortisto-oliot)

• erinaiset rekisteroitymiset (palvelinolio rekisteroi syntyessaanpalvelunsa jarjestelmanlaajuiseen tietokantaan)

• resurssien kayttoonotto (tietokantaolio avaa syntyessaan tieto-kannan sisaltavan tiedoston ja lukee sen muistiin)

• muut toimenpiteet (ikkunaolio piirtaa syntyessaan ikkunanruudulle).

Nama alkutoimenpiteet vastaavat muuttujien alustusta, muttasaattavat oliosta riippuen olla luonteeltaan paljon monimutkaisem-pia. Tietysti ei-olio-ohjelmoinnissakin on taytynyt tehda vastaaviaoperaatioita (esim. tietorakenteen alustus), mutta niiden suorittami-nen on yleensa jaanyt enemman tai vahemman ohjelmoijan itsensavastuulle, ja yksi tyypillinen ohjelmointivirhe on ollut alustusfunk-tiokutsun unohtaminen. Olio-ohjelmoinnissa olisi luontevaa sysataalustustoimenpiteet olion itsensa vastuulle, ja tahan eri oliokielet tar-joavat tukea vaihtelevassa maarin.

Tyypillisesti olion luomiseen liittyvat tehtavat jakautuvat oliokie-lissa kahteen vaiheeseen:

1. olion “datan” (siis jasenmuuttujien) luominen. Jos jasenmuut-tujat ovat itse olioita, niiden luominen on puolestaan jalleenkaksivaiheinen prosessi. . .

2. muut alustustoimenpiteet.

Olion “datan” luominen muistuttaa niin paljon tavallisten muut-tujien luomista, etta kaytannossa kaikki oliokielet tekevat sen au-tomaattisesti olion luomisen yhteydessa. Tahan vaiheeseen kuuluu

Page 71: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

3.2. Olion kuolema 71

muistin varaaminen olion jasenmuuttujille ja mahdollisesti jasen-muuttujien alustus. Muiden alustustoimenpiteiden “automatisoin-nissa” oliokielissa on suuriakin eroja.

3.2 Olion kuolema

Kun oliota on kaytetty ja siita halutaan eroon, taytyy olion suorit-taa yleensa luomiselleen vastakkaiset “siivoustoimenpiteet”. Edelli-sen aliluvun listaa mukaillen tyypillisia siivoustoimenpiteita ovatmuistinvapautus, toisten olioiden tuhoaminen, rekistereista poistu-minen, resurssien vapauttaminen ja vaikkapa ikkunan poistaminenruudulta.

Ei-olio-ohjelmoinnissa tallaiset siivoustoimenpiteet ovat olleetmuuttujan tai tietorakenteen kayttajan vastuulla (ohjelmoijan on pi-tanyt muistaa kutsua siivous- tai vapautusfunktiota ennen muuttujantuhoutumista). Samoin kuin olion luomisessakin, olion tuhoamiseenliittyvat toimenpiteet olisi katevaa saada olion itsensa vastuulle. Erioliokielten tuki tahan vaihtelee aivan kuten olion luomisessakin.

3.3 Olion elinkaaren maaraytyminen

Olion luomisen ja tuhoutumisen yhteydessa suoritettavien toimenpi-teiden lisaksi oliokielissa on eroja sen suhteen, miten olion syntyma-ja varsinkin tuhoutumishetki ja naihin liittyvat toimenpiteeet maa-raytyvat. Tassa oliokielet voi jakaa karkeasti kolmeen ryhmaan:

1. Kieli tarjoaa vain muistin varaamiseen ja vapauttamiseen tar-vittavat operaatiot, ja olion alustaminen ja siivoaminen jaavatohjelmoijan itsensa vastuulle. Muistin vapauttaminen tapahtuujossain kielissa automaattisesti. Usein naissa kielissa on ros-kienkeruu (garbage collection): ohjelmassa on mukana kaanta-jan tuottama koodi, joka osaa etsia turhaksi jaaneet muistialueetja vapauttaa ne.

2. Kieli hoitaa olion tuhoamiseen liittyvat toimenpiteet automaat-tisesti, mutta olion tuhoutumishetki ei ole maaratty, eli se ei oleohjelmoijan kontrolloitavissa. Olion luomisessa ei yleensa oletata vaihtoehtoa, koska useimmissa kielissa olion luominen on(ainakin lahes) aina ohjelmoijan itsensa paatettavissa.

Page 72: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

3.3. Olion elinkaaren maaraytyminen 72

3. Kieli pitaa huolen alustustoimenpiteista oliota luotaessa ja sii-voustoimenpiteista oliota tuhottaessa. Lisaksi olion luomisen jatuhoamisen tapahtumishetki on tarkkaan maaratty.

Oliokielet sisaltavat em. ominaisuuksia vaihtelevissa maarin jaeri tavoin sekoitettuna. Seuraavassa on esitelty joidenkin yleisimpienoliokielten tapoja toteuttaa asiat.

3.3.1 Modula-3

Modula-3 on oliokieli, joka kuuluu miltei puhtaasti kategoriaan 1. Kie-li antaa mahdollisuuden varata oliolle muistia ja tassa yhteydessa an-taa halutuille jasenmuuttujille alkuarvot. Jasenmuuttujien alustuksenlisaksi kieli ei kuitenkaan tue automaattisesti mitaan muita alustus-toimenpiteita, vaan ohjelmoija voi halutessaan itse maaritella alus-tuksen suorittavan jasenfunktion ja kutsua sita omassa koodissaanheti olion luomisen jalkeen.

Samoin olion siivoustoimenpiteet jaavat ohjelmoijan itsensa vas-tuulle. Modula-3:ssa on roskienkeruu, joten olion varaaman muis-tin vapauttaminen tapahtuu automaattisesti. Roskienkeruu ei kuiten-kaan suorita mitaan oliokohtaisia siivoustoimenpiteita, eli tassa mie-lessa Modula-3 vapauttaa automaattisesti vain olion alla olevan muis-tin, kun taas itse olio vain “unohdetaan”. Taman vuoksi ohjelmoijantaytyy itse halutessaan maaritella siivousjasenfunktio ja kutsua sitasopivassa kohdassa ohjelmaa, kun oliota ei enaa tarvita.

Modula-3:n kayttama elinkaarimalli tuo mukanaan paljon vaaroja.Mikali olio ei vaadi mitaan erityisia alustus- ja siivoustoimenpitei-ta, riittaa Modula-3:n tarjoama tuki elinkaarelle taysin. Mikali tallai-sia toimenpiteita kuitenkin tarvitaan, ne jaavat kokonaan ohjelmoi-jan vastuulle, jolloin niiden unohtumisen vaara on suuri. TalloinModula-3:n roskienkeruun tuoma hyoty eliminoituu kokonaan, koskaohjelmoijan taytyy itse pitaa kirjaa siita, milloin oliota ei enaa tarvita,ja kutsua kirjoittamaansa siivousjasenfunktiota.

Listaus 3.1 seuraavalla sivulla nayttaa Modula-3:lla kirjoitetunfunktion naytaViesti, jossa luodaan ja tuhotaan olio. Tassa olionelinkaari kestaa siis funktion suorituksen ajan.

Page 73: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

3.3. Olion elinkaaren maaraytyminen 73

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . OkDialogi.i3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .1 INTERFACE OkDialogi;2 TYPE3 T <: Public;4 Public = OBJECT5 METHODS (* Esitellaan olion julkinen rajapinta *)6 alusta( viesti : TEXT );7 siivoa();8 odotaOKta();9 END; (* Public *)

10 (* Moduulin proseduuri, joka tulostaa viestin ja odottaa OK-nappia *)11 PROCEDURE naytaViesti();12 END OkDialogi.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . OkDialogi.m3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .1 MODULE OkDialogi;2 REVEAL3 T = Public BRANDED OBJECT4 (* Tahan paikalliset muuttujat *)5 OVERRIDES (* Maaritellaan olion tarjoamat palvelut *)6 alusta := Alustus;7 siivoa := Siivous;8 odotaOKta := OKodotus;9 END; (* T *)

1011 PROCEDURE Alustus( self : T; viesti : TEXT ) =12 BEGIN (* Tanne ikkunan avaaminen, viestin kirjoittaminen siihen13 ja muut luomistoimenpiteet *)14 END Alustus;1516 PROCEDURE Siivous( self : T ) =17 BEGIN18 (* Tanne ikkunan sulkeminen ja muut siivoustoimenpiteet *)19 END Siivous;2021 PROCEDURE OKodotus( self : T ) =22 BEGIN23 (* Tanne nappulan painamisen odottamisen koodi *)24 END OKodotus;2526 PROCEDURE naytaViesti() =27 VAR dialogi := NEW(T); (* Varataan oliolle muistialue *)28 BEGIN29 dialogi.alusta("Virhe!"); (* Alustetaan olio *)30 dialogi.odotaOKta(); (* Odota, etta kayttaja kuittaa viestin *)31 dialogi.siivoa(); (* Olion siivoustoimenpiteet *)32 (* proseduurista palattaessa roskienkeruu pitaa huolen olion muistista *)33 END naytaViesti;3435 BEGIN36 (* Moduulin alustuskoodi *)37 END OkDialogi.

LISTAUS 3.1: Esimerkki olion elinkaaresta Modula-3:lla

Page 74: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

3.3. Olion elinkaaren maaraytyminen 74

3.3.2 Smalltalk

Smalltalk-kielessa olioiden elinkaari on olioiden tuhoutumisen kan-nalta samanlainen kuin Modula-3:ssa eli olioille ei suoriteta auto-maattisesti mitaan siivoustoimenpiteita ja roskienkeruu pitaa huolenolion varaaman muistin vapauttamisesta. Sen sijaan olioiden luomi-sen yhteydessa Smalltalk antaa mahdollisuuden jonkinasteiseen auto-matisointiin.

Smalltalkissa olioita luodaan antamalla luokalle kasky new. Tamapuolestaan luo uuden olion ja palauttaa sen paluuarvonaan. Luomis-kaskylle voi antaa parametreja ja sen sisaltama koodi voi suorittaaolion alustustoimenpiteita, joten ohjelmoijan ei tarvitse itse muistaakutsua alustusjasenfunktiota luomisen yhteydessa.

Listaus 3.2 seuraavalla sivulla sisaltaa esimerkin olion elinkaares-ta Smalltalkilla kirjoitettuna (Smalltalkissa ei oikeastaan ole “ohjelma-listauksen” kasitetta, listauksessa on vain kirjoitettu tarvittavat koo-dilohkot perakkain).

3.3.3 Java

Java-kielessa olioiden elinkaari on hieman kaksipiippuinen. Olioitaluotaessa Java tarjoaa mahdollisuuden alustustoimenpiteisiin raken-tajaksi kutsutun jasenfunktion avulla. Tata jasenfunktiota kutsutaanautomaattisesti oliota luotaessa. Sen sijaan olion siivoustoimenpiteetovat ongelmallisempia.

Myos Javassa on roskienkeruu, eli kaytosta poistuneiden olioidenviema muistitila ei tuota ongelmia. Javassa ohjelmoijalla ei ole mitaanmahdollisuutta ilmoittaa, milloin olion elinkaari loppuu, vaan ros-kienkeruualgoritmi paattelee itse, milloin olio on tarpeeton eli mil-loin siihen ei ole ulkopuolisia viitteita. Kieli ei kuitenkaan maarittele,kuinka pian olion tarpeettomaksi tulon jalkeen roskienkeruu suorite-taan.

Periaatteessa Java antaa ohjelmoijalle mahdollisuuden kirjoittaans. finalize-jasenfunktioita, joita roskienkeruu kutsuu ennen kuinolio siivotaan muistista. Tama mekanismi ei kuitenkaan ole aina kayt-tokelpoinen, koska ohjelmoija ei voi tietaa, milloin roskienkeruu sat-tuu finalize-jasenfunktiota kutsumaan. On myos mahdollista, etta oh-jelman suoritus loppuu ennen kuin roskienkeruu siivoaa olion muis-tista, jolloin finalizea ei kutsuta ollenkaan.

Page 75: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

3.3. Olion elinkaaren maaraytyminen 75

1 Object subclass: #OkDialogi2 instanceVariableNames: ’viesti ’3 classVariableNames: ’’4 poolDictionaries: ’’5 category: ’Oliokirja’67 alusta: aViesti8 “OkDialogi-olion luomistoimenpiteet“9 viesti := aViesti.

10 “Tanne ikkunan avaaminen, viestin kirjoittaminen siihen ja muut11 luomistoimenpiteet”12 ^ self1314 new: aViesti15 “OkDialogi-olion alusta-jasenfunktion kutsumiseen tarvittava kikka“16 ^ super new alusta: aViesti1718 odotaOKta19 “Taalla odotetaan OK-napin painamista”2021 siivoa22 “OkDialogi-olion siivoustoimenpiteet“23 “Tanne ikkunan sulkeminen ja muut siivoustoimenpiteet”2425 naytaViesti26 “Funktio joka tulostaa viestin ja odottaa OK-nappia”27 | dialogi | “Paikallinen muuttuja”28 dialogi := OkDialogi new: ’Virhe!!’. “Olio syntyy, luomistoimenpiteet”29 dialogi odotaOKta. “Odota, etta kayttaja kuittaa viestin”30 dialogi siivoa “Suorita siivoustoimenpiteet”31 “Funktiosta palattaessa roskienkeruu pitaa huolen olion muistista”

LISTAUS 3.2: Esimerkki olion elinkaaresta Smalltalkilla

Nain ollen Javassa olion alustustoimenpiteet suoritetaan auto-maattisesti, mutta jos siivoustoimenpiteet vaativat muistin vapautta-mista monimutkaisempia asioita, ohjelmoija joutuu kaytannossa koo-daamaan oman siivousjasenfunktionsa ja kutsumaan sita itse sellai-sessa ohjelman kohdassa, jossa tietaa olion tulleen tarpeettomaksi.Listaus 3.3 seuraavalla sivulla sisaltaa esimerkin olion elinkaarestaJavalla kirjoitettuna.

Page 76: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

3.3. Olion elinkaaren maaraytyminen 76

1 public class OkDialogi // OkDialogi-luokan esittely2 3 public OkDialogi(String viesti)4 // Tanne ikkunan avaaminen, viestin kirjoittaminen siihen5 // ja muut luomistoimenpiteet6 78 public void siivoa()9 // Tanne ikkunan sulkeminen ja muut siivoustoimenpiteet

10 1112 public void odotaOKta()13 // Tahan nappulan painamisen odottamisen koodi14 1516 // Tahan luokan loput jasenfunktiot ja sisainen toteutus1718 // Luokkafunktio joka tulostaa viestin ja odottaa OK-nappia19 public static void naytaViesti()20 21 // Olio luodaan, luomistoimenpiteet22 OkDialogi dialogi = new OkDialogi("Virhe!");23 dialogi.odotaOKta(); // Odota, etta kayttaja kuittaa viestin24 dialogi.siivoa(); // Olion siivoustoimenpiteet25 // Funktiosta palattaessa roskienkeruu pitaa huolen olion muistista26 27

LISTAUS 3.3: Esimerkki olion elinkaaresta Javalla

3.3.4 C++

C++ sisaltaa vaihtelevantasoisen tuen olioiden elinkaaren hallintaan.Tietyissa tapauksissa oliot tuhoutuvat automaattisesti, toisissa tu-hoaminen taas jatetaan ohjelmoijan vastuulle. Kielessa voi Javan ta-paan maaritella olion alustustoimenpiteet suorittavan rakentajajasen-funktion, jota kutsutaan automaattisesti oliota luotaessa. VastaavastiC++:ssa on mahdollista kirjoittaa purkaja, joka on jasenfunktio, jokasisaltaa kaikki siivoustoimenpiteet ja jota C++ kutsuu automaattisestioliota tuhottaessa. C++:n rakentajista ja purkajista kerrotaan enemmanaliluvussa 3.4.

Page 77: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

3.3. Olion elinkaaren maaraytyminen 77

Staattinen elinkaari

C++:ssa on kahdentyyppisia olioiden elinkaaria. Olioilla, jotka luodaantavallisen muuttujan tapaan esimerkiksi funktion paikallisiksi muut-tujiksi, globaaleiksi muuttujiksi tai olion jasenmuuttujiksi, on staat-tinen elinkaari. Tama tarkoittaa, etta olion syntyma- ja tuhoutumis-hetki on maaratty jo kaannosaikana ja kaantaja osaa automaattisestisuorittaa tarvittavat luomistoimenpiteet olion syntyessa ja vastaavas-ti siivoustoimenpiteet heti olion tuhoutuessa.

Listaus 3.4 seuraavalla sivulla sisaltaa esimerkin olion elinkaa-resta C++:lla kirjoitettuna. Riveilla 20–26 on esimerkki staattisen elin-kaaren kayttamisesta. OkDialogi-olio dialogi luodaan aivan kuten ta-vallinen paikallinen muuttuja, ja sen rakentajalle annetaan luomisenyhteydessa tarvittavat parametrit, tassa tapauksessa dialogin teksti.

Koska dialogi on funktion paikallinen muuttuja, sen elinkaari ra-jautuu funktion sisalle. Funktion lopussa rivilla 26 olio tuhotaan au-tomaattisesti ja sen purkajaa kutsutaan siivoustoimenpiteita varten.Jos tallaisia paikallisia olioita on useita, ne tuhotaan kaanteisessa jar-jestyksessa niiden luomisjarjestykseen nahden.

Staattisen elinkaaren kayttaminen on C++:lla ohjelmoitaessa suosi-teltavaa aina, kun se on mahdollista, koska talloin ohjelma pitaa auto-maattisesti huolen olioiden tuhoamisesta elinkaaren lopussa. Talloinei ole vaaraa muistivuodoista eika siivoustoimenpiteiden unohtumi-sesta.

Paikallisten muuttujien, globaalien muuttujien ja olioiden jasen-muuttujien lisaksi funktioiden parametreilla, luokkien luokkamuut-tujilla ja kaantajan luomilla valiaikaisolioilla on C++:ssa staattinenelinkaari. Ohjelmoijan ei itse tarvitse — eika han voikaan — huoleh-tia tallaisten olioiden tuhoamisesta, vaan kaantaja itse tuhoaa oliotniiden elinkaaren paattyessa.

Staattinen elinkari helpottaa ohjelmoijan tyota, kun kaantaja pitaahuolen olioiden tuhoamisesta. Tasta huolimatta staattisenkaan elin-kaaren kayttaminen ei saisi tuudittaa vaaraan turvallisuuden tuntee-seen. C++:ssa on mahdollista tehda vakavia ohjelmointivirheita staat-tisen elinkaarenkin avulla. Jos funktio esimerkiksi palauttaa paluuar-vonaan osoittimen (tai viitteen) paikalliseen muuttujaan, ehtii tamamuuttuja tuhoutua ennen kuin kutsuja pystyy kayttamaan osoitin-ta. Tuloksena on viallinen osoitin, jonka lapi viittaamisen vaikutuk-

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .Ethan kayta globaaleja muuttujia, ethan?/

Page 78: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

3.3. Olion elinkaaren maaraytyminen 78

1 // OkDialogi-luokan esittely2 class OkDialogi3 4 public:5 OkDialogi(string const& viesti);6 ~OkDialogi();7 void odotaOKta();8 // Tahan luokan loput jasenfunktiot ja sisainen toteutus9 ;

10 // OkDialogi-olion luomistoimenpiteet11 OkDialogi::OkDialogi(string const& viesti)12 // Tanne ikkunan avaaminen, viestin kirjoittaminen siihen13 // ja muut luomistoimenpiteet14 15 // OkDialogi-olion siivoustoimenpiteet16 OkDialogi::~OkDialogi()17 // Tanne ikkunan sulkeminen ja muut siivoustoimenpiteet18 19 // Funktio joka tulostaa viestin ja odottaa OK-nappia

20 void naytaViesti1() // Staattisella dialogin elinkaarella21 22 OkDialogi dialogi("Virhe!"); // Olio syntyy, luomistoimenpiteet23 dialogi.odotaOKta(); // Odota, etta kayttaja kuittaa viestin24 // Funktion loppuessa dialogin elinkaari paattyy, siivoustoimenpiteet25 // suoritetaan ja olio tuhoutuu26 27 // Dynaamisella dialogin elinkaarella (typeraa)28 void naytaViesti2()29 30 OkDialogi* dialogip = new OkDialogi("Virhe!"); // Luomistoimenpiteet31 dialogip->odotaOKta(); // Odota, etta kayttaja kuittaa viestin32 delete dialogip; dialogip = 0; // Tuhoaminen ja siivoustoimenpiteet33

LISTAUS 3.4: Esimerkki olion elinkaaresta C++:lla

Page 79: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

3.4. C++: Rakentajat ja purkajat 79

set ovat taysin maarittelemattomat. Sen vuoksi staattisen elinkaarenkaytossa onkin tarkeaa, etta ohjelmoija todella tiedostaa sen, etta oliotuhoutuu automaattisesti tietyssa kohtaa ohjelmaa.

Dynaaminen elinkaari

Toinen mahdollisuus on jattaa olion elinkaaresta huolehtiminen oh-jelmoijan itsensa vastuulle. Talloin olio ei automaattisesti tuhouduollenkaan, vaan se taytyy erikseen tuhota. Tallainen on usein tar-peen, jos olio luodaan yhdessa funktiossa ja tuhotaan toisessa. Myosdynaaminen sitominen ja polymorfismi (aliluku 6.5) vaativat useindynaamisen elinkaaren kayttoa.

Listauksen 3.4 riveilla 27–33 oleva funktio kayttaa dialogissa dy-naamista elinkaarta. Olion elinkaari saadaan dynaamiseksi luomallaolio new-operaattorilla. Parametreikseen new ottaa luotavan olion luo-kan seka rakentajan parametrit. Taman jalkeen se luo uuden olion,suorittaa alustustoimenpiteet ja palauttaa osoittimen olioon. Oliotakaytetaan taman jalkeen ko. osoittimen lapi. Kun oliosta halutaanpaasta eroon, taytyy se erikseen tuhota osoittimen paasta operaatto-rilla delete, joka suorittaa olion siivoustoimenpiteet ja tuhoaa olion.Naita operaattoreita kasitellaan tarkemmin aliluvussa 3.5.

Dynaamisen elinkaaren kaytto on “vaarallisempaa” kuin staatti-sen, koska ohjelmoijan taytyy itse pitaa huoli siita, etta kaikki oliottuhotaan, kun niita ei enaa tarvita. Erityisen vaikeaa tama on virheti-lanteiden sattuessa, jolloin on vaikeaa muistaa, mitka oliot on jo luotuja taytyy nain ollen tuhota. Varsinkin poikkeusmekanismia (luku 11)kaytettaessa tama on vaikeaa. C++-standardi tarjoaa auto ptr-tyypin,joka helpottaa jonkin verran dynaamisen elinkaaren olioiden hallin-taa. Siita kerrotaan aliluvussa 11.7.

3.4 C++: Rakentajat ja purkajat

Kuten edella on kaynyt ilmi, olioiden luominen ja tuhoaminen eroaatavallisten muuttujien luomisesta ja tuhoamisesta siina, etta olioidenluominen ja tuhoaminen saattaa vaatia alustus- ja siivoustoimenpitei-ta luokasta riippuen. C++:ssa tama on toteutettu niin, etta joka luokallaon kaksi erityisjasenfunktiota, rakentaja ja purkaja, joita kutsutaanautomaattisesti aina olion luomisen ja tuhoamisen yhteydessa.

Page 80: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

3.4. C++: Rakentajat ja purkajat 80

3.4.1 Rakentajat

Rakentaja (constructor) on jasenfunktio, jonka tehtavana on hoitaakaikki uuden olion alustamiseen liittyvat toimenpiteet. Siita kayte-taan joskus myos nimitysta “muodostin”. Rakentajan suorittamia toi-menpiteita ovat ainakin jasenmuuttujien alustaminen, olioon kuulu-vien olion ulkopuolisten tietorakenteiden ja olioiden luominen sekamahdollisesti olion rekisteroiminen jonnekin yms.

Kaantaja tunnistaa rakentajan sen nimesta: rakentajan nimi on ai-na sama kuin luokan nimi. Rakentaja voi saada valinnaisen maaranparametreja, mikali niita tarvitaan olion alustamiseen. Listaus 3.5nayttaa Paivays-luokan rakentajan, joka saa parametreinaan uudenpaivayksen paivan, kuukauden ja vuoden. Rakentaja ei koskaan pa-lauta mitaan paluuarvoa, ja tasta johtuen rakentajan esittelyssa jamaarittelyssa ei paluutyyppia merkita lainkaan — ei edes avainsa-nalla void.

Rakentajan esittely luokan esittelyn yhteydessa ei eroa tavalli-sen jasenfunktion esittelysta muuten kuin paluutyypin puuttumisenosalta. Sen sijaan sen maarittely on hieman tavallisuudesta poikkea-

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . paivays.hh . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .1 class Paivays2 3 public:

4 Paivays(unsigned int p, unsigned int k, unsigned int v);...

22 ;. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . paivays.cc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

6 Paivays::Paivays(unsigned int p, unsigned int k, unsigned int v)7 : paiva (p), kuukausi (k), vuosi (v)8 // Ei mitaan tehtavaa taalla9

LISTAUS 3.5: Paivays-luokan rakentaja

Page 81: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

3.4. C++: Rakentajat ja purkajat 81

va. Rakentajan maarittely on muotoa

Luokannimi::Luokannimi(Parametrilista): jmuutt1(alkuarvo1), jmuutt2(alkuarvo2), . . . // Alustuslista

// Muut alustustoimenpiteet

Alustuslista (initialization list) sisaltaa luettelon luokan jasen-muuttujista ja jokaisen jasenmuuttujan perassa suluissa jasenmuut-tujan alustamiseen tarvittavat alkuarvot. Joissain C++-oppikirjoissaalustuslistaa ei kayteta lainkaan, vaan jasenmuuttujiin sijoitetaan ar-vot rakentajan koodilohkossa. Tama ei ole hyvaa tyylia (sijoituksenja alustuksessa kaytettavan kopioinnin eroja kasitellaan myohemminaliluvussa 7.2.1).

Rakentajan runkoon (siis aaltosulkujen sisalle) voi kirjoittaa muu-ta olion alustamiseen tarvittavaa koodia. Varsin usein kay kuitenkinniin, etta runko jaa tyhjaksi, koska luokan oliot eivat tarvitse muitaalustustoimenpiteita kuin jasenmuuttujien alustamisen.

Luokalla voi olla useita vaihtoehtoisia rakentajia, kunhan ne vainsaavat eri maaran tai eri tyyppisia parametreja niin, etta olion luo-misen yhteydessa kaantaja voi parametrien perusteella paatella, mitarakentajaa tulee kutsua.]

Jasenmuuttujien alustaminen

Mikali jasenmuuttuja on jotakin perustyyppia, kuten int, annetaansille alustuslistassa yksinkertaisesti alkuarvo kuten listauksen 3.5 esi-merkissa. Jos taas jasenmuuttuja on olio, annetaan alustuslistassa ja-senmuuttujan nimen peraan sen rakentajan tarvitsemat parametrit— aivan kuten normaalissa olion maarittelyssa olion nimen peraantulevat suluissa rakentajan parametrit. Listaus 3.6 seuraavalla sivul-la nayttaa yksinkertaisen Henkilo-luokan rakentajan, jossa alustetaanPaivays-tyyppinen jasenmuuttujaolio syntymapvm .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .]Tata kielen ominaisuutta, jossa ohjelmassa voi olla useita samannimisia funktioita, jotka

eroavat toisistaan parametrien maaran tai tyyppien perusteella, kutsutaan funktioiden kuor-mittamiseksi (overloading). C++:ssa lahes mita tahansa funktioita voi kuormittaa, kunhan vainfunktiokutsun yhteydessa kaantaja pystyy parametreista paattelemaan oikean version funk-tiosta.

Page 82: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

3.4. C++: Rakentajat ja purkajat 82

1 class Henkilo2 3 public:4 Henkilo(string const& nimi, unsigned int syntymavuosi,5 unsigned int syntymakk, unsigned int syntymapv);

...6 private:7 string nimi ;8 Paivays syntymapvm ;

...9 ;

...10 Henkilo::Henkilo(string const& nimi, unsigned int syntymavuosi,11 unsigned int syntymakk, unsigned int syntymapv)12 : nimi (nimi), syntymapvm (syntymapv, syntymakk, syntymavuosi)13 14 // Tanne loput henkilon alustuksesta15

LISTAUS 3.6: Esimerkki rakentajasta olion ollessa jasenmuuttujana

Oletusrakentaja

Oletusrakentajaksi (default constructor) kutsutaan rakentajaa, jokaei saa yhtaan parametria. Sen taytyy siis pystya alustamaan olio il-man ulkopuolista tietoa, joten on katevaa ajatella, etta sen avulla luo-daan “oletusarvoinen” olio. Oletusrakentajaa kutsutaan seuraavissatapauksissa:

• Jos olio luodaan ilman etta sen rakentajalle annetaan paramet-reja. Huomaa, etta talloin myos parametrien ymparilla normaa-listi olevat sulut jaavat pois:

Lista l; // Luodaan tyhja lista, kutsutaan oletusrakentajaa

• Jos jasenmuuttujana olevan olion alustus unohtuu “isantaolion”rakentajan alustuslistasta, alustetaan jasenmuuttujaolio kayt-taen oletusrakentajaa, mikali sellainen on olemassa (muutenannetaan virheilmoitus). Se, etta alustuksen unohtamisesta eitule virheilmoitusta, on yksi syy valttaa oletusrakentajia.

Page 83: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

3.4. C++: Rakentajat ja purkajat 83

Perustyyppia olevilla muuttujilla ei ole oletusrakentajaa, jotentyyliin int i; esitellyt muuttujat jaavat alustamatta, kuten C-kieles-sakin. Samoin kay perustyyppisille jasenmuuttujille, joita ei aluste-ta rakentajassa. Taman vuoksi on erittain tarkeaa, etta kaikki perus-tyyppia olevat (jasen)muuttujat alustetaan aina johonkin jarkevaanalkuarvoon!

Oletusrakentajan toinen erityisominaisuus on, etta jos luokalle eiole kirjoitettu yhtakaan rakentajaa, tekee kaantaja sille automaatti-sesti tyhjan oletusrakentajan, joka ei tee mitaan muuta kuin alustaakaikki jasenmuuttujat niiden oletusrakentajilla (jos tama ei onnistu,annetaan virheilmoitus). Koska kaantajan itse tekemat rakentajat har-voin tekevat sita mita halutaan, kannattaa jokaiseen luokkaan kirjoit-taa aina oma rakentaja.

Oletusrakentajaa kaytetaan myos silloin, kun luodaan C++:n perus-taulukko, joka sisaltaa olioita:

Lista listaTaulukko[10]; // Luodaan 10 tyhjan listan taulukko

Talloin jokainen taulukon alkio alustetaan kayttaen oletusrakentajaa.Mikali luokalla ei ole oletusrakentajaa, ei siita voi myoskaan teh-da tallaisia taulukoita. Tama ongelma ratkeaa helpoiten kayttamal-la STL:n vector-luokkaa, johon oikein alustetut oliot voi lisata yksikerrallaan vaikkapa silmukassa (vector-luokasta on lyhyt esittely liit-teen A aliluvussa A.3).

Kopiorakentaja

Kopiorakentaja (copy constructor) on rakentaja, joka saa parametri-naan viitteen jo olemassa olevaan saman luokan olioon. Sen tehtava-na on luoda identtinen kopio parametrina saadusta oliosta, ja kaanta-ja kutsuu sita automaattisesti tietyissa tilanteissa, joissa kopion luo-minen on tarpeen. Kopiorakentajaa kasitellaan tarkemmin aliluvus-sa 7.1.2.

3.4.2 Purkajat

Purkajaksi (destructor) kutsutun jasenfunktion tehtavana on suorit-taa tarvittavat siivoustoimenpiteet olion tuhoutuessa. Tallaisia ovat

Page 84: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

3.4. C++: Rakentajat ja purkajat 84

muiden muassa olioon kuuluvien olion ulkopuolisten tietorakentei-den ja olioiden tuhoaminen seka tiedostojen sulkeminen. Purkajastakaytetaan myos nimityksia “hajotin” ja “havitin”.

Kuten rakentajankin tapauksessa, kaantaja tunnistaa purkajan sennimesta. Purkajan nimi alkaa matomerkilla ‘~’, jonka peraan tuleeheti luokan nimi.^ Purkaja ei saa parametreja, eika sen esittelyyn eikamaarittelyyn merkita paluuarvoa kuten ei rakentajiinkaan. Listaus 3.7nayttaa paivaysluokan purkajan esimerkkina purkajan syntaksista.

Kun olion tuhoutumisen aika tulee, kaantaja pitaa itse automaatti-sesti huolen olion jasenmuuttujien tuhoamisesta. Nain ohjelmoijan eitarvitse purkajaa kirjoittaessaan huolehtia tasta. Olion jasenmuuttu-jat tuhotaan vasta purkajan varsinaisen koodin suorituksen jalkeen,joten itse purkajan koodissa jasenmuuttujiin voi viitata normaalis-ti. On kuitenkin huomattava, etta automaattinen tuhoaminen koskeevain olion omia jasenmuuttujia, ei esimerkiksi dynaamisesti osoitti-mien paahan luotuja olioita.

Purkajien toiminta on muutenkin varsin automaattista. Ohjelmoi-jan ei koskaan tarvitse itse kutsua luokan purkajaa, vaan kaantaja pi-taa huolen purkajan kutsumisesta, kun olio tuhotaan. Tama tapahtuumm. seuraavissa tapauksissa:. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .^Lausekkeessa esiintyessaanhan matomerkki ‘~’ on C++:ssa “bitti-not”-operaattori, jolla

kaannetaan luvun bitit painvastaisiksi. Niinpa purkajan nimi ikaan kuin kuvaa sita, etta se onrakentajalle vastakkainen operaatio. . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . paivays.hh . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .1 class Paivays2 3 public:

...5 ~Paivays();

...22 ;

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . paivays.cc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .11 Paivays::~Paivays()12 // Ei siivottavaa13

LISTAUS 3.7: Paivays-luokan purkaja

Page 85: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

3.5. C++: Dynaaminen luominen ja tuhoaminen 85

• Paikallisten olioiden purkajia kutsutaan automaattisesti juuriennen kuin niiden elinkaari (eli nakyvyysalue) loppuu koodi-lohkon loppuaaltosulun kohdalla. Huomaa, etta tama tarkoittaasita, etta paikalliset oliot tuhotaan vasta return-lauseen jalkeen.

• Globaalien olioiden purkajia kutsutaan main-funktion loputtua.

• Funktion parametrioliot tuhoutuvat aivan kuten funktion pai-kalliset oliot.

• Jasenmuuttujina olevien olioiden purkajia kutsutaan, kun nii-den “isantaolio” tuhoutuu.

• Dynaamisesti luotujen olioiden purkajia (katso seuraava alilu-ku) kutsutaan, kun oliota tuhotaan deletella.

• Taulukossa olevien olioiden purkajia kutsutaan, kun taulukkotuhoutuu.

Mikali olion tuhoutumisessa ei tarvita mitaan erityisia siivous-toimenpiteita, luokalle ei periaatteessa ole pakko kirjoittaa purka-jaa. Tallaisessakin tapauksessa kannattaa kuitenkin kirjoittaa luokalletyhja purkaja, koska muutoin koodin lukijalla heraa helposti epailys,etta luokan kirjoittaja on vain unohtanut kirjoittaa purkajan.

3.5 C++: Dynaaminen luominen ja tuhoaminen

Kuten aiemmin tassa luvussa on jo todettu, staattisen elinkaaren kayt-to C++-ohjelmoinnissa on suotavaa, koska talloin kaantaja pitaa huolensiita, etta kaikki luodut oliot aikanaan myos tuhotaan. Varsin useintulee kuitenkin tarve luoda olioita, joiden elinkaarta ei voida kaan-nosaikana rajata tiettyyn osaan koodia. Tallaiset oliot taytyy luodadynaamisella elinkaarella, jolloin kaikki vastuu olioiden tuhoamises-ta siirtyy ohjelmoijalle. Tassa luvussa “olion luomista dynaamisellaelinkaarella” kutsutaan yksinkertaisesti “olion dynaamiseksi luomi-seksi”.

Muistivuodot ovat jo pitkaan olleet tyypillisia ohjelmissa, joissaohjelmoija on unohtanut vapauttaa dynaamisesti varaamansa muis-tin. Usein muistivuotoihin kuitenkin suhtaudutaan vahattelevasti,koska “kayttojarjestelma kuitenkin vapauttaa muistin ohjelman lop-puessa”. Olio-ohjelmoinnissa tilanne on kuitenkin vakavampi, koska

Page 86: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

3.5. C++: Dynaaminen luominen ja tuhoaminen 86

oliolla on yleensa purkaja, jonka koodi suoritetaan olion tuhoamisenyhteydessa. Mikali nyt ohjelmoija unohtaa tuhota dynaamisesti vara-tun olion, ei purkajaa koskaan suoriteta! Nyt siis pelkan muistivuo-don lisaksi myos osa ohjelmakoodista jaa suorittamatta — mahdolli-sesti vakavin seurauksin.

Esimerkiksi kelpaa mainiosti tietokantaolio, joka sailyttaa tieto-kantaa tiedostossa. On hyvin mahdollista, etta olion koodi on kirjoi-tettu optimoidusti niin, etta olio puskuroi tietoa muistiin ja paivittaatiedostossa olevaa tietokantaa vain tietyin valiajoin. Jos nyt tallainentietokantaolio on luotu dynaamisesti ja se unohdetaan tuhota, senpurkaja jaa suorittamatta ja muistiin puskuroitu tieto paivittamattatiedostoon.

Olioiden dynaamiseen luomiseen liittyy muitakin ongelmia. Niis-ta yleisimpia ovat

• olion tuhoamisen unohtaminen

• muistin loppuminen (olion luomisen epaonnistuminen)

• olion tuhoaminen kahteen kertaan

• jo tuhotun olion kayttaminen (tama vaara on muuallakin kuindynaamisen elinkaaren yhteydessa)

• sekoilut C++:n taulukkojen tuhoamisessa (aliluku 3.5.3).

Kaikista naista vaaroista huolimatta olioiden dynaaminen luomi-nen on varsin usein tarpeellista C++:lla ohjelmoitaessa. Sita kaytettaes-sa on vain oltava erittain huolellinen.

3.5.1 new

Olion luominen dynaamisesti tapahtuu syntaksilla

new Tyyppinimi(alustusparametrit)

Operaattori new luo uuden (nimettoman) olion ja palauttaa osoitti-men siihen. Esimerkiksi uuden paivaysolion ja uuden kokonaisluvunvoi luoda seuraavasti:

Paivays* pvm p = new Paivays(1,1,1000);int* luku p = new int(5); // Uusi int, alkuarvona 5

Page 87: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

3.5. C++: Dynaaminen luominen ja tuhoaminen 87

Mikali uudelle oliolle ei saada varatuksi riittavasti muistia, heittaanew poikkeuksen, jonka tyyppi on std::bad alloc. Taman poikkeuk-sen sieppaaminen vaatii otsikkotiedoston <new> lukemista.

. . . . . . . . Ekskursio: Poikkeukset (lisaa luvussa 11) . . . . . . . .Poikkeukset (exception) ovat C++:ssa uusi tapa hoitaa ohjelmanvirhetilanteita. Kun ohjelmassa havaitaan virhe, virheen ha-vainnut koodinkohta heittaa (throw) “ilmaan” poikkeusolion,jonka ylemmilla ohjelman kutsutasoilla olevat virhekasittelijatvoivat siepata (catch).

Yksinkertaisimmillaan poikkeusmekanismi nayttaa seuraavan-laiselta:

1 try2 3 // Tanne koodi, jossa syntyvia virhetilanteita tarkkaillaan4 5 catch (VirheenTyyppi& vo)6 // Virheolio vastaanotetaan ‘‘parametrina’’7 // Tanne virhekasittelykoodi, joka voi kayttaa virheoliota vo8 9 // Taalta jatketaan

Kun tallainen rakenne tulee ohjelmassa vastaan, ohjelman suo-ritus siirtyy suoraan try-lohkon sisalle, jossa jatketaan normaa-listi. Mikali virhetta ei lohkon sisalla tapahdu, hypataan catch-lohkon yli ja jatketaan ohjelman suoritusta koko try-catch-ra-kenteen jalkeen.

Jos try-lohkon sisalla heitetaan poikkeus, tarkastetaan kelpaa-ko se tyyppinsa perusteella catch-lohkon parametriksi. Jos sekelpaa, siirtyy ohjelman suoritus catch-lohkon sisaan. Kun ta-man lohkon koodi on suoritettu, ohjelman suoritus jatkuu try-catch-rakenteen jalkeisesta ohjelmakoodista. Tama tarkoittaasita, etta try-lohkoon ei enaa palata virhekasittelyn jalkeen.

Virhekasittelija voi myos lopuksi heittaa virheen uudelleen il-maan komennolla throw;, jos virheesta ei voida toipua koko-naan eli virhe halutaan valittaa viela ylemmas ohjelmassa. Jospoikkeusta ei oteta kiinni missaan, keskeytetaan ohjelman suo-ritus virheilmoitukseen.

Ylla oleva poikkeuskasittelyn kuvaus on pahasti puutteellinenmutta riittaa tassa vaiheessa.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Page 88: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

3.5. C++: Dynaaminen luominen ja tuhoaminen 88

Muistin loppumiseen tulisi aina varautua. Tyypillinen esimerk-ki dynaamisesta olion luomisesta ja muistin loppumisen hallinnastaloytyy riveilta 12–21 listauksesta 3.8.

Joskus poikkeukset ovat turhan raskas tapa varautua muistin lop-

1 #include "paivays.hh"23 #include <new>4 #include <cstdlib>5 #include <iostream>6 using std::cout;7 using std::cerr;8 using std::endl;9

10 int main()11 12 Paivays* joulu p = 0; // Nollataan kaiken varalta13 try14 15 joulu p = new Paivays(24,12,1999); // Poikkeus, jos muisti loppuu16 17 catch (std::bad alloc&)18 // Virhekasittely, jos muisti loppui19 cerr << "Muisti loppui, lopetan!" << endl;20 return EXIT FAILURE;21 2223 Paivays* vappu p = new(std::nothrow) Paivays(1,5,2000);24 if (vappu p == 0)25 26 // Virhekasittely, jos muisti loppui27 cerr << "Muisti loppui, lopetan!" << endl;28 delete joulu p; joulu p = 0; // Tuhotaan jo varattu olio!29 return EXIT FAILURE;30 3132 // Taalta jatketaan, jos virhetta ei sattunut33 cout << "Joulun ja vapun valissa paivia on "34 << joulu p->paljonkoEdella(*vappu p) << endl;3536 delete vappu p; vappu p = 0; // Dynaamisesti luodut oliot tuhottava37 delete joulu p; joulu p = 0;38

LISTAUS 3.8: Esimerkki olion dynaamisesta luomisesta new’lla

Page 89: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

3.5. C++: Dynaaminen luominen ja tuhoaminen 89

pumiseen. Tallaisia tilanteita varten new-operaattorista on myos ole-massa versio new(std::nothrow), joka muistin loppuessa ei heitapoikkeusta vaan palauttaa nollaosoittimen. Ko. operaattorin kayttovaatii myos otsikkotiedoston <new> lukemista. Tietysti muistin lop-pumiseen taytyy varautua tassakin tapauksessa. Listauksen 3.8 ri-veilla 23–30 on esimerkki tasta. Vanhoissa C++-kielen versioissa nolla-osoittimen palauttaminen oli new’n oletustoiminto, koska poikkeuksiaei viela ollut.

Yleensa olioiden dynaamiseen luomiseen kannattaa kayttaa “nor-maalia” poikkeuksen heittavaa new’ta, koska talloin ohjelman suoritusainakin keskeytyy, jos muistin loppumiseen ei varauduta. Nollaosoit-timen palauttava nothrow-versio sen sijaan jatkaa ohjelman suoritus-ta, kunnes ohjelma jossain vaiheessa todennakoisesti sekoaa, kun seyrittaa kayttaa olematonta oliota. Tassa vaiheessa saattaa kuitenkinolla todella vaikea paikallistaa, mista virhe oikeastaan on johtunut.

3.5.2 delete

Ohjelmoijan taytyy itse muistaa tuhota new’lla luomansa oliot. Ta-ma tapahtuu operaattorilla delete, jolle annetaan operandina osoitinnew’lla varattuun olioon. Operaattori delete tuhoaa kyseisen olion javapauttaa sen kayttaman muistitilan. Tuhoamisen yhteydessa olionpurkajaa kutsutaan normaalisti (periytymista kaytettaessa luokanpurkajan tulee olla virtuaalinen, katso aliluku 6.5.5). Jos deletelle an-taa olioon osoittavan osoittimen sijaan nollaosoittimen, ei kyseessaole virhe. Talloin delete ei vain tee mitaan.

Koska olion tuhoamisen jalkeen deletelle annettu osoitin ei enaaosoita mihinkaan jarkevaan, kannattaa sen arvoksi asettaa nolla. Ta-ma ei ole pakollista, mutta kyllakin hyvan ohjelmointityylin mukais-ta.

Poikkeuksiin ei deleten tapauksessa tarvitse varautua, koskaolion tuhoamisessa ei muistin loppumista tai muita virheita pitaisitapahtua. Poikkeuksista kertovassa luvussa 11 kasitellaan mahdolli-sia olion tuhoamisen aikaisia virhetilanteita tarkemmin.

Esimerkkilistauksen 3.8 riveilla 36–37 tuhotaan dynaamisesti va-ratut oliot. Huomaa myos rivi 28, jossa virheenkasittelyn yhteydes-sa taytyy muistaa tuhota dynaamisesti luotu olio joulu p. Tallaisetvirhetilanteissa tapahtuvat tuhoamiset unohtuvat erittain helposti ja

Page 90: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

3.5. C++: Dynaaminen luominen ja tuhoaminen 90

ovat yksi lisasyy valita staattinen luominen dynaamisen luomisen si-jaan aina, kun se on mahdollista.

3.5.3 Dynaamisesti luodut taulukot

Joskus tulee tarve luoda dynaamisesti myos taulukoita. Talloin kan-nattaa pyrkia mahdollisuuksien mukaan kayttamaan STL:n tauluk-koa vector, jonka kaytto on paljon mukavampaa ja turvallisempaakuin C++:n C-kielesta periytyvan perustaulukkotyypin. Naiden vector-taulukoiden (tai muiden STL:n tietotyyppien) dynaaminen luominentapahtuu normaalisti new’lla ja tuhoaminen deletella. Niihin ei liitymitaan erityisia virhemahdollisuuksia. Mikali perustaulukoita taytyykuitenkin luoda dynaamisesti, se tehdaan operaattorilla new[ ] (“tau-lukko-new”) seuraavasti:

Lista* lista p = new Lista; // Yksittainen olioLista* listaTaulukko p = new Lista[10]; // Taulukollinen olioita

Suurin virhelahde dynaamisesti luotujen taulukkojen kanssa on,etta ne taytyy tuhota operaattorilla delete[ ] (“taulukko-delete”):

delete[ ] listaTaulukko p; listaTaulukko p = 0;

Mikali taulukon yrittaa tuhota tavallisella deletella, on ohjelman toi-minta maarittelematon, mutta tuskin toivotun mukainen. Ongelmal-liseksi tilanteen tekee se, etta itse osoittimen tyypista kaantaja ei voimitenkaan nahda, onko osoittimen paassa taulukko vai yksittainenolio. Taman vuoksi kaantaja ei pysty varoittamaan vaarasta deletesta.Myos painvastainen kielto patee, eli yksittaisia olioita ei saa tuhotataulukko-deletella, vaikka kaantaja ei tasta varoitakaan.

3.5.4 Virheiden valttaminen dynaamisessa luomisessa

Dynaamisen muistinhallinnan virheet ovat yleisimpia virhetyyppe-ja ohjelmoinnissa. Valmiista ohjelmasta virheen loytaminen on useintodella vaivalloista, koska dynaamisen muistinhallinnan virheet il-menevat yleensa aivan eri paikassa kuin missa itse virhe on. Niinpaparas tapa virheiden korjaamiseen on niiden syntymisen estaminenjo ohjelmaa suunniteltaessa.

Page 91: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

3.5. C++: Dynaaminen luominen ja tuhoaminen 91

Virheiden syyt ovat usein samoja ohjelmasta toiseen, joten ohjel-mointiyhteison keskuudessa on kehittynyt erilaisia muistisaantoja ja“kansanperinnetta”, jotka auttavat virheiden ehkaisyssa. Alla esite-taan joitain tyypillisimpia virhetilanteita ja yksinkertaisia saantoja,joilla virheiden syntymista voi pyrkia valttamaan.

Muistivuodot

Ehka yleisin muistivuotojen syy on, etta ohjelmaa suunniteltaessaei ole suunniteltu sita, minka ohjelman osan vastuulla dynaamisestiluodun olion tuhoaminen on. Vastuualueiden suunnittelu on muu-tenkin oliosuunnittelun tarkeimpia asioita, joten kunnollisella olio-suunnittelulla saadaan eliminoitua myos suuri osa dynaamisen muis-tinhallinnan ongelmista. Tuhoamisvastuun voi ajatella myos niin, et-ta jokaisen dynaamisesti (ja muutenkin) luodun olion tulisi olla ainajonkin ohjelmanosan tai olion omistuksessa ja omistajan vastuulla onmyos tuhota dynaamisesti luotu olio.

Dynaamisesti luotujen olioiden hallinnassa patevat samat saannotkuin perinteisessa ei-olio-ohjelmoinnissa dynaamisen muistin hal-linnassa. Olioajattelu voi kyllakin auttaa paljon saantojen noudatta-misessa. Tarkeimpia tallaisia saantoja on, etta dynaamisesti luodunolion omistuksen tulisi mielellaan sailya samana olion elinajan.

Tama tarkoittaa sita, etta mieluiten saman ohjelman osan taiolion, joka on luonut toisen olion dynaamisesti, pitaisi myos vastataolion tuhoamisesta. Taman ohjeen noudattaminen helpottaa suunnit-telua, koska suunnittelijan ei tarvitse talloin pitaa mielessaan, minkaohjelman osan vastuulla kunkin olion tuhoamisvastuu kulloinkin on.

Jos esimerkiksi olion rakentajassa luodaan dynaamisesti uusi olio,on luonnollista, etta se tuhotaan saman olion purkajassa. Vastaavastijos jostain ohjelmamoduulista loytyy funktio, joka palauttaa paluuar-vonaan osoittimen dynaamisesti luotuun olioon, pitaisi samasta oh-jelmamoduulista loytya myos funktio, jonka avulla saatu olio voidaan“palauttaa” moduuliin (toisin sanoen funktio, joka huolehtii olion tu-hoamisesta).

Esimerkkina tasta on vaikkapa STL:n taulukkoluokka vector. Mi-kali luodaan taulukko vector<int>, joka sisaltaa kokonaislukuja, eitaulukon kayttajan tarvitse miettia ollenkaan dynaamista muistin-hallintaa, kuten listaus 3.9 seuraavalla sivulla osoittaa. Taulukkoluo-kan toteutus pitaa huolen kaikesta muistinhallinnasta ja piilottaa sen

Page 92: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

3.5. C++: Dynaaminen luominen ja tuhoaminen 92

kayttajalta. Jos sen sijaan taulukkoon vector<int*> talletetaan dy-naamisesti luotuja kokonaislukuja kuten listauksessa 3.10 seuraaval-la sivulla), nama kokonaisluvut ovat kayttajan luomia ja niinpa nii-den tuhoamisvastuu on taulukon kayttajalla, ei itse taulukkoluokalla.

Mikali olion omistus (vastuu tuhota dynaamisesti luodutoliot) halutaan siirtaa ohjelman osalta toiselle esimerkiksi (ja-sen)funktiokutsun yhteydessa, tama tulee dokumentoida nakyvastifunktion rajapintadokumenttiin. Talloin seka funktion kayttaja ettatoteuttaja ovat ainakin periaatteessa tietoisia siita, kummalla olion tu-hoamisvastuu funktiokutsun jalkeen on. Uudessa C++:n kirjastossa ontallaisia tilanteita varten auto ptr-malli, jota voi kayttaa eksplisiitti-sesti kertomaan, etta olion omistus siirretaan toiseen paikkaan. Ta-man automaattiosoittimen kaytosta kerrotaan lisaa aliluvussa 11.7.

Kahteen kertaan tuhoaminen ja kaytto tuhoamisen jalkeen

Toinen yleinen virhe on, etta muistivuotojen pelossa liioitellaan tu-hoamista ja joko tuhotaan olio kahteen kertaan (yleensa eri osoitti-mien lapi) tai sitten olio tuhotaan liian aikaisin ja sita yritetaan kayt-

1 void esim()2 3 vector<int> taul;45 // Luodaan luvut6 for (int i=0; i<10; ++i)7 8 taul.push back(i);9

1011 // Kaytetaan lukuja12 for (int i=0; i<10; ++i)13 14 cout << taul[i] << endl;15 1617 // Lukujen tuhoaminen vectorin vastuulla, vektori tuhoutuu itsestaan18

LISTAUS 3.9: Taulukko, joka omistaa sisaltamansa kokonaisluvut

Page 93: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

3.5. C++: Dynaaminen luominen ja tuhoaminen 93

1 void esim2()2 3 vector<int*> taul;45 // Luodaan luvut6 for (int i=0; i<10; ++i)7 8 int* lukup = 0;9 try

10 11 lukup = new int(i); // Luodaan uusi kokonaisluku12 taul.push back(lukup);13 14 catch (std::bad alloc&)15 16 // Muisti lopussa, taytyy siivota jaljet eli jo luodut luvut17 delete lukup; lukup = 0;18 for (int j=0; j<i; ++j) delete taul[j]; taul[j] = 0; 19 throw; // Heitetaan virhe edelleen20 21 22 // Kaytetaan lukuja23 for (int i=0; i<10; ++i) cout << *(taul[i]) << endl; 24 // Lopuksi tuhotaan luvut25 for (int i=0; i<10; ++i) delete taul[i]; taul[i] = 0; 26 // Itse taulukko tuhoutuu automaattisesti27

LISTAUS 3.10: Taulukko, joka ei omista sisaltamiaan kokonaislukuja

taa viela tuhoamisen jalkeen. Tasta aiheutuvat virheet ovat yleensayhta vaikeita jaljittaa kuin itse muistivuodotkin, ja virheiden vaiku-tukset saattavat olla paljon mystisempia.

Samat suunnitteluperiaatteet, jotka estavat muistivuotoja, aut-tavat yleensa ehkaisemaan ennalta namakin virheet. Kun ohjelmasuunnitellaan niin, etta jokaisen dynaamisesti luodun olion omistaakerrallaan vain yksi ohjelmanosa, ei pelkoa kahteen kertaan tuhoami-sesta ole.

Toinen hyva peukalosaanto on nollata osoittimet aina sen jalkeen,kun niiden paasta tuhotaan dynaamisesti varattu olio:

Paivays* p = new Paivays;// Kaytetaan paivaystadelete p; p = 0;

Page 94: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

3.5. C++: Dynaaminen luominen ja tuhoaminen 94

Mikali nyt jo tuhottua oliota yritetaan kayttaa osoittimen p kautta,on ohjelmalla hyvat mahdollisuudet kaatua tyhjan osoittimen lapi ta-pahtuvaan muistiviittaukseen — ainakin UNIX-pohjaisissa kayttojar-jestelmissa. Jos p jatettaisiin vanhaan arvoonsa, osoittaisi se suurellatodennakoisyydella siihen osaan muistia, jossa olio on ollut. Talloinosoittimen lapi tapahtuvat viittaukset saattaisivat jopa toimia jonkinaikaa “zombie-oliota” kayttaen, kunnes kyseinen muistialue otetaanuudelleen kayttoon ja siihen luodaan uusi olio, joka kirjoittaa datan-sa vanhan datan paalle.

Muistivuodot virhetilanteissa

Vaikka muistivuodot saisikin muuten kuriin, tulee ongelma paljonmonimutkaisemmaksi, kun ohjelman pitaisi pyrkia toipumaan vir-hetilanteista, vaikkapa muistin loppumisesta tai tiedostovirheesta.Yleensa virheen sattuessa ohjelma siirtyy tavalla tai toisella virheen-kasittelykoodiin, ja tata koodia kirjoitettaessa helposti unohtuu tuho-ta ennen virhetta dynaamisesti luodut oliot, joita ei enaa virheestatoipumisen jalkeen tarvita. Listauksen 3.10 riveilla 14–20 oleva vir-hekasittelija antaa ehka jonkinlaisen kuvan siita, mita kaikkea muis-tivuotojen estaminen vaatii._

Virhetilanteista toipumista C++:ssa kasitellaan tarkemmin luvus-sa 11.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ._Mainittakoon, etta esimerkkia kirjoitettaessa ohjelmaan tahtoi aina jaada jokin virhetilan-

ne, jota ei otettu huomioon. Listauksessa oleva kolmas (!) versio tuntuu toimivalta, mutta josloydat viela siitakin muistivuodon, ota yhteytta.,

Page 95: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

95

Luku 4

Olioiden rajapinnat

None of us is ever exclusively a student or a teacher. Weare always both at the same time, and must always thinkof ourselves as such. A teacher can get you started on com-puters, but no one can teach you everything you need toknow. At a certain point, you must teach yourself throughtrial and error, looking over other people’s shoulders, futz-ing around, figuring out what methods work best for you.The same is true in life. A teacher can guide you, but in theend, you have to make your own way.

As you figure things out on your own, you share what you’velearned with others. This is the Japanese idea of the sensei.We often see the word translated as “teacher” in English,but literally it means “one who has gone before”. In thatregard, we are all senseis to somebody.

Zen masters say, “When the student is ready, the teacherwill appear.” This means two things: When you’re ready tolearn, all things appear before you as teachers. And whenyou’re ready to teach, the teacher within you appears. Themore you recognize the help you’ve received from thosewho gave ahead of you, the easier it becomes to give ofyourself to those coming up behind.

– Philip Toshio Sudo: Zen Computer [Sudo, 1999]

Page 96: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

4.1. Rajapinnan suunnittelu 96

Kun edella on kasitelty modulaarisuutta ja olioita, on kasite “rajapin-ta” tullut esille moneen kertaan. Rajapinta on esitetty sina olion osa-na, jonka olion kayttaja “nakee” ja jota siis paasee kutsumaan olion ul-kopuolelta. Rajapinta on komponentin se osa, jonka avulla ohjelmoi-jan tulisi pystya kayttamaan komponenttia ilman etta tietaa mitaansen sisaisesta toteutuksesta. Rajapinnasta taytyisi siis loytya kaikkikayttamiseen tarvittava informaatio. Tama on jotain enemman kuinkokoelma jasenfunktioita — rajapinnasta pitaisi olla myos kayttooh-je, joka kertoo miten sita on tarkoitus kayttaa ja miten se kayttaytyyerilaisissa (myos poikkeuksillisissa) tilanteissa.

4.1 Rajapinnan suunnittelu

Rajapinta on muille ohjelmoijille kirjoitettu kayttoohje komponentis-tamme. Se vastaan kysymykseen “Miten tata kaytetaan?”. Rajapinnantaakse katketty toteutus ei ole rasittamassa tata kayttoohjetta tai ha-maamassa sen lukijaa.

Rajapintojen kaytto ja toteutuksen katkenta on yksi ehka tarkeim-mista ohjelmistotuotannon perusperiaatteista, mutta silti sen tar-keyden perustelu uraansa aloittelevalle ohjelmistoammattilaiselle onvaikeaa. Merkityksen tajuaa yleensa itsestaanselvyytena sen jalkeen,kun on osallistunut tekemaan niin isoa ohjelmistoa, ettei sen sisaistatoteutusta pysty kerralla hallitsemaan ja ymmartamaan yksi ihminen.Naissa tilanteissa komponenttijako helpottaa ratkaisevasti, kun yksit-taisen ohjelmoijan ei tarvitse jatkuvasti miettia kokonaisuutta, vaanainoastaan omaa koodiaan ja niita rajapintoja ulkopuolelle, joita hansilla hetkella tarvitsee.

David Parnas on muotoillut tasta komponenttien suunnittelustakaksi saantoa, jotka nykyaan yleisesti tunnetaan Parnasin periaatteina(Parnas’s Principles) [Budd, 2002]:

• Ohjelmakomponentin suunnittelijan tulee antaa komponentinkayttajalle kaikki tarvittava tieto, jotta komponenttia pystyykayttamaan tehokkaasti hyvakseen, mutta ei mitaan muuta tie-toa (komponentista).

• Ohjelmakomponentin suunnittelijalla tulee olla kaytossaankaikki tarvittava informaatio komponentille maarattyjen vastui-

Page 97: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

4.1. Rajapinnan suunnittelu 97

den toteuttamiseksi, mutta ei mitaan muuta (ylimaaraista) tie-toa.

4.1.1 Hyvan rajapinnan tunnusmerkkeja

Rajapintojen suunnittelusta on hyvin vaikeata antaa tarkkoja ohjei-ta, ja edella mainittujen Parnasin periaatteiden noudattaminen johtaamelko nopeasti ristiriitatilanteisiin. Kokemus ja tieto lisaavat kuiten-kin jokaisella ohjelmoijalla kasitysta siita, minka tyyppisia rajapin-toja on helppo, jopa mukava, kayttaa, ja mitka ovat hankalia. Nais-ta kokemuksista kannattaa ottaa oppia ja pyrkia itse aina tuottamaanrajapintoja, jotka ovat silla mukavalla puolella. Ehkapa osaksi ohjel-moinnin opetusta tulisi ottaa “hyvien ohjelmien ja rajapintojen kirjal-lisuus”, jossa tutustutaan yleisesti hyviksi todettuihin ohjelmistoihinja komponenttikirjastoihin.

Seuraava lista ei pyri olemaan taydellinen, vaan on tarkoitettu he-rattamaan ajattelemaan niita asioita, joita rajapintasuunnittelussa tu-lisi osata huomioida:

• Noudata hyvaksi havaittuja toimintamalleja. Esimerkiksi oheis-laitteita kuvaavat rakenteet sisaltavat usein seuraavat kayttovai-heet: avaaminen, kaytto ja sulkeminen — tama on useille oh-jelmoijille tuttu ja luonteva toimintaketju. Vastaavasti jos kom-ponentti sisaltaa paljon erilaisia tietoalkioita tai olioita, niidenluettelointi ja lapikaynti kannattaa toteuttaa kaikissa rajapin-noissa yhtenaisella tavalla.

• Mieti komponentin kaytto alusta loppuun ulkopuolisen kayt-tajan kannalta. Miten komponentti loytyy jarjestelmassa? Mis-sa jarjestyksessa sen tarjoamia palveluita on tarkoitus kayttaa?Ovatko jotkin palvelut kaytettavissa vain osan aikaa? Dokumen-taatiossa oleva kayttotapaus “normaalikayttojarjestyksesta” voihelpottaa rajapinnan ymmartamista.

• Dokumentoi komponentin riippuvuudet. Kayttajalle ei saa tullayllatyksena, etta komponentti vaatii muita komponentteja toi-miakseen, tai etta rajapinnan kasittelemilla olioilla pitaa olla jo-kin ominaisuus (esimerkiksi jokainen olio osaa tehda itsestaankopion).

Page 98: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

4.1. Rajapinnan suunnittelu 98

• Dokumentoi rajapintaan liittyvat elinkaaret ja omistusvastuut.Jos komponentin sisalle annetaan rajapinnan avulla olio, tuhou-tuuko se komponentin toimesta (eli omistusvastuu siirtyy) vaitaytyyko rajapinnan kayttajan edelleen huolehtia olion tuhoa-misesta?

• Maarittele ja dokumentoi virhetilanteiden kasittely. Pyrkiikokomponentti kasittelemaan huomaamansa virhetilanteet itse,vai valittaako se tiedon niista edelleen? Milla mekanismilla vir-heista ilmoitetaan?

• Mieti rajapinnan operaatioiden nimeaminen tarkkaan. Valta sa-noja, joilla komponentin kayttoalueella voi olla useita merkityk-sia. Yleensa aaneen lausuttavissa olevat nimet ovat helpoimminymmarrettavia.

Listalla esiintyy useaan kertaan “dokumentoi”. Tama korostaa si-ta, etta aarimmaisen harvoin rajapinnan luettelemat operaatiot (jasen-funktiot) kertovat riittavasti komponentista. Rajapintaoperaatioidenyhteyteen on liitettava kuvausta, joka kertoo tarkemmin rajapinnankaytosta ja kayttaytymisesta kokonaisuutena.

4.1.2 Erilaisia rajapintoja ohjelmoinnissa

Olion rajapinnan maaraytyminen ohjelmointikielen tasolla vaihte-lee ohjelmointikielesta toiseen. Joissain oliokielissa esimerkiksi ja-senmuuttujat eivat koskaan nay kuin oliolle itselleen, mutta kaikkijasenfunktiot nakyvat ulospain. Toisissa kielissa (kuten C++:ssa) taasohjelmoija paattaa nakyvyydesta.

Ohjelmassa saattaa tulla myos tarve siihen, etta yhdella oliolla oli-si erilainen rajapinta riippuen siita, mika ohjelman osa oliota kayttaa.Erilaisia rajapintatarpeita voivat olla ainakin

• luokan “tavallisen” kayttajan nakema rajapinta (yleisin, julki-nen rajapinta)

• luokan sisaiseen toteutukseen tarvittava rajapinta

• rajapinta, jonka olio nakee toisesta saman luokan oliosta

• rajapinta, jonka kantaluokka tarjoaa aliluokalle (aliluku 6.3.1)

Page 99: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

4.1. Rajapinnan suunnittelu 99

• toisiinsa kiinteasti liittyvien luokkien (esim. komponentin jamoduulin) toisilleen tarjoamat rajapinnat (aliluku 8.4)

• rajapinta, jonka lapi ei voi muuttaa olion sisaltoa (aliluku 4.3)

• “rajapinta”, jonka lapi olioon voi vain viitata, mutta ei kayttaa(aliluku 4.4)

• rajapintaluokat tai muut erilliset rajapinnat, jotka luokka lupaatoteuttaa (aliluku 6.9)

• aikariippuva rajapinta, jossa vain osa rajapinnasta on kaytetta-vissa eri ajanhetkilla (esimerkiksi suurin osa tiedostorajapinnanoperaatioista on kaytettavissa vasta sitten kuin kasiteltava tie-dosto on avattu)

• toteutuksen riippuvuudet maaritteleva rajapinta. Tama taval-laan kaanteinen rajapinta luettelee esimerkiksi ne palvelut, joitatoteutus tarvitsee kayttojarjestelmalta ja kirjastoilta.

Suurin osa oliokielista tarjoaa mahdollisuuden maarata vain osanyllamainituista rajapinnoista, ja tavat joilla “erityisrajapintoja” onmahdollista maaritella, vaihtelevat suuresti kielesta toiseen ja ovatenemman tai vahemman teennaisia. Tassa teoksessa kasitellaan la-hinna C++-kielen tarjoamia mahdollisuuksia. Jotkin C++:n rajapintao-minaisuuksista ovat parempia kuin monissa muissa kielissa, toisettaas selvasti huonompia.

4.1.3 Rajapintadokumentaation tuottaminen ja yllapito

Rajapintojen dokumentaatio on yksi tarkeimmista ohjelmoijan tyova-lineista, koska moduulien, kirjastojen ja luokkien oikeaa kayttoa eiyleensa pysty paattelemaan pelkasta ohjelmakoodista. Dokumentaa-tiosta tulisi selkeasti kayda ilmi, miten rajapintaa on tarkoitus kayttaaja mita ehtoja sen kaytolle asetetaan (naita ehtoja kaydaan tarkemminlapi aliluvussa 8.1, jossa puhutaan sopimussuunnittelusta, design bycontract).

Koska luokan tai moduulin ulkopuoliset kayttajat nojautuvat etu-paassa rajapinnan dokumentaatioon, on erittain tarkeaa etta tama do-kumentaatio paivitetaan aina rajapinnan muuttuessa. Rajapintadoku-mentaatiota tarvitaan usein myos suunnitteluvaiheen lisaksi varsi-

Page 100: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

4.1. Rajapinnan suunnittelu 100

naisessa koodausvaiheessa, kun rajapinnasta taytyy tarkastaa rajapin-nan kayton yksityiskohtia kuten parametrien tyyppeja ja jarjestysta.Talloin olisi katevaa etta dokumentaatio nayttaisi rajapinnan myosohjelmointikielen tasolla.

Naista tarpeista on syntynyt idea kirjoittaa ainakin osa rajapin-nan dokumentaatiosta ohjelman kooditiedostojen sisaan komment-tien muodossa. Maaramuotoisista kommenteista voidaan sitten sopi-valla tyokalulla tuottaa automaattisesti ihmiselle helppolukuinen ra-japintadokumentaatio. Tama tietysti helpottaa dokumentaation ajantasalla pitamista suuresti, koska dokumentaatio voidaan helpostituottaa uudelleen rajapinnan muuttuessa. Lisaksi rajapintadokumen-taatiosta voidaan tuottaa nettiselaimella kaytettava versio, jolloin senlinkkeja seuraamalla pystyy helposti navigoimaan dokumentaationsisalla.

Java-kielessa tyokalu rajapintadokumentaation tuottamiseen onnimeltaan Javadoc [Sun Microsystems, 2005], ja se on integroituosaksi Javan normaalia kehitysymparistoa. Muun muassa Javan omienkirjastojen rajapintadokumentaatiot on yleensa tuotettu Javadocinavulla.

Toinen laajalti kaytetty rajapintojen dokumentaatiotyokalu on ni-meltaan Doxygen [Doxygen, 2005]. Se on ilmainen open source-ohjelma, jonka tukema kielivalikoima on varsin laaja: C++, C, Java,Objective-C, IDL, seka rajoitetusti PHP, C# ja D (tilanne kevaalla2005). Doxygen tekee myos lahdekoodista rajapintadokumentaationtueksi osittaisia luokkakaavioita, riippuvuusgraafeja, ohjelmalistauk-sia. Kaikki nama voidaan tuottaa seka perinteisina dokumentteina et-ta selaimella navigoitavassa muodossa.

Vaikka rajapintadokumentaation yllapitamiseen kaytettaisiinkinautomaattisia tyokaluja, eivat tyokalut kuitenkaan vapauta ohjelmoi-jaa rajapinnan suunnittelusta ja dokumentoinnista. Kaikki aliluvus-sa 4.1.1 mainitut suunnittelusaannot patevat riippumatta siita, mitenrajapintadokumentaatio tuotetaan. Sen sijaan pelkasta dokumentoi-mattomasta ohjelmakoodista kiireessa tyokalulla tuotettu “rajapinta-dokumentti” saattaa jopa antaa valheellisen kuvan siita, etta rajapin-ta olisi kunnolla dokumentoitu, vaikka todellisuudessa tuotettu raja-pintadokumentaatio toistaakin vain pelkan ohjelmakoodirajapinnanhieman koreammassa muodossa.

Page 101: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

4.2. C++: Nakyvyysmaareet 101

4.2 C++: Nakyvyysmaareet

C++:ssa kieli itse ei pakota olion jasenten nakyvyytta tiettyyn muot-tiin. Nakyvyyden saatamista varten kielessa on avainsanat public,protected ja private. Luokan esittelyssa nama avainsanat toimivat“otsikkoina”, jotka maaraavat niiden jalkeen tulevien esittelyjen na-kyvyyden. Perinteisesti nama maareet esiintyvat luokan esittelyssaedella mainitussa jarjestyksessa, mutta tama on vain tyyliseikka, eikielen maarama jarjestys. Luokan esittely on tyypillisesti muotoa

class Luokannimipublic:

// Tanne tulevat asiat nakyvat luokasta ulosprotected:

// Tanne tulevat asiat nakyvat vain aliluokilleprivate:

// Tanne tulevat asiat eivat nay ulospain;

Jos luokkaesittelyn alussa ei anneta minkaanlaista nakyvyysmaa-retta, on oletuksena C++:ssa private, koska se on “tiukin” nakyvyys-maareista. Vaikka monet oppikirjat (esim. [Stroustrup, 1997]) kaytta-vatkin tata hyvakseen ja esittelevat luokan sisaiset jasenmuuttujat en-simmaisena ilman mitaan nakyvyysmaaretta, ei niiden antamaa esi-merkkia kannata seurata. Luokan esittelyn tarkoituksena on kertoaluokan kayttajalle, miten luokan olioita kaytetaan, ja tata varten kayt-taja tarvitsee luokan julkisen rajapinnan eli public-osan. Selkeydenvuoksi on siis syyta kirjoittaa public-osa ensimmaisena, jotta sita eitarvitse etsia luokan sisaisen toteutuksen perasta.

Edellisen koodiesimerkin kommenteissa olevat “selitykset” eri na-kyvyysmaareiden merkityksesta ovat vain ylimalkaisia. Alla seloste-taan nakyvyysmaareiden public ja private tarkka merkitys ja yri-tetaan antaa jonkinlainen kuva siita, miten niita on tarkoitus kayt-taa. Maare protected liittyy olennaisesti periytymiseen ja kasitellaanmyohemmin aliluvussa 6.3.1. Ilman periytymista protected kayttay-tyy olennaisilta osin samoin kuin private.

Page 102: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

4.2. C++: Nakyvyysmaareet 102

4.2.1 public

Nakyvyysmaare public on maareista yksinkertaisin siina mielessa,ettei se rajoita jasenfunktioiden (ja -muuttujien) nakyvyytta millaanlailla, ja niita voi kayttaa missa tahansa ohjelman osassa. Nain luokanpublic-osa maaraa luokan julkisen rajapinnan, jonka kautta luokannormaali kaytto on tarkoitettu tapahtuvaksi.

Luokan julkisen rajapinnan suunnitteleminen on vaativa tehtava.Mikali rajapinnasta jaa pois jotain oleellista — esimerkiksi jasenfunk-tio jonkin olennaisen asian tekemiseksi —, ei luokan kayttajalla olemitaan mahdollisuutta korjata puutetta, koska luokan sisaiseen toteu-tukseen ei paase kasiksi. Toisaalta julkiseen rajapintaan ei kannata“varmuuden vuoksi” laittaa mitaan ylimaaraista. Luokan suunnitte-lijan kannalta julkinen rajapinta on lupaus luokan tarjoamista pal-veluista, joten julkiseen rajapintaan laitettujen asioiden pitaisi pysyamuuttumattomina — luokan kayttajan koodihan riippuu julkisestarajapinnasta. Nain luokan yllapidon vuoksi rajapinta pitaisi pyrkiasailyttamaan mahdollisimman yksinkertaisena. Tama pyrkimys “mi-nimaaliseen mutta taydelliseen” rajapintaan onkin usein mainittu hy-van rajapinnan tunnusmerkkina [Meyers, 1998, Item 18].

Jasenmuuttujat on syyta pitaa visusti poissa julkisesta rajapinnas-ta useistakin syista. Ensinnakin koko olioajattelun peruskivi on sisai-sen toteutuksen katkeminen. Vaikka jasenmuuttuja olisikin luonteel-taan sellainen, etta olion kayttajan pitaisi paasta kasiksi siihen, ei sitasilti kannata laittaa julkiseen rajapintaan. Tahan on etupaassa kaksisyyta:

• Joskus myohemmin luokkaa yllapidettaessa saattaa tulla tarvesiirtaa kyseinen jasenmuuttuja jonnekin muualle, esimerkiksiosoittimen paahan olion ulkopuolelle tai kenties korvata kokojasenmuuttuja jollain toisella rakenteella. Mikali jasenmuuttujaon julkisessa rajapinnassa, luokan kayttajien koodi riippuu siitaeika sita voi poistaa.

• Olio itse ei saa mitaan tietoa siita, milloin julkisessa rajapinnas-sa olevasta jasenmuuttujasta luetaan tietoa tai milloin siihen si-joitetaan uusi arvo. Talloin olio ei voi mitenkaan reagoida esi-merkiksi jasenmuuttujan arvon vaihdoksiin tai siihen, etta ja-senmuuttujan arvoa ylipaataan on kysytty.

Page 103: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

4.2. C++: Nakyvyysmaareet 103

Mikali jotakin jasenmuuttujaa tunnutaan tarvitsevan luokan jul-kisessa rajapinnassa, kannattaa ensin miettia onko tarve todellinen.Yleensa jasenmuuttujiin liittyvat palvelut tulisi toteuttaa itse luokanjasenfunktioissa, joten tarve julkiseen jasenmuuttujaan saattaa ollamerkki siita, etta luokan vastuualueeseen kuuluvia palveluita yrite-taan toteuttaa luokan ulkopuolella. Jos jasenmuuttujan arvoa todel-la kuitenkin tarvitaan, kannattaa lisata julkiseen rajapintaan sopivat“aseta”- ja “anna”-jasenfunktiot (setter ja getter), joiden koodissa ja-senmuuttujaan sijoitetaan tai vastaavasti sen arvo luetaan. Nain ontehty esim. PieniPaivays-luokassa, josta loytyvat asetus- ja lukufunk-tiot paivalle, kuukaudelle ja vuodelle (listaus 2.1 sivulla 62).

Jos julkiseen rajapintaan pyrkiva jasenmuuttuja on olio, eivat ase-tus- ja lukufunktiot yleensa riita kattamaan jasenmuuttujaolion kayt-totarvetta. Talloin on joskus katevaa kirjoittaa “anna”-jasenfunktio,joka palauttaa viitteen jasenmuuttujaan. Funktion paluuarvon avul-la paasee kasiksi jasenmuuttujaolioon, mutta jasenmuuttujaa ei sil-ti tarvitse siirtaa public-puolelle. Listauksessa 4.1 seuraavalla sivul-la on esimerkki luokan Kirja jasenfunktiosta annaPalautusPvm, jonkakautta kirjan palautuspaivamaaraan paasee kasiksi. Tallaisten funk-tioiden kautta olion kayttaja paasee edelleen muuttamaan jasenmuut-tujan arvoa olion huomaamatta. Tama voidaan estaa palauttamallajasenfunktiosta vakioviite, jolloin jasenmuuttujaoliota ei voi viitteenlapi muuttaa (vakioviitteet kasitellaan aliluvussa 4.3.3).

4.2.2 private

Nakyvyysmaare private on C++:n maareista kaikkein rajoittavin. Ja-senmuuttujiin ja jasenfunktioihin, jotka on esitelty private-osassa,paasee kasiksi vain saman luokan jasenfunktioiden koodissa (ja ysta-vafunktioissa ja -luokkissa, joista kerrotaan enemman aliluvussa 8.4).Koska koko olioajattelun yksi lahtokohdista on ollut olion sisaisen to-teutuksen katkeminen, tulisi luokan jasenmuuttujien aina olla kap-seloituna luokan private-osaan.

Luokan jasenfunktioiden toteutuksessa tulee usein tarve kirjoit-taa “apufunktioita”, joita kutsutaan useista eri jasenfunktioista. Na-ma on katevaa kirjoittaa luokan private-puolelle “yksityisiksi” jasen-funktioiksi. Talloin luokan omat jasenfunktiot paasevat kutsumaanniita, mutta apufunktiot eivat silti kuulu luokan julkiseen rajapin-taan, joten niihin ei paase kasiksi luokan ulkopuolelta.

Page 104: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

4.2. C++: Nakyvyysmaareet 104

1 class Kirja2 3 public:

...4 PieniPaivays& annaPalautusPvm();5 private:

...6 PieniPaivays palautuspvm ;7 ;

...8 PieniPaivays& Kirja::annaPalautusPvm()9

10 return palautuspvm ;11

...12 int aikaaJaljella(Kirja& kirja)13 14 return kirja.annaPalautusPvm().paljonkoEdella(tanaan);15

LISTAUS 4.1: Jasenfunktio, joka palauttaa viitteen jasenmuuttujaan

C++:ssa (ja Javassa) luokan private-osaan paasee kasiksi olionomien jasenfunktioiden koodi. Lisaksi myos toiset saman luokanoliot voivat kasitella toistensa private-osia. Kaytannossa tama tar-koittaa sita, etta jos olion jasenfunktio saa kayttoonsa toisen samanluokan olion esimerkiksi parametrina, se paasee kasiksi myos ta-man toisen olion private-osaan ja siis myos jasenmuuttujiin. LuokanPieniPaivays jasenfunktio sijoitaPaivays on esimerkkina tasta lis-tauksessa 4.2 seuraavalla sivulla. Siina paivayksen jasenmuuttujienarvot sijoitetaan toisesta samantyyppisesta oliosta.

Paasy toisen olion private-osaan on tietylla tavalla luontevaa,koska olion sisaisen toteutuksen piilottaminen toiselta saman luokanoliolta on sinansa turhaa. Joissain oliokielissa kuten Smalltalkissa onkuitenkin otettu viela tiukempi kanta, eika toinen saman luokan oliopaase kasiksi toisen olion jasenmuuttujiin.

Page 105: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

4.3. C++: const ja vakio-oliot 105

1 void PieniPaivays::sijoitaPaivays(PieniPaivays& p)2 3 paiva = p.paiva ;4 kuukausi = p.kuukausi ;5 vuosi = p.vuosi ;6

LISTAUS 4.2: Paasy toisen saman luokan olion private-osaan

4.3 C++: const ja vakio-oliot

Monissa ohjelmointikielissa on jo ammoisista ajoista lahtien ollutmahdollisuus maaritella muuttujien lisaksi vakioita, jotka eroavatmuuttujista siina, etta niiden arvoa ei voi muuttaa. C-kieleen tamamahdollisuus tuli ANSI-standardin mukana 80-luvulla, kun kieleenlisattiin avainsana const. C++:ssa const on otettu mukaan myos olio-ominaisuuksiin, jossa sen kaytto on osoittautunut erittain hyodylli-seksi.

4.3.1 Perustyyppiset vakiot

C-kielessa perustyyppiset vakiot — lahinna kokonaisluku- ja liukulu-kuvakiot — maariteltiin perinteisesti #define-esikaantajakomennol-la. Syyna tahan oli, etta vaikka ANSI-standardi toikin kieleen const-maareen, sen toiminta oli tehotonta eika sita voinut kayttaa kaikissatilanteissa. C++:ssa tilanne on korjattu, eika #define-vakioita ole enaasyyta kayttaa.

Perustyyppinen vakio maaritellaan aivan kuten muuttuja, muttatyypin yhteyteen lisataan maare const:

1 int const MAX MJONON KOKO = 30000;2 double const PI = 3.14159265;3 int const RAJA = annaRaja();4 char mjono[MAX MJONON KOKO];

const-vakiot kayttaytyvat muuten kuin muuttujat, mutta niihin eivoi sijoittaa. Taman lisaksi kokonaislukuvakiota voi kayttaa myospaikoissa, joissa kaantaja vaatii kaannosaikaisia vakioita, kuten esi-merkiksi taulukkojen ko’oissa (katso rivi 4 edellisessa esimerkissa).

Page 106: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

4.3. C++: const ja vakio-oliot 106

C:sta poiketen vakioiden alustusarvon ei tarvitse olla kaannosaikai-nen vaan se voi olla esim. funktion paluuarvo kuten rivilla 3 — tal-laista ei-kaannosaikaista vakiota ei kyllakaan sitten voi kayttaa esi-merkiksi taulukon kokoa maaraamaan.

Kielen kannalta on aivan sama, onko sana const ennen tyypinnimea vai sen jalkeen. Aiemmin kaytettiin yksinomaan tapaa, jossaconst tulee ennen tyyppia (const int i), ja tata tapaa nakee edelleen-kin valtaosassa koodia. Tapa laittaa const-sana vasta tyypin nimenjalkeen on kuitenkin C++:n kannalta loogisempi, ja sita nakee kaytet-tavan yha enemman uudessa C++-koodissa. (Vastaavat muutkin tyypinmaareet kuten osoitin-* ja viite-& tulevat vasta tyypin jalkeen. Tal-la on merkitysta kun maareita on monta perakkain.) Tassa teoksessaon siirrytty kayttamaan uutta kaytantoa vuoden 2005 painoksesta al-kaen.

const-vakiot ovat normaalisti paikallisia siina kaannosyksikossa,jossa ne maaritellaan. Kaytannossa tama tarkoittaa sita, etta const-vakiot voi sijoittaa otsikkotiedostoihin, vaikka sinne ei normaalistimuuttujia laitetakaan.

Usein olio-ohjelmoinnissa vakiot liittyvat kiinteasti jonkin tietynluokan kayttoon. Talloin vakiot kannattaa kapseloida luokan sisaanluokkavakioiksi, joista kerrotaan tarkemmin aliluvussa 8.2.2.

4.3.2 Vakio-oliot

Olioista voi tehda “vakio-olioita” aivan samaan tapaan kuin perus-tyypeista luodaan vakioita — lisataan olion maarittelyn jalkeen sanaconst:

Paivays const joulu(24,12,1999);

Olioiden tapauksessa const-sanan vaikutus on kuitenkin moni-mutkaisempi asia. Perustyypeista puhuttaessa const-sanan vaikutuson helppo selittaa — muuttujaan ei yksinkertaisesti saa sijoittaa.Olioiden tapauksessa tilanne on paljon hankalampi. Sijoittaminen eiole valttamatta mielekas toimenpide kaikille olioille (olioiden sijoitta-mista kasitellaan aliluvussa 7.2). Jos halutaan puhua “vakio-olioista”,onkin oleellista se, ettei tallaisen vakio-olion tilaa pystyta muutta-maan. Koska olion tila on kapseloitu olion sisaan, voi olion tila muut-tua vain jasenfunktiokutsun seurauksena.

Page 107: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

4.3. C++: const ja vakio-oliot 107

C++:ssa vakio-olioiden kasite on toteutettu jakamalla jasenfunktiotkahteen ryhmaan — niihin, jotka voivat muuttaa olion tilaa ja niihin,jotka eivat. Taman jalkeen on maaratty, etta vakio-olioille saa kutsuavain niita jasenfunktioita, jotka eivat voi muuttaa olion tilaa. Samanasian voi myos ajatella niin, etta C++:ssa vakio-olioilla on erilainenrajapinta, joka on vain osajoukko luokan koko rajapinnasta.

Jasenfunktiot, joiden ei haluta muuttavan olion tilaa, merkitaanmaareella const, joka tulee jasenfunktion parametrilistan peraan sekaluokan esittelyssa etta jasenfunktion maarittelyssa. Listauksessa 4.3on luokan Paivays esittely, jossa paivaysta muuttamattomat jasen-funktiot on merkitty vakioiksi. Listauksessa on myos esimerkkina yh-den jasenfunktion maarittely.

Kaantaja pitaa huolen siita, etta const-sanan mukanaan tuomaa

1 class Paivays2 3 public:4 Paivays(unsigned int p, unsigned int k, unsigned int v);5 ~Paivays();67 void asetaPaiva(unsigned int paiva);8 void asetaKk(unsigned int kuukausi);9 void asetaVuosi(unsigned int vuosi);

1011 unsigned int annaPaiva() const;12 unsigned int annaKk() const;13 unsigned int annaVuosi() const;1415 void etene(int n);16 int paljonkoEdella(Paivays const& p) const;1718 private:19 unsigned int paiva ;20 unsigned int kuukausi ;21 unsigned int vuosi ;22 ;

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . maarittely . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .1 unsigned int Paivays::annaPaiva() const2 3 return paiva ;4

LISTAUS 4.3: Paivaysluokka const-sanoineen

Page 108: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

4.3. C++: const ja vakio-oliot 108

“vakioisuutta” noudatetaan. Jo aiemmin on mainittu, etta vakio-olioille voi kutsua vain vakiojasenfunktioita. Taman lisaksi kaanta-ja pyrkii valvomaan, etta vakiojasenfunktioiden koodissa ei muutetaolion tilaa. Taman se tekee asettamalla seuraavat rajoitukset vakioja-senfunktion koodille:

• Vakiojasenfunktion koodissa ei voi muuttaa jasenmuuttujien ar-voja (toisin sanoen jasenmuuttujat kayttaytyvat ikaan kuin neolisi maaritelty const-maareella).

• Vakiojasenfunktion koodissa voi kutsua omalle oliolle vain toi-sia vakiojasenfunktioita. Tama rajoitus koskee siis vain jasen-funktion omaan olioon kohdistuvia kutsuja.

• Vakiojasenfunktion koodissa osoitin this on tyyppia “osoitinvakio-olioon” (katso seuraava aliluku).

Edella mainitut rajoitukset eivat kuitenkaan riita varmistamaantaydellisesti, ettei vakiojasenfunktiossa olion tila muutu. Osa oliontilaan kuuluvasta tiedostahan saattaa nimittain olla olion ulkopuolel-la esim. osoittimien paassa. Tallaiseen dataan eivat kaantajan tarkas-tukset ulotu, vaan ohjelmoijan on itsensa pidettava huoli siita, etteivakiojasenfunktiossa muuteta mitaan sellaista, jonka katsotaan kuu-luvan olion tilaan.

Joskus harvoin saattaa olla aihetta maaritella jasenmuuttuja, jotavoisi muuttaa myos vakiojasenfunktioissa. Tallainen on perusteltuavain, jos jasenmuuttujan muuttaminen ei vaikuta olion “todelliseen”tilaan. Tallaisia jasenmuuttujia on mahdollista saada aikaan lisaamal-la niiden esittelyn eteen avainsana mutable.

C++ antaa myos mahdollisuuden siihen, etta luokka tarjoaa kak-si samannimista jasenfunktiota samoilla parametreilla, jos toinen onvakiojasenfunktio ja toinen ei. Tallaisessa tapauksessa jasenfunk-tion kutsuminen toimii niin, etta vakio-olioille ja vakio-osoittimienja -viitteiden lapi kutsutaan vakiojasenfunktiota, muuten tavallista.Tasta erottelusta on joskus hyotya, koska vakiojasenfunktio voi esi-merkiksi palauttaa vakio-osoittimen olion dataan, tavallinen jasen-funktio taas normaalin osoittimen.

Page 109: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

4.3. C++: const ja vakio-oliot 109

4.3.3 Vakioviitteet ja -osoittimet

Varsinaisia vakio-olioita tarvitaan olio-ohjelmoinnissa aarimmaisenharvoin, koska olio-ohjelmoinnin yksi perusajatuksista on, etta olioi-den tila muuttuu niihin kohdistettujen toimintojen tuloksena. Vakio-olion kasite muuttuu kuitenkin erittain kayttokelpoiseksi, kun ote-taan kayttoon vakioviitteet ja -osoittimet.

Vakioviitteella tarkoitetaan tassa kirjassa viitetta, jonka lapi asiatnayttavat vakioilta. Vastaavasti vakio-osoitin on osoitin, jota kaytet-taessa sen paassa oleva asia vaikuttaa vakiolta. Termia “vakio-osoi-tin” ei tule sekoittaa termiin “osoitinvakio”, jolla tarkoitetaan osoitin-ta, joka itse on vakio, ts. osoitinta ei voi muuttaa osoittamaan toiseenpaikkaan. Huomaa, etta kaikki alla esitetyt asiat patevat niin vakio-osoittimille kuin vakioviitteille, vaikka tekstissa mainittaisiinkin vaintoinen.

Osoittimista ja viitteista saadaan vakio-osoittimia ja -viitteita li-saamalla niiden esittelyyn maare const:

char const* mjono;Paivays const& p;

Vakio-osoittimia kaytettaessa osoittimen paassa oleva olio tai datakayttaytyy ikaan kuin se olisi vakio — riippumatta siita, onko oliotai data alunperin maaritelty vakioksi. Tama siis tarkoittaa sita, ettavakio-osoittimen lapi sijoittaminen ja muiden kuin vakiojasenfunk-tioiden kutsuminen on mahdotonta.

Vakioviitteiden hyoty tulee siita, etta niiden avulla voidaan olios-ta nakyva rajapinta rajata sellaiseksi, etta olion muuttaminen vakio-viitteen kautta tulee mahdottomaksi. Jos esimerkiksi funktio ottaa pa-rametrikseen vakioviitteen paivaysolioon, voi funktion kayttaja luot-taa siihen, etta funktiolle valitetyn paivaysolion sisaltama paivayson varmasti sama myos funktiokutsun jalkeen. Vakioviitteiden kayttoon myos tehokas “dokumentointikeino”, jolla voi kertoa, ettei tiettyaoliota tai dataa ole tarkoitus muuttaa. Erityisen tehokkaaksi tamandokumentointikeinon tekee se, etta kaantaja takaa sen noudattami-sen. Esimerkiksi seuraava koodi ei mene kaantajasta lapi:

void muutanKuitenkin(Paivays const& pvm)pvm.asetaPaiva(1); // KAANNOSVIRHE: asetaPaiva ei vakiojf.

Page 110: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

4.3. C++: const ja vakio-oliot 110

C++ sisaltaa automaattiset tyyppimuunnokset ei-vakio-osoittimistavakio-osoittimiksi (ja sama viitteille), mutta ei toiseen suuntaan:

1 char* mjono = "Kayta string-luokkaa char*:n sijaan";2 char const* vakiomjono = mjono; // Ok: ei-vakio ⇒ vakio3 char* mjono2 = vakiomjono; // KAANNOSVIRHE: vakio ⇒ ei-vakio

Nama kielen saannot tarkoittavat kaytannossa, etta ei-vakio-olioita-kin voi kasitella ikaan kuin ne olisivat vakioita, mutta vakio-olioistaei millaan saa tavallisia. Tama on jarkevaa, kun muistetaan, etta va-kio-olion rajapinta on vain osajoukko normaaliolion rajapinnasta.

Vakioviitteiden yleisin kayttokohde on epailematta funktioiden jajasenfunktioiden parametrit. Mikali funktio ottaa parametrinaan viit-teen olioon, jota sen ei ole tarve muuttaa, tulisi parametriviitteen ollaaina vakioviite. Sama patee tietysti myos osoittimille. Osoittimia kay-tetaan yleisesti myos olioiden jasenmuuttujina yms. Talloin kannat-taa miettia, onko osoittimen lapi tarpeen muuttaa oliota. Jos vastauson ei, kannattaa kayttaa vakio-osoitinta.

Edella on jo mainittu kaksi vakioviitteiden ja -osoittimien tarke-aa kayttosyyta: kaytto dokumentointimielessa rajapintaa rajaamaanja kaantajan tekemat tarkastukset siita, etta vakiorajapintaa todellanoudatetaan. Kolmas syy kayttaa vakioviitteita esimerkiksi funktioi-den parametrina on niin yksinkertainen, etta se unohtuu helposti:vakio-olion voi laittaa ainoastaan vakioviitteen tai vakio-osoittimenpaahan.

Jos funktio ottaa parametrinaan tavallisen viitteen olioon, ei funk-tiolle voi antaa parametrina vakio-oliota, koska tavallisen viitteenkautta tulisi olion muuttaminen mahdolliseksi. Listauksessa 4.4 seu-raavalla sivulla on esimerkki tyypillisesta tilanteesta, joka syntyy kunyhdesta funktiosta unohtuu const-sana viitteen edesta pois. FunktiosaakoHameenKuukaudessa on kirjoitettu oikeaoppisesti niin, etta se ot-taa parametrinaan vakioviitteen paivaykseen — eihan funktion oletarkoitus muuttaa parametrina tullutta paivaysta. Funktion toteutuk-sessa kuitenkin kutsutaan toista funktiota onkoKarkauspaivaa, jonkakirjoittaja ei ole kayttanyt vakioviitetta parametrina, vaikka karkaus-paivan testaamisen ei varmaankaan ole tarkoitus muuttaa testattavaa

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .Funktio testaa, onko annetusta paivasta kuukauden sisalla mahdollisuus saada hamekan-

gasta. (Karkauspaivana kosimisesta kieltaytyvan taytyy ostaa kosijalle hamekangas. Epareiluaniita miehia kohtaan, jotka eivat kayta hametta.)

Page 111: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

4.3. C++: const ja vakio-oliot 111

paivaa. Tama aiheuttaa kaannosvirheen, kun vakioviitetta yritetaanantaa parametrina funktiolle, jonka ottama viite ei ole vakio.

Toinen tyypillinen vakio-olioihin liittyva virhe on, etta luokkaasuunniteltaessa unohdetaan varustaa olion tilaa muuttamattomat ja-senfunktiot const-maareella. Talloin kay niin, etta vakioviitteidenkautta oliolle ei voi kutsua ainuttakaan jasenfunktiota! Aiemmin tas-sa luvussa ollut PieniPaivays-luokka (listaus 2.1 sivulla 62) on esi-merkki tallaisesta virheellisesta luokasta, jossa luokan suunnitteli-jan hutilointi estaa luokan kayttajia hyodyntamasta kielen turvaomi-naisuuksia. Tallaisten virhetilanteiden varalle C++-kielessa on tyyppi-muunnos const cast, josta kerrotaan aliluvussa 7.4.1.

Joskus hyvin harvoin on tarve saada itse osoittimesta vakio senosoittaman olion sijaan — toisin sanoen halutaan, etta osoitinta eivoi muuttaa osoittamaan toiseen paikkaan. Silloin puhutaan osoitin-vakiosta. C++:n syntaksi seuraa tassa kohtaa taman kirjan kaytantoalaittaa const aina tyypin jalkeen. Osoittimen saa vakioksi lisaamallaconst-sanan osoitintyypin tahden jalkeen:

char* const osoitinvakio = "Loogista, eiko totta";

Vastaavasti osoittimen, jota ei saa muuttaa ja jonka osoittamaa dataaei myoskaan saa muuttaa, tyyppi on char const* const.

1 #include "paivays.hh"23 bool onkoKarkauspaivaa(Paivays* pvm p);45 bool saakoHameenKuukaudessa(Paivays const& nyt)6 // Karkauspaiva on kuukauden sisalla, jos on helmikuu ja7 // vuoden sisalle osuu karkauspaiva8 if (nyt.annaKk() != 2)9

10 return false; // Ei helmikuu11 12 else13 14 return onkoKarkauspaivaa(&nyt); // KAANNOSVIRHE15 16

LISTAUS 4.4: Esimerkki virheesta, kun const-sana unohtuu

Page 112: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

4.4. C++: Luokan ennakkoesittely 112

4.4 C++: Luokan ennakkoesittely

Huolellisesta ohjelmiston rakenteen suunnittelusta huolimatta tuleevastaan tilanteita, joissa luokkien valinen assosiaatio on kaksisuun-tainen, eli kaksi luokkaa tarvitsee tietoa toisistaan vastuualueensa to-teuttamisessa. Esimerkiksi kirjastojarjestelmassa lainaustapahtumaamallintava luokka tarvitsee tiedon kohteena olevasta kirjasta ja kirjavoi sisaltaa tiedon mihin lainaukseen se liittyy.

C++-ohjelmoinnin kannalta tama tarkoittaa tilannetta, jossa mo-lempien luokkien tulisi tietaa toistensa esittely ennen omaa toteu-tustaan (kuva 4.1). Tallainen tilanne on mahdoton, koska siina en-nen luokan Laina esittelya (laina.hh) yritetaan lukea sisaan luokanKirjastonKirja esittely, jonka alussa luetaan luokan Laina esittely,jonka alussa. . . Tallainen syklinen rakenne johtaa vaistamatta siihen,etta kaantaja antaa otsikkotiedostoja lukiessaan virheilmoituksen.

Ratkaisu tahan “kumpi esitellaan ensin: muna vai kana?” -ongel-maan on esitella toisesta luokasta vain tieto sen olemassaolosta (ei ko-konaista rakennetta). Talloin luokan koko esittelya ei C++:ssa tarvitselukea sisaan ja edella mainittu ongelma poistuu. Pelkan luokan ole-massaolon esittely tehdaan C++:ssa ennakkoesittelylla (forward decla-ration), jossa kerrotaan luokasta vain sen nimi:

class Laina;

Koska ennakkoesitellyn luokan sisainen rakenne (muistinkulutus),rajapintafunktiot ja periytymissuhteet eivat ole tiedossa, C++ salliikayttaa ennakkoesiteltya luokkaa vain sellaisissa paikoissa, joissa nai-ta ominaisuuksia ei tarvita. Kaytannossa ennakkoesitellysta luokasta

VÄÄRIN!laina.hh kkirja.hh

// tarvitaan "KirjastonKirja"#include "kkirja.hh"

class Laina // ...private: KirjastonKirja& kohde_;;

class KirjastonKirja // ...private: Laina* kenella_;;

// käytetään luokkaa "Laina"#include "laina.hh"

KUVA 4.1: Vaarin tehty luokkien keskinainen esittely (ei toimi)

Page 113: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

4.4. C++: Luokan ennakkoesittely 113

voidaan tehda taman luokan olioihin osoittavia viitteita ja osoittimia,mutta naiden lapi ei voi tehda rajapintakutsuja. Samoin luokkaa voikayttaa funktioiden ja jasenfunktioiden esittelyissa parametrina ja pa-luuarvona.

Luokan KirjastonKirja sisaltavassa otsikkotiedostossa voidaannyt vain ennakkoesitella luokka Laina, jolloin sen koko esittelya eitarvitse lukea sisaan eika syklisyysongelmaa tule, kuten listaukses-ta 4.5 nahdaan.

Luokan Laina otsikko- ja toteutustiedostossa taas voidaan kayt-taa luokan KirjastonKirja esittelya ilman ongelmia (kuva 4.2 sivul-la 115). Kuvaan on myos merkitty numeroin kaannoksen eteneminentiedostoa kkirja.cc kaannettaessa.

4.4.1 Ennakkoesittely kapseloinnissa

Ennakkoesittely ei ole ainoastaan suunnittelun silmukoiden “oikomi-seen” tarkoitettu menetelma. Sen avulla voi halutessaan jattaa ker-tomatta rajapinnan kayttajalle yksityiskohtia tietorakenteiden sisal-loista. Jos rajapinta julkistaa luokasta tai struct-tietorakenteesta vainennakkoesittelyn, rajapintaa kayttava ohjelmoija pakotetaan kasitte-lemaan esiteltya rakennetta vain rajapintafunktioiden avulla. Vertaaoheista moduuliesimerkkia (listaus 4.6 seuraavalla sivulla) vastaa-vaan esimerkkiin Modula-3:lla kirjoitettuna (aliluku 1.6.1 sivulla 51).

Ennakkoesittelylla voidaan myos vahentaa #include-kaskyn kaut-ta syntyvia tiedostojen valisia riippuvuuksia kayttamalla ennakko-esittelya siella, missa taydellisen luokkaesittelyn kaytto ei ole aivanvalttamatonta. Tasta ominaisuudesta on esimerkki aliluvussa 9.3.4.

1 // kkirja.hh2 class Laina; // ennakkoesittely34 class KirjastonKirja

...5 private:6 Laina* kenella ;7 ;

LISTAUS 4.5: Esimerkki ennakkoesittelysta

Page 114: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

4.4. C++: Luokan ennakkoesittely 114

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ennakko-paivays.hh . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .1 namespace Paivays 2 // Ennakkoesittely paivayksia kuvaavasta tietorakenteesta:3 struct PVM;4 // Palauttaa uuden paivayksen. HUOM! Tuhottava rutiinilla ‘‘Tuhoa()’’5 PVM* Luo( int paiva, int kuukausi, int vuosi );6 // Poistaa kaytosta rutiinilla ‘‘Luo()’’ kayttoonotetun paivayksen7 void Tuhoa( PVM* p );8

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ennakko-paivays.cc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .1 #include "ennakko-paivays.hh"2 namespace Paivays 3 // Paivaysten tietorakenne:4 struct PVM 5 inline PVM( int p, int k, int v );6 int p , k , v ;7 ;8 inline PVM::PVM( int p, int k, int v ) : p (p), k (k), v (v) 9

10 PVM* Luo( int paiva, int kuukausi, int vuosi )11 12 return new PVM( paiva, kuukausi, vuosi );13 1415 void Tuhoa( PVM* p )16 17 delete p;18 19

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ennakko-kaytto.cc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .1 #include "ennakko-paivays.hh"23 void kaytto() 4 Paivays::PVM* vappu = 0;5 vappu = Paivays::Luo( 1, 5, 2001 );

...6 Paivays::Tuhoa( vappu );7

LISTAUS 4.6: Ennakkoesittely kapseloinnissa

Page 115: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

4.4. C++: Luokan ennakkoesittely 115

#include "laina.hh"

// jäsenfunktioiden toteutus

2

3

4

käännös alkaa

1

käännös päättyy

#include "laina.hh"

#include "kkirja.hh"

// jäsenfunktioiden toteutus

kkirja.cclaina.cc

kkirja.hh

class KirjastonKirja // ...;

#include "kkirja.hh"

class Laina // ...;

laina.hh

// viitataan luokkaan "Laina"class Laina;

6

5

KUVA 4.2: Oikein tehty luokkien keskinainen esittely

Page 116: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

116

Luku 5

Oliosuunnittelu

Koska eraat suunnittelun piirteet eivat ole analyyttisia taitieteellisia, suunnittelun opetus ei yleensa sovi hyvin ar-vovaltaisiin teknisiin korkeakouluihin, joiden tyontekijattuntevat olonsa kotoisammaksi opettaessaan matematiik-kaa ja tieteita kuin kertoessaan, miten yksilon fysikaali-sen maailman ilmioita koskevaa arvostelukykya, estetiik-kaa, luovuutta ja herkkyytta kehitetaan. Monet tekniikanprofessorit toivovat, etta suunnittelu olisi tieteellisempaa.Itse asiassa tekniikan piirissa on ajoittain liikkeita, joidentavoitteena on tehda suunnittelusta analyyttisempaa. Neepaonnistuvat suunnittelun pehmeampien osien suhteen.Kaytannossa suunnittelu on huomattavasti kriittisempaaja arvostetumpaa toimintaa kuin sen asema tekniikan kou-lutuksessa antaa ymmartaa.

– Insinoorin maailma [Adams, 1991]

Ohjelmistomaarittely ja -suunnittelu on hyvin laaja-alainen alue, jo-hon on olemassa erilaisia teorioita, menetelmia ja kansanperinnet-ta. Kokonaiskuvan alueesta saa esim. teoksesta “Ohjelmistotuotanto”[Haikala ja Marijarvi, 2002]. Kannattaa myos pitaa aina mielessa, ettaohjelmistojen valmistaminen ei ole ainoastaan tietojenkasittelya —ohjelmiston “sieluna” olevan toimintojen filosofian maarittelee ainaihminen ja lahes aina ihmisia varten [Roszak, 1992]. Tassa luvussaesitellaan muutamia oliosuunnitteluun sopivia yleisia periaatteita jakuvaustapoja.

Page 117: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

5.1. Oliosuunnittelua ohjaavat ominaisuudet 117

5.1 Oliosuunnittelua ohjaavat ominaisuudet

Ohjelmiston jako moduuleihin ja luokkiin on ratkaisu, jota ohjaavatmonet tekijat: ongelman ratkaisu, kaytetyt tyokalut, menetelmat, ko-kemus, “talon perinteet”, reunaehdot, jatkokehityksen suunnitelmatjne. Luokkahuone-esimerkkeja laajemmissa ohjelmistoissa ei koskaanole olemassa vain yhta ainoata oikeata tapaa tehda moduuli- ja luok-kajakoa. Seuraavat aliluvut esittelevat niita asioita, joita oliosuunnit-telussa tulisi osata ottaa huomioon [Booch, 1987].

5.1.1 Mita suunnittelu on?

Kaikessa insinoorityossa suunnittelulla pyritaan loytamaan ratkai-su johonkin ongelmaan. Ratkaisun “rakennepiirustusten” avulla voi-daan valmistaa haluttu tuote. Suunnittelu on reitti ongelman kuvauk-sen ja maarittelyn seka lopullisen tuotteen valilla.

Suunnittelun tarkoituksena on saada aikaan jarjestelma, joka

• toteuttaa ongelman (toiminnallisen) kuvauksen

• voidaan toteuttaa kaytossa olevilla raaka-aineilla ja resursseilla

• sopii implisiittisiin ja eksplisiittisiin resurssirajoihin [Booch,1991, s. 20]

• erityisesti ohjelmistojen tapauksessa lopputuloksen tulisi ollavarautunut jatkokehitykseen ja yllapitoon (esimerkiksi laaduk-kaan dokumentaation avulla).

Varsinkin jarjestelman “piilo-oletusten” kaivaminen esille osaksisuunnitelmaa on yksi vaikeimmista suunnittelutyon osista.

Ohjelmistosuunnittelu on termi, joka voidaan maaritella tarkoit-tamaan ohjelmiston vaatimusten ja toiminnallisuuden suunnitteluamenetelmalla, jossa lopputuloksena saadaan mahdollisimman hel-posti toteutettava (ohjelmoitava) suunnitelma (esimerkiksi suunnit-teludokumentti). Oliosuunnittelussa tama tarkoittaa sita, etta jo suun-nitteluvaiheessa on otettava huomioon valitun menetelman (olioiden)ominaisuudet ja pyrittava tekemaan suunnitelma, joka tukee olioidenkayttoa toteutusvaiheessa.

Ohjelmistotuotteiden tapauksessa lopputulos on aarimmaisenharvoin kerralla valmis (ja heti peraan unohdettu) kokonaisuus vaan

Page 118: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

5.1. Oliosuunnittelua ohjaavat ominaisuudet 118

tuotteen julkistuksen jalkeen sita kehitetaan eteenpain lisaamallaominaisuuksia ja (valitettavan usein) korjaamalla aikaisempiin ver-sioihin jaaneita virheita.

5.1.2 Abstraktio ja tiedon katkenta

Oliosuunnittelun tarkeimpia tyokaluja on jo aikaisemmin esiteltymoduulien esittelyn yhteydessa (katso luku 1.3.3). Modulaarisuus onohjelmiston jakoa paremmin hallittaviin kokonaisuuksiin abstrahoin-nin ja tiedon katkennan avulla. Suunnitteluvaiheessa tama tarkoittaaohjelmiston jakamista paloihin joko osittavalla tai kokoavalla jaotte-lulla.

• Osittava (top-down). Haetaan jarjestelman suurimmat kokonai-suudet, jotka usein ovat toiminnallisia, kuten kayttoliittyma,tietokanta, syotto ja tulostus, tietoliikenne ja rajapinnat muihinohjelmiin. Naita osakokonaisuuksia jaetaan taas vuorostaan pie-nempiin paloihin, joista lopulta muodostuu moduuleja ja luok-kia.

• Kokoava (bottom-up). Jos suunnittelun alussa tunnetaan par-haiten joidenkin osajarjestelmien toiminta, niin moduulisuun-nittelu voidaan aloittaa niista ja myohemmin kerata naita osiasuuremmiksi kokonaisuuksiksi.

Kaytannon suunnittelutyo ei tietenkaan seuraa pelkastaan jompaakumpaa naista tavoista vaan on niiden yhdistelma. Jaottelun yhtey-dessa tunnistetaan ohjelmiston staattista rakennetta, josta tulee mo-duulijako ja dynaamisia rakenteita (olioita), jotka kuvataan luokki-na. Koska luokka pystyy ilmaisemaan kaikki moduulin tarkeimmatominaisuudet, useat oliosuunnittelumenetelmat kayttavat suunnitte-luvaiheessa vain luokkia.

5.1.3 Osien valiset yhteydet ja lokaalisuusperiaate

Jotta ohjelmiston moduulien valinen kommunikaatio ei muistuttaisispagettikoodin aikaisia sotkuja, suunnitteluvaiheessa on pyrittava pi-tamaan moduulien valiset viittaukset mahdollisimman pienina. Mo-duulin A katsotaan viittaavan moduuliin B, kun A tarvitsee jotain

Page 119: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

5.1. Oliosuunnittelua ohjaavat ominaisuudet 119

palvelua B:n julkisesta rajapinnasta. Kokoavassa suunnittelussa pyri-taan keraamaan kaikki laheisesti toisiinsa kuuluvat moduulit samaanylemman tason kokonaisuuteen (esimerkiksi kaikki ajanlaskuun jakalenteriin liittyvat moduulit). Tama vahvasti kytkeytyneiden mo-duulien paketoiminen uuden pelkistetymman rajapinnan taakse onesimerkki lokaalisuuden sailyttamisesta suunnittelussa (lokaalisuus-periaate).

Kuvassa 5.1 on erilaisia riippuvuusvaihtoehtoja moduulien valil-la. Jos alijarjestelmassa on n moduulia, niiden valilla on vahintaann − 1 riippuvuutta (jokainen osa tietaa kokonaisuuden julkisen ra-japinnan, joka on yksi osa). Maksimissaan kaikki moduulit tietavatkaikkien muiden olemassaolosta, jolloin riippuvuuksien lukumaaraon n(n−1)

2[Meyer, 1997].

Lokaalisuusperiaate pyrkii minimoimaan ohjelmakomponenttienvalisia yhteyksia ja nain pitamaan kokonaisuuden kompleksisuuttaparemmin hallinnassa. Osien valisten riippuvuuksien lukumaaranvahenemisen lisaksi ohjelmiston rakenne yksinkertaistuu, jos riip-puvuudet pidetaan aina mahdollisuuksien mukaan yksisuuntaisina.Paivaysmoduulin osat eivat tieda (eivatka valita siita), etta niita kay-tetaan suuremman kokonaisuuden osina. Jos riippuvuudet muodos-tavat syklisia silmukkaviittauksia, mutkistuu rakenteiden toteuttami-nen: jos A tarvitsee B:ta ja B tarvitsee A:ta, niin kuinka A voidaantoteuttaa ennen kuin on esitelty millainen on B, jota taas ei voida to-teuttaa ennen kuin A:n toteutus on valmis jne. (C++:n ratkaisu tahan“muna-kana” -ongelmaan on esitetty aliluvussa 4.4.)

Kuvassa 5.2 seuraavalla sivulla on esimerkkeja erilaisista yksi-suuntaisista riippuvuuksista, joista yhdessa riippuvuudet muodosta-vat silmukan.

KUVA 5.1: Erilaisia yhteysvaihtoehtoja kuuden moduulin valilla

Page 120: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

5.1. Oliosuunnittelua ohjaavat ominaisuudet 120

Syklin

en

KUVA 5.2: Moduulien valisia yksisuuntaisia riippuvuuksia

5.1.4 Laatu

Laadukkaisiin suunnitelmiin, moduuleihin ja luokkiin kuuluvia omi-naisuuksia on helppo luetella ([Booch, 1987], [Liberty, 1998], [Meyer,1997]), mutta kaytannossa vaikea toteuttaa kaikilta osiltaan:

• Oikeellisuus. Ohjelmiston komponenttien (moduuli, luokka taikoko ohjelmisto) tulee toteuttaa kaikki toiminnot suunnitelmanmaarittelemalla tavalla.

• Virheita sietava. Komponenttien tulee varautua virhetilantei-siin (vaara syote, muistin loppuminen jne.) ja osata toimia vir-hetilanteen havaitessaan etukateen suunnitellulla tavalla (ro-bustness).

• Selkeys. Ohjelmiston toiminnallisten yksikoiden rajapintojendokumentointi ja toteutus on tehtava yhtenaisella ja selkeallamenetelmalla, jotta niiden muuttaminen ja uudelleenkaytto oli-si helppoa.

• Etukateissuunnittelu. Moduulien ja luokkien suunnittelussatulee pyrkia ottamaan huomioon kohdeprojektin tarpeiden li-saksi yleisempia nakokohtia, jotta syntyva komponentti olisisuuremmalla todennakoisyydella myos kayttokelpoinen osa tu-levia projekteja.

• Tehokkuus. Komponenttien tulee kayttaa tuhlailematta hyvak-seen jarjestelman resursseja (prosessointiaika ja muisti).

Page 121: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

5.2. Oliosuunnittelun aloittaminen 121

• Siirrettavyys. Komponenttien suunnittelussa ja toteutuksessatulisi ottaa huomioon, etta osia mahdollisesti kaytetaan tulevai-suudessa niiden kehitysymparistosta poikkeavassa ymparistos-sa.

• Elinkaaren huomioiva. Laadukkaan ohjelmiston rakenteen jadokumentaation tulisi tukea jatkokehitysta, jota kaikille “todel-lisille” ohjelmistoille tullaan jossain vaiheessa tekemaan.

“Rational design process and how to fake it”

Tama David Parnasin [Clements ja Parnas, 1986] artikkelin otsikkokuvaa kaytannon ohjelmistotyon ristiriitaa usein hyvinkin yleviensuunnitteluperiaatteiden kanssa.

Naita ristiriitoja on useita. Mika on oikein toimiva ohjelma?(Asiakkaan ja maarittelijan kanta ei valttamatta ole aina sama.) Mi-hin kaikkiin mahdollisiin (ja mahdottomalta tuntuviin) virhetilantei-siin ohjelmiston tulisi varautua — ja miten? Kun aikataulut, kiire jastressi painavat paalle, niin kuka jaksaa ajatella ohjelmiston tuleviayllapitajia ja koodin selkeytta?

Vaikka kaytannon tyo aina valilla onkin kaukana akatemian yle-vista paamaarista, niin harva silti vaittaa, etta huolellisesta ohjelmis-tojen suunnittelusta tulisi luopua kokonaan. Vaikka joissain osissajoudutaankin joustamaan, niin jo pitamalla aina mielessaan ylevam-pia paamaaria pystyy varmasti pitamaan laatunsa “aloittelijan spaget-tihakkyraa” parempana.

5.2 Oliosuunnittelun aloittaminen

Alku aina hankalaa. Ensimmaisena taytyy pitaa mielessa kaikki ko-konaisuuteen liittyvat asiat. Oliosuunnittelun paavaiheet ovat (luet-telossa tarkoitetaan komponentilla ohjelmiston moduuleja ja luokkia)[Booch, 1987]

1. komponenttien tunnistaminen

2. komponenttien vastuualueiden maarittely. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .Kun joku keksii ideointiin, suunnitteluun, ohjelmointiin ja ihmisten johtamiseen menetel-

man, jolla saa ylivoimaista laatua taysin aikataulujen ja budjetin mukaisesti, niin pyydammenoyrasti kertomaan siita meille ja muullekin maailmalle.

Page 122: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

5.2. Oliosuunnittelun aloittaminen 122

3. komponenttien valisten suhteiden maarittely (keskinainen na-kyvyys)

4. komponenttien rajapintojen maarittely (esimerkiksi formaalis-ti matemaattisella notaatiolla, mahdollisimman yksikasitteises-ti ohjelmointikielen rakenteilla tai sanallisella kuvauksella)

5. viimeisena vaiheena edella maariteltyjen luokkien ja moduu-lien seka niiden muodostaman kokonaisuuden (=ohjelmisto)toteutus.

Luettelo on hyva esimerkki saannoista, joissa kaytannon suunnit-telutyossa ei edeta yksi kohta kerrallaan — “Tama saanto mietitaannyt loppuun ennen kuin jatketaan seuraavaan vaiheeseen”. Tarkeaosa suunnittelua on ideointi, jota ei kannata rajoittaa vain yhteen osa-alueeseen kerrallaan. Kun johonkin kohtaan sopiva idea tulee mie-leen, niin se kannattaa kirjoittaa muistiin ja luottaa siihen, etta myo-hemmin suunnittelun viimeistelyssa mahdolliset turhat tai mahdot-tomat ideat karsiutuvat pois.

5.2.1 Luokan vastuualue

Jokaisesta olio-ohjelmassa olevasta luokasta pitaisi olla olemassa sel-kea ja kattava kuvaus. Yhdella sanalla ilmaistuna on maariteltava luo-kan vastuualue [Budd, 2002]. Vastuualue maarittelee sen, mita ky-seisen luokan olioiden on tarkoitus mallintaa ohjelmistossa. Vastuu-alueeseen kuuluvat luokan tarjoamat palvelut (jotka ovat kaytettavis-sa luokan julkisen rajapinnan kautta) seka luokkaan kuuluvan oliontoiminnan ymmartamisen kannalta oleellinen tilatieto eli attribuutit.Kaytannon luokissa tilatietoon kuuluu usein muutakin kuin yksittai-sia ohjelmointikielen muuttujia. Luokka voi muun ohella omistaa toi-sia olioita (esim. Paivays voi sisaltya jonkin toisen luokan tilaan).

Erityisesti kannattaa ottaa huomioon, etta suunnitteluvaiheessaon tarkoitus kirjata vain “julkisen” toiminnallisuuden ymmartamisenkannalta oleellista informaatiota. Esimerkiksi nayttolaitteella olevaagrafiikkapistetta kuvaava luokka sisaltaa suunnitteluvaiheessa attri-buutin “sijainti”, ja tata tietoa voidaan kasitella rajapinnan tarjoamienpalveluiden avulla. Suunnitteluvaiheessa ei ole oleellista maaritellasita, onko jarjestelmassa sijainti-informaation toteutus x- ja y-koordi-naatit kokonaislukuina vaiko esimerkiksi polaarikoordinaatiston kul-

Page 123: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

5.2. Oliosuunnittelun aloittaminen 123

ma- ja sadetiedot. Tama sijaintiattribuutin toteutus on luokan sisai-nen asia, ja siihen liittyvat paatokset tulisi tehda luokan toteutusvai-heessa. (Edelleen kaytannon tyossa on usein hyodyllista miettia myostoteutukseen liittyvia asioita suunnittelun aikana, mutta niiden ei tu-lisi paasta hallitsemaan ja sotkemaan ylemman tason suunnittelua.)

5.2.2 Kuinka loytaa luokkia?

Helpommin sanottu kuin tehty. Useat oliomenetelmat lahtevat ongel-man kuvauksesta. Ohjelmistosuunnittelun kaynnistyessa pitaisi ol-la olemassa vahintaan toiminnallinen kuvaus siita, mita ollaan teke-massa (sama patee kaikkeen insinooritoimintaan [Adams, 1991]). Ta-man niin sanotun maarittelyvaiheen dokumentista etsitaan (vaikka-pa alleviivaamalla) substantiivit, joista sitten kenties tehdaan olioita.Yksi mahdollinen lahtokohta talle “oliometsastykselle” on kayttota-paukset (use case), [Jacobson ja muut, 1994], joissa on pyritty kuvaa-maan yksittainen ohjelmiston osa kayttajaroolin (actor) nakokulmas-ta. Kayttotapaus pyrkii kuvaamaan lyhyesti, mutta mahdollisimmankattavasti, yksittaiset ohjelmiston toimintaan liittyvat osat. (Naytta-vat keskenaan ristiriitaisilta vaatimuksilta — ja ovatkin sita!) Naista“naytellyista” ohjelman tarkeimmista toiminnoista muodostuu maa-rittely koko ohjelmiston toiminnallisista vaatimuksista. Kuvassa 5.3seuraavalla sivulla on esimerkki kirjaston tietojarjestelman yhdestakayttotapauksesta.

Substantiivien hakumenetelman hyvana puolena on, etta mallin-nettavan ongelman alueelta otetaan sopivan kokoisia kokonaisuuk-sia ohjelman rakenteeseen. Toisaalta ohjelma tarvitsee luultavastitoimiakseen muitakin osia kuin ne, jotka loytyvat suoraan ongel-man kuvauksesta. Emme voi siis olla varmoja, etta saamme laaduk-kaan oliosuunnitelman vain pelkan maarittelyn substantiivien avul-la. Esimerkkina kayttamamme kirjaston toiminnallisuuden kuvauk-sessa esiintyy varmasti tietoa lainausajoista ja muista vastaavistaajankohdista ja ajanjaksoista. Pelkalla substantiivihaulla emme sil-ti valttamatta keksi, etta saatamme tarvita paivayspalveluita vartenoman olion tai moduulin ohjelmistoomme.

Page 124: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

5.2. Oliosuunnittelun aloittaminen 124

Nimi: Kirjan lainaaminen, versio 1.0 / JykeSuorittajat: Asiakas itsepalvelupaatteella tai virkailijaEsiehdot: Lainattavan kirjan ja lainaajan tiedot ovat jar-

jestelmassa, lainauksen suorittaja on kerrottujarjestelmalle.

Kuvaus: Asiakas tai virkailija syottaa kirjan tiedot jar-jestelmaan joko viivakoodin lukijalla tai nap-painsyotteella (ISBN-tunniste tai muu yksi-selitteinen tieto). Jarjestelma kirjaa lainaus-tapahtuman asiakkaan lainaustietoihin. Lai-naustapahtuman paattyminen kerrotaan lai-nauksen suorittajalle nayttolaitteen avulla.

Poikkeukset: Esiehdot eivat ole kunnossa, lainaaja on lai-nauskiellossa tai kirja on merkitty varatuksimuualle.

Lopputulos: Kirja on merkitty lainatuksi asiakkaalle maa-raajaksi.

Muut vaatimukset: Paivassa pystyttava kasittelemaan 50 000 lai-naustapahtumaa, onnistuneen lainauksen onkirjauduttava alle kahdessa sekunnissa, vir-hetilanteissa kayttajalle on annettava selvattoimintaohjeet jatkotoimenpiteista.

KUVA 5.3: Esimerkki kayttotapauksesta

5.2.3 CRC-kortti

CRC-korttimenetelma on “korttipeli”, jossa yhdistetaan komponent-tien vastuualueiden ja niiden valisten suhteiden miettiminen. Jokai-sesta potentiaalisesta komponentista kirjoitetaan kortti, jossa on mer-kittyna seuraavat asiat: komponentin (olio tai moduuli) nimi (Com-ponent), kuvaus komponentin vastuualueesta (Responsibility) ja lis-ta niista komponenteista (julkisista rajapinnoista), joita tama kompo-nentti toimiakseen tarvitsee (Collaborators).

Kun komponenttia mietittaessa tulee mieleen uusi mahdollinenkomponentti, tehdaan siita heti kortti. Kun kortin jo omaavasta kom-ponentista tulee mieleen sille kuuluva toiminnallisuus, kirjataan ta-

Page 125: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

5.2. Oliosuunnittelun aloittaminen 125

ma vastuu kortille. Jos vastuun toteuttamiseen tarvitaan selvasti toi-sen komponentin apua, niin taman yhteistyokumppanin nimi kirja-taan myos muistiin. Tata kuka-mita -syklia toistamalla saadaan kir-jatuksi hyvin luontevasti eri jarjestyksessa tulleita ideoita heti muis-tiin. Kuvassa 5.4 on esimerkki CRC-kortista. [Beck ja Cunningham,1989]

CRC-kortteja voidaan hyodyntaa myos kayttotapausten kanssa:kayttotapaus kaydaan lavitse korttien avulla ja tarkastetaan, onko ole-massa tarvittavat kortit ja niiden yhteistyokumppanit, joilla kayttota-pauksen toiminnallisuus saadaan aikaan (ja jos kaikkia ei ole, niinsamalla saadaan mietityksi uusia kortteja) [Wilkinson, 1995]. Vastaa-vasti tasta korttien “pyorittamisesta” voidaan saada aikaan hyodylli-sia aikaisemmin huomaamatta jaaneita kayttotapauksia. Kun kompo-nentteja on keratty tarpeeksi, niiden rakennetta ja keskinaisia suh-teita voidaan kuvata tarkemmin seuraavassa aliluvussa esiteltavallagraafisella kuvaustavalla.

KUVA 5.4: Kuva keskeneraisen CRC-korttipelin yhdesta kortista

Page 126: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

5.3. Oliosuunnitelman graafinen kuvaus 126

CRC-korttien haittapuolia ovat huono skaalautuvuus ja yllapidonvaikeus. Suurissa ohjelmistoissa tulisi “korttipeli”-ideoinnin tasostariippuen satoja tai jopa tuhansia kortteja, joiden hallinta menee jos-sain vaiheessa varmasti mahdottomaksi. Kaytannon suunnittelun ai-kana tulee usein myos tarve palata tarkentamaan ja korjaamaan suun-nittelun aikaisempaa vaihetta, jolloin CRC-korttien yllapidosta tuleeongelma. Naiden ongelmien hallitsemiseksi on kehitetty tietokoneel-la kasiteltavia oliosuunnitelman graafisia kuvausmenetelmia, joistatutustumme tarkemmin UML:aan aliluvussa 5.3.

5.2.4 Luokka, attribuutti vai operaatio?

Oliosuunnittelussa tulee (varsinkin aloittelevalle suunnittelijalle)usein vastaan tilanne, jossa ei ole aivan varma siita, onko kasiteltavaasia luokka vaiko “vain” jonkin luokan attribuuttiominaisuus. Tahan-kaan suunnittelun osaan ei ole olemassa viisastenkivea, mutta muu-tamia ohjenuoria kyllakin.

Toiminnalliset osat, kuten “siirtaminen”, “kasvattaminen” ja “mo-nistaminen” on melko helppo ymmartaa olioiden toimintoina eli nii-den luokkien operaatioina. Ominaisuudet kuvaavat jotain jo olemas-sa olevaa, joten ne sopisivat luokkien attribuuteiksi: “vari”, “sijain-ti”, “koko”, “ika” jne. Kaikki ne osat, joilla on ominaisuuksia ja joi-ta kaytetaan operaatioiden avulla, ovat olioita (suunnittelun luokkia).[Koskimies, 2000]

5.3 Oliosuunnitelman graafinen kuvaus

Unified Modelling Language (UML) [OMG, 2002b] on nykyisin enitenhuomiota saanut oliosuunnitelmien graafinen kuvausnotaatio. UMLon Object Management Groupin vuonna 1997 hyvaksyma standardi,jonka viimeisin versio (8.6.1999) on 1.3 [OMG, 2002a].

Seuraavien alilukujen on tarkoitus antaa UML:sta sellainen yleis-kuvaus, etta myohemmissa luvuissa sita hyodyntaen piirretyt kaa-viot olisivat ymmarrettavia. Kattava kuvaus UML:sta loytyy teoksista“The Unified Modeling Language Reference Manual” [Rumbaugh jamuut, 1999] ja “The Unified Modeling Language User Guide” [Boochja muut, 1999].

Page 127: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

5.3. Oliosuunnitelman graafinen kuvaus 127

On tarkeata huomata, etta UML on vain kuvaustapa. Se ei ole me-netelma, joka ottaisi kantaa siihen, miten luokkia ja niiden valisiasuhteita tulisi suunnitella, mika olisi hyva rajapinta tai miten ylipaa-taan ohjelmisto pitaisi jakaa osiin. UML sijoittuu nain ohjelmointi-kielten ja suunnittelumenetelmien valimaastoon.

5.3.1 UML:n historiaa

UML on kehittynyt useista 90-luvulla syntyneista olioiden kuvaus- jasuunnittelumenetelmista yhdistaen niiden ominaisuuksia. UML:ntarkeimmat edeltajat ovat

• James Rumbaughin Object Modeling Technique, OMT[Rumbaugh ja muut, 1991]

• Grady Boochin kayttama esitystapa, joka tunnetaan tekijan mu-kaan Boochin notaationa [Booch, 1991]

• Ivar Jacobsonin kayttotapaukset [Jacobson ja muut, 1994].

UML:n suunnittelijat ovat asettaneet sille muun muassa seuraaviapaamaaria [OMG, 2002b]:

• UML tarjoaa valmiiksi maaritellyt visuaaliset rakenteet (ulkoa-su ja kayttotarkoitus), joilla suunnittelija voi yhtenaistaa ohjel-mistosuunnitelmien “rakennepiirustuksia”.

• UML sisaltaa laajennus- ja erikoistamismekanismit, joilla perus-notaatiota pystytaan soveltamaan myos erityistilanteissa.

• UML on riippumaton ohjelmointikielista ja ohjelmiston tuotan-toprosessista.

• UML pyrkii tukemaan graafisen suunnitelman automaattista(koneellista) kasittelya.

UML:n avulla tehdyt ohjelmiston kuvaukset jakautuvat kahteenpaaosaan: pysyvaa (staattista) perusrakennetta kuvaaviin luokkakaa-vioihin (ohjelmiston luokat, rajapinnat ja oliot seka niiden valisetsuhteet) ja ajoaikaista kayttaytymista kuvaaviin (dynaamisiin) kaa-vioihin. Seuraavissa aliluvuissa esitellaan UML:n perusnotaatioitaesimerkeilla.

Page 128: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

5.3. Oliosuunnitelman graafinen kuvaus 128

5.3.2 Luokat, oliot ja rajapinnat

UML:ssa luokka kuvataan laatikkona, jossa luetellaan luokan ominai-suudet, joista tarkeimmat ovat luokan nimi, attribuutit, julkisen ra-japinnan palvelut ja kuvaus luokan vastuualueesta. Kukin naista onluokkalaatikossa omassa lokerossaan. Yleensa nakyville piirretaan ai-nakin kolme lokeroa (nimi, attribuutit ja palvelut). Jos jokin naistakolmesta osasta on kuvatun asian kannalta turha, niin lokero jatetaantyhjaksi.

Luokasta tehty olio on kuvaustavaltaan muuten samanlainen,mutta laatikon ylareunassa on alleviivattuna olion nimi (identiteet-ti) ja tyyppi eli olion luokan nimi. Myos oliolla olevien attribuuttienarvot voidaan merkita nakyviin.

Usein yksi toiminnallinen rajapinta halutaan kuvata omana yk-sikkonaan. Tallainen julkisen rajapinnan esittelyn sisaltava laatikkomerkitaan UML:ssa sanalla interface. Useampi kuin yksi luokka pys-tyy lupaamaan, etta se toteuttaa jonkin rajapinnan maaritteleman toi-minnallisuuden (ja yksi luokka voi toteuttaa useita rajapintoja).

Kuvassa 5.5 seuraavalla sivulla on esimerkki luokasta, oliosta jarajapinnasta. Luokka kuvaa yksinkertaista grafiikkapistetta, ja siita ontehty olio, jolle on annettu nimi p1. Luokassa on maaratty pisteen vas-tuulle tieto siita, missa kohdassa nayttolaitetta se sijaitsee, attribuutil-la “sijainti”. Oliosta nahdaan, etta tama attribuutti on toteutettu olionsisalla olevilla koordinaattimuuttujilla x ja y. “Nakyvyys” on rajapin-ta, joka maarittelee mita operaatioita nakyvyytta tukeville olioille voisuorittaa.

UML maarittelee luokan alkioille myos erilaisia nakyvyysmaarei-ta ja muita niiden kaltaisia ohjelmointikielten rakenteita, kuten jasen-funktioiden paluuarvoja ja parametreja. Nama ominaisuudet kertovatjo melko tarkalla tasolla miten luokka halutaan toteuttaa valitulla oh-jelmointikielella. Naita lisamaareita kaytetaan myos hyvaksi tietoko-neavusteiseen ohjelmakoodin tuottamiseen UML-suunnitelmista.

Ohjelmiston rakentamisen alkuvaiheessa (maarittely, ylimman ta-son suunnitelmat ja analyysit) luokkien pitaisi sisaltaa ainoastaanluokan vastuualueen ymmartamisen kannalta oleelliset asiat (att-ribuutit, palvelut ja vastuualueen kuvaus). Toteutukseen liittyvienasioiden ei pitaisi olla sotkemassa ylimman tason suunnitteludoku-mentteja ja luokkakaavioita.

Page 129: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

5.3. Oliosuunnitelman graafinen kuvaus 129

sijainti lue_sijainti()

siirrä( uusi_sijainti )

Piste

sijainti

Edustaa yhtä grafiikkanäytöllä

sijaitsevaa pistettä.

(a) Luokka

p1 : Piste

pair<int,int> lue_sijainti()

siirrä( pair<int,int> )

sijainti = ( 7, 9 )

(b) Olio

piirrä()

piilota()

Näkyvyys

«interface»

(c) Rajapinta

KUVA 5.5: Luokka, olio ja rajapinta

Isoissa ohjelmistoissa ylimman tason abstrakteja suunnitteluku-vauksia usein tarkennetaan lahemmaksi toteutusta ja talloin myosluokkien ja rajapintojen tarkempi kuvaus on usein kaytossa. Kuvas-sa 5.6 seuraavalla sivulla on esimerkki aikaisemmasta pisteluokas-ta, jolle on maaritelty kaksi jasenmuuttujaa (private-nakyvyydella)ja julkisen rajapinnan (public) jasenfunktioita. Lisaksi yksi jasen-funktioista on jatetty ainoastaan luokasta periytettyjen osien kayttoon(protected).

Kuvassa on myos taulukko erilaisista UML:n maarittelemista na-kyvyystasoista. Koska nakyvyydet liittyvat laheisesti ohjelmointikie-liin, niita ei kaikissa toteutuskielissa ole suoraan kaytettavissa. Esi-merkiksi pakkauksen nakyvyystaso on Java:ssa helposti suoraan kay-tettavissa oleva ominaisuus, mutta C++:ssa se toteutetaan ystava-omi-naisuuden avulla (aliluku 8.4.1 sivulla 259).

UML luokkasuunnittelussa pidetaan yleisena rajanvetona, ettakaaviot sisaltavat ne asiat, jotka kertovat luokalta vaaditut asiat (vas-tuualue). Koska toteutus kuitenkin tehdaan erikseen ohjelmointikie-lella, niin on melko turhaa laittaa suunnittelukaavioon asioita jotkaovat vain ohjelmointia tukevia tai ovat ainoastaan toteutusvaihee-seen liittyvia asioita. Yksi esimerkki tallaisesta UML -kaavion “tur-hasta” tavarasta (scaffolding code) ovat jasenmuuttujille halutut asetaja anna -jasenfunktiot (aliluku 4.2 sivulla 101). Toteutuksen kannal-

Page 130: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

5.3. Oliosuunnitelman graafinen kuvaus 130

+ bool siirrä( int dx, int dy )

+ pair<int,int> lue_sijainti()

Piste

− x : int− y : int

# aseta_xy( pair<int,int> )

(a) Toteutusluokka

Nakyvyys Merkinta Kaytettavissa

public + kaikki ohjelmiston oliot voivat kayttaaprotected # luokan oliot ja periytettyjen luokkien

oliotprivate − vain samaa luokkaa olevat oliotpackage ∼ samassa pakkauksessa olevien luok-

kien oliot(b) Nakyvyysmaareita

KUVA 5.6: Tarkennetun suunnitelman luokka ja nakyvyysmaareita

ta kyseiset jasenfunktiot voivat olla oleellisia, mutta niiden merkintaluokkakaavioon ei tuo mitaan lisainformaatiota siita mika on luokanvastuualue.

5.3.3 Luokkien valiset yhteydet

Suunniteltaessa ohjelmiston luokkia on tarkeata pystya merkitse-maan myos luokkien keskinaisia yhteyksia, riippuvuuksia ja niidenkokonaisuuksien toimintoja. Koska ohjelmisto on maara koota kes-kenaan kommunikoivista olioista, tarvitsemme tavan kuvata naidenolioiden ja niiden luokkien valisia yhteyksia. UML:n erilaisia yhteys-tapoja on koottuna kuvassa 5.7 seuraavalla sivulla.

Page 131: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

5.3. Oliosuunnitelman graafinen kuvaus 131

Yhteys Tarkenne Selitys Symboli

Riippuvuus Luokka tai moduuliviittaa kohteeseen.

Assosiaatio Luokat tai oliottarvitsevat toisiaanvastuualueensatoteutuksessa.

Yksisuuntainen Vain toinenassosiaation paistatietaa assosiaatiosta(kuka ⇒ keta -suhdekerrotaan nuolella).

Muodostuminen Olioiden elinkaareton tiukasti sidottutoisiinsa.

Koostuminen Olioiden elinkaaretliittyvat toisiinsa.

Periytyminen Laajennussuhde.

Toteuttaminen Luokka toteuttaaerikseen maaritellynrajapinnan. Rajapintanimi

KUVA 5.7: UML:n yhteystyyppeja

Riippuvuus

Heikoin yhteys on tieto siita, etta luokka A tarvitsee luokan B olioi-ta (esimerkiksi rajapintafunktioidensa parametrina). Tama ilmaistaankatkoviivalla varustetulla nuolella, joka osoittaa luokasta A luokkaanB. Luokan A sanotaan riippuvan (depend) luokasta B. Kuvassa 5.8seuraavalla sivulla luokka Heippa vaatii (riippuvuudella ilmaistuna),etta luokka java.awt.Graphics on sen kaytettavissa (vertaa Java-esi-merkkiin aliluvussa 1.6.2).

Page 132: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

5.3. Oliosuunnitelman graafinen kuvaus 132

Heippa java.awt.Graphics

KUVA 5.8: Luokka “Heippa” kayttaa Javan grafiikkaolioita

Assosiaatiot

UML:n assosiaatio (association) on kahden luokan valille piirrettyviiva, jonka avulla kerrotaan luokkien liittyvan toisiinsa ohjelmis-ton rakenteen kannalta. Assosiaatio on riippuvuutta vahvempi omi-naisuus (luokat kayttavat toistensa rajapintapalveluita osana omanvastuualueensa toteuttamista). Assosiaatioon merkitaan usein tar-kennuksia kuvaamaan sen ominaisuuksia. Tarkeimmat tarkennuk-set ovat assosiaation nimi, lukumaarasuhteet (multiplicity) ja luok-kien “roolinimet” (role). Yleisimmin kaytettyja lukumaaran ilmaisujaUML:n yhteyksissa on esitetty kuvassa 5.9.

Kuvassa 5.10 seuraavalla sivulla on kolme esimerkkia assosiaa-tioista. Grafiikkapisteita ja nayttolaitetta mallintavien luokkien valillaon merkitty tarkennuksena lukumaarasuhteet. Kunkin luokan olioitakoskeva lukumaaratieto luetaan assosiaatioviivan toisesta paasta, elikuvassa on seuraavat lukumaarasuhteet:

• “Nayttolaitteeseen liittyy yksi tai useampi piste” (siis naytollaon aina vahintaan yksi piste).

Merkinta SelitysJos lukumaaraa ei ilmoiteta, se voi olla mika tahansa

n Tasan n kappaletta∗ Useita (nolla tai useampia)0..1 Vaihtoehto (nolla tai yksi kappale)m..n Lukumaara (m ≤ olioiden lukumaara ≤ n)

1..2, 7, 8..10 Useita lukumaaravaleja

KUVA 5.9: UML-assosiaatioiden lukumaaramerkintoja

Page 133: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

5.3. Oliosuunnitelman graafinen kuvaus 133

• “Jokaiseen pisteeseen liittyy aina yksi nayttolaite” (siis jokainenpisteolio tietaa milla naytolla se sijaitsee).

Luokkien Yritys ja Henkilo valilla on “palkkaussuhteeksi” nimettyassosiaatio. Tassa tapauksessa Yritys on tyonantajan roolissa ja Hen-kilo tyontekijana.

Usein luokkien valinen yhteys on olemassa vain toiseen suun-taan (lokaalisuusperiaate) eli vain toisen luokan tarvitsee tietaa toisenolemassaolosta. Oletuksena UML-assosiaation katsotaan olevan kak-sisuuntainen (kumpikin luokka tietaa yhteyden olemassaolosta). Josyhteys on yksisuuntainen, se ilmoitetaan piirtamalla nuoli kohti sitaluokkaa, joka ei tieda tai valita assosiaation olemassaolosta (esimerk-kikuvassa lainausjarjestelma kasittelee kirjaston kirjoja, mutta niitamallintavat oliot eivat tieda Lainausjarjestelma-luokan olemassaolos-ta).

C++: Yksisuuntaiset suhteet pystytaan toteuttamaan C++:lla hyvinsuoraviivaisesti: toista luokkaa tarvitseva osa ottaa nakyville kohde-luokan esittelyn #include-komennolla kohdeluokan otsikkotiedostos-ta ja viittaus kaytettaviin olioihin talletetaan osoitin- tai viitejasen-muuttujaan (listaus 5.1 seuraavalla sivulla). Esimerkissa on valittutalletustietorakenteeksi vektori, koska maarittelyn (kuva 5.10) mu-kaisesti emme etukateen tieda lukumaaran ylarajaa (∗). Vaihtoehdon(0..1) yleisin toteutus on osoitinjasenmuuttuja kohdeolioon, joka voiolla arvossa nolla, kun kohdetta ei ole olemassa. Kun lukumaara ontasan n kappaletta, lukumaaratieto kannattaa kertoa vektorin raken-tajan kutsun yhteydessa:

1..* 1 NäyttölaitePiste

Lainausjärjestelmä Kirjastonkirja*

Yritys Henkilö

Työnantaja

Palkkaa

Työntekijä

KUVA 5.10: Esimerkki luokkien valisista assosiaatioista

Page 134: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

5.3. Oliosuunnitelman graafinen kuvaus 134

1 #ifndef LAINAUSJARJESTELMA HH2 #define LAINAUSJARJESTELMA HH34 // Lainauksissa kasitellaan kirjastonkirjoja5 #include "kirjastonkirja.hh"6 class LainausJarjestelma7

...8 private:9 vector<KirjastonKirja*> PikkuKirjastonTietokanta ;

10 ;11 #endif

LISTAUS 5.1: Lainausjarjestelman esittely, lainausjarjestelma.hh

1 LainausJarjestelma::LainausJarjestelma() :2 PikkuKirjastonTietokanta ( n )3

...

Kun luokkien valinen assosiaatio on kaksisuuntainen, joudutaankayttamaan aliluvussa 4.4 selostettua ennakkoesittelymekanismia.

Koosteet

Kooste (aggregate) on assosiaation erikoistapaus, jonka avulla maari-tellaan luokkien valisen yhteyden lisaksi niiden valille omistussuh-de. UML maarittelee kaksi koostetyyppia: kokoonpanollinen kooste(muodostuminen, composite aggregate) ja jaettu kooste (koostuminen,shared aggregate).

Koosteen symboli on UML:ssa vinonelio (“salmiakki”). Salmiakkion assosiaatioviivan paassa kiinni siina luokassa, jonka osana toisenluokan olio on. Kuvassa 5.11 seuraavalla sivulla on esimerkki kooste-suhteista. Umpinaisella mustalla salmiakilla on merkitty, etta kirjas-ton kirja muodostuu yhdesta paivaysoliosta (kokoonpanollinen koos-tuminen). Kun tama paivaysolio on liitetty isantaluokkaansa, vastuusen elinkaaresta on sidottu isantaluokkaan. Kun kirjastonkirjaolio tu-houtuu, samalla tuhoutuu myos sen omistama paivaysolio. Taman

Page 135: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

5.3. Oliosuunnitelman graafinen kuvaus 135

elinkaarisuhteen vuoksi maaritellaan myos, etta yksi olio saa ollamuodostumissuhteessa vain yhteen paikkaan.

Jaetussa koosteessa (koostuminen) olio voi kuulua useaan isanta-luokkaan ja olion elinkaari ei valttamatta paaty yhtaaikaa emoluokankanssa. Esimerkkikuvassa jokainen kirjaston kirja koostuu kustanta-jan tiedot sisaltavasta erillisesta oliosta. Sama kustantajaolio voi ollaosana useamman kirjan tietoja.

C++: Koosteet toteutetaan C++:ssa jasenmuuttujien avulla. Jos koos-tumissuhteen kohde voi muuttua olion elinkaaren aikana (jaettukooste), toteutuksessa kaytetaan osoitinjasenmuuttujaa. Kun koos-teoliolle halutaan tasmalleen sama elinkaari kuin emolle, kannattaaC++:ssa kayttaa oliojasenmuuttujaa.

Milloin kayttaa koostetta?

UML ei tee ohjelmistosuunnittelijan tehtavaa helpoksi maarittelemal-la tarkkoja saantoja siita, milloin mitakin yhteytta tulisi kayttaa (tamaon tietoinen valinta, silla UML ei ota kantaa kaytettyyn suunnittelu-menetelmaan).

Mielestamme useimmissa tapauksissa ohje yhteyden “lajin” va-lintaan on selkea: Jos luokka A tarvitsee tiukasti koko elinkaaren-sa ajan luokkaa B, kyseessa on kooste, muutoin assosiaatio. Mitensitten maaritellaan “tiukasti”? Kun koosteen kohteella ei ole enaa ole-massa yhtakaan “emo-oliota”, se on ohjelmiston kannalta turha. As-sosiaatiossa olioilla on oma oikeutuksensa olemassaoloon myos yk-sinaan. Kuva 5.12 seuraavalla sivulla yrittaa valottaa tilannetta: joskuvan mukaisessa mallinnuksessa osasto lakkaa olemasta, niin sen

PäiväysKirjastonKirja

Kustantaja tiedot

N

1

1

KUVA 5.11: UML:n koostesuhteita

Page 136: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

5.3. Oliosuunnitelman graafinen kuvaus 136

koosteosat eli laitokset lakkaavat myos olemasta (ovat turhia); tyonte-kijaoliot jatkavat kuitenkin olemassaoloaan vaikka niiden tyonantaja-assosiaatio poistuisikin.

Koostetyypin (koostuminen tai muodostuminen) valitsemiseenvaikuttaa useimmiten myos kohdeluokan olion vastuualue koko oh-jelmistossa. Jos samaa kohdeluokan oliota kayttaa yksikin toinen oliotai kohdeoliota ei haluta (esim. operaation raskauden takia) luoda jatuhota useita kertoja, kyseessa on koostuminen (jaettu kooste).

Yleisimmin kaytetty yhteys on assosiaatio. Jos halutaan korostaayhteyden “heikkoutta” tai lyhytkestoisuutta (esimerkiksi kohdeoliotatarvitaan vain parametreina, tai kohteena on tietty jarjestelman ik-kunointikirjasto, jonka on pakko olla olemassa toiminnan kannalta),kaytetaan riippuvuutta.

Periytyminen ja toteuttaminen

UML:ssa periytyminen (inheritance, generalization) merkitaan nuo-liviivalla, joka osoittaa aliluokasta kohti kantaluokkaa. Kuvassa 5.13seuraavalla sivulla Kirja on kantaluokka, josta on periytetty uusiluokka KirjastonKirja.

. . . . . . . . Ekskursio: Periytyminen (lisaa luvussa 6) . . . . . . . .

Periytyminen on olio-ohjelmoinnin ominaisuus, jossa tehdaanuusi luokka olemassa olevan mallin (luokan) pohjalta. Periy-tetty luokka eli aliluokka sisaltaa (perii) kaikki sen mallin elikantaluokan ominaisuudet (attribuutit ja rajapinnan). Aliluok-

LaitosOsasto

Työntekijä

6

työnantaja

*

KUVA 5.12: Osaston, sen laitosten ja tyontekijoiden valisia yhteyksia

Page 137: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

5.3. Oliosuunnitelman graafinen kuvaus 137

Kirja

KirjastonKirja

UDKhyllyluokka()UDKmerkkijono()UDKkoodi()

UDKkoodaus «interface»

SQLtalletus

KUVA 5.13: Kirjasta periytetty luokka, joka toteuttaa kaksi rajapintaa

kaan voidaan lisata uusia ominaisuuksia, ja kantaluokan omi-naisuuksia voidaan tarvittaessa muuttaa.

Koska periytymisella luodun uuden luokan rajapinta on ole-tuksena sama kuin kantaluokalla, uuden luokan sanotaan ole-van kayttaytymiseltaan myos kantaluokan olio (laajennettuversio siita). Tama niin sanottu “is-a”-suhde on yksi olio-oh-jelmoinnin ja oliosuunnittelun tarkeimmista ominaisuuksista.

Olio-ohjelmointikielten periytymisominaisuuksista ja niidenvaikutuksista suunnittelupaatoksiin kerrotaan myohemmintarkemmin.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

UML:ssa luokka voi ilmaista kahdella tavalla toteuttavansa (real-ization, refinement) tietyn rajapinnan: luokan kylkeen piirretaan vii-van paassa oleva rajapinnan nimella varustettu pallo (eraanlainentikkunekku) tai periytymista muistuttavalla katkoviivalla. Esimerkki-kuvassa KirjastonKirja lupaa toteuttaa UDKkoodaus- ja SQLtalletus-nimisten rajapintojen maaritteleman toiminnallisuuden. Molemmatkuvaustavat tarkoittavat samaa asiaa, ja yleensa kuvaan valitaan sii-hen parhaiten sopiva tapa (tikkunekku on yleisesti kaytossa silloin,kun rajapinta on esitelty kuvan ulkopuolella tai kaukana sita kaytta-vasta luokasta).

Page 138: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

5.3. Oliosuunnitelman graafinen kuvaus 138

5.3.4 Ajoaikaisen kayttaytymisen kuvaamistapoja

Edella esitellyilla luokkakaavioilla voidaan kuvata ohjelmiston tarvit-semien luokkien rakenne, niiden valiset yhteydet ja lukumaarat. Na-ma ovat staattisia rakennekuvauksia. Kyseessa on kuitenkin vain puo-let kattavasta ohjelmiston kuvauksesta. Yhta tarkeaa on kuvata, mitenoliot toimivat sisaisesti ja keskenaan ohjelman suorituksen aikana.Myos taman dynaamisen kayttaytymisen kuvaamiseen UML tarjoaauseita kuvaustapoja.

Tapahtumasekvenssit

Lopullisessa ohjelmistossa luokista tehdaan olioita, jotka kutsuvattoistensa palveluita jossain jarjestyksessa. Naita ajoaikaisia “suori-tusjalkia” voidaan kuvata UML:lla graafisesti tapahtumasekvensseil-la (sequence diagram). Esimerkki tapahtumasekvenssista on kuvas-sa 5.14.

Tapahtumasekvenssikuvan perusideana on piirtaa rinnakkainpystyviivoja, jotka kuvaavat aina yhden olion rajapintaa. Kuvassa kul-kee aika ylhaalta alas, ja ajan funktiona piirretaan rajapintojen va-lilla tapahtuvia funktiokutsuja. Esimerkissa tarkasteltavalle funktiol-

: KirjaTietokanta

a1 : KirjastonKirja

<<destroy>>

<<create>>

return a1

asetaPalPVM(pvm)

LainaaKirja()

haeKirja( ISBN )

päivitä( a1 )

vapauta( a1 )

KUVA 5.14: Palautuspaivamaaran asettamisen tapahtumasekvenssi

Page 139: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

5.3. Oliosuunnitelman graafinen kuvaus 139

le (kuvan vasemmassa reunassa oleva pystyviiva) on annettu teh-tavaksi paivittaa lainattavaan kirjaston kirjaan tieto siita, koska lai-na on palautettava. Rutiini pyytaa ensin tietokannasta kyseista kir-jaa mallintavan olion, jonka rajapintaan kohdistetaan paivityskutsu(AsetaPalPVM). Lopuksi tietokanta paivitetaan uuteen tietoon ja ope-raation suorittamista varten luotu (valiaikainen) olio a1 tuhotaan.Olion tuhoutuminen eli sen elinkaaren paattyminen ilmaistaan ta-pahtumasekvenssissa pystyviivan alareunaan piirretylla ruksilla.

Tilakoneet

Kun olion kayttaytyminen halutaan maaritella tarkemmin kuin sanal-lisesti ja rajapintafunktioiden kutsujen avulla (tapahtumasekvenssi),voidaan kayttaa hyvaksi tilakonetta (state machine).

Tilakoneessa (katso kuva 5.15) on piirretyna toiminnallisia tilo-ja ja niiden valisia siirtymia. Olion (tai yksittaisen rajapintafunktion)kokonaistoiminta kerrotaan kuvaamalla kaikki mahdolliset tilat, jois-sa se voi olla, ja tapahtumat, joilla siirrytaan tilasta toiseen. Olio onaina jossain tilassa, jota nimitetaan nykyiseksi tilaksi. Tilasiirtyma ta-pahtuu, kun siirtymaa kuvaavaan nuoleen liittyva ehto toteutuu. Ti-lasiirtymassa voidaan kuvata ehdon lisaksi toiminta (esimerkiksi ra-japintafunktion kutsu), joka suoritetaan samalla kun olio siirretaansiirtyman maaraamaan uuteen tilaan.

Poistettu

Käytettävissä

Kateissa

Lainauskielto

Voimassa

poiskäytöstäpois

arkistosta

Anottu

hylkäys

hyväksyttyanomus

KUVA 5.15: Kirjastokortin tiloja

Page 140: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

5.4. Saatteeksi suunnitteluun 140

Tilakoneen mallintama suoritus alkaa aina alkutilasta (musta ym-pyra), ja jos nykyiseksi tilaksi tulee tilasiirtymien seurauksena erityi-nen lopetustila (musta ympyra valkoisella reunuksella), tilakoneensuoritus paattyy (tilakoneen kuvaaman olion tai funktiokutsun toi-minnallisuus paattyy).

Esimerkkikuvassa on mallinnettu kirjastokortin mahdollisia tilojasen “elinaikana”. Kun hakemus kirjastokortista on hyvaksytty, korttiaktivoidaan ja suoritus toimii kokoomatilan (“Voimassa”) sisalla. Kunkortti poistetaan kaytosta, siirrytaan tilaan “Poistettu” riippumatta sii-ta, missa “Voimassa”-tilan alitilassa tapahtumahetkella on oltu.

5.3.5 Ohjelmiston rakennekuvaukset

Suurissa ohjelmistoissa luokkiin keskittyvat luokkakaaviot ja tapah-tumasekvenssit eivat riita kuvaamaan ohjelmiston kokonaisuutta tar-peeksi abstraktilla tasolla. Tata ongelmaa varten UML maarittelee eri-laisia arkkitehtuurikuvauksia, joilla on tarkoitus kuvata ohjelmistonrakennetta sen ylimmalla tasolla. (Kayttotapaukset kirjoitetaan useintaman ylimman tason terminologiaa ja rakennetta kayttaen.)

Looginen nakyma (Logical View) ohjelmistoon on kuva kaikistatarkeimmista komponenteista ja niiden keskinaisista suhteista (esi-merkiksi kayttoliittyma, ulkopuoliset kayttoliittymakirjastot, bisnes-logiikka, tietokanta ja tietoliikenne). Naiden komponenttien sisalla onluokkakaaviot kunkin komponentin tarkemmasta suunnittelusta.

Jos ohjelmistossa on useita suoritussaikeita tai prosesseja, Proses-sinakyma (Process View) kuvaa naiden saikeiden valista yhteistoi-mintaa ja keskinaisen kommunikaation protokollaa.

Sijoittelunakyma (Deployment View) nayttaa hajautetussa ohjel-mistossa sen, missa tietoverkon tietokoneessa (tai vaikkapa UNIXinprosessissa) mikin ohjelmiston osa suoritetaan.

5.4 Saatteeksi suunnitteluun

Suunnittelu on paattymaton matka ja tehtavaa suunnitelmaa pys-tyy “viilaamaan” loputtomiin. Vaikka kuinka haluaisi jatkaa kayttota-pausten kirjoittamista aina levyaseman kirjoituspaan kayttajarooliinasti, tulisi kaytannon tyossa osata lopettaa maarittely ja suunnittelu

Page 141: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

5.4. Saatteeksi suunnitteluun 141

joskus. Liiallisuuksiin mennytta maarittelya kutsutaan termilla anal-ysis paralysis [Liberty, 1998]. Koska sitten on suunniteltu tarpeeksi?Koska perustelimme suunnittelun tarkeytta silla, etta mutkikas oh-jelmisto saadaan jaetuksi yhden ihmisen ymmartamiin ja toteutetta-vissa oleviin osiin, niin voimme maaritella lopetusehdoksi tilanteen,jossa kaikki projektin moduulien toteuttajat ovat ymmartaneet omal-ta osaltaan vaadittavat vastuut ja osaavat kayttaa muiden moduulienjulkisia rajapintoja. Kuinka sitten varmistutaan tasta taitaa olla toisentarinan paikka. . .

Page 142: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

142

Luku 6

Periytyminen

For animals, the entire universe has been neatly dividedinto things to (a) mate with, (b) eat, (c) run away from, and(d) rocks.

– Equal Rites [Pratchett, 1987]

Yksi ihmismielelle ominainen piirre on pyrkimys kategorisoida —ryhmitella asioita ja kasitteita eri luokkiin niista loytyvien yhtalai-syyksien perusteella. Tata kategorisointia tekevat jo aivan pienet lap-setkin, ja ihmisten kayttama kielikin perustuu suurelta osalta sanoi-hin, jotka eivat tarkoita yksittaista asiaa vaan koko joukkoa keskenaanjollain lailla samankaltaisia asioita.

Kategorisointia kaytetaan yleisesti myos tieteissa. Biologiassa Carlvon Linne kaytti tata periaatetta 1700-luvulla jaotellessaan kasve-ja ja sienia kategorioihin teoksessaan “Systema Naturae” [Linnaeus,1748]. On kuitenkin huomattava, etta tama pyrkimys jaotteluun onnimenomaan ihmisen ajattelusta lahtoisin ja etta todellinen maailmaei valttamatta aina taivu kovin hyvin tallaisiin malleihin.

Koska jaottelu aliryhmiin erilaisten yhteisten ominaisuuksien pe-rusteella on niin luontevaa ihmisille, se on pyritty ottamaan kayt-toon myos ohjelmoinnissa. Olio-ohjelmoinnin painopiste on kahdes-sa asiassa: olioiden ulkoisessa kayttaytymisessa ja niiden sisaisessatoteutuksessa. Niinpa onkin luonnollista keskittya kategorisoinnissanaihin aihealueisiin.

Page 143: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

6.1. Periytyminen, luokkahierarkiat, polymorfismi 143

Ulkoisen kayttaytymisen jaottelulla saadaan ryhmiteltya luokkiajoukoiksi, jotka joiltain osin kayttaytyvat yhtenevaisella tavalla. Tas-ta on hyotya, silla talloin sama ohjelmakoodi voi kasitella kaikkiennaiden luokkien olioita, koska olioiden kayttaytyminen on samanta-paista.

Sisaisen toteutuksen jaottelussa taas on pyrkimys saada “tislatuk-si” eri luokkien yhteisia osia yhteen paikkaan, jotta samaa ohjelma-koodia ei tarvitsisi kirjoittaa moneen kertaan. Tasta on tietysti selvaahyotya yllapidossa ja virheiden korjaamisessa, ja ohjelman kokokinvoi pienentya. Lisaksi koodiin lisatyt uudet luokat voivat ottaa suo-raan kayttoonsa olemassa olevien luokkien ominaisuuksia, eli koodiavoidaan kayttaa uudelleen.

Talle luokkien jaottelulle ja yhteisista ominaisuuksista muodos-tuville “sukulaisuussuhteille” on annettu olio-ohjelmoinnissa nimiperiytyminen (inheritance), ja monet pitavat sita kapseloinnin ohel-la olio-ohjelmoinnin tarkeimpana uutena ominaisuutena perinteisiinohjelmointikieliin verrattuna. Periytymisen tarkasta merkityksesta jakayttotavoista keskustellaan ja vaitellaan olioteoreetikkojen keskenkuitenkin edelleen (matematiikkaa pelkaamattomat voivat tutustuavaikka Martın Abadin ja Luca Cardellin kirjaan “Theory of Objects”[Abadi ja Cardelli, 1996]).

Periytymisessa on kyse suhteesta, jossa yksi luokka pohjautuu toi-seen luokkaan ja perii sen ominaisuudet. Monissa “puhtaissa” olio-kielissa jokainen kayttajan maarittelema luokka on aina periytettyjostain toisesta luokasta, ainakin kieleen erikseen maaritellysta “kaik-kien luokkien aidista” — luokasta Object tai vastaavasta. Toisissa kie-lissa, kuten C++:ssa, tallaista periytymispakkoa ei kuitenkaan ole vaanoletusarvoisesti uusi luokka ei liity mitenkaan jo olemassa oleviinluokkiin. Kuvassa 6.1 seuraavalla sivulla on lueteltu tassa teoksessakaytettyja periytymiseen liittyvia termeja ja selitetty ne lyhyesti.

6.1 Periytyminen, luokkahierarkiat, polymorfis-mi

Varsinkin eurooppalaiset olio-ohjelmoinnin asiantuntijat korostavatperiytymisessa olioiden ulkoisen kayttaytymisen — rajapintojen —kategorista jaottelua [Koskimies, 2000]. Kuvassa 6.2 sivulla 145 on

Page 144: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

6.1. Periytyminen, luokkahierarkiat, polymorfismi 144

Termi SelitysPeriytyminen(inheritance),(johtaminen)

Uuden luokan muodostuminen olemassa ole-van luokan pohjalta niin, etta uusi luokka si-saltaa kaikki toisen luokan ominaisuudet.

Kantaluokka (baseclass), (yliluokka(superclass, parent class))

Alkuperainen luokka, jonka ominaisuudet pe-riytyvat uuteen luokkaan.

Aliluokka (subclass),(periytetty/johdettuluokka (derived class))

Uusi luokka, joka perii kantaluokan ominai-suudet ja voi taman lisaksi sisaltaa uusia omi-naisuuksia.

Periytymishierarkia(inheritance hierarchy),(luokkahierarkia, (classhierarchy))

Kantaluokista ja niiden aliluokista muodos-tuva puumainen rakenne, jossa puussa alem-pana olevat luokat perivat ylempana olevienominaisuudet. Katso kuva 6.4 sivulla 149.

Esi-isa (ancestor) Periytymishierarkiassa luokan kantaluokka,kantaluokan kantaluokka, taman kantaluokkajne. ovat luokan esi-isia.

Jalkelainen (descendant) Kaikki kantaluokasta periytetyt luokat, niistaperiytetyt luokat jne. ovat luokan jalkelaisia.

KUVA 6.1: Periytymiseen liittyvaa terminologiaa

esitetty elioiden toimintaa mallintavan ohjelman periytymishierar-kiaa (inheritance hierarchy), tosin varsin rajoitetusti.

Tassa esimerkissa vain luokat Homesieni, Koira, Ihminen, Kanaja Satakieli ovat varsinaisia “todellisia elioita mallintavia” luokkia,joista ohjelmassa tehdaan olioita. Luokat Elio, Sieni, Elain, Nisakasja Lintu puolestaan vain kuvaavat todellisten luokkien “ylakasittei-ta”. Niita kaytetaan ryhmittelemaan todellisia luokkia kategorioihin,joihin kuuluvien olioiden rajapinta ja kayttaytyminen ovat jollain ta-valla yhtenevaisia.

Tallaisia luokkia, joista ei ole mielekasta tehda olioita, mutta joi-ta silti tarvitaan kuvaamaan ohjelmassa esiintyvien todellisten luok-kien rajapintoja ja niiden suhteita, kutsutaan abstrakteiksi kantaluo-kiksi (abstract base class). UML:ssa abstraktit kantaluokat usein mer-kitaan merkinnalla “abstract” kuten kuvassa. Samoin merkinnalla

Page 145: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

6.1. Periytyminen, luokkahierarkiat, polymorfismi 145

Eliö

abstract

lisäänny() abstract

Sieni

abstract

hajota() abstract

Eläin

abstract

liiku() abstract

Homesieni

lisäänny()hajota()

Lintu

abstract

laula() abstract

pure()imetä()

lisäänny()liiku()

Koira Ihminen

lisäänny()liiku()imetä()

Kana

lisäänny()liiku()laula()

Satakieli

lisäänny()liiku()laula()

Nisäkäs

abstract

imetä() abstract

KUVA 6.2: Elioita mallintavan ohjelman periytymishierarkiaa

“abstract” voidaan viela merkita ne rajapinnan jasenfunktiot, joilleabstrakti kantaluokka ei tarjoa toteutusta.

Periytymista kaytetaan kuvaamaan luokkien valisia suhteita.Luokka Elio kuvaa kaikkia ohjelmassa esiintyvia elioita. Sen rajapintasisaltaa kaikille elioille yhteisen rajapinnan, tassa esimerkissa lisaan-tymisen. Kaikkia esimerkin todellisia olioita voi pyytaa lisaantymaan,ja jokaisen elion lisaantymispalvelu nayttaa ulospain tasmalleen sa-manlaiselta. Sen sijaan lisaantymisen varsinainen toteutus saattaahyvinkin vaihdella luokasta toiseen — on varsin todennakoista, ettahomesieni ja koira lisaantyvat eri tavalla!

Esimerkissa eliot jaetaan kahteen alikategoriaan, sieniin ja elai-miin. Nama eroavat toisistaan ulkoisesti siina, etta sienia voi pyytaahajottamaan eloperaista materiaalia, elaimia puolestaan liikkumaanpaikasta toiseen. Luokat Sieni ja Elain on periytetty (derived) luokas-ta Elio. Talloin niiden rajapintaan kuuluu niiden omien palveluidenlisaksi automaattisesti myos luokan Elio rajapinta — sienten ja elain-ten rajapinta on siis laajennettu elioiden rajapinnasta. Jalleen kysy-mys on vain rajapinnasta, ja jokainen todellinen sieniluokka voi to-teuttaa hajottamispalvelun haluamallaan tavalla.

Page 146: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

6.1. Periytyminen, luokkahierarkiat, polymorfismi 146

Samalla tavoin jaetaan elaimet viela nisakkaisiin, joita voi kaskeaimettamaan, seka lintuihin, joita voi pyytaa laulamaan. Viimein nais-ta luokista on periytetty todelliset ohjelmassa esiintyvat elainluokatKoira, Ihminen, Kana ja Satakieli.

Periytymisen hyotyna on, etta hierarkia antaa mahdollisuudenpuhua esimerkiksi kaikista nisakkaista luokkaa Nisakas kayttamalla.Jos myohemmin ohjelmassa tulee esimerkiksi tarve lisata kaikkiennisakkaiden rajapintaan uusi palvelu — vaikkapa synnyttaminen —,tama kay yksinkertaisesti lisaamalla kyseinen operaatio luokan Nisa-kas rajapintaan. Taman jalkeen taytyy tietysti viela toteuttaa synnyt-taminen kussakin nisakkaassa lajille ominaisella tavalla.

Vastaavasti jos ohjelmassa on funktio, jonka tehtavana on siirtaasille parametrina annettuja elaimia, tama funktio voi ottaa parametri-naan yksinkertaisesti viitteen Elain-luokan olioon. Koska seka Koira,Ihminen, Kana etta Satakieli ovat luokkahierarkiassa elaimia, siirty-misfunktiolle voi talloin antaa siirrettavaksi minka tahansa elaimen.

Siirtymisfunktion itsensa ei tarvitse tietaa yksityiskohtia siita,minka elainlajin edustajaa se on siirtamassa. Sille riittaa tieto siita,etta koska kyseessa on elain, sen julkisessa rajapinnassa on tarvittavapalvelu elaimen siirtamiseen. Tallaista tilannetta, jossa funktio hy-vaksyy eri tyyppisia parametreja, kutsutaan polymorfismiksi (poly-morphism) eli “monimuotoisuudeksi”. Kaantaja voi luokkahierarkianavulla lisaksi tarkastaa, etta siirtymisfunktiolle annetaan siirrettavak-si vain sellaisia olioita, joita todella voi siirtaa — ei esimerkiksi home-sienia. Kielissa, joissa ei ole vahvaa tyypitysta, tarkastetaan sopivanjasenfunktion loytyminen yleensa vasta ajoaikana. Tallaisissa kielis-sa luokkahierarkian kaytto rajapintojen luokitteluun on tarpeetonta,koska esimerkiksi siirtymisfunktiolle voisi antaa parametrina minkatahansa olion, ja jos kyseinen olio ei osaa siirtya, annetaan ajoaikai-nen virheilmoitus. Esimerkiksi Smalltalk kuuluu tallaisiin oliokieliin.

Luokkahierarkioiden etuna on viela se, etta jokaisessa todellises-sa luokassa rajapintafunktio voidaan tarvittaessa toteuttaa eri taval-la. Edella mainittu siirtymisfunktio pystyy siirtamaan mita tahansaelaimia kutsumalla naiden “liiku”-palvelua. Vaikka siirtymisfunktioei valitakaan siita, minka lajin elaimesta on kysymys, voi jokainenelain silti toteuttaa liikkumisen omalla tavallaan — linnut lentamal-la, koira jolkottamalla jne. Nain sama siirtymisfunktiossa oleva “lii-ku”-palvelun kutsu voi ajoaikana aiheuttaa palvelupyynnon vastaa-nottajaolion luokasta riippuen erilaisen toiminnon. Tata kutsutaan

Page 147: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

6.2. Periytyminen ja uudelleenkaytto 147

puolestaan dynaamiseksi sitomiseksi (aliluku 6.5.2).Periytymiseen ja hierarkioihin perustuvassa kategorisoinnissa ko-

rostuu olion tarjoaman palvelun ja sen toteutuksen ero. Kaikki Elain-luokasta periytetyt luokat sisaltavat palvelun “liiku” ja lupaavat, ettasita kutsumalla olio liikkuu paikasta toiseen. Rajapinta ei kuitenkaanlupaa mitaan siita, milla tavalla liikkuminen tapahtuu. Taman voiajatella myos niin, etta hierarkiassa alempana olevat luokat erikoista-vat (specialize) ylempana maarattyjen palveluiden toimintaa. Erikois-taminen voi olla myos useampivaiheista, esimerkiksi luokassa Lintusaatettaisiin erikoistaa liikkumista maaraamalla, etta se tapahtuu len-taen. Siita huolimatta kanat ja satakielet voisivat viela erikoistaa tatalisaa toteuttamalla lentamisen eri tavalla.

6.2 Periytyminen ja uudelleenkaytto

Luokkasuunnittelun edetessa tulee vastaan tilanteita, joissa huoma-taan osan luokkaa (attribuuttien tai toiminnallisuuden) olevan sa-manlainen useassa luokassa. Esimerkiksi jos mietimme muita grafiik-kaluokkia kuin aikaisemmin esimerkkina ollut Pistetta, niin keksim-me ehka ympyran, viivan ja valokuvan. Nailla kaikilla on yhteise-na ominaisuutena tieto nakyvyydesta (onko mallinnettu grafiikkao-lio piirrettyna nayttolaitteelle), joka ilmaistaan luokan attribuuttinaja sen kayttoon liittyvina rajapintafunktioina. Attribuutin ilmaisematilatieto ja siihen liittyva rajapinta on kaikilla hierarkian luokilla sa-ma.

Ohjelmiston yllapidettavyys paranee, jos kaikille luokille yhtei-set attribuutit ja rajapintafunktiot toteutetaan vain kerran ja sama to-teutus on kaikkien luokkien kaytossa. Oliosuunnittelussa tama yleis-taminen (generalization) saadaan aikaiseksi maarittelemalla yhteisetosat yhteen luokkaan, josta muut ominaisuutta tarvitsevat luokat pe-rivat toteutuksen osaksi itseaan. Yhteinen toiminnallisuus tavallaannostetaan periytymishierarkiassa ylemmalle tasolle yhteiseen kanta-luokkaan.

Kuvassa 6.3 seuraavalla sivulla on maaritelty kantaluokka Naky-vyys, jonka vastuulle on merkitty tietamys siita, onko grafiikkaolionakyvilla nayttolaitteella (attribuutti), ja siihen liittyvat rajapinta-funktiot. Kantaluokasta periytetyt luokat perivat kaikki kantaluokanominaisuudet osaksi itseaan. Tama tarkoittaa sita, etta kun Ympy-

Page 148: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

6.2. Periytyminen ja uudelleenkaytto 148

ra-luokasta tehdaan olio, se sisaltaa seka Nakyvyys-luokan etta ym-pyran ominaisuudet. Tarkasteltaessa periytymista periytetyn luokankannalta se on erikoistettu versio kantaluokasta.

Luokkien periyttamista voidaan jatkaa periytymishierarkiassaeteenpain. Jos Kirjastokomponentilla on hyvin samanlaiset ominai-suudet kuin naytolla esitettavalla valokuvalla, voimme periyttaa luo-kan Valokuva Kirjastokomponentista. Valokuva-luokalla on nyt kaik-ki samat ominaisuudet kuin sen molemmilla kantaluokilla — myosnakyvyyteen liittyvat.

Periytyminen liittyy laheisesti ohjelmakoodin uudelleenkayttoon.Aikaisemmin olleesta luokasta (mahdollisesti ostetussa komponentis-sa tai aikaisemmassa projektissa) saadaan kaytetyksi kaikki halututominaisuudet, ja johdetussa uudessa versiossa tarvitsee ainoastaanlisata ja muuttaa tarvittavat uudet osuudet. Tama vahva ominaisuussisaltaa myos riskin: pitkat (ja huonosti dokumentoidut) periytymis-hierarkiat ovat usein perinteista vaikealukuisempaa ohjelmakoodia,koska hierarkian eri osat (luokat) voivat sijaita eri puolilla ohjelma-koodia (esimerkiksi eri tiedostoissa).

Viiva Ympyrä Kirjastokomponentti

Valokuva

Näkyvyys

piirrä()

onkoNäkyvissä: bool

piilota()

KUVA 6.3: Nakyvyytta kuvaava kantaluokka ja periytettyja luokkia

Page 149: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

6.3. C++: Periytymisen perusteet 149

6.3 C++: Periytymisen perusteet

C++:ssa periytyminen tarkoittaa, etta kielessa on mahdollista luodauusi luokka jo olemassa olevaan luokkaan perustuen niin, etta uudenluokan oliot perivat automaattisesti kaikki toisen luokan ominaisuu-det, niin rajapinnan kuin sisaisen toteutuksenkin. Kuva 6.4 nayttaa,mista periytymisessa yksinkertaistettuna on kyse. Jokainen aliluokkaperii kaikki esi-isiensa ominaisuudet ja voi lisaksi laajentaa ja rajoite-tusti muokata niita. Kuvaan on piirretty myos aliluokan olioita, joistanakyy olioiden “kerrosrakenne” — oliot ovat ikaan kuin kantaluokanolioita, joihin on liimattu kiinni aliluokkien vaatimat lisaosat.

C++:ssa on myos mahdollista periyttaa luokka useammasta kuinyhdesta luokasta. Tama moniperiytyminen (multiple inheritance) onvarsin kiistelty ominaisuus, ja sen kayttoa ei yleensa suositella kuintiettyihin tarkoituksiin. Moniperiytymista kasitellaan jonkin verran

B

B:n ominaisuudet

(A:n ominaisuudet)

A

A:n ominaisuudet

C

C:n ominaisuudet

(A:n ominaisuudet)

D

(A:n ominaisuudet)

D:n ominaisuudet

(C:n ominaisuudet)

E

(A:n ominaisuudet)

E:n ominaisuudet

(D:n ominaisuudet)

(C:n ominaisuudet)

F

(A:n ominaisuudet)

(C:n ominaisuudet)

F:n ominaisuudet

(D:n ominaisuudet)

(a) Hierarkia

A−osa

C−Osa

D−Osa

F−Osa

A−osa

C−Osa

F−OlioC−Olio

(b) Aliluokan olioita

KUVA 6.4: Periytymishierarkia ja oliot [Koskimies, 2000]

Page 150: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

6.3. C++: Periytymisen perusteet 150

aliluvussa 6.7. Aliluvussa 6.9.2 kasitellaan myos yhta moniperiyty-misen kayttotapaa — rajapintaluokkia.

Periytymisen syntaksi on yksinkertainen: aliluokkaa esiteltaessamerkitaan luokan nimen jalkeen kaksoispiste ja sen jalkeen periyty-mistyyppi (lahes aina public) ja kantaluokan nimi (moniperiytymi-sen yhteydessa naita periytymistyyppi-kantaluokka-pareja on useitapilkulla toisistaan erotettuina). Listauksessa 6.1 on esimerkkina ku-van 6.4 luokkahierarkiaa C++:lla toteutettuna. C++:ssa on mahdollistapublic-periytymisen lisaksi myos private- ja protected-periytyminen,mutta niiden kaytto on varsin harvoin olio-ohjelmoinnissa tarpeellis-ta, eika niita kasitella tassa teoksessa.

6.3.1 Periytyminen ja nakyvyys

Periytymisen yhteydessa aliluokka perii kantaluokasta niin ulospainnakyvan rajapinnan kuin sisaisen toteutuksenkin. Perittyjen osien na-kyvyys aliluokan oliossa on kuitenkin osin erilainen kuin kantaluo-kassa. Kuva 6.5 seuraavalla sivulla kuvaa aliluokan paasya kantaluo-kan eri osiin. Alla on luettelo, josta kay ilmi eri nakyvyysmaareidenvaikutus periytymiseen.

1 class A2 3 // Luokan A ominaisuudet4 ;5 class B : public A6 7 // Luokan B A:han lisaamat ominaisuudet8 ;9 class C : public A

10 11 // Luokan C A:han lisaamat ominaisuudet12 ;13 class D : public C14 15 // Luokan D C:hen lisaamat ominaisuudet16 ;

...LISTAUS 6.1: Periytymisen syntaksi C++:lla

Page 151: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

6.3. C++: Periytymisen perusteet 151

A−osa

C−Osa

privateprotected

protected private

public

public

C−Olio

publfunkt

protfunkt privmuutt

uusifunkt

uusiprivmuusiprotf

KUVA 6.5: Periytyminen ja nakyvyys

• public: Kantaluokan public-osat — sen ulkoinen rajapinta —ovat myos aliluokassa public-puolella. Nain aliluokan ulkoises-sa rajapinnassa on kaikki se, mita kantaluokassakin, seka lisaksialiluokan maarittelemat uudet rajapintafunktiot.

• private: Vaikka kantaluokan private-osa periytyykin aliluok-kaan, ei niihin paase kasiksi aliluokan jasenfunktioista. Tamajohtuu siita nakokannasta, etta private-osat ovat kantaluokan si-saisena toteutuksena kantaluokan oma asia, eika aliluokan tar-vitse paasta niihin kasiksi — eihan private-osa muutenkaan nayluokasta ulos. Aliluokan jasenfunktiot voivat tietysti valillisestikayttaa kantaluokan private-osia kutsumalla kantaluokan raja-pintafunktioita.

Page 152: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

6.3. C++: Periytymisen perusteet 152

• protected: Tahan saakka protected-maareesta ei ole mainittujuuri mitaan muuta, kuin etta se on hyvin samantapainen kuinprivate. Protected-maareen kaytto liittyykin nimenomaan pe-riytymiseen. Kantaluokan protected-osa periytyy aliluokan pro-tected-osaksi. Nain protected-osa on kuin private-osa siina mie-lessa, ettei se kuulu luokan ulkoiseen rajapintaan, mutta se onkuitenkin aliluokkien koodin kaytettavissa. Protected-osan teh-tava onkin laajentaa kantaluokan rajapintaa aliluokan suuntaantarjoamalla talle erityisrajapinnan. Talla tavoin kantaluokka voitarjota aliluokille luokan laajentamiseen tarvittavia apufunk-tioita, joita ei kuitenkaan haluta yleiseen kayttoon. Kuten pub-lic-osaankin, protected-osaan tulee kirjoittaa vain jasenfunktioi-ta, vaikkei C++:n syntaksi tata rajoitakaan.

Edella olevista nakyvyyssaannoista aloittelijaa ihmetyttaa useinkantaluokan private-osan “eristaminen” aliluokalta. Kapseloinninkannalta tama C++:n (ja Javan) ratkaisu on kuitenkin oikea, koska ali-luokkaa ei pitaisi toteuttaa niin, etta se riippuu kantaluokan sisaisestatoteutuksesta. Mikali aliluokan taytyy saada suorittaa sellaisia kanta-luokan operaatioita, joita ei loydy kantaluokan julkisesta rajapinnas-ta, protected-maare tarjoaa kaytannollisen ratkaisun ongelmaan.

Jotkut oppikirjat [Lippman ja Lajoie, 1997] ovat ottaneet sen kan-nan, etta koska kantaluokan suunnittelija harvoin tietaa tarkoin, mil-laisia luokkia kantaluokasta tullaan periyttamaan, pannaan varmuu-den vuoksi koko kantaluokan sisainen toteutus protected-puolelle —jasenmuuttujineen kaikkineen. Talloin kantaluokan ja aliluokan va-linen kapselointi kuitenkin murtuu ja aliluokka paasee riippumaankantaluokan sisaisesta toteutuksesta. Paljon parempi ratkaisu on kir-joittaa protected-puolelle sopivat apujasenfunktiot, joiden avulla ali-luokka paasee sopivasti kasiksi kantaluokan sisimpaan.

6.3.2 Periytyminen ja rakentajat

Kuten kaikki oliot, myos aliluokan oliot taytyy alustaa kun ne luo-daan. Aliluvussa 3.4 kasiteltiin olion alustamista rakentajajasenfunk-tion avulla. Periytyminen tuo kuitenkin omat lisansa olioiden alusta-miseen. Jokainen aliluokan olio koostuu kantaluokkaosasta tai -osis-ta seka aliluokan lisaamista laajennuksista. Selvasti aliluokan taytyymaaritella oma rakentajansa, jotta aliluokan uudet osat saadaan alus-

Page 153: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

6.3. C++: Periytymisen perusteet 153

tetuksi, mutta miten pitaisi toimia kantaluokan jasenmuuttujien alus-tamisen kanssa? Aliluokan jasenfunktiothan — rakentaja mukaanlu-kien — eivat paase kantaluokan private-osiin kasiksi.

Ratkaisu alustusongelmaan loytyy jalleen luokkien vastuualueis-ta. Aliluokan vastuulla on aliluokan mukanaan tuomien uusien jasen-muuttujien ja muiden tietorakenteiden alustaminen. Tata tarkoitustavarten aliluokkaan kirjoitetaan oma rakentaja tai rakentajat. Kanta-luokan vastuulla on pitaa huoli siita, etta aliluokan olion kantaluok-kaosa tulee alustetuksi oikein, aivan kuin se olisi irrallinen kanta-luokan olio. Taman alustuksen hoitavat aivan normaalit kantaluokanrakentajat.

Ainoaksi ongelmaksi jaavat rakentajien parametrit. Aliluokan ra-kentaja saa kylla parametrinsa aivan normaaliin tapaan aliluokanoliota luotaessa, mutta ongelmana on, miten kantaluokan rakentajallesaadaan valitetyksi sen tarvitsemat parametrit. C++:n tarjoama ratkaisuon, etta aliluokan rakentajan alustuslistassa “kutsutaan” kantaluokanrakentajaa ja valitetaan sille tarvittavat parametrit. Talla tavoin ali-luokka voi itse paattaa, millaisia parametreja sen kantaluokalle valite-taan olion luomisen yhteydessa. Listaus 6.2 seuraavalla sivulla nayt-taa esimerkin periytymisesta ja rakentajien kaytosta.

Jos aliluokan rakentajan alustuslistassa ei kutsuta mitaan kanta-luokan rakentajaa, yrittaa kaantaja olla ystavallinen. Tallaisessa tilan-teessa se kutsuu nimittain kantaluokan oletusrakentajaa, joka ei tar-vitse parametreja. Tama aiheuttaa kuitenkin sen, etta rakentajakutsununohtuessa alustuslistasta olion kantaluokkaosa alustetaan oletusar-voonsa, joka tuskin on haluttu lopputulos! Onkin erittain tarkeaa, ettaaliluokan rakentajan alustuslistassa kutsutaan aina jotain kantaluo-kan rakentajaa.

Aliluokan olion alustusjarjestys C++:ssa on sellainen, etta ensim-maisena suoritetaan kantaluokan rakentaja kokonaisuudessaan, jonkajalkeen suoritetaan aliluokan oma rakentaja. Mikali periytymishierar-kia on korkeampi kuin kaksi luokkaa, lahdetaan rakentajia suoritta-maan aivan hierarkian huipusta lahtien alaspain, niin etta olio ikaankuin rakentuu vahitellen laajemmaksi ja laajemmaksi. Tama alustus-jarjestys takaa sen, etta aliluokan rakentajassa voidaan jo turvallisestikutsua kantaluokan jasenfunktioita, koska aliluokan rakentajan koo-diin paastaessa kantaluokkaosa on jo alustettu kuntoon (poikkeuk-sena ovat aliluokan uudelleenmaarittelemat virtuaalifunktiot, joidenkayttaytymista rakentajien kanssa kasitellaan tarkemmin aliluvus-

Page 154: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

6.3. C++: Periytymisen perusteet 154

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Kantaluokka . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .1 class Lokiviesti2 3 public:4 Lokiviesti(string const& viesti);

...5 private:6 string viesti ;7 ;89 Lokiviesti::Lokiviesti(string const& viesti) : viesti (viesti)

10 11

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Aliluokka . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .1 class PaivattyLokiviesti : public Lokiviesti2 3 public:4 PaivattyLokiviesti(Paivays const& pvm, string const& viesti);

...5 private:6 Paivays pvm ;7 ;89 PaivattyLokiviesti::PaivattyLokiviesti(Paivays const& pvm,

10 string const& viesti) : Lokiviesti(viesti), pvm (pvm)11 12

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Olion luominen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .1 Lokiviesti viesti("Kavin leffassa");2 PaivattyLokiviesti pvmviesti(tanaan(), "Huono oli");

LISTAUS 6.2: Periytyminen ja rakentajat

sa 6.5.7).

6.3.3 Periytyminen ja purkajat

Alustamisen tapaan myos aliluokan olion siivoustoimenpiteet vaa-tivat erikoiskohtelua luokan “kerrosrakenteen” vuoksi. Samoin kuinaliluokan olion alustaminen, myos sen siivoaminen on jaettu vastuu-alueiden kesken. Kantaluokan purkajan tehtavana on siivota kanta-luokkaolio sellaiseen kuntoon, etta se voi rauhassa tuhoutua. Aliluo-kan purkajan vastuulla on vastaavasti pitaa huoli siita, etta aliluokan

Page 155: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

6.3. C++: Periytymisen perusteet 155

olion periytymisessa lisatty laajennusosa siivotaan tuhoutumiskun-toon.

Kuten aiemminkin, ohjelmoijan ei itse tarvitse huolehtia purka-jien kutsumisesta vaan olion tuhoutuessa kaantaja kutsuu automaat-tisesti tarvittavia purkajia. Periytymisen yhteydessa olion purkajienkutsujarjestys on painvastainen rakentajien kutsujarjestykseen nah-den eli ensin kutsutaan aliluokan purkajaa, sitten kantaluokan pur-kajaa ja tarvittaessa taman kantaluokan purkajaa ja niin edelleen. Ta-ma kutsujarjestys varmistaa sen, etta aliluokan purkajaa suoritettaes-sa olion kantaluokkaosa on viela kayttokelpoinen, ja nain aliluokanpurkaja voi viela turvallisesti kutsua kantaluokan jasenfunktioita (ku-ten rakentajissakin, aliluokan uudelleenmaarittelemat virtuaalifunk-tiot ovat poikkeustapaus. Niita ja purkajia kasitellaan tarkemmin ali-luvussa 6.5.7).

Kantaluokan purkaja kannattaa maaritella lahes aina virtuaalisek-si, mutta tata kasitellaan vasta aliluvussa 6.5.5.

6.3.4 Aliluokan olion ja kantaluokan suhde

Koska aliluokka perii kantaluokan kaikki ominaisuudet, tarjoaa ali-luokan olio ulospain kaikki ne palvelut, jotka kantaluokan oliokin tar-joaa. Nain ollen aliluokan oliota voisi sen ulkoista rajapintaa ajatellenkayttaa kaikkialla, missa kantaluokan oliotakin — periytymisessahanvain lisataan ominaisuuksia.

Tama ajatus aliluokan olion kelpaamisesta kantaluokan olion pai-kalle on viety useissa oliokielissa viela pitemmalle maarittelemalla,etta kielen tyypityksen kannalta aliluokan olio on tyypiltaan myoskantaluokan olio. Nain aliluokan oliot kuuluvat ikaan kuin useaanluokkaan: aliluokkaan itseensa, kantaluokkaan, mahdollisesti kanta-luokan kantaluokkaan jne. Tama is-a -suhde tulisi pitaa mielessa ai-na, kun periytymista kaytetaan. Jos aliluokka on muuttunut vastuu-alueeltaan niin paljon, etta se ei enaa ole kantaluokan mukainen, pe-riytymista on kaytetty mita ilmeisimmin vaarin.

C++:n kannalta tama ominaisuus tarkoittaa, etta kielen tyypitykses-sa aliluokan olio kelpaa kaikkialle minne kantaluokan oliokin. Erityi-sesti kantaluokkaosoittimen tai -viitteen voi laittaa osoittamaan myos

Page 156: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

6.4. C++: Periytymisen kaytto laajentamiseen 156

aliluokan olioon:

class Kantaluokka . . . ;class Aliluokka : public Kantaluokka . . . ;void funktio(Kantaluokka& kantaolio);

Kantaluokka* k p = 0;Aliluokka aliolio;k p = &aliolio;funktio(aliolio);

Tama tilanne, jossa osoittimen paassa olevan olion tyyppi ei olesama kuin osoittimen tyyppi, ei aiheuta yleensa ongelmia, koska jo-kainen aliluokan olio tarjoaa periytymisesta johtuen kaikki kantaluo-kan tarjoamat palvelut. Tama mahdollisuus kayttaa aliluokan olioitaohjelmassa kantaluokan sijaan on yksi tarkeimpia olio-ohjelmoinninja periytymisen tyokaluja. Sen kayttoa kasitellaan aliluvussa 6.5.2.

6.4 C++: Periytymisen kaytto laajentamiseen

Periytymisen kenties yksinkertaisin kayttotarkoitus on olemassa ole-van luokan laajentaminen. Siina aliluokka laajentaa kantaluokan tar-joamia palveluita tarjoamalla kaikki kantaluokan tarjoamat palvelutidenttisina ja sen lisaksi viela uusia palveluita. Tallainen periytymi-sen kaytto mahdollistaa koodin uudelleenkayton, koska aliluokan eitarvitse kirjoittaa uudelleen kantaluokan jo kertaalleen toteuttamiapalveluita.

Listaus 6.3 seuraavalla sivulla nayttaa yksinkertaisen luokanKirja, joka muistaa yksittaisen kirjan tiedot. Periaatteessa listauk-sessa esitetty luokka sopisi muuten hyvin kirjaston kortistojarjestel-massa kaytetyksi kirjaksi, mutta kirjaston kirjoilla on yksi olennai-nen lisaominaisuus: niilla on viimeinen palautuspaivamaara. (Lis-tauksen rivilla 5 on purkajan edessa avainsana virtual. Silla ei ole ta-man esimerkin kannalta merkitysta, mutta nain tehdaan yleensa kai-kissa kantaluokissa. Virtuaalipurkajan merkitys selitetaan aliluvus-sa 6.5.5.)

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .C++:ssa vaaraksi muodostuu viipaloituminen (slicing). Kun esimerkiksi kantaluokan olioon

sijoitetaan aliluokan olio (joka on tyypiltaan myos kantaluokan olio), suoritetaan sijoituksessavain kantaluokkaosan sijoitus ja aliluokkaosa jaa sijoituksessa kayttamatta. Tata kasitellaanaliluvussa 7.1.3.

Page 157: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

6.4. C++: Periytymisen kaytto laajentamiseen 157

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Luokan esittely . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .1 class Kirja2 3 public:4 Kirja(std::string const& nimi, std::string const& tekija);5 virtual ~Kirja();6 std::string annaNimi() const;7 std::string annaTekija() const;

...11 private:

...13 std::string nimi ;14 std::string tekija ;15 ;

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Luokan toteutus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .1 Kirja::Kirja(string const& n, string const& t) : nimi (n), tekija (t)2 3 cout << "Kirja " << nimi << " luotu" << endl;4 56 Kirja::~Kirja()7 8 cout << "Kirja " << nimi << " tuhottu" << endl;9

1011 string Kirja::annaNimi() const12 13 return nimi ;14

...LISTAUS 6.3: Kirjan tiedot muistava luokka

Page 158: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

6.4. C++: Periytymisen kaytto laajentamiseen 158

Jos olemassa olevaa kirjaluokkaa halutaan kayttaa uuden luokanpohjana periytymisessa, on tarkeaa ensin varmistua siita, etta uusiluokka tarjoaa sellaisenaan kaikki vanhan luokan palvelut ja lisaa sii-hen lisaksi uusia palveluita. Kirjan ja kirjaston kirjan tapauksessa na-ma edellytykset toteutuvat, koska kaikki kirjan tarjoamat palvelut so-pivat sellaisenaan myos kirjaston kirjalle. Listauksessa 6.4 on luokas-ta Kirja periytetty uusi laajempi luokka KirjastonKirja, joka tarjoaakaikki kirjan palvelut ja lisaksi palautuspaivamaaran kasittelyn.

Periyttamista ei koskaan kannata kayttaa turhaan, koska se moni-mutkaistaa ohjelmaa. Esimerkiksi ylla oleva kirjastonkirjan periytta-minen kirjasta on perustelua vain, jos jossain todella tarvitaan myostavallista kirjaluokkaa tai jos kirjaluokka on saatu muualta. Mikali

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Luokan esittely . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .1 class KirjastonKirja : public Kirja2 3 public:4 KirjastonKirja(std::string const& nimi, std::string const& tekija,5 Paivays const& palpvm);6 virtual ~KirjastonKirja();7 bool onkoMyohassa(Paivays const& tanaan) const;

...9 private:

10 Paivays palpvm ;11 ;

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Luokan toteutus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .1 KirjastonKirja::KirjastonKirja(string const& nimi, string const& tekija,2 Paivays const& palpvm) : Kirja(nimi, tekija), palpvm (palpvm)3 4 cout << "Kirjastonkirja " << nimi << " luotu" << endl;5 67 KirjastonKirja::~KirjastonKirja()8 9 cout << "Kirjastonkirja " << annaNimi() << " tuhottu" << endl;

10 1112 bool KirjastonKirja::onkoMyohassa(Paivays const& tanaan) const13 14 return palpvm .paljonkoEdella(tanaan) < 0;15

LISTAUS 6.4: Kirjaston kirjan palvelut tarjoava aliluokka

Page 159: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

6.5. C++: Virtuaalifunktiot ja dynaaminen sitominen 159

sen sijaan ollaan kirjoittamassa alusta alkaen kirjaston lainausrekis-teria ja tiedetaan, etta kaikki ohjelmassa esiintyvat kirjat ovat palau-tuspaivamaarallisia kirjaston kirjoja, kannattaa ehka suoraan lisatapalautuspaivamaaran kasittely luokan Kirja sisalle ja kenties nimetaluokka uudelleen.

Jos sen sijaan ohjelmassa tarvitaan seka tavallisia kirjoja etta kir-jaston kirjoja, kahden luokan ja periytymisen kaytto kannattaa. Ta-vallinen kirjaolio ainakin vaatii vahemman muistia kuin kirjastonkir-jaolio, jonka taytyy myos muistaa paivamaara. Lisaksi kirjaston kirjaaon vaikea kayttaa tavallisena kirjana, koska esimerkissa uutta kirjaaluotaessa rakentajalle taytyy aina antaa palautuspaivamaara.

6.5 C++: Virtuaalifunktiot ja dynaaminen sitomi-nen

Joskus aliluokan olion on tarpeen suorittaa kantaluokasta periman-sa palvelu hieman kantaluokasta poikkeavalla tavalla. Toisella taval-la ilmaistuna aliluokka saattaa haluta peria kantaluokalta vain jasen-funktion ulkoisen rajapinnan, mutta ei toteutusta. C++ tarjoaa aliluo-kalle mahdollisuuden tarjota oman toteutuksensa kantaluokalta peri-malleen jasenfunktiolle, jos kyseinen jasenfunktio on kantaluokassamaaritelty virtuaaliseksi. Tallaista virtuaalista jasenfunktiota kutsu-taan yleensa lyhyesti virtuaalifunktioksi (virtual function).

6.5.1 Virtuaalifunktiot

Jasenfunktio maaritellaan kantaluokan esittelyssa virtuaaliseksi lisaa-malla jasenfunktion eteen avainsana virtual. Taman jalkeen kanta-luokasta periytettavilla aliluokilla on kaksi mahdollisuutta:

• Hyvaksya kantaluokan tarjoama jasenfunktion toteutus. Talloinaliluokan ei tarvitse tehda mitaan eli kantaluokan toteutus pe-riytyy automaattisesti myos aliluokkaan.

• Kirjoittaa oma toteutuksensa perimalleen jasenfunktiolle. Tas-sa tapauksessa aliluokan esittelyssa esitellaan jasenfunktio uu-delleen, ja sen jalkeen aliluokan toteutuksessa kirjoitetaan ja-senfunktiolle uusi toteutus aivan kuin normaalille aliluokan ja-

Page 160: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

6.5. C++: Virtuaalifunktiot ja dynaaminen sitominen 160

senfunktiolle. Aliluokan esittelyssa avainsanan virtual toista-minen ei ole pakollista, mutta kyllakin hyvan ohjelmointityylinmukaista.

Olio-ohjelmoinnin kannalta on tarkeaa, etta muutettu jasenfunk-tion toteutus tarjoaa kantaluokan kannalta saman palvelun kuin al-kuperainenkin. On myos huomattava, etta aliluokka voi muuttaa vainvirtuaalifunktion toteutusta, ei esimerkiksi paluutyyppia tai paramet-rien lukumaaraa tai tyyppia. ISO C++ sallii nykyisin, etta jos alkuperai-nen paluutyyppi on osoitin tai viite luokkaan, niin uuden jasenfunk-tion paluutyyppi voi olla osoitin tai viite alkuperaisen paluutyypinaliluokkaan. Talle paluutyypin kovarianssille (covariance) ei kuiten-kaan ole kovin usein kayttoa (mutta sita tullaan kylla kayttamaan ali-luvussa 7.1.3).]

Listauksessa 6.5 seuraavalla sivulla on kaksi aiemmin esitellynluokan Kirja jasenfunktiota, jotka luokka maarittelee virtuaalisiksi,joten aliluokilla on mahdollisuus toteuttaa ne omalla tavallaan. Lis-tauksessa 6.6 sivulla 162 on luokka KirjastonKirja maaritellyt uu-delleen jasenfunktion tulostaTiedot niin, etta se tulostaa myos pa-lautuspaivamaaran. Sen sijaan jasenfunktion sopiikoHakusana luok-ka perii kantaluokalta sellaisenaan toteutusta myoten.

Kirjaston kirjan tietojen tulostuksen toteutuksessa taytyy saa-da jotenkin tulostettua myos kantaluokkaosassa olevat kirjan tie-dot. Taman toteuttaa kantaluokan versio funktiosta tulostaTiedot,joten aliluokan uusi versio kutsuu sita rivilla 29. Pelkka kutsutulostaTiedot(virta) kutsuisi rekursiivisesti aliluokan funktiota it-seaan, joten kyseisella rivilla taytyy kertoa erikseen, etta halutaankutsua kantaluokan versiota funktiosta. Tama tehdaan lisaamallafunktion eteen maare Kirja::, joka maaraa minka luokan versiotakutsutaan.

6.5.2 Dynaaminen sitominen

Periytymishierarkiat ja virtuaalifunktiot saavat aikaan sen, etta jasen-funktion toteutus saattaa olla luokkahierarkiassa alempana, kuin mis-

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .]Periaatteessa oliokieli voi sallia vastaavantyyppisen asian myos jasenfunktion parametreis-

sa, mutta talloin periytymissuhteen taytyy menna toiseen suuntaan – periytetyn luokan para-metrit ovat kantaluokan parametrien kantaluokkatyyppia. C++ ei kuitenkaan salli tata kontra-varianssia (contravariance), eivatka salli monet muutkaan oliokielet. Naista asioista voi halu-tessaan lukea mm. kirjasta “Theory of Objects” [Abadi ja Cardelli, 1996].

Page 161: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

6.5. C++: Virtuaalifunktiot ja dynaaminen sitominen 161

. . . . . . . . . . . . . . . . . . . . . . . . . . . . Kantaluokan esittelyssa . . . . . . . . . . . . . . . . . . . . . . . . . . . .1 class Kirja2

...8 virtual void tulostaTiedot(std::ostream& virta) const;9 virtual bool sopiikoHakusana(std::string const& sana) const;

1011 private:12 void tulostaVirhe(std::string const& virheteksti) const;

...15 ;

. . . . . . . . . . . . . . . . . . . . . . . . . . Kantaluokan toteutuksessa . . . . . . . . . . . . . . . . . . . . . . . . . .19 void Kirja::tulostaVirhe(string const& virheteksti) const20 21 cerr << "Virhe: " << virheteksti << endl;22 cerr << "Kirjassa: ";23 tulostaTiedot(cerr);24 cerr << endl;25 2627 void Kirja::tulostaTiedot(ostream& virta) const28 29 virta << tekija << " : \"" << nimi << "\"";30 3132 bool Kirja::sopiikoHakusana(string const& sana) const33 34 return nimi .find(sana) != string::npos | |

35 tekija .find(sana) != string::npos; // Loytyiko nimesta tai tekijasta36

LISTAUS 6.5: Luokan Kirja virtuaalifunktiot

sa jasenfunktio on alunperin otettu mukaan rajapintaan. Tama ja pe-riytymisen “aliluokan-olio-kuuluu-kantaluokkaan” (is-a) -ilmio saa-vat aikaan sen, etta kaantaja ei kaikissa tapauksissa pysty viela kaan-nosaikana paattelemaan, mita rajapintafunktion toteutusta on tarkoi-tus kutsua, vaan paatos tasta siirtyy ajoaikaiseksi. Tasta kaytetaan ni-mitysta dynaaminen sitominen (dynamic binding).

Listauksessa 6.7 sivulla 163 on funktio tulostaKirjat, joka ottaaparametrikseen taulukollisen kirjaosoittimia. Koska jokainen kirjas-ton kirja on periytymishierarkian mukaisesti myos kirja, tallainentaulukko voi sisaltaa todellisuudessa osoittimia seka kirjoihin etta

Page 162: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

6.5. C++: Virtuaalifunktiot ja dynaaminen sitominen 162

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . Aliluokan esittelyssa . . . . . . . . . . . . . . . . . . . . . . . . . . . . .1 class KirjastonKirja : public Kirja2

...8 virtual void tulostaTiedot(std::ostream& virta) const;

...11 ;

. . . . . . . . . . . . . . . . . . . . . . . . . . . Aliluokan toteutuksessa . . . . . . . . . . . . . . . . . . . . . . . . . . .27 void KirjastonKirja::tulostaTiedot(ostream& virta) const28 29 Kirja::tulostaTiedot(virta); // Erikseen pyydetaan kantaluokan palvelua30 virta << ", palautus " << palpvm ;31

LISTAUS 6.6: Luokan KirjastonKirja virtuaalifunktiot

kirjastonkirjoihin, kuten listauksen riveilta 24–28 nakyy. Mikaan eitietysti esta muita ohjelman osia periyttamasta Kirja-luokasta omiaerikoistettuja kirjaluokkiaan, joita ne voivat myos lisata taulukkoon.

Koska kirjan jasenfunktio tulostaTiedot on virtuaalinen, voidaansen toteutus maaritella uudelleen missa tahansa periytetyssa luokas-sa. Niinpa rivilla 14 kaantaja tietaa vain, etta siina kutsutaan jotain ja-senfunktion tulostaTiedot toteutusta. Kaantajalla ei kuitenkaan ky-seisella rivilla ole mitaan tietoa edes siita, mita toteutuksia jasenfunk-tiolla voi olla, puhumattakaan siita, etta kaantaja pystyisi valitsemaannaista oikean. Niinpa kaantaja tuottaa kyseiseen kohtaan ohjelmaakoodin, joka ensin tarkastaa osoittimen paassa olevan olion todellisenluokan ja vasta sen jalkeen kutsuu sille sopivaa jasenfunktion toteu-tusta.

Kun nyt funktiota kutsutaan rivilla 30, tapahtuu seuraavaa: Pa-rametrina annetun taulukon ensimmainen alkio osoittaa luokanKirja olioon. Rivin 14 koodi kutsuu taman vuoksi jasenfunktio-ta Kirja::tulostaTiedot. Silmukan seuraavalla kierroksella taulu-kon toinen alkio osoittaakin luokan KirjastonKirja olioon. Tal-loin tasmalleen sama ohjelman rivi 14 kutsuukin jasenfunktiotaKirjastonKirja::tulostaTiedot, koska se on oikea toteutus taman-tyyppiselle oliolle.

Talla tavoin dynaaminen sitominen mahdollistaa sen, etta samajasenfunktiokutsu kayttaytyy eri tavalla riippuen siita, minka tyyppi-

Page 163: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

6.5. C++: Virtuaalifunktiot ja dynaaminen sitominen 163

10 void tulostaKirjat(vector<Kirja*> const& kirjat)11 12 for (unsigned int i = 0; i != kirjat.size(); ++i)13 14 kirjat[i]->tulostaTiedot(cout);15 cout << endl;16 17 1819 int main()20 21 vector<Kirja*> kirjaHylly;2223 // Huom! Alla olevasta puuttuu muistin loppumiseen varautuminen24 kirjaHylly.push back(25 new Kirja("Axiomatic", "Greg Egan")); // [Egan, 1995]26 kirjaHylly.push back(27 new KirjastonKirja("Matemaattisia olioita", "Leena Krohn",28 Paivays(31,10,1999))); // [Krohn, 1992]2930 tulostaKirjat(kirjaHylly); // Tulostetaan kirjat3132 for (unsigned int i = 0; i != kirjaHylly.size(); ++i)33 34 delete kirjaHylly[i]; kirjaHylly[i] = 0;35 36

LISTAUS 6.7: Dynaaminen sitominen C++:ssa

nen olio osoittimen tai viitteen paassa on. Jos kantaluokasta Kirjaperiytetaan jossain vaiheessa jokin toinen kirjaluokka, esimerkiksiMyytavaKirja, funktioon tulostaKirjat ei tarvitse tehda mitaan muu-toksia, vaan se osaa automaattisesti kutsua myos uuden kirjan tulos-tusjasenfunktiota. Tassa mielessa funktion koodi on yleiskayttoineneli geneerinen.

Dynaaminen sitominen toimii myos, jos virtuaalifunktiota kut-sutaan kantaluokan omasta jasenfunktiosta. Listauksessa 6.5 sivul-la 161 oli maaritelty riveilla 19–25 luokan sisainen jasenfunk-tio tulostaVirhe, jota kirjaluokan jasenfunktiot voivat kayttaa vir-heilmoitusten tulostamiseen. Tama koodi kutsuu jasenfunktiotatulostaTiedot. Koska tama jasenfunktio on virtuaalinen, kaytetaansen kutsumiseen dynaamista sitomista myos luokan omien jasen-

Page 164: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

6.5. C++: Virtuaalifunktiot ja dynaaminen sitominen 164

funktioiden koodissa.Kun nyt virheilmoitusfunktiota kutsutaan jollekin kirjaoliolle tai

siita periytetylle oliolle, tutkitaan tulostaTiedot-kutsun yhteydessa,mika on olion itsensa todellinen luokka. Taman jalkeen kutsutaan ta-man todellisen luokan maaraamaa tulostusfunktiota. Nain kantaluo-kan omat jasenfunktiot voivat kayttaa hyvakseen aliluokissa maaritel-tyja virtuaalifunktioiden toteutuksia, vaikka niilla ei edes ole mitaantietoa siita, millaisia aliluokkia ohjelmassa on olemassa!

6.5.3 Olion tyypin ajoaikainen tarkastaminen

Kantaluokkaosoittimen paassa olevalle oliolle voi kutsua vain kanta-luokan rajapinnassa olevia funktioita, vaikka osoittimen paassa todel-lisuudessa olisikin aliluokan olio. Tama johtuu siita, etta kaannos-vaiheessa kaantajalla ei ole mitaan mahdollisuutta varmistua siita,minka tyyppinen olio kantaluokkaosoittimen paassa on. Normaalistikantaluokan rajapinnan kayttaminen riittaa ohjelmalle, ja dynaami-nen sitominen mahdollistaa sen, etta aliluokan olio kayttaytyy silleominaisella tavalla.

Joskus tulee kuitenkin tarve paasta kasiksi aliluokan rajapintaan.Jos aliluokan olio on kuitenkin kantaluokkaosoittimen paassa, ei ali-luokan rajapinta ole nakyvissa. Ainoa vaihtoehto on luoda osoitinaliluokkaan ja laittaa se osoittamaan kantaluokkaosoittimen paassaolevaan olioon. Taman jalkeen aliluokan rajapintaan paasee kasiksikayttamalla saatua uutta osoitinta.

ISOC++:ssa on olion tyypin ajoaikaista tutkimista varten ominai-suus, jota yleisesti kutsutaan nimella RTTI (Run-Time Type Informa-tion). Jotta olion tyyppia voisi tutkia ajoaikana, taytyy olion luokassaolla vahintaan yksi virtuaalinen jasenfunktio. Tama vaatimus toteu-tuu kaytannossa aina, koska jokaisen kantaluokan purkajan tulisi ainaolla virtuaalinen ja tama virtuaalisuus periytyy myos aliluokkiin.

Kantaluokkaosoittimesta aliluokkaosoittimeksi

Muunnos kantaluokkaosoittimesta aliluokkaosoittimeksi onnistuutyyppimuunnoksella dynamic cast<Aliluokka*>(kluokkaosoitin).Sen toiminta on kaksivaiheinen. Ensin tarkastetaan, etta kantaluok-kaosoittimen paassa oleva olio todella on aliluokan olio tai jonkin ali-luokasta edelleen periytetyn jalkelaisluokan olio. Nain varmistetaan,

Page 165: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

6.5. C++: Virtuaalifunktiot ja dynaaminen sitominen 165

etta olion voi turvallisesti sijoittaa aliluokkaosoittimen paahan. Kan-taluokkaosoitinhan saattaa myos osoittaa kantaluokan tai jonkin toi-sen kantaluokasta periytetyn aliluokan olioon.

Jos kantaluokkaosoittimen paassa on vaaran tyyppinen olio, pa-lautetaan tyhja osoitin 0. Jos kantaluokkaosoittimen paassa on oikeantyyppinen olio, palautetaan kyseiseen olioon osoittava aliluokkaosoi-tin. Talla tavoin dynamic cast-muunnoksen avulla ohjelma voi sa-malla kertaa tarkastaa, etta olio todella on oikeaa tyyppia, ja vielasaada olion oikeantyyppisen osoittimen paahan. Listaus 6.8 sisaltaaesimerkin tyyppimuunnoksen kaytosta.

dynamic cast-muunnosta voi kayttaa myos olioviitteisiin, siistuottamaan aliluokkaviitteen, joka viittaa samaan olioon kuin annet-tu kantaluokkaviite. Tassa tapauksessa ainoa ero osoitinmuunnok-seen on, etta jos kantaluokkaviitteen paassa on vaaran tyyppinen olio,dynamic cast heittaa poikkeuksen (jonka tyyppi on std::bad cast).Syyna poikkeuksen heittoon on, etta ei ole olemassa “tyhjaa viitetta”,joka voitaisiin palauttaa virhetilanteessa.

Olion luokan selvittaminen

Edella mainittu dynamic cast on kateva, kun halutaan paasta kasik-si aliluokan laajennettuun rajapintaan silloin, kun aliluokan olio onkantaluokkaosoittimen paassa. Samoin dynamic cast on riittava, josvain halutaan testata, toteuttaako kantaluokkaosoittimen paassa ole-

1 bool myohassako(Kirja* kp, Paivays const& tanaan)2 3 KirjastonKirja* kkp = dynamic cast<KirjastonKirja*>(kp);4 if (kkp != 0)5 // Jos tultiin tanne, kirja on kirjastonkirja6 return kkp->onkoMyohassa(tanaan);7 8 else9 // Jos tultiin tanne, kirja ei ole kirjastonkirja

10 return false; // Ei siis ole myohassa11 12

LISTAUS 6.8: Olion tyypin ajoaikainen tarkastaminen

Page 166: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

6.5. C++: Virtuaalifunktiot ja dynaaminen sitominen 166

va olio tietyn aliluokan rajapinnan eli kuuluuko olio tiettyyn aliluok-kaan tai johonkin siita periytettyyn jalkelaisluokkaan.

Joissain erittain harvinaisissa tilanteissa tulee kuitenkin tarvesaada selville, mihin luokkaan olio kuuluu, ja kenties viela tallettaatama tieto johonkin tietorakenteeseen. Tahan dynamic cast ei kelpaa,koska silta voi vain kysya, kuuluuko olio tiettyyn luokkaan tai sen jal-kelaisluokkaan. Tallaista luokan kysymista varten C++:sta loytyy ope-raattori typeid ja luokka type info.

typeid-mekanismin kayttoon tulisi kuitenkin suhtautua erittainsuurella varauksella. Lahes kaikissa tapauksissa virtuaalifunktioiden(tai joskus kenties dynamic castin) kaytto on parempi, uudelleenkay-tettavampi ja selkeampi vaihtoehto. Esimerkiksi toteutus, jossa funk-tiossa kysytaan typeid:lla luokan tyyppi ja sitten if-lauseilla valitaansopiva koodi, ei ole hyvaa suunnittelua. Se vaatisi, etta luokkia lisat-taessa lisataan funktioon aina uusi vaihtoehto jokaista uutta luokkaavarten. Sen sijaan kannattaa lisata luokkien yhteiseen kantaluokkaanvirtuaalifunktio, jonka toteutukset periytetyissa luokissa sitten suo-rittavat halutun toiminnallisuuden. Nain saadaan kaikki luokkaanliittyvat asiat kapseloitua samaan paikkaan.

Luokka type info esitellaan komennolla #include <typeinfo>.Luokan oliot “edustavat” jokainen jotain ohjelman luokkaa. Olionluokan saa selville lausekkeella typeid(olio), joka palauttaa sellaisentype info-olion, joka edustaa olion luokkaa. Lauseke typeid(Luokka)palauttaa taas annettua luokkaa vastaavan type info-olion.

Kahta type info-luokan oliota voi vertailla keskenaan normaaleil-la operaattoreilla == ja !=. Oliot ovat keskenaan yhta suuria, jos needustavat samaa luokkaa, muuten erisuuria. Osoittimia naihin olioi-hin voi sitten ohjelmassa kayttaa vertailuavaimina, jos esimerkiksijostain tietorakenteesta pitaa etsia haluttuun luokkaan kuuluva olio.Luokan type info rajapinnassa on myos jasenfunktio name, joka pa-lauttaa oliota vastaavaa luokkaa kuvaavan merkkijonon. Taman tark-ka muoto on kaantajakohtainen eika valttamatta pelkka luokan nimi.

Sen testaaminen, onko osoittimen kp paassa oleva olio kirjastonkirja, kay periaatteessa vertailulla

if (typeid(*kp) == typeid(KirjastonKirja))

Tama kuitenkin testaa vain, onko osoittimen paassa tasmalleen luok-kaan KirjastonKirja kuuluva olio. Olio-ohjelmoinnin mukaan myos

Page 167: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

6.5. C++: Virtuaalifunktiot ja dynaaminen sitominen 167

jokainen tasta luokasta periytetty olio on kirjaston kirja, joten tal-lainen tasmallinen testaus ei yleensa ole se mita halutaan. Tamanvuoksi dynamic cast on yleensa oikea vaihtoehto tallaisiin testeihinja typeid-operaattorin kaytto kannattaa jattaa tilanteisiin, joissa tie-to olion luokasta talletetaan jonnekin tai valitetaan parametrina. Lis-taus 6.9 nayttaa esimerkin koodista, joka etsii taulukosta ensimmai-sen annettuun luokkaan kuuluvan olion.

6.5.4 Ei-virtuaalifunktiot ja peittaminen

Dynaamista sitomista kaytettaessa kaantajan on osattava jasenfunk-tiokutsun yhteydessa tuottaa ylimaarainen koodi, joka tarkastaa oliontodellisen tyypin ajoaikana. Taman vuoksi kaantajan on kasiteltava

1 #include <typeinfo>2 using std::type info;

...13 vector<Kirja*> ktaulukko;1415 Kirja* haeEnsimmainen(type info const& kirjanTyyppi)16 17 for (unsigned int i = 0; i < ktaulukko.size(); ++i)18 19 if (typeid(*ktaulukko[i]) == kirjanTyyppi)20 21 return ktaulukko[i];22 23 24 return 0; // Ei loytynyt25 2627 int main()28 29 // Etsitaan ensimmainen Kirjastonkirja30 Kirja* kp = haeEnsimmainen(typeid(KirjastonKirja));31 if (kp != 0)32 33 kp->tulostaTiedot(cout);34 35

LISTAUS 6.9: Esimerkki typeid-operaattorin kaytosta

Page 168: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

6.5. C++: Virtuaalifunktiot ja dynaaminen sitominen 168

virtuaalifunktioita ja tavallisia jasenfunktioita eri tavalla — tavallisenjasenfunktiokutsun yhteydessahan kaantaja tietaa jo kaannosaikana,minka luokan jasenfunktiota kutsutaan.

C++ antaa ohjelmoijalle myos mahdollisuuden maaritella periyte-tyssa luokassa uudelleen kantaluokan ei-virtuaalisia jasenfunktioita.Tallaisten jasenfunktioiden kutsumisessa ei kuitenkaan kayteta dy-naamista sitomista vaan aliluokan uusi toteutus peittaa (hide) kanta-luokan toteutuksen (tarkasti ottaen kaikki kantaluokan samannimi-set jasenfunktiot) aliluokassa. Tama tarkoittaa sita, etta mikali ky-seista jasenfunktiota kutsutaan suoraan aliluokan oliolle, kutsutaanaliluokan toteutusta. Jos taas kutsu tapahtuu kantaluokkaosoittimentai -viitteen kautta, kutsutaan kantaluokan toteutusta. Nain kutsutta-va jasenfunktio riippuu taysin siita, miten jasenfunktiota ohjelmassakutsutaan.

Tallainen kutsutilanteesta riippuva jasenfunktion valinta ei oleolio-ohjelmoinnissa lahes koskaan toivottavaa. Se tapahtuu kuitenkinhelposti vahingossa silloin, kun kantaluokan jasenfunktion esittelys-ta unohtuu avainsana virtual. Vaikka avainsana olisikin paikallaanaliluokassa, kantaluokkaosoittimen lapi ei talloin kayteta dynaamistasitomista ja ohjelma valitsee tallaisissa tilanteissa vaaran toteutuksenjasenfunktiolle.

Koska virtual-sanan unohtuminen kantaluokan esittelysta ei ai-heuta kaannosvirhetta vaan vaaran toiminnan, on erittain tarkeaa, et-ta kantaluokissa muistetaan merkita virtual-sanalla kaikki sellaisetjasenfunktiot, joiden toteutus saatetaan maaritella uudellen aliluokis-sa! Useimmissa muissa oliokielissa dynaamista sitomista kaytetaanoletusarvoisesti, joten niissa tallaista vaaratekijaa ei ole.

6.5.5 Virtuaalipurkajat

Olio-ohjelmissa tulee varsin usein esiin tilanne, jossa kantaluokka-osoitin laitetaan osoittamaan aliluokan olioon, joka on luotu dynaa-misesti new’lla. Tallainen tilanne vaatii hieman erikoistoimenpiteita,jos olio aiotaan ohjelmassa tuhota deletella kantaluokkaosoittimenkautta.

Jotta olioiden tuhoaminen kantaluokkaosoittien kautta toimisi oi-kein, kantaluokassa taytyy merkita luokan purkaja virtuaaliseksi li-saamalla sen eteen avainsana virtual. Sama tehdaan hyvan tyylinmukaisesti myos aliluokkien purkajissa, mutta kielen kannalta se ei

Page 169: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

6.5. C++: Virtuaalifunktiot ja dynaaminen sitominen 169

enaa ole valttamatonta, vaan purkajan virtuaalisuus periytyy aliluok-kiin automaattisesti.

Mikali olio tuhotaan kantaluokkaosoittimen kautta ilman, ettakantaluokan purkaja on virtuaalinen, ohjelman toiminta on C++-stan-dardin mukaan maarittelematonta. Kaytannossa on mahdollista, ettaohjelma kaatuu tai sitten kutsuu tuhottavalle oliolle vaaria purkajiatai toimii muuten vaarin. Naiden virheiden vuoksi on tarkeaa, ettajokaisen kantaluokan purkaja maaritellaan virtuaaliseksi!

Ongelma juontaa juurensa siita, etta kaantaja ei deleten koodiatuottaessaan pysty osoittimen tyypista paattelemaan, minka tyyppi-nen olio osoittimen paassa on. Historiallisista ja optimointiteknisis-ta syista C++ ei oletusarvoisesti yrita paatella osoittimen paassa olevanolion todellista tyyppia ajoaikaisesti, vaan ohjelman toiminta on jatet-ty maarittelemattomaksi. Kantaluokan purkajan virtuaalisuus toimiivinkkina kaantajalle, jolloin kaantaja generoi tuhoamisen yhteyteenkoodin, joka tarkastaa olion todellisen tyypin ja tuhoaa sen asianmu-kaisella tavalla.

Useimmissa muissa oliokielissa aliluokan olion tuhoaminen kan-taluokkaviitteen lapi on tehty aina oikein toimivaksi tai olioiden tu-houtuminen tapahtuu aina automaattisesti. Niinpa niissa ei yleensaole mitaan C++:n tapaisia ansoja tassa suhteessa.

6.5.6 Virtuaalifunktioiden hinta

Mikaan hyva ei ole koskaan ilmaista. Virtuaalifunktiot ja dynaami-nen sitominen tekevat mahdollisiksi todella joustavat ohjelmaraken-teet, joissa jasenfunktion kutsujan ei tarvitse tietaa yksityiskohtia sii-ta, mita jasenfunktion toteutusta kutsutaan. Tama parantaa ohjelmanyllapidettavyytta, laajennettavuutta seka luettavuutta. Dynaamisellasitomisella on kuitenkin hintansa.

Mikali dynaamista sitomista kaytetaan, ohjelman koodin taytyyaina virtuaalifunktion kutsun yhteydessa tarkastaa kutsun kohteenaolevan olion todellinen luokka ja valita oikea versio jasenfunktiontoteutuksesta. Tama valinta jaa lahes aina ajoaikaiseksi, joten valin-nan tekeminen hidastaa aina jasenfunktion kutsumista hiukan. Kay-tannon testit osoittavat, etta jasenfunktion kutsuminen dynaamistasitomista kayttaen on yleensa noin 4 % hitaampaa kuin normaalisti[Driesen ja Holzle, 1996]. On kuitenkin muistettava, etta mikali dy-naamista sitomista todella tarvitaan ohjelmassa, vaatisi ilman virtu-

Page 170: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

6.5. C++: Virtuaalifunktiot ja dynaaminen sitominen 170

aalifunktioita kirjoitettu koodi joka tapauksessa ylimaaraista koodiadynaamisen sitomisen matkimiseen, joten virtuaalifunktioiden hintaon todellisuudessa jonkin verran pienempi.

Suoritusnopeuden lisaksi virtuaalifunktiot vaikuttavat olioidenmuistinkulutukseen. Mikali luokassa tai sen kantaluokassa on yksi-kin virtuaalifunktio, taytyy luokan olioihin tallettaa jonnekin tietosiita, minka luokan olioita ne ovat. Yleensa tama tapahtuu virtuaa-litaulujen ja virtuaalitauluosoittimien avulla (niista voi lukea enem-man vaikkapa kirjoista “Inside the C++ Object Model” [Lippman, 1996]ja “The Design and Evolution of C++” [Stroustrup, 1994]). Tama tietovie useimmissa kaantajissa muistia yhden osoittimen verran, jotenvirtuaalifunktioita kayttavien luokkien olioiden muistinkulutus kas-vaa useimmissa jarjestelmissa 4 tavun verran.

Tama lisamuistinkulutus ei riipu luokan virtuaalifunktioidenmaarasta, joten olioiden koko ei enaa ensimmaisen virtuaalifunktionlisaamisen jalkeen kasva, vaikka virtuaalifunktioita olisikin useita.Koska jokaisella kantaluokalla tulisi joka tapauksessa olla virtuaali-purkaja, ei muiden jasenfunktioiden maarittely virtuaaliseksi kaytan-nossa vaikuta olioiden muistinkulutukseen.

Olioiden koon kasvun lisaksi kaantaja joutuu yleensa varaamaanjonkin verran muistia jokaista virtuaalifunktioita sisaltavaa luokkaavarten. Tata lisamuistia tarvitaan tyypillisesti yhdesta kolmeen osoit-timen verran jokaista luokassa olevaa virtuaalifunktiota varten. Kos-ka ylimaaraista muistia varataan kuitenkin vain kerran koko luokkaakohti eika sen maara riipu luokan olioiden maarasta, sen vaikutusohjelman muistinkulutukseen on yleensa mitaton.

On muistettava, etta kaantajalla on aina lupa optimoida koodia.Edella mainittu ohjelman hidastuminen on mahdollinen, mutta joskaantaja pystyy jo kaannosaikana paattelemaan, ettei dynaamista si-tomista tarvita jossain yhteydessa, se voi tietysti tuottaa myos tehok-kaampaa koodia.

6.5.7 Virtuaalifunktiot rakentajissa ja purkajissa

Normaalisti dynaamista sitomista kaytetaan aina, kun virtuaalifunk-tiota kutsutaan, ja nain kutsutaan aina “oikeaa” versiota virtuaali-funktiosta. Tasta ovat kuitenkin poikkeuksena rakentajissa ja purka-jissa tapahtuvat virtuaalifunktioiden kutsut.

Page 171: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

6.5. C++: Virtuaalifunktiot ja dynaaminen sitominen 171

Luvussa 6.3.2 selitettiin, kuinka aliluokan olion rakentaminen ta-pahtuu “kerroksittain” niin, etta ensin suoritetaan kantaluokan ra-kentaja ja sitten aliluokan rakentaja. Tama suoritusjarjestys aiheut-taa sen, etta kantaluokan rakentajan koodissa olion “aliluokkaosaa”ei ole viela alustettu, eika se nain ollen ole kayttokunnossa. Asian voiajatella niinkin, etta ennen aliluokan rakentajan suorittamista luota-va olio ei viela ole aliluokan olio, vaan vasta kantaluokan olio. Kunsitten kantaluokan rakentaja on saatu suoritetuksi, olion tyyppi “tay-dentyy” aliluokan olioksi aliluokan rakentajaan siirryttaessa, kunneslopulta kaikki rakentajat on saatu suoritetuksi ja koko olio alustetuk-si.

Tallainen kerroksittainen rakentuminen vaikuttaa siihen, mitenvirtuaalifunktiot kayttaytyvat. Kantaluokan rakentajan koodissa olioei viela oikeastaan ole aliluokan olio eika nain ollen pysty suoritta-maan aliluokassa maariteltya virtuaalifunktion toteutusta. Niinpa ra-kentajassa kutsuttu virtuaalifunktio kayttaytyykin ikaan kuin olio oli-si vain kantaluokan olio. Sen toteutusta ei etsita alempaa luokkahie-rarkiasta, vaan ainoastaan itse kantaluokasta tai sen omista kantaluo-kista. Tama ei yleensa ole se, mita ohjelmoija haluaa, joten rakenta-jien koodissa ei yleensa pitaisi kutsua virtuaalifunktioita, ellei oleaivan varma siita, etta haluaa kutsua nimenomaan kantaluokan omaatoteutusta.

Tasmalleen sama tilanne tapahtuu purkajissa, mutta painvastai-sista syista. Kun aliluokan olio tuhotaan, kutsutaan ensin aliluokanomaa purkajaa, jonka tehtavana on siivota aliluokan osa oliosta. Ta-man jalkeen siirrytaan kantaluokan purkajaan ja niin edelleen, kun-nes paastaan periytymishierarkian huipulle. Kantaluokan purkajankoodissa olio ei enaa oikeastaan ole aliluokan olio, koska tama osaoliosta on jo siivottu tuhoamiskuntoon. Niinpa kantaluokan purka-jassa kutsutut virtuaalifunktiot kayttaytyvat samoin kuin kantaluo-kan rakentajissa, ja niita kutsutaan aivan kuin olio olisi kantaluokanolio. Tama tarkoittaa, etta virtuaalifunktion toteutusta etsitaan vainkantaluokasta itsestaan tai sen omista kantaluokista. Nain myoskaanpurkajien koodissa ei yleensa pitaisi kutsua virtuaalifunktioita.

Page 172: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

6.6. Abstraktit kantaluokat 172

6.6 Abstraktit kantaluokat

Aiemmin aliluvussa 6.1 mainittiin abstraktit kantaluokat ja maari-teltiin ne luokkahierarkiassa oleviksi luokiksi, joiden ainoa merkityson olla periytymisessa kantaluokkina ja joista ei ole mielekasta teh-da olioita. Abstraktin kantaluokan perustava ominaisuus on, etta sii-na maaritellaan rajapintafunktioita, joille maaritellaan toteutus vastamyohemmin luokkahierarkiassa.

C++:ssa abstrakteja kantaluokkia voi maaritella kayttamalla puhtai-ta virtuaalifunktioita (pure virtual function). Puhtaalla virtuaalifunk-tiolla tarkoitetaan virtuaalifunktiota, jonka toteutus on pakko maa-ritella aliluokissa. Virtuaalifunktio tehdaan puhdas virtuaalifunktiolisaamalla luokan esittelyssa sen jalkeen maare =0. Listaus 6.10 seu-raavalla sivulla sisaltaa esimerkkina luokkien Elain ja Lintu esittelyt,joissa funktiot liiku ja laula on maaritelty puhtaiksi virtuaalifunk-tioiksi.

Abstraktiksi kantaluokaksi maaritellaan C++:ssa mika tahansa luok-ka, jossa on ainakin yksi puhdas virtuaalifunktio. Tama funktio voiolla luokassa itsessaan maaritelty tai peritty kantaluokalta. Periyte-tyissa luokissa voidaan tallainen puhdas virtuaalifunktio sitten maa-ritella taas tavalliseksi virtuaalifunktioksi ja kirjoittaa sille kyseiseenluokkaan sopiva toteutus. Nain periytymisen yhteydessa puhtaat vir-tuaalifunktiot muuttuvat taas normaaleiksi virtuaalifunktioiksi.

Lopulta periytymishierarkian alapaassa ollaan tilanteessa, jossaluokissa ei enaa ole yhtaan puhdasta virtuaalifunktiota vaan kaikil-le kantaluokissa maaritellyille puhtaille virtuaalifunktioille on ole-massa toteutus. Tallaiset luokat ovat vihdoin “konkreettisia” luokkia,joista loytyy toteutus kaikille rajapintafunktioille ja joista on nain jar-kevaa luoda oliota. Listauksen 6.10 esimerkissa luokka Kana maarit-telee kaikki kantaluokkien puhtaat virtuaalifunktiot tavallisiksi vir-tuaalifunktioiksi ja tarjoaa niille toteutuksen, joten kanat edustavatluokkahierarkiassa “konkreettisia” olioita.

Kaantaja pitaa automaattisesti huolen siita, etta abstrakteista kan-taluokista ei voi luoda oliota. Niiden kaytto ohjelmassa rajoittuukinsiihen, etta niita voidaan kayttaa periytymisessa kantaluokkana, jasiihen, etta ohjelmassa voi olla osoittimia ja viitteita tallaiseen kanta-luokkaan. Naiden osoittimien ja viitteiden paahan voi sitten sijoittaaabstrakteista kantaluokista periytettyjen luokkien olioita ja kutsua ali-

Page 173: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

6.6. Abstraktit kantaluokat 173

11 class Elain : public Elio12 13 public:14 virtual ~Elain();15 virtual void liiku(Sijainti paamaara) = 0;18 ;

...19 class Lintu : public Elain20 21 public:22 virtual ~Lintu();23 virtual void laula() = 0;24 ;

...25 class Kana : public Lintu26 27 public:28 virtual ~Kana();29 virtual void lisaanny(); // Toteutus lisaantymisfunktiolle30 virtual void liiku(Sijainti paamaara); // Toteutus liikkumiselle31 virtual void laula(); // Toteutus laulamiselle3233 private:34 // Tanne tarvittava sisainen toteutus35 ;

LISTAUS 6.10: Abstrakteja kantaluokkia ja puhtaita virtuaalifunktioita

luokissa maariteltyja puhtaiden virtuaalifunktioiden toteutuksia dy-naamista sitomista hyvaksikayttaen.

Vaikka abstrakti kantaluokka pakottaakin aliluokat kirjoittamaanomat toteutuksensa puhtaalle virtuaalifunktiolle, voi kantaluokkaC++:ssa tasta huolimatta sisaltaa oman toteutuksensa talle funktiol-le. Tallaisessa tapauksessa voidaan ajatella, etta kantaluokka sisal-taa osittaisen toteutuksen, joka toteuttaa rajapintafunktiosta sellai-sen osan, joka on yhteista kaikille tasta luokasta periytetyille luokille.Aliluokkien toteutukset voivat sitten kutsua tata kantaluokan tarjoa-maa toteutusta, ja talla tavalla kaikissa aliluokissa toistuvaa yhteis-ta osaa ei tarvitse kopioida erikseen jokaiseen aliluokkaan. Puhtaal-le virtuaalifunktiolle, jolla on toteutus, loytyy joskus myos muutakinkayttoa. Niista loytyy tietoa esimerkiksi teoksesta “More ExceptionalC++” [Sutter, 2002c].

Page 174: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

6.6. Abstraktit kantaluokat 174

Listauksessa 6.11 on esimerkki luokasta Elain, joka sisaltaa ja-senmuuttujanaan tiedon elaimen sijainnista. Tassa luokassa maari-tellaan myos “kaikille elaimille yhteinen” toteutus liikkumiselle, ni-mittain elaimen sijainnin paivitys. Tama toteutus ei kuitenkaan oleriittava, koska liikkumiseen liittyy paljon muutakin (reitin valinta, li-hasten liikuttaminen), joten aliluokissa on pakko maaritella “tarkem-pi” toteutus liikkumiselle. On huomattava, etta vaikka abstrakti kan-taluokka sisaltaisikin toteutuksen puhtaalle virtuaalifunktiolle, kon-kreettisten aliluokkien on silti pakko maaritella funktiolle oma toteu-tuksensa.

C++:n abstraktit kantaluokat voivat puhtaiden virtuaalifunktioidenlisaksi sisaltaa mita tahansa muutakin. Abstrakti kantaluokka voimaaritella kaikille aliluokille yhteisia jasenmuuttujia, uusia jasen-funktioita ja niin edelleen. Se voi myos maaritella toteutuksia omienkantaluokkiensa maarittelemille puhtaille virtuaalifunktioille ja naintarjota naille rajapintafunktiolle toteutuksen, joka periytyy myos kai-kille aliluokille. Nama aliluokat voivat puolestaan joko hyvaksya kan-

11 class Elain : public Elio12 13 public:14 virtual ~Elain();15 virtual void liiku(Sijainti paamaara) = 0;16 private:17 Sijainti paikka ;18 ;

...1 // Kaikille elaimille yhteinen osa liikkumista2 void Elain::liiku(Sijainti paamaara)3 4 paikka = paamaara;5

...1 // Kanan liikkuminen2 void Kana::liiku(Sijainti paamaara)3 4 // Tahan kanaan liittyvat erityistoimet, kaveleminen jne.5 Elain::liiku(paamaara); // Kantaluokka suorittaa kaikille yhteiset toimet6

LISTAUS 6.11: Puhdas virtuaalifunktio, jolla on myos toteutus

Page 175: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

6.7. Moniperiytyminen 175

taluokan tarjoaman toteutuksen tai kirjoittaa omansa. Nain abstrak-tien kantaluokkien kayttomahdollisuudet ovat varsin laajat.

6.7 Moniperiytyminen

Periytymisessa uusi luokka luodaan periyttamalla siihen jonkin ole-massa olevan luokan ominaisuudet. Mikaan ei tietysti esta laajenta-masta tata mekanismia niin, etta luokka periytetaan useasta kanta-luokasta. Tasta kaytetaan nimitysta moniperiytyminen (multiple in-heritance).

Moniperiytyminen on yksinkertaisesta ideastaan huolimatta var-sin kiistelty mekanismi, jonka kaytto oikein on joskus vaikeaa ja jo-hon liittyy paljon piilevia vaaroja. Niinpa moniperiytymista ei oleotettu mukaan laheskaan kaikiin oliokieliin ainakaan taydellisesti to-teutettuna. Taman aliluvun tarkoituksena on antaa yleiskatsaus mo-niperiytymiseen C++:ssa ja antaa muutama esimerkki sen vaaroista jahyotykaytosta. On kuitenkin heti alkuun syyta varoittaa, etta moni-periytymisen kayttoa tulisi yleensa valttaa, koska se saattaa varsinusein johtaa ongelmiin. Silla on kuitenkin oma paikkansa olio-ohjel-moinnissa, joten sen perusteet on syyta kasitella.

6.7.1 Moniperiytymisen idea

Moniperiytymisessa luokka perii ominaisuudet useammalta kuin yh-delta kantaluokalta. Tassa suhteessa moniperiytyminen ei tuo mitaanuutta periytymiseen. Aliluokan olio koostuu normaalissa moniperiy-tymisessa kaikkien kantaluokkiensa kantaluokkaosista seka aliluokanomasta laajennusosasta. Kuva 6.6 seuraavalla sivulla kuvaa tata tilan-netta. Niin sanotussa yhdistavassa moniperiytymisessa eli virtuaa-limoniperiytymisessa (shared multiple inheritance, virtual multipleinheritance) tilanne on hiukan toinen, mutta sita kasitellaan vasta ali-luvussa 6.8.2.

Tavallisessa periytymisessa aliluokan oliota voi aina ajatella myoskantaluokan oliona. Samalla tavalla moniperiytymisessa aliluokanolio kuuluu ikaan kuin yhtaikaa kaikkiin kantaluokkiinsa. Toisin sa-nottuna aliluokan olio kay aina minka tahansa kantaluokkansa edus-tajaksi. Kuvan 6.6 esimerkissa tama toteutuu, koska kirjaston kirja onaina yhtaikaa seka kirja etta kirjaston teos. Tama “aliluokan olio on

Page 176: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

6.7. Moniperiytyminen 176

Kirja

Nimi, tekijä yms.

KirjastonKirja

(Hyllyluokka, laina−aika yms.)

Mahdolliset uudet ominaisuudet

(Nimi, tekijä yms.)

KirjastonTeos

Hyllyluokka, laina−aika yms.

(a) Moniperiytyminen

Kirja−osa

KirjastonKirja−osa

KirjastonKirja−Olio

KirjastonTeos−osa

(b) Aliluokan olio

KUVA 6.6: Moniperiytyminen ja sen vaikutus

aina kaikkien kantaluokkiensa olio” -periaate on erittain tarkeaa mo-niperiytymisen oikean kayton kannalta.

6.7.2 Moniperiytyminen eri oliokielissa

Moniperiytyminen tuo mukanaan hyotyjen lisaksi monia ongelmia javirhemahdollisuuksia. Niinpa eri oliokielten kehittajat ovat suhtau-tuneet moniperiytymiseen varsin eri tavoin. Joissain kielissa moni-periytymista ei ole ollenkaan (Smalltalk), joissain sen kayttoa on ra-joitettu (Java) ja joissain (kuten C++:ssa) se on annettu ongelmineenkaikkineen ohjelmoijan kayttoon.

C++:n suhtautuminen moniperiytymiseen on yksi sallivimmistaoliokielten joukossa. C++:ssa on pyritty antamaan ohjelmoijalle mah-dollisuus kayttaa moniperiytymista juuri niin kuin ohjelmoija halu-aa, ja samalla kaikki moniperiytymisen mukanaan tuomat riskit ja on-gelmat on jatetty ohjelmoijan ratkaistavaksi. Taman tuloksena C++:nmoniperiytyminen on kylla voimakas tyokalu, mutta toisaalta sita onerittain helppo kayttaa vaarin niin, etta sen aiheuttamat ongelmat ha-vaitaan vasta liian myohaan. Moniperiytymisen kaytto C++:ssa vaatii-kin ohjelmoijalta itsekuria ja olioajattelun hallitsemista.

Javassa varsinaista luokkien moniperiytymista ei ole lainkaan. Jo-kaisella luokalla on vain yksi kantaluokka, josta se periytyy. Sen si-

Page 177: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

6.7. Moniperiytyminen 177

jaan Java sallii erilliset rajapintaluokat (interface class), joita voi yh-distella moniperiytymisen tapaan. Tama tekee mahdolliseksi sen, et-ta luokkien rajapintoja voi yhdistella monesta luokasta vapaasti, sensijaan toteutuksen moniperiytyminen ei ole mahdollista. Rajapinta-luokkia kasitellaan tarkemmin aliluvussa 6.9.

Smalltalk ottaa viela Javaakin tiukemman kannan moniperiytymi-seen. Tassa kielessa moniperiytymista ei yksinkertaisesti ole. Jokai-sella luokalla on tasan yksi valiton kantaluokka, eika tahan voi vai-kuttaa millaan tavalla. Kaytannossa Smalltalkin periytyminen salliikuitenkin kaiken minka Javankin. Smalltalkissa ei nimittain ole tiuk-kaa kaannosaikaista tyypitysta, vaan esimerkiksi kielen olioviitteetvoivat aina viitata mihin tahansa olioon sen luokasta riippumatta jamille tahansa oliolle voi yrittaa kutsua mita tahansa palvelua. Tastajohtuen Smalltalkissa voi helposti tehda usealle eri luokalle toimivaakoodia, vaikkei luokilla olisikaan yhteista rajapintaa maaraavaa kan-taluokkaa.

6.7.3 Moniperiytymisen kayttokohteita

Kiistanalaisuudestaan huolimatta moniperiytymiselle on muutamiaselkeita kayttokohteita, joissa siita on hyotya. Heti alkuun on kuiten-kin syyta huomauttaa, etta missaan seuraavassa esiteltavista tilanteis-ta moniperiytyminen ei ole aivan valttamaton. Kaikki esitetyt tilan-teet voidaan aina ratkaista myos ilman moniperiytymista — kylla-kin usein hieman tyolaammin ja monimutkaisemmin (samaan tapaankuin kaikki ohjelmat on mahdollista koodata ilman koko olio-ohjel-mointia).

Ehka tyypillisin ja turvallisin kayttokohde moniperiytymiselle onrajapintojen yhdistaminen. Jos sama luokka toteuttaa kerralla useita(yleensa abstraktien) kantaluokkien maaraamia rajapintoja, tekee mo-niperiytyminen taman ilmaisemisen helpoksi. Luokka moniperiyte-taan kaikista niista kantaluokista (rajapintaluokista), joiden rajapin-nat se toteuttaa, ja puhtaiden virtuaalifunktioiden toteutukset kirjoi-tetaan aliluokkaan. Tama moniperiytymisen muoto on juuri se, jotaJava-kieli tukee, vaikkei siina taydellista moniperiytymista olekaan.Moniperiytymisen kaytto tahan tarkoitukseen on suhteellisen mutka-tonta ja varsin hyodyllista, joten sita kasitellaan tarkemmin aliluvus-sa 6.9.

Page 178: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

6.7. Moniperiytyminen 178

Toinen kayttokohde moniperiytymiselle on kahden tai useammanolemassa olevan luokan yhdistaminen. Varsin monet valmiit olio-kirjastot sisaltavat luokkia, jotka on suunniteltu kaytettavaksi kanta-luokkina, joita ohjelmoija laajentaa ja tarkentaa periyttamalla niistaomat luokkansa. Tama patee erityisesti sovelluskehyksissa, jotka esi-tellaan lyhyesti aliluvussa 6.11. Jos nyt ohjelmoijalle tulee tarve kir-joittaa luokka, jonka selkeasti pitaisi laajentaa tai tarkentaa useaa val-mista kantaluokkaa, on moniperiytyminen usein ainoa kaytannolli-nen vaihtoehto. Tallaisessa tilanteessa on kuitenkin syyta olla tarkka-na, koska joissain tapauksissa moniperiytettavat kantaluokat saatta-vat hairita toistensa toimintaa. Tallaisiin tilanteisiin tutustutaan ali-luvuissa 6.8.1 ja 6.8.2.

Kolmantena moniperiytymista kaytetaan joskus luokkien koosta-miseen valmiista ominaisuuskokoelmista. Tasta kaytetaan englannin-kielisessa kirjallisuudessa usein nimitysta mixin tai flavours.^ Esi-merkkina tallaisesta periytymisesta voisi olla tilanne, jossa kaikki lai-naamiseen ja palautuspaivamaaraan liittyvat rajapinnat ja toiminnal-lisuus on kirjoitettu erilliseen luokkaan Lainattava. Vastaavasti tuot-teen myymiseen liittyvat asiat ovat luokassa Myytava. Naiden avullaluokka KirjastonKirja voitaisiin luoda moniperiyttamalla peruskanta-luokka Kirja ja ominaisuusluokka Lainattava. Samoin kaupan olevaCD-ROM saataisiin moniperiyttamalla luokat Cdrom ja Myytava.

Edella esitettyja moniperiytymisen kayttotapoja on viela mahdol-lisuus yhdistaa. On esimerkiksi mahdollista tehda erillinen rajapintaja useita sen eri tavalla toteuttavia ominaisuusluokkia. Naita kayttaenohjelmoija voi moniperiyttamalla ilmoittaa oman luokkansa toteutta-van tietyn rajapinnan ja viela periyttaa mukaan valitsemansa toteu-tuksen. Tallainen moniperiytymisen kaytto ei kuitenkaan ole enaaaivan yksinkertaista, eika sita kasitella tassa tarkemmin.

6.7.4 Moniperiytymisen vaaroja

Suuri osa moniperiytymisen vaaroista johtuu siita, etta moniperiyty-minen on houkuttelevan helpontuntuinen vaihtoehto sellaisissakintapauksissa, joissa se ei olioajattelun kannalta ole perusteltua. Moni-

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .^Englanninkieliset termit ovat lahtoisin amerikkalaisista jaatelobaareista, joissa asiakkaat

saavat usein itse paattaa jaatelonsa maun, ja jaatelo valmistetaan paikan paalla sekoittamalla(mixing) vaniljajaateloon marjoja, suklaata, pahkinoita tai muita makuaineita (flavours). Tastasyysta oliokirjallisuudessa “peruskantaluokasta” kaytetaan joskus nimitysta vanilla.

Page 179: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

6.7. Moniperiytyminen 179

periytyminenkin on periytymista, joten periytetyn luokan olion tay-tyy kaikissa tilanteissa olla kasitteellisesti myos kaikkien kantaluok-kiensa olio. Moniperiytymista ei voi esimerkiksi kayttaa siihen, ettaaliluokan olio olisi valilla yhden, valilla toisen kantaluokan edusta-ja. Vaikkapa luokkaa Vesitaso ei voisi todennakoisesti moniperiyttaaluokista Lentokone ja Vene, koska vesitaso ei kykene yhtaikaa liikku-maan seka ilmassa etta vedessa, toisin sanoen ilmassa ollessaan se eiole vene ja vastaavasti vedessa liikkuessaan se ei ole lentokone.

Moniperiytyminen aiheuttaa normaalin periytymisen tapaanmyos sen, etta kaikkien kantaluokkien julkiset rajapinnat nakyvat ali-luokasta ulospain (poislukien C++:n yksityinen ja suojattu periytymis-tapa, jotka eivat ole perinteisen olioajattelun mukaisia). Niinpa mo-niperiytymista ei tule kayttaa tilanteissa, joissa halutaan saada luok-kaan mukaan vain jonkin toisen luokan toiminnallisuus, muttei jul-kista rajapintaa. Esimerkiksi vaikka Paivays-luokka sisaltaakin palau-tuspaivamaaran kasittelyyn tarvittavan toiminnallisuuden, ei luok-kaa KirjastonKirja voi moniperiyttaa luokista Kirja ja Paivays. Vaikkakirjaston kirja sisaltaakin palautuspaivamaaran, se ei ole paivays, ei-ka sen julkinen rajapinta sisalla Paivays-luokan operaatioita. TassaPaivays-jasenmuuttuja on selkeasti oikea ratkaisu.

Vaikka moniperiytymista kaytettaisiinkin olioajattelun kannaltaoikein, se tekee ohjelman luokkarakenteesta helposti vaikeaselkoisen.Lisaksi moniperiytyminen aiheuttaa helposti ongelmia kuten rajapin-tojen moniselitteisyytta ja vaikeuksia olion elinkaaren hallinnassa.Naita ongelmia ja niiden ratkaisemista kasitellaan lyhyesti myohem-missa aliluvuissa. Kaikki tama aiheuttaa kuitenkin sen, etta monipe-riytymista ei kannata kayttaa, ellei sille ole painavia perusteita.

6.7.5 Vaihtoehtoja moniperiytymiselle

Jos moniperiytymista kaytetaan todella mallintamaan sita, etta aliluo-kan olio pysyvasti kuuluu useaan kantaluokkaan ja toteuttaa niidenrajapinnat, ei moniperiytymiselle ole helppoa vaihtoehtoa. Talloin onkuitenkin usein kyse rajapintaluokkien periyttamisesta, joka ei juu-rikaan aiheuta ongelmia ja joka onnistuu myos esimerkiksi Javassa,jossa varsinaista moniperiytymista ei ole.

Mikali tarve moniperiytymiseen sen sijaan tulee siita, etta halu-taan yhdistella jo olemassa olevien luokkien ominaisuuksia, voi mo-niperiytymisen usein kiertaa. Talloin on nimittain usein mahdollista

Page 180: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

6.8. C++: Moniperiytyminen 180

periytymisen sijaan kayttaa koostetta eli ottaa valmiit luokat kirjoi-tettavan luokan jasenmuuttujiksi. Talloin valmiiden luokkien jasen-funktiot eivat kuitenkaan nay ulospain. Jos naita jasenfunktioita pi-taisi pystya kutsumaan kirjoitettavan luokan ulkopuolelta, taytyy ta-han luokkaan viela kirjoittaa “lapikutsufunktiot” (call-through func-tion). Nailla tarkoitetaan jasenfunktioita, jotka yksinkertaisesti kutsu-vat vastaavaa jasenmuuttujan jasenfunktiota, valittavat sille saaman-sa parametrit ja palauttavat jasenfunktiolta saamansa paluuarvon kut-sujalle.

Moniperiytymisen korvaaminen koostamisella vaatii jonkin ver-ran kasityota, mutta silla valtetaan yleensa moniperiytymisen muka-naan tuomat ongelmat ja vaarat. Lisaksi se on ainoa vaihtoehto kie-lissa, joissa moniperiytymista ei ole. On ehka viela syyta korostaa,etta koostaminen ei kay vaihtoehdoksi moniperiytymiselle, jos luok-kien valinen “is-a”-periytymissuhde on valttamaton esimerkiksi sentakia, etta uutta luokkaa on pystyttava kasittelemaan kantaluokkao-soittimien kautta.

6.8 C++: Moniperiytyminen

C++:ssa moniperiytyminen tehdaan yksinkertaisesti luettelemalla luo-kan esittelyn yhteydessa kaksoispisteen jalkeen kaikki luokan kanta-luokat ja niiden periytymistavat (siis kaytannossa lahes aina public,kuten aliluvussa 6.3 todettiin). Kuvan 6.6 luokka KirjastonKirja esi-teltaisiin seuraavasti:

class KirjastonKirja : public KirjastonTeos, public Kirja

// Tanne Kirjastonkirjan uudet lisaominaisuudet;

Ominaisuuksiltaan moniperiytyminen ei C++:ssa eroa mitenkaannormaalista periytymisesta. Kantaluokilta periytyneiden osien naky-vyydet ja muut ominaisuudet toimivat samoin kuin tavallisessa pe-riytymisessakin. Sama kantaluokka ei voi kuitenkaan esiintya periy-tymislistassa kahteen kertaan. Toisin sanoen aliluokkaa ei voi suo-raan periyttaa “kahteen kertaan” samasta kantaluokasta. Epasuorastitama kuitenkin onnistuu, jos kahdella eri kantaluokalla on keskenaan

Page 181: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

6.8. C++: Moniperiytyminen 181

yhteisia kantaluokkia. Tallainen tilanne johtaa kuitenkin helposti on-gelmiin, ja sita kasitellaan myohemmin tarkemmin toistuvan moni-periytymisen yhteydessa (aliluku 6.8.2).

Koska kantaluokkia on nyt useita, taytyy aliluokan rakentajanalustuslistassa luonnollisesti kutsua kaikkien kantaluokkien raken-tajia. Muuten moniperiytyminen ei tuo ongelmia olioiden elinkaa-reen (poislukien jalleen pahamaineinen toistuva moniperiytyminen).Olion tuhoutumisen yhteydessa kaikkia tarvittavia purkajia kutsu-taan edelleen automaattisesti.

6.8.1 Moniperiytyminen ja moniselitteisyys

Moniperiytyminen tuo joitain lisaongelmia verrattuna normaaliinperiytymiseen. Kun kantaluokkia on useita, saattaa tietysti kaydaniin, etta samanniminen jasenfunktio periytyy aliluokan rajapintaanuseasta kantaluokasta. Talloin ongelmaksi tulee paattaa, minka kan-taluokan jasenfunktiota tulisi kutsua, kun jasenfunktiota kutsutaanaliluokan oliolle. C++:ssa tama ongelma tulee esiin, kunhan jasenfunk-tio nimi vain on sama, vaikka parametreissa olisikin eroa. Tallainentilanne voisi esimerkiksi ilmeta, jos molemmat luokista KirjastonTeosja Kirja maarittelisivat jasenfunktion tulostaTiedot.

Tama tilanne on ongelmallinen jo olioajattelunkin kannalta. Ali-luokan olion tulisi rajapinnaltaan olla kaikkien kantaluokkiensa olio.Jos nyt vaikka paatettaisiin, etta periytymislistalla ensimmainen kan-taluokka “voittaa” ja sen jasenfunktio valitaan, oltaisiin ristiriidassasen kanssa, etta aliluokan olion tulisi olla toistenkin kantaluokkienolio, ja niissa tama jasenfunktio on erilainen. C++ ratkaisee ongelmanniin, etta yritys kutsua kahdesta eri kantaluokasta periytynytta jasen-funktiota aiheuttaa kaannosaikaisen virheilmoituksen siita, etta ja-senfunktion kutsu on moniselitteinen (ambiguous).

Joskus eri kantaluokkien samannimiset jasenfunktiot tekevat sa-mantyyppisia asioita (mihin samalla tavalla nimetty jasenfunktio tie-tysti saattaa viitata), ja aliluokan toteutuksen tulisi suorittaa kaik-kien kantaluokkien toteutus kyseisesta jasenfunktiosta. Tama onnis-tuu kohtalaisen helposti, jos kyseinen jasenfunktio on kaikissa kan-taluokissa virtuaalinen, jolloin aliluokka voi antaa sille oman toteu-tuksen. Aliluokka voi nyt omassa toteutuksessaan kutsua vuorollaankaikkien kantaluokkien toteutusta jasenfunktiosta ja kenties viela li-sata jasenfunktioon omaa toiminnallisuuttaan.

Page 182: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

6.8. C++: Moniperiytyminen 182

Kuten jo mainittiin, tallainen ratkaisu on perusteltu vain, jos kan-taluokkien maarittelyt ja toteutukset jasenfunktiolle eivat ole millaanlailla ristiriidassa. Esimerkiksi KirjastonKirja-luokan tulostaTiedot-jasenfunktion voisi kenties toteuttaa listauksen 6.12 esittamalla ta-valla. Ikava kylla, kaytannossa moniselitteisyytta ei usein voi ratkais-ta nain helposti, mika vahentaa moniperiytymisen kayttomahdolli-suuksia.

Jos moniselitteinen jasenfunktio tosiaan tekee erilaisia, keskenaanepayhteensopivia asioita eri kantaluokissa, ei ole mitaan mahdol-lisuutta saada aliluokan oliota kayttaytymaan taman jasenfunktionosalta molempia kantaluokkia tyydyttavalla tavalla. Jos moniperiy-

4 class KirjastonTeos5 6 public:7 virtual void tulostaTiedot(std::ostream& virta) const;

...8 ;9

10 class Kirja11 12 public:13 virtual void tulostaTiedot(std::ostream& virta) const;

...14 ;1516 class KirjastonKirja : public KirjastonTeos, public Kirja17 18 public:19 virtual void tulostaTiedot(std::ostream& virta) const;

...20 ;

...21 void KirjastonKirja::tulostaTiedot(std::ostream& virta) const22 23 Kirja::tulostaTiedot(virta);24 KirjastonTeos::tulostaTiedot(virta);25 // Tanne mahdollisesti viela lisaa tulostusta26

LISTAUS 6.12: Moniselitteisyyden yksi valttamistapa

Page 183: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

6.8. C++: Moniperiytyminen 183

tymista kuitenkin halutaan kayttaa, voi tata ongelmaa ratkaista eri-laisilla tekniikoilla riippuen siita, ovatko jasenfunktiot virtuaalisiavai eivat. Seuraavassa esitetaan muutaman tallaisen tekniikan pe-rusteet. Tarkemmin niita on kasitelty esimerkiksi kirjoissa “EffectiveC++” [Meyers, 1998] ja “More Exceptional C++” [Sutter, 2002c].

Mikali moniselitteisille jasenfunktioille ei ole tarkoitus antaauusia toteutuksia moniperiytetyssa aliluokassa, muodostuu ainoak-si ongelmaksi jasenfunktion kutsuminen. Tamakin on ongelma vain,kun jasenfunktiota kutsutaan suoraan aliluokan rajapinnan kautta —siis suoraan oliota kayttaen tai aliluokkatyyppisen osoittimen tai viit-teen kautta. Kantaluokkaosoittimienhan kautta moniselitteisyytta eiole, koska kullakin kantaluokalla on vain yksi mahdollinen toteutusjasenfunktiolle. Tallaisessa tilanteessa ratkaisuvaihtoehtoja on kol-me:

• Kutsutaan moniselitteista jasenfunktiota aina kantaluokkaosoit-timien kautta — tarvittaessa vaikkapa valiaikaisia osoitinmuut-tujia kayttaen. Tama on helpoin mutta ehka kompeloin ratkai-su.

• Moniselitteisen jasenfunktion kutsun yhteydessa on mahdollis-ta erikseen kertoa, minka kantaluokan versiota halutaan kut-sua. Tama onnistuu ::-syntaksilla. Esimerkiksi Kirja-luokantulostaTiedot-jasenfunktiota voi kutsua syntaksilla

KirjastonKirja k;k.Kirja::tulostaTiedot();

Tama syntaksi on kuitenkin myos ehka hieman oudon nakoi-nen ja vaatii kantaluokan nimen kirjoittamista nakyviin kutsunyhteyteen.

• Kolmas vaihtoehto on kirjoittaa aliluokkaan uudet keskenaanerinimiset jasenfunktiot, jotka kutsuvat kunkin kantaluokan to-teutusta moniselitteiselle jasenfunktiolle ::-syntaksilla. Talloinaliluokan rajapintaan taytyy dokumentoida tarkasti, etta kysei-set jasenfunktiot vastaavat kantaluokkien toisennimisia jasen-funktiota. Tallainen rajapinnan osittainen uudelleennimeami-nen on joskus tarpeen. Listaus 6.13 seuraavalla sivulla nayttaaesimerkin tasta ratkaisusta.

Page 184: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

6.8. C++: Moniperiytyminen 184

1 class KirjastonKirja : public KirjastonTeos, public Kirja2 3 public:

...15 void tulostaKTeostiedot(std::ostream& virta) const;16 void tulostaKirjatiedot(std::ostream& virta) const;17 ;

...18 void KirjastonKirja::tulostaKTeostiedot(std::ostream& virta) const19 20 KirjastonTeos::tulostaTiedot(virta);21 2223 void KirjastonKirja::tulostaKirjatiedot(std::ostream& virta) const24 25 Kirja::tulostaTiedot(virta);26

LISTAUS 6.13: Jasenfunktiokutsun moniselitteisyyden eliminointi

Joskus moniperiytetyssa aliluokassa olisi tarpeen toteuttaa kulta-kin kantaluokalta periytynyt samanniminen (ja nain moniselitteinen)jasenfunktio niin, etta toteutus olisi erilainen kullekin kantaluokalle.Toisin sanoen kaksi kantaluokkaa tai useampi haluaa periytetyn luo-kan tarjoavan toteutuksen samannimisille jasenfunktioille (samoillaparametreilla), mutta itse toteutuksien pitaisi olla keskenaan erilai-sia. Tallaisen pulman ratkaiseminen vaatii vahan lisakikkailua. On-gelman voi ratkaista lisaamalla hierarkiaan kaksi abstraktia valikanta-luokkaa, jotka “uudelleennimeavat” ongelmallisen jasenfunktion. Ta-ma tapahtuu niin, etta valiluokat maarittelevat keskenaan erinimisetpuhtaat virtuaalifunktiot, joita ne sitten kutsuvat ongelmallisen ja-senfunktion toteutuksessa. Lopullinen aliluokka toteuttaa nama uu-det jasenfunktiot haluamallaan tavalla. Naita toteutuksia voi sittenkutsua uusilla nimilla aliluokan rajapinnan kautta ja normaalisti al-kuperaisella nimella kantaluokkaosoittimen tai -viitteen kautta. Lis-taus 6.14 seuraavalla sivulla nayttaa esimerkin tasta tekniikasta.

C++:ssa on myos mahdollista maarata aliluokka kayttamaan yh-den kantaluokan toteutusta using-maareen avulla. Tama ei kuiten-kaan ole olioajattelun kannalta oikea ratkaisu, koska talloin dy-naaminen sitominen ei tapahdu halutulla tavalla ja eri kantaluok-

Page 185: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

6.8. C++: Moniperiytyminen 185

1 class KirjastonTeosApu : public KirjastonTeos2 3 public:4 // Rakentaja parametrien valittamiseksi kantaluokan rakentajalle5 virtual void tulostaTiedot(std::ostream& virta) const;6 virtual void tulostaKTeostiedot(std::ostream& virta) const = 0;7 ;89 void KirjastonTeosApu::tulostaTiedot(std::ostream& virta) const

10 11 tulostaKTeostiedot(virta); // Kutsutaan aliluokan toteutusta12 131415 class KirjaApu : public Kirja16 17 public:29 // Rakentaja parametrien valittamiseksi kantaluokan rakentajalle30 virtual void tulostaTiedot(std::ostream& virta) const;31 virtual void tulostaKirjatiedot(std::ostream& virta) const = 0;32 ;3334 void KirjaApu::tulostaTiedot(std::ostream& virta) const35 36 tulostaKirjatiedot(virta); // Kutsutaan aliluokan toteutusta37 383940 class KirjastonKirja : public KirjastonTeosApu, public KirjaApu41 42 public:43 virtual void tulostaKTeostiedot(std::ostream& virta) const;44 virtual void tulostaKirjatiedot(std::ostream& virta) const;45 ;4647 void KirjastonKirja::tulostaKTeostiedot(std::ostream& virta) const48 49 // Tanne KirjastonTeos-luokalle sopiva toteutus50 5152 void KirjastonKirja::tulostaKirjatiedot(std::ostream& virta) const53 54 // Tanne Kirja-luokalle sopiva toteutus55

LISTAUS 6.14: Moniselitteisyyden eliminointi valiluokilla

Page 186: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

6.8. C++: Moniperiytyminen 186

kaosoittimien kautta kutsutaan eri toteutuksia moniselitteiselle ja-senfunktiolle. Esimerkin tapauksessa luokan KirjastonKirja esitte-lyyn lisattaisiin jasenfunktion tulostaTiedot esittelyn sijaan rivi“using Kirja::tulostaTiedot;”, joka valitsee Kirja-luokan toteutuk-sen kayttoon. Talloin kuitenkin edelleen KirjastonTeos-osoittimen la-pi kutsuttaessa kaytettaisiin KirjastonTeos-luokan toteutusta, mika eiole oikein.

6.8.2 Toistuva moniperiytyminen

Moniperiytyminen ei salli sita, etta sama kantaluokka esiintyisi pe-riytymisessa suoraan kahteen kertaan. Tasta huolimatta kantaluokkavoi silti periytya mukaan useaan kertaan epasuorasti valissa olevienkantaluokkien kautta. Kuva 6.7 seuraavalla sivulla nayttaa tilanteen,jossa kantaluokka Teos paatyy aliluokkaan KirjastonKirja kahta reit-tia kantaluokkien Kirja ja KirjastonTeos kautta. Tallaisesta kaytetaanusein termia toistuva moniperiytyminen [Koskimies, 2000] (repeatedmultiple inheritance [Meyer, 1997]).

Tilanne, jossa kantaluokka periytyy aliluokkaan useaa eri reittia,on hieman ongelmallinen. Se nimittain herattaa kysymyksen siita,millainen aliluokan olion rakenteen tulisi olla. Jokaisessa Kirjaston-Kirja-oliossa taytyy selvasti olla tyyppeja Kirja ja KirjastonTeos olevatkantaluokkaosat. Kummassakin kantaluokan oliossa puolestaan tay-tyy olla tyyppia Teos oleva kantaluokkaosa. Tasta heraa kysymys, on-ko KirjastonKirja-oliossa naita Teos-tyyppisia kantaluokkaosia kaksi(yksi kummallekin valittomalle kantaluokalle) vai yksi. Kuva 6.8 si-vulla 188 havainnollistaa naita vaihtoehtoja.

Kummassakin vaihtoehdossa on omat loogiset perustelunsa. JosTeos-kantaluokkaosia on kaksi, muodostuu KirjastonKirja-olio selvas-ti kahdesta erillisesta kantaluokkaosasta. Talloin puhutaan erottele-vasta moniperiytymisesta (replicated multiple inheritance). Tassa ta-vassa on se hyva puoli, etta yhdessa kantaluokkaosassa tehdyt muu-tokset eivat mitenkaan vaikuta toiseen kantaluokkaosaan ja kanta-luokkaosat eivat hairitse toisiaan. Lisaksi tama vaihtoehto on mah-dollista toteuttaa ilman, etta olion muistinkulutus tai tehokkuus kar-sii mitenkaan. Niinpa C++:n “normaalissa” moniperiytymisessa kanta-luokkaosia voi olla talla tavoin epasuorasti useita kappaleita. Joissaintapauksissa tama on myos selkeasti “oikea” vaihtoehto.

Page 187: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

6.8. C++: Moniperiytyminen 187

Kirja

Sivumäärä, painovuosi yms.

KirjastonKirja

KirjastonTeos

Hyllyluokka, laina−aika yms.

Teos

Nimi, tekijä

KUVA 6.7: Toistuva moniperiytyminen

Usean kantaluokkaosan vaihtoehdossa on kuitenkin haittana se,etta laheskaan aina — ja varsinkaan esimerkin tapauksessa — ei olejarkevaa, etta KirjastonKirja-oliossa on ikaan kuin kaksi erillista Teos-oliota. Kirjaston kirjahan on kaikesta huolimatta nimenomaan yksiainoa Teos! Erottelevassa moniperiytymisessa kirjaston kirjan nimija tekija talletetaan turhaan kahteen kertaan. Jos kirjaston kirjan ni-mea nyt viela muutetaan vain Kirja-luokan rajapinnan kautta, sisalta-vat Teos-kantaluokkaosat eri nimet, mika ei tietenkaan ole suotavaa.Kaiken kukkuraksi Teos-osoitinta ei voi laittaa osoittamaan Kirjaston-Kirja-olioon, koska kaantaja ei voi tietaa, kumpaan kantaluokkaosaanosoittimen pitaisi osoittaa. Tassa syntyy siis samantapainen monise-litteisyys kuin aiemmin jasenfunktioiden tapauksessa.

C++:ssa kahden kantaluokkaosan ongelma on ratkaistu niin, etta pe-riytymisen yhteydessa luokka voi “antaa luvan” siihen, etta tarvittaes-sa moniperiytymisen yhteydessa muut aliolion osat voivat pitaa tatakantaluokkaosaa yhteisena. Tata sanotaan virtuaaliseksi moniperiy-tymiseksi (virtual multiple inheritance) tai yhdistavaksi moniperiy-tymiseksi (shared multiple inheritance), ja se merkitaan C++:n periyty-

Page 188: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

6.8. C++: Moniperiytyminen 188

Kirja−osa

Teos−osa

KirjastonKirja−osa

osaKirjastonTeos−

Teos−osa

KirjastonKirja−Olio

(a) Erilliset kantaluokkaosat

Kirja−osa

Teos−osa

KirjastonKirja−osa

osaKirjastonTeos−

KirjastonKirja−Olio

(b) Yhteinen kantaluokkaosa

KUVA 6.8: Toistuva moniperiytyminen ja olion rakenne

misen yhteydessa avainsanalla virtual. Jos esimerkissa luokka Kirjaon sallinut kantaluokan Teos jakamisen syntaksilla

class Kirja : public virtual Teos // Tai virtual public. . .

ja luokka KirjastonTeos on tehnyt saman, tulee KirjastonKirja-olioonvain yksi Teos-kantaluokkaosa. Moniperiytymisen yhteydessa siiskaikki luokat, jotka on periytetty virtuaaliperiytymisella samasta kan-taluokasta, jakavat keskenaan taman kantaluokkaosan.

Virtuaaliperiytyminen tuo mukanaan yhden mutkan aliluokanolion elinkaareen. Aliluokan olion luomisen yhteydessa kutsutaanaliluokan rakentajaa, joka kutsuu kantaluokan rakentajaa ja niin edel-leen. Virtuaaliperiytymisessa tama tarkoittaisi, etta jaetun kantaluok-kaosan rakentaja suoritettaisiin useita kertoja, koska sita luonnollises-ti kutsutaan kaikkien siita periytettyjen luokkien rakentajissa. Tamaei tietenkaan ole jarkevaa, koska yksi olio voidaan alustaa vain ker-taalleen.

Page 189: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

6.8. C++: Moniperiytyminen 189

C++ ratkaisee taman ongelman niin, etta virtuaalisesti periytetynkantaluokan rakentajaa taytyy kutsua suoraan sen luokan rakenta-jassa, jonka tyyppista oliota ollaan luomassa, ja muut kantaluokkienrakentajissa olevat virtuaalisen kantaluokan rakentajakutsut jatetaansuorittamatta. Esimerkin tapauksessa tama tarkoittaa sita, etta Teos-luokan rakentajaa pitaa kutsua suoraan KirjastonKirja-luokan ra-kentajan alustuslistassa, vaikka Teos ei olekaan taman luokan valitonkantaluokka. Kun luokan KirjastonKirja oliota luodaan, Teos-luokanrakentajan kutsut luokissa KirjastonTeos ja Kirja jatetaan suoritta-matta. Nain yhteinen kantaluokkaosa alustetaan vain kertaalleen suo-raan “alimman tason” luokan rakentajassa. Kuva 6.9 selventaa tilan-netta. Jos yhteisen kantaluokkaosan rakentajan kutsu puuttuu alim-man tason rakentajasta, yrittaa C++ tyypilliseen tapaansa kutsua kan-taluokan oletusrakentajaa.

Kaytannossa tama vaatimus kutsua jaetun kantaluokan rakentajaaalimmassa aliluokassa “yli periytymishierarkian” hankaloittaa virtu-

Kirja

Kirja::Kirja(...) : Teos(...), ...

KirjastonTeos

KirjastonTeos::KirjastonTeos(...)

: Teos(...), ...

Teos

Teos::Teos(...) ...

KirjastonKirja

KirjastonKirja::KirjastonKirja(...)

: Teos(...), KirjastonTeos(...),

Kirja(...), ...

Ei suoritetaEi suoriteta

virtualvirtual

KUVA 6.9: Rakentajat virtuaalisessa moniperiytymisessa

Page 190: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

6.9. Periytyminen ja rajapintaluokat 190

aalisen moniperiytymisen kayttoa ja tekee luokkien valisen vastuun-jaon vaikeammaksi. Se on kuitenkin valttamaton rajoitus olioajatte-lun kannalta. Jos kaksi toisistaan tietamatonta kantaluokkaa joutuujakamaan yhteisen kantaluokkaosan, on luonnollista etta nama luokatmoniperiyttava aliluokka maaraa, miten tama yhteinen kantaluokkaalustetaan. Helpommaksi tilanne muuttuu, jos jaetulla kantaluokal-la on vain oletusrakentaja, jolloin rakentajan parametreista ei tarvitsevalittaa aliluokissa.

Olioiden tuhoamisen yhteydessa vastaavia ongelmia ei tule, vaanjaetun kantaluokan purkajaa kutsutaan normaalisti kertaalleen ilman,etta aliluokkien taytyy ottaa siihen kantaa._

Kaiken kaikkiaan toistuva kantaluokka aiheuttaa moniperiytymi-seen niin paljon monimutkaisuutta, rajoituksia ja ongelmia, etta jot-kut ovat antaneet tallaiselle periytymishierarkialle osuvan nimen“Dreaded Diamond of Death”. Varsinkin virtuaalista moniperiytymis-ta pitaisikin yleensa valttaa, ellei tieda tarkkaan mita on tekemassa.Joskus se on kuitenkin kelvollinen apukeino ohjelmoijan tyokalupa-kissa.

6.9 Periytyminen ja rajapintaluokat

On varsin yleista, etta abstrakteissa kantaluokissa maaritellaan lu-vun alun elioesimerkin tapaan pelkastaan puhtaita virtuaalifunktioi-ta, jolloin abstraktit kantaluokat eivat sisalla mitaan muuta kuin ra-japinnan maarittelyja. Talloin puhutaan usein rajapintaluokista (in-terface class). Rajapintaluokissa ei siis ole jasenmuuttujia eika jasen-funktioiden toteutuksia, vaan ainoastaan (yleensa julkisen) rajapin-nan maarittely. Joissain oliokielissa, kuten Javassa, tallaisille puhtaillerajapinnoille on oma syntaksinsa eika niita edes varsinaisesti lasketaluokiksi.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ._Tarkasti ottaen C++-standardi joutuu ottamaan kantaa virtuaaliseen periytymiseen purka-

jienkin yhteydessa, kun se maarittelee purkajien keskinaisen kutsujarjestyksen. Talla ei kui-tenkaan normaalisti ole mitaan merkitysta, ja purkajia edelleen kutsutaan kaanteisessa jarjes-tyksessa rakentajiin verrattuna.

Page 191: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

6.9. Periytyminen ja rajapintaluokat 191

6.9.1 Rajapintaluokkien kaytto

Rajapintaluokat ovat varsin katevia, koska niiden avulla voidaankayttajalle paljastaa luokkahierarkiasta pelkka hierarkkinen rajapin-ta ja katkea itse rajapintafunktioiden toteutus konkreettisiin luokkiin.Rajapintaluokkia ja dynaamista sitomista kayttamalla ohjelmoija voilisaksi itse paattaa, milla hierarkiatasolla olioita kasittelee. Joitainfunktioita kiinnostaa vain, etta niiden parametrit ovat mita tahansaelaimia, johonkin tietorakenteeseen talletetaan mita tahansa sienia janiin edelleen.

Usein tulee eteen tilanne, jossa kaikkia haluttuja rajapintoja eivoi millaan panna samaan luokkahierarkiaan, koska itse rajapinnatovat toisistaan riippumattomia ja konkreettisten luokkien rajapinnatovat erilaisia yhdistelmia naista rajapinnoista. Tallaisessa tapaukses-sa konkreettiset luokat pitaisi pystya koostamaan erilaisista rajapin-takomponenteista luokkahierarkiasta riippumatta. Kuva 6.10 nayttaaesimerkin useiden toisistaan riippumattomien rajapintojen kaytosta.

Eri oliokielissa ongelma on ratkaistu eri tavoilla. Ongelmaa ei oleniissa kielissa, joissa ei ole kaannosaikaista tyypitysta, koska itse ra-japintaluokan kasitetta ei tarvita. Esimerkiksi Smalltalkissa milta ta-

Eliö

abstract

lisäänny() abstract

Eläin

abstract

hajota()liiku()

Limasieni

lisäänny()

Nisäkäs

abstract

imetä() abstract

Vesinokkaeläin

muni()imetä()liiku()lisäänny()

Ihminen

lisäänny()liiku()imetä()

Kana

lisäänny()laula()liiku()

Satakieli

lisäänny()laula()liiku()

Liikkuva

Muniva

Liikkuva

<<interface>>

liiku() abstract

Lintu

abstract

laula() abstractmuni()

Muniva

<<interface>>

muni() abstract

Sieni

abstract

hajota() abstract

KUVA 6.10: Luokat, jotka toteuttavat erilaisia rajapintoja

Page 192: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

6.9. Periytyminen ja rajapintaluokat 192

hansa oliolta voidaan pyytaa mita tahansa palvelua ja vasta ohjelmanajoaikana tarkastetaan, pystyyko olio tallaista palvelua tarjoamaan.

Javan erilliset rajapinnat tarjoavat elegantin tavan yhdistella raja-pintoja todellisissa luokissa. Java ei rajoita tallaisten rajapintojen maa-raa yhteen, vaan luokka voi luetella mielivaltaisen maaran rajapin-toja, jotka se toteuttaa. Talla tavoin luokat voivat luokkahierarkiastariippumatta toteuttaa erilaisia rajapintoja. Naiden rajapintojen avul-la voidaan sitten kasitella kaikkia rajapinnan toteuttavia luokkia sa-massa koodissa, koska luokista ei tarvita muuta tietoa kuin se, etta netoteuttavat halutun rajapinnan. Listaus 6.15 nayttaa osan kuvan 6.10luokkien toteutuksesta Javalla.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Liikkuva.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .1 public interface Liikkuva2 3 public void liiku(Sijainti paamaara);4

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Muniva.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .1 public interface Muniva2 3 public void muni();4

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Elain.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .1 public abstract class Elain extends Elio implements Liikkuva2

...3

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Vesinokkaelain.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .1 public class Vesinokkaelain extends Nisakas implements Muniva2 3 public void lisaanny() 4 public void liiku(Sijainti paamaara) 5 public void imeta() 6 public void muni()

...7

LISTAUS 6.15: Erilliset rajapinnat Javassa

Page 193: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

6.9. Periytyminen ja rajapintaluokat 193

6.9.2 C++: Rajapintaluokat ja moniperiytyminen

C++:ssa rajapintaluokkia mallinnetaan abstrakteilla kantaluokilla jamoniperiytymisella. Mikali todellinen luokka toteuttaa useita toisis-taan riippumattomia rajapintoja, se periytetaan kaikista rajapinnatmaaraavista abstrakteista kantaluokista. Talla tavoin saadaan aikaanuseita juuriluokkia sisaltava luokkahierarkia, jossa rajapintaluokatovat kaikkien rajapinnan toteuttavien todellisten luokkien kantaluok-kia. Listaus 6.16 nayttaa osan kuvan 6.10 toteutuksesta C++:lla.

Mikali abstraktit kantaluokat sisaltavat ainoastaan puhtaita virtu-

17 class Liikkuva18 19 public:20 virtual ~Liikkuva();21 virtual void liiku(Sijainti paamaara) = 0;22 ;

...23 class Muniva24 25 public:26 virtual ~Muniva();27 virtual void muni() = 0;28 ;

...29 class Elain : public Elio, public Liikkuva30 31 public:32 private:33 ;

...47 class Vesinokkaelain : public Nisakas, public Muniva48 49 public:50 virtual ~Vesinokkaelain();51 virtual void lisaanny();52 virtual void liiku(Sijainti paamaara);53 virtual void imeta();54 virtual void muni();55 ;

LISTAUS 6.16: Rajapintaluokkien toteutus moniperiytymisella C++:ssa

Page 194: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

6.9. Periytyminen ja rajapintaluokat 194

aalifunktioita ja ovat nain pelkkia rajapintaluokkia, ei moniperiyty-misen kaytosta aiheudu yleensa ongelmia. Mikali moniperiytymises-sa kantaluokat sen sijaan sisaltavat myos rajapintojen toteutuksia jajasenmuuttujia, moniperiytyminen aiheuttaa yleensa enemman on-gelmia kuin ratkaisee, kuten aiemmin on todettu. C++:ssa moniperiy-tymisen jarkeva kaytto on jatetty ohjelmoijan vastuulle, eika kieli itseyrita varjella ohjelmoijaa siina esiintyvilta vaaroilta.

Rajapintaluokkien toteuttaminen C++:ssa moniperiytymisen avullaaiheuttaa kuitenkin jonkin verran kompelyytta ohjelmaan. Ensinna-kin, koska seka normaali periytyminen etta rajapinnan toteuttaminentehdaan kielessa periytymissyntaksilla, ei luokan esittelysta suoraannae, mitka sen kantaluokista ovat puhtaita rajapintoja ja mitka sisal-tavat myos toteutusta. Tahan ei C++:ssa ole muuta ratkaisukeinoa kuinnimeta rajapintaluokat niin, etta kay selvasti ilmi niiden olevan pelk-kia rajapintoja.

Rajapintaluokat, rakentajat ja virtuaalipurkaja

Rajapintaluokat ovat C++:ssa normaaleja luokkia, joten niillakin on ra-kentaja, jota kutsutaan aliluokan rakentajasta. Kaytannossa tama ra-kentaja on kuitenkin aina tyhja, koska rajapintaluokat nimensa mu-kaan maarittavat vain rajapinnan eivatka sisalla jasenmuuttujia taitoiminnallisuutta. Taman vuoksi rakentajien kirjoittaminen rajapin-taluokille olisi turhauttavaa. Tassa onneksi C++:n normaalisti vaaralli-nen automatiikka auttaa.

Aliluvussa 3.4.1 todettiin, etta jos luokalle ei kirjoiteta yhtaan ra-kentajaa, tekee kaantaja sinne automaattisesti “tyhjan” oletusrakenta-jan. Vastaavasti aliluvussa 6.3.2 kavi ilmi, etta jos kantaluokan raken-tajan kutsu jatetaan pois aliluokan rakentajan alustuslistasta, kutsuukaantaja automaattisesti kantaluokan oletusrakentajaa. Naiden omi-naisuuksien ansiosta rajapintaluokkaan ei tarvitse kirjoittaa rakenta-jaa ollenkaan, koska talloin kaantaja automaattisesti luo tyhjan ole-tusrakentajan ja kutsuu sita aliluokissa. Rajapintaluokat ovat C++:ssalahes ainoa tilanne, jolloin luokalle ei tyyliohjeista poiketen kannatakirjoittaa rakentajaa.

C++:ssa kantaluokkien purkajien tulisi olla virtuaalisia (alilu-ku 6.5.5), jotta niista periytettyjen luokkien oliot tuhottaisiin ainaoikein. Rajapintaluokat ovat kantaluokkia, joten tama saanto kos-kee myos niita. Ikava kylla, jos rajapintaluokan esittelyssa esitellaan

Page 195: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

6.9. Periytyminen ja rajapintaluokat 195

purkaja virtuaaliseksi, pitaa talle purkajalle kirjoittaa myos toteutus,vaikka kyseessa onkin pelkka rajapintaluokka ilman toiminnallisuut-ta tai dataa, ja nain purkajan toteutus on aina tyhja.

Arsyttavaksi taman vaatimuksen virtuaalipurkajan toteutuksestatekee se, etta koska rajapintaluokassa ei muuten ole toiminnallisuut-ta, olisi luontevaa kirjoittaa sille pelkka otsikkotiedosto (.hh) ja jattaavarsinainen kooditiedosto (.cc) kokonaan kirjoittamatta. Purkajan to-teutus taas normaalisti kirjoitettaisiin juuri kooditiedostoon. Tahanongelmaan loytyy onneksi ratkaisu. C++ antaa mahdollisuuden kir-joittaa jasenfunktion toteutuksen suoraan luokkaesittelyn sisalle (sa-maan tapaan kuin Javassa tehdaan). Tama ei ole normaalisti hyvaatyylia, koska luokan toteutusta ja esittelya ei ole syyta sotkea kes-kenaan. Lisaksi tallaiset luokan esittelyyn upotetut jasenfunktiot op-timoidaan aina kuten inline-funktiot (liitteen aliluku A.2), mika eiyleisessa tapauksessa ole valttamatta hyva asia.

Koska kuitenkin rajapintaluokan purkaja on aina tyhja, voi tas-ta tyylisaannosta ainakin taman kirjan kirjoittajien mielesta poike-ta tassa tilanteessa. Nain rajapintaluokan purkaja saadaan kirjoitet-tua kokonaan otsikkotiedostoon eika erillista toteutustiedostoa tarvi-ta. Listaus 6.17 nayttaa esimerkkina rajapintaluokan Liikkuva esitte-lyn nain kirjoitettuna.

6.9.3 Ongelmia rajapintaluokkien kaytossa

Erilliset rajapinnat tai rajapintaluokat selkeyttavat luokkahierarkiaaja mahdollistavat katevasti sen, etta saman rajapinnan toteuttaviaolioita voidaan kasitella rajapintaosoittimen tai -viitteen avulla riip-pumatta luokkien sijainnista luokkahierarkiassa. Rajapintaluokat ei-vat kuitenkaan ratkaise kaikkia ongelmia.

1 class Liikkuva2 3 public:4 // Kaantaja tuottaa automaattisesti tyhjan oletusrakentajan5 virtual ~Liikkuva() // Upotettu tyhja virtuaalipurkaja (inline)6 virtual void liiku(Sijainti paamaara) = 0;7 ;

LISTAUS 6.17: Rajapintaluokan purkaja esittelyyn upotettuna

Page 196: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

6.9. Periytyminen ja rajapintaluokat 196

Rajapintojen nimien paallekkaisyys

Moniperiytymisen yhteydessa todettiin, etta monen kantaluokan ta-pauksessa voi kayda niin, etta usean kantaluokan rajapinnassa on tas-malleen samanniminen jasenfunktio samoilla parametreilla. Tasmal-leen sama ongelma voi ilmeta myos rajapintaluokkien tapauksessa,joten ongelma koskee myos esimerkiksi Javaa, vaikka siina ei varsi-naista moniperiytymista olekaan.

Samannimisten jasenfunktioiden erottaminen toisistaan kayC++:ssa aliluvussa 6.8.1 sivulla 184 esitetylla tekniikalla apukanta-luokkia kayttamalla. Talla tavoin rajapinnat toteuttava luokka voi an-taa eri toteutuksen kumpaakin rajapintaa varten. Tama tekniikka vaa-tii kielessa kuitenkin aitoa moniperiytymista, joten sita ei, ikava kyl-la, voi kayttaa Javassa. Taman kirjan kirjoittajat eivat valitettavasti on-nistuneet loytamaan tai keksimaan yhtaan tapaa, jolla ongelman voisiratkaista Javan keinoin.

Rajapintaluokkien yhdistaminen

Rajapintaluokat voivat aiheuttaa myos ongelmia itse periyty-misessa. Oletetaan esimerkiksi, etta halutaan kirjoittaa funktiomenePesaanJaMuni, joka kaskee eliota ensin liikkumaan pesaansa jasen jalkeen munimaan sinne. Olisi luonnollista, etta tallaiselle funk-tiolle voisi antaa parametrina minka tahansa olion, joka pystyy sekaliikkumaan etta munimaan. Liikkuva ja Muniva ovat kuitenkin eril-lisia rajapintaluokkia, eika funktion parametrilla voi olla kuin yksityyppi.

Yksi ratkaisuyritys olisi luoda uusi rajapintaluokkaLiikkuvaJaMuniva, johon periyttamalla yhdistetaan molemmattarvittavat rajapinnat.

class LiikkuvaJaMuniva : public Liikkuva, public Muniva;

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .Rajapintaluokan LiikkuvaJaMuniva purkaja on automaattisesti virtuaalinen, koska tama

ominaisuus periytyy sen kantaluokilta. Taman vuoksi virtuaalipurkajan esitteleminen ei olevalttamatonta, vaikka ehka kyllakin hyvaa tyylia.

Page 197: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

6.9. Periytyminen ja rajapintaluokat 197

Taman uuden rajapintaluokan avulla funktio olisi helppo esitellaseuraavasti:

void menePesaanJaMuni(LiikkuvaJaMuniva& emo);

Vaikka edella esitetty tapa tuntuukin jarkevalta, se ei yllattaen toi-mi. Jos funktiolle yrittaa esimerkiksi antaa parametrina luokan Kanaoliota, valittaa kaantaja ettei Kana-olio kelpaa funktion parametrik-si. Syyna tahan on, etta vaikka Kana toteuttaakin erikseen periyty-misen kautta seka rajapinnan Liikkuva etta Muniva, se ei luokka-hierarkian mukaan kuitenkaan ole periytetty naiden yhdistelmastaLiikkuvaJaMuniva. Niinpa Kana-olio ei kuulu tahan luokkaan eika kel-paa parametriksi. Kuva 6.11 havainnollistaa tilannetta.

Kaikki tama seuraa suoraan periytymisen ominaisuuksista. Vaik-ka luokkaan LiikkuvaJaMuniva ei olekaan lisatty yhtaan ylimaa-raista jasenfunktiota luokkiin Liikkuva ja Muniva verrattuna, saat-

Eläin

abstract Liikkuva

<<interface>>

liiku() abstract

Lintu

abstract

laula() abstractmuni()

Muniva

<<interface>>

muni() abstract

Kana

lisäänny()laula()liiku()

LiikkuvaJaMuniva

<<interface>>

PUUTTUU!

KUVA 6.11: Ongelma yhdistettyjen rajapintojen kanssa

Page 198: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

6.10. Periytyminen vai kooste? 198

taa olla etta taman luokan semantiikka (merkitys) sisaltaa muutakinkuin kahden rajapinnan yhdistamisen. Esimerkiksi rajapintaluokkaLentavaJaMuniva nayttaisi maarittelyltaan samalta, mutta siina voi-si olla dokumentoituna, etta liikkuminen tapahtuu lentaen. TalloinKanan ei tietysti pitaisikaan kelvata toteuttamaan tata rajapintaa.

Ainoa ratkaisu tahan ongelmaan C++:n ja Javan tapaisissa kielis-sa olisi luoda ohjelman luokkahierarkiaan varsinaisten rajapintaluok-kien lisaksi myos kaikki naiden yhdistelmat ja merkita erikseen hie-rarkiaan, mitka luokat toteuttavat myos nama yhdistelmat. Tama kui-tenkin tekisi luokkahierarkioista erittain sotkuisia, ja yhdistelmienmaara rajahtaisi helposti todella suureksi. Naissa kielissa ei olekaanmitaan eleganttia ratkaisua tahan ongelmaan. Smalltalkissa puolestaantata ongelmaa ei tule, koska siina ei ole tarvetta rajapintaluokille lai-sinkaan. Toisaalta tama taas johtuu siita, etta koko kieli ei tunne ra-japinnan kasitetta, koska kaikki tyyppitarkastukset tehdaan kielessavasta ajoaikana.

6.10 Periytyminen vai kooste?

Milloin luokkien valilla on olemassa periytymissuhde ja milloin onkyse koosteesta? Koosteessa on kyse kahden tai useamman olion muo-dostamasta kokonaisuudesta ja periytymisessa taas yhdesta oliosta,jonka luokkarakenne koostuu usean luokan ominaisuuksista. Vaik-ka kyse on kahdesta hyvin erilaisesta asiasta, niin suunnittelussa onusein vaikeata tehda valintaa koosteen ja periytymisen valilla.

Perinteinen esimerkki oliosuunnittelussa on ihmisten (kohtuuton)yksinkertaistaminen mallintamalla heidan ominaisuuksiaan erillisi-na luokkina: insinoori, isa, viulisti ja niin edelleen. (Ei attribuutteina,silla nama ominaisuudet itsessaan ovat mutkikkaita, ja siten luokanarvoisia.) Halutessamme mallin Sibelius-akatemiasta ja TTY:lta val-mistuneesta ohjelmistosuunnittelijasta voimme katsoa hanella ole-van seka Insinoorin ja Viulistin ominaisuudet, joten periytyminennaista luokista olisi perusteltua. Mutta mitapa silloin kun han muu-taman vuoden kuluttua saa lapsia ja palkanmaksujarjestelmamme pi-taisi liittaa haneen myos isaa kuvaavan luokan ominaisuudet? Nytjoudummekin tekemaan hanta kuvaamaan kokonaan uuden olion,jonka rakenne on periytetty luokista Insinoori, Viulisti ja Isa. Mi-ta enemman erilaisia vaihtoehtoja mallinnamme, sita enemman jou-

Page 199: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

6.11. Sovelluskehykset 199

dumme tekemaan eri tavoin periytettyja luokkia ja ohjelmassa vaih-tamaan ihmisia kuvaavia olioita heidan elamantilanteidensa muut-tuessa. Selvasti parempi vaihtoehto tassa dynaamisessa tilanteessaon liittaa tarvittava maara ihmiseen liittyvia “ominaisuuksia” koos-teella. Kuva 6.12 esittaa molemmat mahdollisuudet.

Periytymisen “nyrkkisaannoiksi” suositellaan: [Meyer, 1997]

1. Periyta luokka B luokasta A vain silloin, kun pystyt perustele-maan, etta luokan B oliot voidaan aina nahda myos luokan Aolioina.

2. Ala kayta periytymista, jos periytymisella saatu olion rakennevoi muuttua ohjelman ajoaikana (kayta talloin koostetta).

6.11 Sovelluskehykset

Ohjelmistojen toteutuksessa voidaan hyodyntaa aiemmissa projek-teissa tehtyja tai projektin ulkopuolelta ostettuja ohjelmakomponent-

VÄÄRIN!IsäInsinööri Viulisti

Ohjelmistosuunnittelija

Insinööri Viulisti Isä

Ohjelmistosuunnittelija

0..1 0..1 0..1

KUVA 6.12: Insinoori, viulisti ja isa

Page 200: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

6.11. Sovelluskehykset 200

teja. Nama komponentit ovat usein luokkakirjastoja, joiden avullasaadaan kayttoon valmiita olioita. Ohjelmiston toiminnallinen logiik-ka eli olioiden valinen yhteistyo (kommunikointi) on edelleen taysintoteuttajan vastuulla ja hallinnassa.

Ohjelmistoa voidaan ryhtya rakentamaan myos siten, etta valmiskomponentti maarittelee etukateen myos toiminnallisuuden kannal-ta olioiden kommunikointiin liittyvia vaatimuksia. Tallaista raken-netta sanotaan sovelluskehykseksi (framework). Olio-ohjelmoinnis-sa sovelluskehykset ovat luokkakirjastoja, joista on tarkoitus periyt-tamalla erikoistaa oman sovelluksen toteuttavat luokkien versiot. Ku-vassa 6.13 nakyy ero sovelluskehysohjelman rakenteessa verrattuna“perinteiseen” aliohjelmakirjastorakenteeseen. Kirjastorutiineja kut-sutaan toteutetusta sovelluksesta, jolla on vastuu kokonaiskontrol-lista. Sovelluskehyksessa kehys on maaritellyt perusrakenteen, jonkaosaset kutsuvat kehyksen maarittelemissa tapahtumissa sovelluksenoperaatioita (jotka on toteutettu esimerkiksi kehyksen koodista periy-tetyissa luokissa).

Sovelluskehys tavallaan maarittelee koko sovelluksen “tunnel-man” tai arkkitehtuurin, jonka puitteissa ohjelmoijan on toimitta-va. Ikkunointijarjestelman sovelluskehys voi maaritella tarkasti, mi-ta piirto-operaatioita ja tapahtumatietoja ohjelmoijan on toteutettava.Jarjestelma voi tarjota vaikkapa “perusikkunan”, jonka luokasta pe-riyttamalla toteutetaan varsinainen oma sovellus.

Sovellus

Kirjasto

(a) Aliohjelma-kirjasto

Sovellus

Sovelluskehys

(b) Sovelluskehys

KUVA 6.13: Aliohjelmakirjasto ja sovelluskehys

Page 201: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

201

Luku 7

Lisaa olioiden elinkaaresta

— Keita ovat he? han kysyi. — Nimenomaan kenen luuletkoettavan murhata sinut?– Jokaisen heista, Jossarian sanoi hanelle.. . . Clevinger luuli olevansa oikeassa, mutta Jossarianillaoli todisteita, silla vieraat ihmiset, joita han ei tuntenut,ampuivat hanta tykeilla joka kerta kun han oli noussut il-maan pudottamaan pommeja heidan niskaansa, eika seollut hauskaa.

– CATCH-22 [Heller, 1961]

Tavallisia perustietotyyppeja kaytettaessa tulee usein vastaan tilanne,jossa muuttujasta halutaan tehda kopio tai vastaavasti yhden muuttu-jan arvo halutaan sijoittaa toiseen. Sama tilanne toistuu joskus olioi-den kanssa, varsinkin jos oliot edustavat abstrakteja tietotyyppeja,kuten paivayksia tai kompleksilukuja. Olio-ohjelmoinnissa sijoituk-sen ja kopioinnin merkitys ei kuitenkaan ole yhta selva kuin perintei-sessa ohjelmoinnissa, joten niita on syyta kasitella tarkemmin.

C++:ssa varsinkin kopioimisen merkitys korostuu entisestaan, kos-ka kaantaja itse tarvitsee olioiden kopiointia esimerkiksi valittaessaanolioita tavallisina arvoparametreina tai palauttaessaan niita paluuar-voina. Koska olioiden kopioiminen ja sijoitus saattaa olla hyvin ras-kas operaatio, on tarkeata etta ohjelmoija tietaa, mita kaikkea niihinliittyy ja missa tapauksissa kopioiminen on automaattista.

Page 202: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

7.1. Olioiden kopiointi 202

7.1 Olioiden kopiointi

Olioiden kopiointia tarvitaan useisiin eri tarkoituksiin. Joskus olios-ta tulee tarve tehda varmuuskopio, joskus taas taulukkoon halutaantallettaa itsenainen kopio oliosta, ei pelkastaan viitetta alkuperaiseenolioon. Olio-ohjelmoinnin kannalta oleellinen kysymys kopioinnissakuitenkin on: “Mika oikein on kopio?”

Perustyyppien tapauksessa kopion maaritteleminen ei ole vaike-aa, koska kopio voidaan luoda yksinkertaisesti luomalla uusi muut-tuja ja kopioimalla vanhan muuttujan muisti uuteen bitti kerrallaan.Olioiden tapauksessa tama ei kuitenkaan riita, koska olion tilaan voikuulua paljon muutakin kuin olion jasenmuuttujat.

Yksi tapa maaritella olioiden kopiointi olisi sanoa, etta uudenolion tulee olla identtinen vanhan kanssa. Tamakin maaritelma tuot-taa kuitenkin ongelmia: jos kyseessa kerran ovat taysin identtisetoliot, miten ne voi erottaa toisistaan, toisin sanoen miten talloin tie-detaan, etta kyseessa todella on kaksi eri oliota? Jos oliot olisivat tay-sin identtiset, pitaisi periaatteessa toisen muuttamisen muuttaa myostoista, mika tuskin on yleensa toivottavaa.

Parempi tapa maaritella kopiointi on sanoa, etta oliota kopioitaes-sa uuden olion ja vanhan olion arvojen tai tilojen taytyy olla samat.Tassa tilalla ei tarkoiteta yksinkertaisesti jasenmuuttujia vaan oliontilaa korkeammalla abstraktiotasolla ajatellen. Talla tavoin ajateltunaesimerkiksi paivaysolion tila on se paivays, jonka se sisaltaa. Vastaa-vasti merkkijono-olion tila on sen sisaltama teksti ja dialogi-ikkunantila sisaltaa myos ruudulla nakyvan ikkunan. Nain ajateltuna oliontilan kasite ei enaa riipu sen sisaisesta toteutuksesta vaan siita, mitaolio ohjelmassa edustaa.

Edella esitetty olioiden kopioinnin maaritelma tarkoittaa, ettaeri tyyppisia olioita kopioidaan hyvin eri tavalla. Kompleksilukuo-lion voi kenties kopioida yksinkertaisesti muistia kopioimalla, kuntaas merkkijonon kopiointi saattaa toteutuksesta riippuen vaatia yli-maaraista muistinvarausta olion ulkopuolelta ja muita toimenpitei-ta. Nain kaantaja ei pysty automaattisesti kopioimaan olioita, vaanluokan tekijan taytyy itse maaritella, mita kaikkea oliota kopioitaessataytyy tehda.

Kaikkia olioita ei edes ole jarkevaa kopioida. Esimerkiksi hissinmoottoria ohjaavan olion kopioiminen on todennakoisesti jarjetontoimenpide, koska se vaatisi periaatteessa oliomallinnuksen mukai-

Page 203: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

7.1. Olioiden kopiointi 203

sesti myos itse fyysisen moottorin kopioimista. Taman vuoksi olisihyva, jos luokan kirjoittaja voisi myos halutessaan estaa kyseisen luo-kan olioiden kopioinnin kokonaan.

7.1.1 Erilaiset kopiointitavat

Kirjallisuudessa jaotellaan erilaiset olioiden kopiointitavat usein kol-meen kategoriaan: viitekopiointiin, matalakopiointiin ja syvako-piointiin. Tavallisesti olioiden kopiointi onkin mielekasta tehda jol-lain edella mainituista tavoista, mutta todellisuus on jalleen kerranteoriaa ihmeellisempaa. Joskus saattaa tulla tarve kopioida osia olios-ta yhdella tavalla ja toisia osia toisella. Perusperiaatteiltaan tama jaot-telu on kuitenkin jarkeva, joten tassa aliluvussa kaydaan lapi kaikkikolme kopiointitapaa.

Viitekopiointi

Viitekopiointi (reference copy) on kaikkein helpoin kopiointitavoista.Siina ei yksinkertaisesti luoda ollenkaan uutta oliota, vaan “uutenaoliona” kaytetaan viitetta vanhaan olioon. Kuva 7.1 havainnollistaatata. Kuvan tilanteessa viitteen B paahan “kopioidaan” olio A.

Viitekopiointia kaytetaan erityisesti oliokielissa, jossa itse muut-tujat ovat aina vain viitteita olioihin, jotka puolestaan luodaan dy-

Olion dataa

Olio

3

A

(a) Ennen kopiointia

Olion dataa

B

Olio

3

A

(b) Kopioinnin jalkeen

KUVA 7.1: Viitekopiointi

Page 204: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

7.1. Olioiden kopiointi 204

naamisesti. Tallaisia kielia ovat esimerkiksi Java ja Smalltalk. Kun nais-sa kielissa luodaan uusi muuttuja ja alustetaan se olemassa olevallamuuttujalla, viittaavat molemmat muuttujat taman jalkeen samaanolioon. C++:ssa sen sijaan viitekopiointia kaytetaan vain, kun erikseenluodaan viitteita olioiden sijaan.

Viitekopioinnin hyvana puolena on sen nopeus. “Kopion” luomi-nen ei kaytannossa vaadi ollenkaan aikaa, koska mitaan kopioimistaei tarvitse tehda. Viitekopiointi toimiikin hyvin niin kauan, kun olionarvoa ei muuteta. Mikali sen sijaan oliota muutetaan toisen muuttu-jan kautta, vaikuttaa muutos tietysti myos toiseen muuttujaan. Tallai-nen kayttaytyminen on varsin haitallista, koska yksi tarkea kopioin-nin syy on tehda oliosta “varmuuskopio”, joka sailyttaa arvonsa.

Matalakopiointi

Matalakopioinnissa (shallow copy) itse oliosta ja sen jasenmuuttujis-ta tehdaan kopiot. Jos kuitenkin jasenmuuttujina on viitteita tai osoit-timia olion ulkopuolisiin tietorakenteisiin, ei naita tietorakenteita ko-pioida vaan matalakopioinnin lopputuloksena molemmat oliot jaka-vat samat tietorakenteet. Kuva 7.2 havainnollistaa matalakopioinninvaikutuksia.

Ohjelmointikielten toteutuksen kannalta matalakopiointi on sel-kea operaatio, koska siina kopioidaan aina kaikki olion jasenmuuttu-jat eika mitaan muuta. Tasta syysta esimerkiksi C++ kayttaa oletusar-

Olion dataa

Olio

3

A

(a) Ennen kopiointia

Olion dataa

B

Olio Olio

3 3

A

(b) Kopioinnin jalkeen

KUVA 7.2: Matalakopiointi

Page 205: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

7.1. Olioiden kopiointi 205

voisesti matalakopiointia, jos luokan kirjoittaja ei muuta maaraa. Ko-pioinnin tuloksena on ainakin paallisin puolin kaksi oliota, ja aikamonessa tapauksessa matalakopiointi onkin hyvaksyttava menetel-ma. Yleensa viitekopiointia kayttavissa oliokielissa on myos jokin ta-pa matalakopiointiin. Esimerkiksi Javassa jokaisesta luokasta loytyyjasenfunktio clone, joka oletusarvoisesti tuottaa oliosta matalakopioi-dun kopion. Samoin Smalltalk:n olioita voi pyytaa suorittamaan toi-minnon copy, joka tekee matalakopioinnin.

Ongelmaksi matalakopioinnissa muodostuu, etta usein osa oliontilaan kuuluvasta tiedosta sijaitsee olion ulkopuolella viitteiden jaosoittimien paassa. Jotta kopioituun olioon tehdyt muutokset eivatheijastuisi alkuperaiseen olioon ja painvastoin, taytyisi myos namaolion ulkopuoliset tietorakenteet kopioida. C++:ssa kaikki olion dynaa-misesti luomat oliot ja tietorakenteet sijaitsevat aina olion ulkopuo-lella, joten matalakopiointi ei yksinkertaisuudestaan huolimatta oleriittava laheskaan kaikille luokille.

Syvakopiointi

Syvakopiointi (deep copy) on kopiointimenetelma, jossa olion ja senjasenmuuttujien lisaksi kopioidaan myos ne olion tilaan kuuluvatoliot ja tietorakenteet, jotka sijaitsevat olion ulkopuolella. Tama na-kyy kuvassa 7.3.

Olion dataa

Olio

3

A

(a) Ennen kopiointia

Olion dataa Olion dataa

B

Olio Olio

3 3

A

(b) Kopioinnin jalkeen

KUVA 7.3: Syvakopiointi

Page 206: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

7.1. Olioiden kopiointi 206

Olioiden kannalta syvakopiointi on ehdottomasti paras kopiointi-tapa, koska siina luodaan kopio kaikista olion tilaan kuuluvista asiois-ta. Nain kopioinnin jalkeen uusi olio ja alkuperainen olio ovat taysinerilliset. Ohjelmointikielen kannalta syvakopiointi on kuitenkin on-gelmallinen. Varsin tyypillisesti oliot sisaltavat osoittimia myos sel-laisiin olioihin ja tietorakenteisiin, jotka eivat varsinaisesti ole osaolion tilaa ja joita ei tulisi kopioida. Esimerkiksi kirjaston kirjat saat-taisivat sisaltaa osoittimen siihen kirjastoon, josta ne on lainattu. Kir-jan tietojen kopioiminen ei kuitenkaan saisi aiheuttaa koko kirjastonkopioimista.

Kaantajan kannalta tilanne on ongelmallinen, koska useimmis-sa ohjelmointikielissa ei ole mitaan tapaa ilmaista, mitka osoittimetosoittavat olion tilaa sisaltaviin tietoihin ja mitka osoittavat olion ul-kopuolisiin asioihin. Taman vuoksi lahes mikaan ohjelmointikieli eiautomaattisesti tue syvakopiointia. Poikkeuksen tekee Smalltalk, jossaoliolta loytyy myos palvelu deepCopy.

Yleensa oliokielissa annetaan ohjelmoijalle itselleen mahdolli-suus kirjoittaa syvakopioinnille toteutus, jota kieli sitten osaa auto-maattisesti kayttaa. C++:ssa ohjelmoija kirjoittaa luokalle kopioraken-tajan, joka suorittaa kopioinnin ohjelmoijan sopivaksi katsomalla ta-valla. Samoin Javassa luokan kirjoittaja voi toteuttaa luokalle omanclone-jasenfunktion, joka matalakopioinnin sijaan suorittaa sopivan-laisen syvakopioinnin. Nain kaantaja tarvitsee syvakopioinnin toteu-tukseen apua luokan kirjoittajalta.

7.1.2 C++: Kopiorakentaja

C++:ssa olio kopioidaan kayttamalla kopiorakentajaa (copy construc-tor). Kopiorakentaja on rakentaja, joka ottaa parametrinaan viitteentoiseen samantyyppiseen olioon. Ideana on, etta kun oliosta halutaantehda kopio, tama olio luodaan kopiorakentajaa kayttaen. Talloin ko-piorakentaja voi alustaa uuden olion niin, etta se on kopio paramet-rina annetusta alkuperaisesta oliosta. Kaantaja kayttaa itsekin auto-maattisesti kopiorakentajaa olion kopioimiseen tietyissa tilanteissa,joita kasitellaan tarkemmin aliluvussa 7.3.

Listaus 7.1 seuraavalla sivulla nayttaa osan yksinkertaisen merk-kijonoluokan esittelysta ja sen kopiorakentajan toteutuksen. Kopiora-kentaja alustaa uuden merkkijonon koon suoraan alkuperaisen merk-kijonon vastaavasta jasenmuuttujasta. Sen jalkeen se varaa tarvittaes-

Page 207: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

7.1. Olioiden kopiointi 207

sa tilaa uuden merkkijonon merkeille ja kopioi ne yksi kerrallaan van-hasta. Nain kopiorakentaja ei orjallisesti kopioi jasenmuuttujia, vaanmerkkijono-olion “syvimman olemuksen” eli itse merkit.

Periytyminen ja kopiorakentaja

Periytyminen tuo omat lisansa kopion luomiseen. Aliluokan oliokoostuu useista osista, ja kantaluokan osilla on jo omat kopioraken-tajansa, joilla kopion kantaluokkaosat saadaan alustetuksi. Aliluokanolion kopioiminen onkin jaettu eri luokkien kesken samoin kuin ra-kentajat yleensa: aliluokan kopiorakentajan vastuulla on kutsua kan-taluokan kopiorakentajaa ja lisaksi alustaa aliluokan osa olioista ko-pioksi alkuperaisesta. Listaus 7.2 seuraavalla sivulla nayttaa esimer-kin paivatysta merkkijonoluokasta, joka on periytetty luokasta Mjono.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . mjono.hh . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .1 class Mjono2 3 public:4 Mjono(char const* merkit);5 Mjono(Mjono const& vanha); // Kopiorakentaja6 virtual ~Mjono();

...11 private:12 unsigned long koko ;13 char* merkit ;14 ;

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . mjono.cc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .1 Mjono::Mjono(Mjono const& vanha) : koko (vanha.koko ), merkit (0)2 3 if (koko != 0)4 // Varaa tilaa, jos koko ei ole nolla5 merkit = new char[koko + 1];6 for (unsigned long i = 0; i != koko ; ++i)7 merkit [i] = vanha.merkit [i]; // Kopioi merkit8 merkit [koko ] = ’\0’; // Loppumerkki9

10

LISTAUS 7.1: Esimerkki kopiorakentajasta

Page 208: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

7.1. Olioiden kopiointi 208

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . pmjono.hh . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .1 class PaivattyMjono : public Mjono2 3 public:4 PaivattyMjono(char const* merkit, Paivays const& paivays);5 PaivattyMjono(PaivattyMjono const& vanha); // Kopiorakentaja67 virtual ~PaivattyMjono();

...15 private:16 Paivays paivays ;17 ;

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . pmjono.cc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .1 // Olettaa, etta Paivays-luokalla on kopiorakentaja2 PaivattyMjono::PaivattyMjono(PaivattyMjono const& vanha)3 : Mjono(vanha), paivays (vanha.paivays )4 5

LISTAUS 7.2: Kopiorakentaja aliluokassa

On huomattava, etta jos aliluokan kopiorakentajassa unohdetaankutsua kantaluokan kopiorakentajaa, patevat normaalit C++:n saan-not, jotka sanovat etta talloin kutsutaan kantaluokan oletusraken-tajaa. Oletusrakentaja puolestaan alustaa “kopion” kantaluokkaosanoletusarvoonsa, eika olio nain kopioidu kunnolla! Jotkin kaantajat an-tavat varoituksen, kun ne joutuvat kutsumaan kopiorakentajasta kan-taluokan oletusrakentajaa, mutta itse C++-standardi ei vaadi tallaistavaroitusta. Jos kantaluokalla ei ole oletusrakentajaa, ongelmia ei tule,koska talloin kaantaja antaa virheilmoituksen, jos kantaluokan raken-tajan kutsu unohtuu aliluokan kopiorakentajasta.

Kaantajan luoma oletusarvoinen kopiorakentaja

Jos luokalle ei ole maaritelty kopiorakentajaa, kaantaja olettaa luokanolevan niin yksinkertainen, etta voidaan kayttaa matalakopiointiaeli kopioiminen voidaan suorittaa alustamalla kopion jasenmuuttu-jat alkuperaisen olion jasenmuuttujista. Niinpa kaantaja kirjoittaa au-tomaattisesti luokkaan tallaisen oletusarvoisen kopiorakentajan, josluokasta ei loydy omaa kopiorakentajaa. Esimerkiksi aiemmin tas-sa teoksessa esiintyneella Paivays-luokalla ei ollut kopiorakentajaa,

Page 209: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

7.1. Olioiden kopiointi 209

mutta listauksen 7.2 rivilla 3 voidaan alustuslistassa silti luoda kopiopaivaysoliosta juuri tata oletusarvoista kopiorakentajaa kayttaen.

Periaatteessa oletusarvoisen kopiorakentajan olemassaolo helpot-taa yksinkertaisten ohjelmien tekemista, koska aloittelevan ohjelmoi-jan ei tarvitse miettia olioiden kopioimista. Kaytanto kuitenkin osoit-taa, etta noin 90 %:ssa tosielaman olioista kopiointiin liittyy muuta-kin kuin jasenmuuttujien kopiointi. Erityisesti tama tulee esille, josluokassa on dataa osoittimien paassa, kuten listauksen 7.1 merkki-jonoluokassa, jossa oletusarvoinen kopiorakentaja aiheuttaisi ohjel-maan vakavia toimintavirheita. Taman vuoksi jokaiseen luokkaantulisi erikseen kirjoittaa kopiorakentaja eika luottaa oletusarvoi-sen kopiorakentajan toimintaan.

Kopioinnin estaminen

Kaantajan automaattisesti kirjoittamasta oletusarvoisesta kopioraken-tajasta on haittaa, jos luokan olioita ei ole jarkevaa kopioida. Talloin-han luokan kirjoittaja ei halua kirjoittaa omaa kopiorakentajaansa,mutta kaantaja siita huolimatta tarjoaa oletusarvoisen kopiorakenta-jan. Kopioinnin estaminen vaatiikin luokan kirjoittajalta lisakikkoja.

Kopioinnin estaminen tapahtuu esittelemalla kopiorakentaja luo-kan private-puolella. Talloin luokan ulkopuolinen koodi ei paase kut-sumaan kopiorakentajaa eika nain ollen kopioimaan olioita. Koska ko-piorakentaja on kuitenkin esitelty, kaantaja ei yrita tuputtaa oletusar-voista kopiorakentajaa. Nain kaikki ulkopuoliset kopiointiyrityksetaiheuttavat kaannosvirheen.

Edella esitetty kikka kuitenkin ratkaisee vasta puolet ongelmasta.Luokan omat jasenfunktiot paasevat nimittain tietysti kasiksi private-osaan ja voivat nain kutsua kopiorakentajaa. Tama estetaan silla, ettakopiorakentajan esittelysta huolimatta kopiorakentajalle ei kirjoitetaollenkaan toteutusta. Jos nyt luokan oma koodi yrittaa kopioida luo-kan olioita, linkkeri huomaa objektitiedostojen linkitysvaiheessa, et-ta kopiorakentajalle ei loydy koodia ja aiheuttaa virheilmoituksen. Eriasia sitten on, kuinka helppo linkkerin virheilmoituksesta on paatel-la, mika on mennyt vikaan ja missa tiedostossa olevasta koodista vikaaiheutuu.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .Kopioinnin — ja myohemmin sijoituksen — estamisen vaikeus C++:ssa on yksi kielen

suurimpia munauksia, ja estamiseen kaytettava kikka vastaavasti yksi rumimpia tekniikoita,joihin jopa tunnollinen ohjelmoija joutuu turvautumaan.

Page 210: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

7.1. Olioiden kopiointi 210

Vaikka ylla esitetty kikka onkin esteettisyydeltaan kyseenalainen,on se ainoa tapa estaa olioiden kopioiminen. Sita tulisikin kayttaaaina, kun luokan olioiden kopioiminen ei ole mielekasta.

7.1.3 Kopiointi ja viipaloituminen

Periytyminen tarkoittaa, etta jokainen aliluokan olio on myos kanta-luokan olio. Ikava kylla, tama suhde ei ole ihan niin helppo ja yksin-kertainen, kuin mita voisi toivoa. Esimerkiksi olioiden kopioiminentuottaa tietyissa tapauksissa ongelmia.

Kun C++:ssa luodaan kopiorakentajalla oliosta kopio, taytyy kopio-ta luovan ohjelmanosan tietaa, minka luokan oliota ollaan luomas-sa. Saman voi sanoa myos niin, etta C++:ssa ei ole suoraan mitaan ta-paa luoda oliota ilman, etta kaannosaikana tiedetaan sen tyyppi (tamakoskee seka kopiointia etta muutakin olion luomista). Tama vaatimustulee siita, etta kaantajan on kaannosaikana osattava varata jokaiselleoliolle riittavasti muistia, kutsua oikeaa rakentajaa yms.

Tama rajoitus tarkoittaa, etta kantaluokkaosoittimen tai -viitteenpaassa olevasta oliosta ei voi luoda kunnollista kopiota, ellei osoitti-men paassa olevan olion tyypista voida olla varmoja jo kaannosaika-na. Kuten luvussa 6 todettiin, periytymista kaytettaessa on varsin ta-vallista, etta kantaluokkaosoittimen paassa voi olla kantaluokkaolionlisaksi minka tahansa periytetyn luokan olio.

Ongelmalliseksi tilanteen tekee se, etta kaantajan kannalta jokai-nen aliluokan olio on myos kantaluokan olio. Niinpa aliluokan oliokelpaa parametriksi kantaluokan kopiorakentajalle, koska se ottaa pa-rametrinaan viitteen kantaluokkaan. Niinpa koodissa

PaivattyMjono pmj("Viipaloidun", jokupaivays);Mjono mj(pmj);

kutsutaan oliota mj luotaessa Mjono-luokan kopiorakentajaa ja silleannetaan viite pmj:hin parametrina. Kopiorakentajan koodissa tamaparametri nayttaa tavalliselta Mjono-oliolta, joten kopiorakentaja luokopion muuttujan pmj kantaluokkaosasta! Sen sijaan aliluokan lisaa-maan paivaykseen ei kosketa lainkaan, koska Mjono-luokan kopiora-kentaja ei ole siita kuullutkaan! Kuva 7.4 seuraavalla sivulla havain-nollistaa tilannetta. Koodia katsottaessa on tietysti selvaa, ettei mj voiolla kopio pmj:sta, koska se ei edes ole oikeaa tyyppia. Koodi kuiten-kin kelpaa kaantajalle, koska se noudattaa periytymisen saantoja.

Page 211: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

7.1. Olioiden kopiointi 211

Mjono−osa

koko

merkit

PaivattyMjono−osa

paivays

Mjono−osa

koko

merkit

pmj mj

Viipaloidun Viipaloidun

Mjono−luokankopiorakentaja

Viipaloituminen!

KUVA 7.4: Viipaloituminen olion kopioinnissa

Tata ilmiota, jossa oliota kopioitaessa kopioidaankin erehdyksessavain olion kantaluokkaosa, kutsutaan nimella viipaloituminen (slic-ing) [Budd, 2002, luku 12]. Viipaloituminen esiintyy paitsi kopioimi-sessa myos sijoittamisessa (josta tarkemmin aliluvussa 7.2.3). Ilmioosoittaa, kuinka ongelmallinen “aliluokan olio on kantaluokan olio”-suhde voi olla — aliluokan olion tulee olla kuin kantaluokan olio,paitsi etta siita ei pitaisi voida tehda kantaluokkakopiota.

Viipaloitumisen esiintyminen

Viipaloituminen on C++:ssa vaarana kulissien takana monessa tilan-teessa. Esimerkiksi allaolevassa koodissa tapahtuu viipaloituminen:

vector<Mjono> mjonovektori;PaivattyMjono pmj("Metsaan mennaan", tanaan);mjonovektori.push back(pmj);

Koodissa vektorin loppuun lisataan kopio push back:lle annetustaoliosta. Mjono-vektori voi sisaltaa vain Mjono-olioita, joten vektoriinlisataan viipaloitunut kopio pmj:n kantaluokkaosasta. Taman vuok-si C++:ssa periytymista ja polymorfismia kaytettaessa vektoreihin jamuihin tietorakenteisiin tulee aina tallettaa osoittimia olioihin, ei itse

Page 212: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

7.1. Olioiden kopiointi 212

olioita. Viitteet puolestaan eivat kelpaa STL:n tietorakenteiden kutenvektorin alkioiksi. Tata kasitellaan aliluvussa 10.2 sivulla 311.

C++ kopioi olioita automaattisesti myos muissa tilanteissa, ja sil-loinkin viipaloituminen on vaarana. Tallaisia tilanteita ovat olioidenvalittaminen arvoparametreina ja paluuarvoina, joista kerrotaan ali-luvussa 7.3. Naissakin tilanteissa ongelma ilmenee, kun parametri taipaluuarvo on kantaluokkatyyppia, mutta ohjelmassa valitetaan ali-luokan olioita. Talloin aliluokan oliosta valittyy vain viipaloitunutkantaluokkaosan kopio.

Viipaloituminen ja muut oliokielet

Kopioitumisen yhteydessa viipaloituminen on oliokielissa lahes yksi-nomaan C++:n ongelma. Tama saattaa vaikuttaa yllattavalta — aiheu-tuuhan viipaloituminen periytymisesta, joka toimii samalla tavallalahes kaikissa oliokielissa. Syy ongelman C++-keskeisyyteen juontaajuurensa taman aliluvun alussa mainitusta C++:n vaatimuksesta, ettaolion (myos kopion) tyyppi taytyy tietaa oliota luotaessa.

Suurimmassa osassa muita oliokielia olioita kasitellaan aina C++:nosoittimia muistuttavien mekanismien kautta (joita naissa kielissausein kutsutaan viitteiksi). Nain tehdaan muuan muassa Javassa jaSmalltalkissa. Naissa kielissa millaan olioilla ei ole esimerkiksi omaanimea lainkaan, vaan niihin viitataan aina nimetyn olioviitteen kaut-ta. Tahan liittyy myos se luvussa 3 mainittu asia, etta olioidenelinkaari ei ole staattisesti maaratty, vaan roskienkeruu pitaa huo-len olioiden tuhoamisesta. C++:n nakokulmasta voitaisiin sanoa, ettaJavassa ja Smalltalkissa oliot luodaan aina dynaamisesti new’ta vastaa-valla mekanismilla.

Aliluvussa 7.1.1 todettiin, etta Javassa ja Smalltalkissa oliot kopioi-daan kutsumalla kopioitavan olion kopiointipalvelua (Javan clone,Smalltalkin copy tai deepCopy). Yllattavaa kylla, tama seikka yhdistet-tyna siihen, etta olioita kasitellaan aina viitteiden kautta, estaa vii-palointiongelman syntymisen kopioinnin yhteydessa. Kun nimittainolio itse suorittaa kopioinnin, se pystyy luomaan oikeantyyppisenkopio-olion. Olio itse tietaa oman todellisen tyyppinsa, joten se voiluoda oikeantyyppisen kopion itsestaan, vaikka kopiointikutsu teh-taisiinkin kantaluokkaviitteen kautta.

Java ja Smalltalk eivat lisaksi tunne olioiden valittamista arvopara-metreina tai palauttamista paluuarvoina, vaan parametrit ja paluuar-

Page 213: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

7.1. Olioiden kopiointi 213

vot ovat aina olioviitteita (osoittimia). Samoin tietorakenteisiin talle-tetaan aina olioviitteita, koska muuta vaihtoehtoa ei kielissa ole. Niin-pa kopiointiviipaloitumista ei naissa kielissa paase syntymaan muu-allakaan. Sen sijaan viipaloituminen saattaa kylla aiheuttaa ongelmiamuualla, esimerkiksi sijoituksessa (tata kasitellaan aliluvussa 7.2.3).

Viipaloitumisen kiertaminen C++:ssa

Viipaloituminen ei muodostu C++:ssa ongelmaksi kovinkaan usein,koska varsin usein olioita kuljetetaan ohjelmassa osoittimia ja viittei-ta kayttaen ilman, etta niita taytyy missaan vaiheessa kopioida. Ko-piointitapauksissakin tiedetaan usein jo ohjelmointivaiheessa oliontodellinen tyyppi, eika viipaloitumista paase tapahtumaan.

Aina silloin talloin ohjelmissa tulee kuitenkin vastaan tilanne,jossa kantaluokkaosoittimen paassa oleva olio pitaisi pystya kopioi-maan, eika oliosta tiedeta tarkasti, mihin luokkaan se kuuluu (paitsietta se on joko kantaluokan tai jonkin siita periytetyn luokan olio).Talloin ongelman voi ratkaista matkimalla muiden oliokielten ko-piointitapaa ja antamalla kopioitavan olion kloonata itsensa. Kirjoi-tetaan kantaluokkaan virtuaalifunktio kloonaa, joka palauttaa osoitti-men dynaamisesti luotuun kopioon oliosta. Tama kloonaa-jasenfunk-tio sitten toteutetaan jokaisessa hierarkian luokassa niin, etta se luonew’lla kopion itsestaan kopiorakentajaa kayttaen.

Listaus 7.3 seuraavalla sivulla nayttaa esimerkin tallaisesta kloo-naamisesta. Viipaloitumista ei paase tapahtumaan, koska dynaami-nen sitominen pitaa huolen siita, etta oliolle kutsutaan aina sen omaakloonaa-toteutusta, vaikka itse kutsu olisikin tapahtunut kantaluok-kaosoittimen tai -viitteen kautta. Haittana tassa kopiointitavassa onse, etta jokainen kopio luodaan dynaamisesti. Tasta johtuen kopio-ta ei tuhota automaattisesti, vaan kopioijan tulee itse muistaa tuhotakopio deletella, kun sita ei enaa tarvita. Lisaksi tyypillisesti olioidendynaaminen luominen vie hieman enemman muistia ja on vahan hi-taampaa kuin staattinen. Tasta ei kuitenkaan yleensa aiheudu kay-tannossa tehokkuushaittaa.

Ikavaa kloonausratkaisussa on myos se, etta jokaisen periytetynluokan on muistettava itse toteuttaa kloonaa-jasenfunktio. Jos ta-ma unohtuu, periytyy kantaluokan toteutus aliluokkaan, ja viipaloi-

Page 214: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

7.1. Olioiden kopiointi 214

1 class Mjono2 3 public:

10 Mjono(Mjono const& m);11 virtual Mjono* kloonaa() const;

...12 ;

...13 Mjono* Mjono::kloonaa() const14 15 return new Mjono(*this);16

...17 class PaivattyMjono : public Mjono18 19 public:21 PaivattyMjono(PaivattyMjono const& m);22 virtual PaivattyMjono* kloonaa() const;

...23 ;

...24 PaivattyMjono* PaivattyMjono::kloonaa() const25 26 return new PaivattyMjono(*this);27

...28 void kaytakopiota(Mjono const& mj)29 30 Mjono* kopiop = mj.kloonaa(); // Tulos voi olla periytetyn kopio31 // Taalla sitten kaytetaan kopiota32 delete kopiop; kopiop = 0; // Pitaa muistaa myos tuhota33

LISTAUS 7.3: Viipaloitumisen kiertaminen kloonaa-jasenfunktiolla

Page 215: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

7.2. Olioiden sijoittaminen 215

tuminen paasee jalleen tapahtumaan.] Abstrakteissa kantaluokissakloonaa-jasenfunktiota ei voi toteuttaa, koska abstraktista kantaluo-kasta ei voi luoda olioita (eika nain ollen myoskaan kopiota). Tallai-sessa kantaluokassa kloonaa esitellaankin puhtaana virtuaalifunktio-na, jolloin sen toteuttaminen jaa konkreettisten aliluokkien vastuulle.

Listauksessa 7.3 saattaa ihmetyttaa se, etta Mjono-luokassakloonaa-funktiosta palautetaan Mjono-osoitin, kun taas periytetys-sa luokassa jasenfunktion paluutyyppi onkin PaivattyMjono-osoitin.Tassa on kyseessa aliluvussa 6.5 mainittu paluutyypin kovarianssi.Se mahdollistaa sen, etta kun kloonausta kutsutaan jonkin tyyppisenosoittimen kautta, saadaan paluuarvona samantyyppinen osoitin ko-pioon.

Kloonausfunktio antaa mahdollisuuden kopiointiin ilman viipa-loitumista. Viipaloituminen on kuitenkin edelleen vaarana aiemminkasitellyissa tietorakenteissa, parametrinvalityksessa ja paluuarvois-sa. Naihin vaaroihin paras apu on huolellinen suunnittelu ja ongel-man tiedostaminen. Yksi mahdollinen ratkaisu on myos suunnitellaohjelman luokkahierarkiat niin, etta kaikki kantaluokat ovat abstrak-teja. Talloin viipaloitumista ei paase syntymaan, koska virheellistakantaluokkakopiota on mahdotonta tehda (abstrakteista kantaluokis-ta ei saa tehda olioita) [Meyers, 1996, Item 33].

7.2 Olioiden sijoittaminen

Olioiden kopioimisen lisaksi on toinenkin tapa saada aikaan kaksikeskenaan samanlaista oliota: sijoittaminen. Sijoittaminen on impe-ratiivisessa ohjelmoinnissa erittain tavallinen toimenpide, ja se ope-tetaan useimmilla ohjelmointikursseilla lahes ensimmaisena. Useim-miten sijoitettavat muuttujat ovat jotain kielen perustyyppia, muttaolio-ohjelmoinnissa luonnollisesti myos sijoitetaan olioita toisiinsa.

7.2.1 Sijoituksen ja kopioinnin erot

Olioiden sijoittaminen toisiinsa liittyy laheisesti olioiden kopiointiin,jopa siina maarin, etta joissain oliokielissa ei sijoittamista tunneta ol-

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .]Tata mekaanista kloonaa-funktion kopioimista voi jonkin verran helpottaa sopivalla esi-

kaantajamakrokikkailulla, mutta se menee jo reilusti ohi taman kirjan aihepiirin. Mutta kylla#define:llakin paikkansa on. . . ,

Page 216: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

7.2. Olioiden sijoittaminen 216

lenkaan vaan se on korvattu kopioinnilla. Sijoituksen ja kopioinninlopputulos on sama: kaksi oliota, joiden “arvo” on sama. Kopioinnissakuitenkin luodaan uusi olio, joka alustetaan olemassa olevan perus-teella, kun taas sijoituksessa on jo olemassa vanha olio, jonka “arvo”muutetaan vastaamaan toista oliota.

Sijoitukseen liittyvat ongelmat liittyvat yleensa juuri olion van-han sisallon kasittelyyn. Usein sijoituksessa joudutaan ensin vapaut-tamaan vanhaa muistia ja muuten siivoamaan oliota vahan purkajantapaan ja taman jalkeen kopioimaan toisen olion sisalto. Tama aiheut-taa tiettyja vaaratilanteita, joita uuden olion luomisessa ei ole.

Erityisen hankalaksi sijoitus tulee, jos ohjelman taytyy varautuasijoituksessa mahdollisesti sattuviin virhetilanteisiin. Jos esimerkik-si halutaan, etta sijoituksen epaonnistuessa olion vanha arvo sailyy,sijoitus taytyy tehda varsin varovaisesti, jotta vanhaa arvoa ei heitetaroskiin liian aikaisin. Naita ongelmia kasitellaan jonkin verran virhe-kasittelyn yhteydessa aliluvussa 11.8. Tallaisten vikasietoisten sijoi-tusten kasittely on kuitenkin varsin hankalaa (sijoituksesta ja virheis-ta C++:ssa on mainio artikkeli “The Anatomy of the Assignment Opera-tor” [Gillam, 1997]).

On myos tapauksia, joissa olion kopioiminen on mahdollista jasallittua mutta sijoittaminen ei kuitenkaan ole mielekasta. Esimerk-kina voisi olla vaikka piirto-ohjelman ympyraolio: kopion luominenolemassa olevasta ympyrasta on varmasti hyodyllinen ominaisuus,mutta ympyran sijoittaminen toiseen ympyraan ei ole edes ajatukse-na jarkeva. Niinpa sijoittamisen ja kopioimisen salliminen tai estami-nen on syyta pystya tekemaan toisistaan riippumatta.

7.2.2 C++: Sijoitusoperaattori

C++:ssa olioiden sijoittaminen tapahtuu erityisella jasenfunktiolla, jo-ta kutsutaan sijoitusoperaattoriksi (assignment operator). Kun ohjel-massa tehdaan kahden olion sijoitus a = b, kyseisella ohjelmarivillakutsutaan itse asiassa olion a sijoitusoperaattoria ja annetaan sille vii-te olioon b parametrina. Sijoitusoperaattorin tehtavana on sitten tu-hota olion a vanha arvo ja korvata se olion b arvolla. Se, mita kaikkiaoperaatioita tahan liittyy, riippuu taysin kyseessa olevasta luokasta,kuten kopioinnissakin.

Sijoitusoperaattorin syntaksi nakyy listauksesta 7.4 seuraavallasivulla. Sijoitusoperaattori on jasenfunktio, joten se taytyy esitel-

Page 217: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

7.2. Olioiden sijoittaminen 217

la luokan esittelyssa. Sen nimi on “operator =” (sanavali yhtasuu-ruusmerkin ja sanan operator valissa ei ole pakollinen, joten myos“operator=” kelpaa). Merkkijonoluokan sijoitusoperaattori ottaa pa-rametrikseen vakioviitteen toiseen merkkijonoon, joka siis on sijoi-tuslauseessa yhtasuuruusmerkin oikealla puolella oleva olio, jonkaarvoa ollaan sijoittamassa. Tata operaattoria kayttaen sijoitus a = baiheuttaa jasenfunktiokutsun a.operator =(b).

Sijoitusoperaattori palauttaa paluuarvonaan viitteen olioon it-seensa. Syyna tahan on C-kielesta periytyva mahdollisuus ketjusijoi-tukseen a = b = c, joka ensin sijoittaa c:n arvon b:hen ja sen jalkeenb:n a:han. Kun nyt ensimmainen sijoitus palauttaa viitteen b:hen, ket-

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . mjono.hh . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .1 class Mjono2 3 public:

10 Mjono& operator =(Mjono const& vanha);...

14 ;. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . mjono.cc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

1 Mjono& Mjono::operator =(Mjono const& vanha)2

3 if (this != &vanha)4 // Jos ei sijoiteta itseen5 delete[ ] merkit ; merkit = 0; // Vapauta vanha6 koko = vanha.koko ; // Sijoita koko7 if (koko != 0)8 // Varaa tilaa, jos koko ei nolla9 merkit = new char[koko + 1];

10 for (unsigned long i = 0; i != koko ; ++i)11 // Kopioi merkit12 merkit [i] = vanha.merkit [i];13 14 merkit [koko ] = ’\0’; // Loppumerkki15 16 17 return *this;18

LISTAUS 7.4: Esimerkki sijoitusoperaattorista

Page 218: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

7.2. Olioiden sijoittaminen 218

jusijoituksesti aiheutuu lopulta jasenfunktioketju

a.operator =(b.operator =(c))

Sijoitusoperaattorin koodissa olion itsensa palauttaminen tapah-tuu this-osoittimen avulla. Tama osoitin osoittaa olioon itseensa, jo-ten *this on tapa sanoa “mina itse”. Niinpa rivi 17 palauttaa viitteenolioon itseensa.

Sijoitusoperaattorin koodi on yleensa jonkinlainen yhdistelmapurkajan ja kopiorakentajan koodeista. Rivilla 3 olevaan ehtolausee-seen palataan myohemmin. Rivi 5 tuhoaa merkkijonon vanhan sisal-lon, ja riveilla 6–15 varataan tilaa kopiolle uudesta merkkijonosta jakopioidaan merkkijono talteen. Taman jalkeen sijoitus onkin suori-tettu.

Sijoitus itseen

Sijoituksessa on tietysti periaatteessa mahdollista, etta joku yrittaa si-joittaa olion itseensa, siis tyyliin a = a. Vaikka kukaan tuskin tallaistasijoitusta ohjelmaansa kirjoittaakaan, saattaa itseen sijoitus piiloutuaohjelmaan tilanteissa, joissa parametreina tai osoittimien kautta saa-tuja olioita sijoitetaan toisiinsa. Itseen sijoitus tuntuu taysin vaaratto-malta operaatiolta, mutta sijoitusoperaattorin koodi on todella helppokirjoittaa niin, etta itseensa sijoittamisella on vakavat seuraukset.

Normaalisti sijoitusoperaattori toimii vapauttamalla ensin olionvanhaan arvoon liittyvan muistin ja mahdollisesti muut resurssit,jonka jalkeen se varaa uutta muistia ja tekee varsinaisen sijoituksen.Jos nyt oliota ollaan sijoittamassa itseensa, olion vanha ja uusi arvoovat itse asiassa samat. Talloin sijoitusoperaattori aloittaa toimintan-sa tuhoamalla olion arvon, jonka jalkeen se yrittaa sijoittaa olion nytjo tuhottua arvoa takaisin olioon itseensa.

Esimerkin merkkijonoluokan tapauksessa tama aiheuttaisi sen, et-ta merkkijonon merkit sisaltava muisti vapautetaan, jonka jalkeen si-joitusoperaattori varaa osoittimen paahan uutta muistia ja sen jalkeentekee “tyhjan” kopioinnin, jossa uuden muistialueen alustamaton si-salto kopioidaan itsensa paalle. Joka tapauksessa olion sisalto on hu-kassa.

Itseen sijoituksen ongelma jaa helposti huomaamatta, koska sijoi-tusoperaattorin koodi nayttaa silta, kuin meilla olisi kaksi oliota: olio

Page 219: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

7.2. Olioiden sijoittaminen 219

itse ja viiteparametrina saatu olio. Itseensa sijoituksessa nama mo-lemmat ovat kuitenkin sama olio.

Ongelmalle on kuitenkin onneksi helppo ratkaisu. Olion sijoitta-misen itseensa ei tietenkaan pitaisi tehda mitaan, joten sijoitusope-raattorin koodissa pitaa vain tarkastaa, ollaanko oliota sijoittamassaitseensa. Jos nain on, voidaan koko sijoitus jattaa suorittamatta. Itseensijoittamisen tarkastamisessa taytyy tutkia, ovatko olio itse ja viitepa-rametrina saatu olio samat. C++:ssa olioiden samuutta voidaan tutkiaosoittimien avulla. Jos kaksi samantyyppista osoitinta ovat yhta suu-ret, osoittavat ne varmasti samaan olioon. Niinpa listauksen 7.4 rivil-la 3 tutkitaan, onko olioon itseensa osoittava osoitin this yhta suu-ri kuin osoitin parametrina saatuun olioon. Jos osoittimet ovat yhtasuuret eli ollaan suorittamassa sijoitusta itseen, hypataan koko sijoi-tuskoodin yli ja poistutaan sijoitusoperaattorista.

Periytyminen ja sijoitusoperaattori

Aliluokan sijoituksessa taytyy pitaa huolta myos olion kantaluokkao-san sijoittamisesta aivan kuten kopioinnissakin. Kopiorakentajassatama tapahtui kantaluokan kopiorakentajaa kutsumalla, sijoituksessavastaavasti periytetyn luokan sijoitusoperaattorissa tulee kutsua kan-taluokan sijoitusoperaattoria. Listaus 7.5 seuraavalla sivulla nayttaapaivatyn merkkijonon sijoitusoperaattorin, jossa merkkijono-osan si-joitus tapahtuu rivilla 5 eksplisiittisesti kantaluokan sijoitusoperaat-toria kutsumalla. Taman jalkeen rivilla 7 sijoitetaan aliluokan pai-vaysjasenmuuttuja sen omaa sijoitusta kayttaen.

Kaantajan luoma oletusarvoinen sijoitusoperaattori

Jos luokkaan ei kirjoiteta omaa sijoitusoperaattoria, kaantaja lisaa sii-hen automaattisesti oletusarvoisen sijoitusoperaattorin, joten tassa-kin suhteessa kopiorakentaja ja sijoitusoperaattori muistuttavat toi-siaan. Tama oletusarvoinen sijoitusoperaattori yksinkertaisesti sijoit-taa kaikki olion jasenmuuttujat yksi kerrallaan. Tasta syysta listauk-sen 7.5 rivin 7 paivaysolion sijoitus onnistuu, vaikka paivaysluokalleei ole kirjoitettu sijoitusoperaattoria.

Oletusarvoisen sijoitusoperaattorin ongelmat ovat samat kuin ole-tusarvoisen kopiorakentajankin. Jos luokassa on jasenmuuttujinaosoittimia, ne sijoitetaan normaalia osoittimien sijoitusta kayttaen,

Page 220: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

7.2. Olioiden sijoittaminen 220

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . pmjono.hh . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .1 class PaivattyMjono : public Mjono2 3 public:

14 PaivattyMjono& operator =(PaivattyMjono const& vanha);...

17 ;. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . pmjono.cc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

1 PaivattyMjono& PaivattyMjono::operator =(PaivattyMjono const& vanha)2 3 if (this != &vanha)4 // Jos ei sijoiteta itseen5 Mjono::operator =(vanha); // Kantaluokan sijoitusoperaattori6 // Oma sijoitus, oletetaan etta Paivays-luokalla on sijoitusoperaattori7 paivays = vanha.paivays ;8 9 return *this;

10

LISTAUS 7.5: Sijoitusoperaattori periytetyssa luokassa

jolloin sijoituksen jalkeen molempien olioiden osoittimet osoittavatsamaan paikkaan ja oliot jakavat osoittimen paassa olevan datan.Useimmissa tapauksissa tama ei kuitenkaan ole se, mita halutaan,ja esimerkiksi merkkijonoluokassa oletusarvoinen sijoitusoperaattoritoimisi pahasti vaarin. Taman takia jokaiseen luokkaan tulisi erik-seen kirjoittaa sijoitusoperaattori.

Sijoituksen estaminen

Jos luokan olioiden sijoitus ei ole mielekasta, voidaan sijoittaminenestaa samalla tavoin kuin kopioiminen. Luokan sijoitusoperaattoriesitellaan luokan esittelyssa private-puolella, jolloin sita ei paastakutsumaan luokan ulkopuolelta. Taman jalkeen sijoitusoperaattoril-le ei kirjoiteta ollenkaan toteutusta, jolloin sen kaytto luokan sisallaaiheuttaa linkitysaikaisen virheilmoituksen.

Varsin usein sijoituksen ja kopioinnin mielekkyys kayvat kasi ka-dessa, joten yleensa luokalle joko kirjoitetaan seka kopiorakentaja et-ta sijoitusoperaattori tai sitten molempien kaytto estetaan. Mutta ku-ten jo aiemmin todettiin, poikkeuksia tahan periaatteeseen on helppokeksia.

Page 221: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

7.2. Olioiden sijoittaminen 221

7.2.3 Sijoitus ja viipaloituminen

Aliluvussa 7.1.3 mainittu viipaloitumisongelma ei koske ainoastaanolioiden kopiointia. Koska sijoitus ja kopiointi ovat hyvin lahella toi-siaan, on luonnollista, etta viipaloituminen on vaarana myos sijoituk-sessa. Lahtokohdiltaan tilanne on sama kuin kopioimisessakin: jokai-nen aliluokan olio on myos kantaluokan olio, joten aliluokan olionvoi sijoittaa kantaluokan olioon. Talloin aliluokasta sijoitetaan ai-noastaan kantaluokkaosa ja periyttamalla lisattya osaa ei huomioidamitenkaan. Jalleen kerran kaantaja ei varoita tasta mitenkaan, koskaperiytymisen tyypityksen kannalta asia on niin kuin pitaakin.

Sijoitus tuo viipaloitumiseen viela lisaa kiemuroita. Olion, johonsijoitus tapahtuu, ei valttamatta tarvitse olla kantaluokan olio, vaanse voi olla myos jonkin toisen samasta kantaluokasta periytetyn luo-kan olio. Tallaisen erityyppisten olioiden sijoituksen ei tietenkaan pi-taisi olla edes mahdollista. Sijoitus ja viipaloituminen tulevat kuiten-kin mahdolliseksi, jos sijoittaminen tapahtuu olioon osoittavan kan-taluokkaosoittimen tai -viitteen kautta. Kaantaja valitsee kaytettavansijoitusoperaattorin osoittimen tyypin perusteella, joten valittua tuleekantaluokan sijoitus. Talloin kantaluokan sijoitusoperaattori sijoit-taa erityyppisten olioiden kantaluokkaosat, ja aliluokkien lisaosiin eikosketa. Listaus 7.6 seuraavalla sivulla nayttaa esimerkin molemmis-ta viipaloitumismahdollisuuksista. Kuva 7.5 sivulla 223 havainnol-listaa listauksessa tapahtuvaa viipaloitumista.

Koska sijoitusviipaloitumisessa on kysymys vaaran sijoitusope-raattorin valitsemisesta, luonnolliselta ratkaisulta saattaisi tuntua si-joitusoperaattorin muuttaminen virtuaaliseksi. Tama ei kuitenkaanole mahdollista. Syyna on se, etta virtuaalifunktion parametrien tu-lee pysya luokasta toiseen samana, kun taas sijoitusoperaattorin para-metrin tulee olla aina vakioviite luokkaan itseensa. Toisekseen virtu-aalisuus ei muutenkaan ratkaisisi koko ongelmaa, koska sijoituksessaaliluokasta kantaluokkaan kantaluokan sijoitusoperaattori on ainoamahdollinen.

Viipaloitumisvaaran estamiskeinoja C++:ssa on muutama. Helpoinon tehda luokkahierarkia, jossa kaikki kantaluokat ovat abstrakte-ja, jolloin niilla ei ole sijoitusoperaattoria ollenkaan. Talloin viipa-loitumista ei voi tapahtua [Meyers, 1996, Item 33]. Toinen ratkaisuon tarkastaa aina sijoituksen yhteydessa, etta olio itse ja sijoitettavaolio kuuluvat samaan luokkaan kuin suoritettava sijoitusoperaatto-

Page 222: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

7.2. Olioiden sijoittaminen 222

...1 class NumMjono : public Mjono2 3 public:4 NumMjono(char const* merkit, int numero);

...5 NumMjono& operator =(NumMjono const& nmj);67 private:8 int numero ;9 ;

...10 void sijoita(Mjono& mihin, Mjono const& mista)11 12 mihin = mista;13 1415 int main()16 17 Mjono mj("Tavallinen");18 PaivattyMjono pmj("Paivatty", tanaan);19 NumMjono nmj("Numeroitu", 12);2021 sijoita(pmj, nmj); // Viipaloituminen funktion sisalla!22 sijoita(mj, pmj); // Taalla myos!23

LISTAUS 7.6: Viipaloitumismahdollisuudet sijoituksessa

ri. Tama onnistuu listauksen 7.7 seuraavalla sivulla osoittamalla ta-valla typeid-operaattorin avulla. Tama tarkastus on kuitenkin vastaajoaikainen, joten mahdollisten virheiden havaitseminen jaa ohjel-man testausvaiheeseen.

Sijoitukseen liittyva viipaloituminen ei sinansa ole pelkastaanC++:n ongelma, vaan se ilmenee myos muissa oliokielissa kuten Javassaja Smalltalkissa, jos olioita (eika pelkastaan olio-osoittimia) voi sijoit-taa toisiinsa. Naissa kielissa ei kuitenkaan yleensa ole erillista sijoi-tusoperaattoria, vaan ohjelmoijan taytyy itse kirjoittaa sijoita-pal-velu tms. Tallaisissa tilanteissa viipaloituminen tulee mahdolliseksinaissakin kielissa.

Page 223: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

7.2. Olioiden sijoittaminen 223

Mjono−osa

koko

merkit

PaivattyMjono−osa

paivays

Mjono−osa

koko

merkit

NumMjono−osa

numero

Mjono−osa

koko

merkit

pmjnmj mj

Viipaloituminen!Viipaloituminen!

Mjono−luokansijoitusoper.

Mjono−luokansijoitusoper.

Vain varjostettu osa näkyy kantaluokkaviitteiden kautta

KUVA 7.5: Viipaloituminen olioiden sijoituksessa

1 #include <typeinfo>2 Mjono& Mjono::operator =(Mjono const& m)3 4 if (typeid(*this) != typeid(MJono) | | typeid(m) != typeid(MJono))5 6 /* Virhetoiminta */7 8 // Tai assert(typeid(*this) == typeid(MJono) && typeid(m) == typeid(MJono));9 // (ks. aliluku 8.1.4)

10 if (this != &m)11

...12 13 return *this;14

LISTAUS 7.7: Viipaloitumisen estaminen ajoaikaisella tarkastuksella

Page 224: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

7.3. Oliot arvoparametreina ja paluuarvoina 224

7.3 Oliot arvoparametreina ja paluuarvoina

Kun C:ssa ja C++:ssa kokonaislukumuuttuja valitetaan funktioon nor-maalina parametrina, kaytetaan arvonvalitysta (call by value). Tamatarkoittaa, etta funktioon ei valiteta itse muuttujaa vaan sen arvo. Kunnyt C++:ssa halutaan valittaa olioita funktioon vastaavanlaisina arvo-parametreina, joudutaan jalleen miettimaan, mika oikein on se olion“arvo”, joka funktioon valitetaan.

Kopioinnin yhteydessa olion arvon kasitetta on jo pohdittu, jotenon luonnollista kayttaa samaa ideaa tassakin tapauksessa. Kun oliovalitetaan funktiolle arvoparametrina, funktion sisalle luodaan kopioparametrioliosta kayttamalla luokan kopiorakentajaa. Koska kopiora-kentaja tulee maaritella niin, etta alkuperaisen olion ja uuden olion“arvot” ovat samat, nain saadaan samalla valitetyksi parametrin arvofunktion sisalle.

Funktion sisalla parametrin kopio kayttaytyy kuten tavallinenfunktion paikallinen olio, ja sita pystyy kayttamaan normaalisti para-metrin nimen avulla. Parametriin tehtavat muutokset muuttavat tie-tysti vain kopiota, joten alkuperainen olio sailyy muuttumattomana.Kun lopulta funktiosta palataan, parametrikopio tuhotaan aivan ku-ten normaalit paikalliset muuttujatkin.

Olioiden kaytto arvoparametreina on siis taysin mahdollistaC++:ssa, kunhan luokalla vain on toimiva kopiorakentaja. Arvopara-metrien kaytto ei kuitenkaan yleensa ole suositeltavaa, koska olionkopioiminen ja kopion tuhoaminen funktion lopussa ovat mahdolli-sesti hyvin raskaita toimenpiteita. Toinen vaara on, etta funktion si-sassa muutetaan parametrikopiota ja luullaan, etta muutos tapahtuu-kin parametrina annettuun alkuperaiseen olioon. Viimeiseksi periy-tyminen ja arvonvalitys saavat aikaan viipaloitumisen vaaran (alilu-ku 7.1.3). Kaiken taman vuoksi olioiden parametrinvalityksessa kan-nattaa aina kayttaa viitteita — ja erityisesti const-viitteita — aina kunse on mahdollista.

Kun funktiosta palautetaan olio paluuarvona, tilanne on vielamutkikkaampi. Olio, joka palautetaan return-lauseessa, on todenna-koisesti funktion paikallinen olio, joten se tuhoutuu heti funktiostapalattaessa. Tama kuitenkin tarkoittaa sita, etta olio tuhoutuu, ennenkuin paluuarvoa ehditaan kayttaa kutsujan puolella! Niinpa funk-tion paluuarvo taytyykin saada talteen kutsujan puolelle, ennen kuinfunktion paikalliset oliot tuhotaan.

Page 225: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

7.3. Oliot arvoparametreina ja paluuarvoina 225

Sana “arvo” esiintyy tassakin yhteydessa, joten ongelma ratkeaakopioinnilla. Kun funktiosta palautetaan return-lauseella olio, funk-tion kutsujan puolelle luodaan nimeton valiaikaisolio (temporaryobject), joka alustetaan kopiorakentajalla kopioksi return-lauseessaesiintyvasta oliosta. Taman jalkeen funktiosta voidaan palata ja tu-hota kaikki paikalliset oliot, mukaanlukien alkuperainen paluuarvo.Kutsujan koodissa valiaikaisolio toimii funktion “paluuarvona”, ja jospaluuarvo esimerkiksi sijoitetaan talteen toiseen olioon, valiaikaiso-lio toimii sijoituksen alkuarvona. Kun valiaikaisoliota ei lopulta enaatarvita, se tuhotaan automaattisesti. Nain valiaikaisoliot eivat voi ai-heuttaa muistivuotoja.

C++ antaa kaantajalle olioita palautettaessa mahdollisuuden opti-mointeihin ja tehokkaan koodin tuottamiseen. Paluuarvojen tapauk-sessa kaantaja voi esimerkiksi kayttaa valiaikaisolion sijaan jotain ta-vallista oliota, jos se huomaa etta valiaikaisolio valittomasti sijoitet-taisiin tai kopioitaisiin johonkin toiseen olioon.

Kuten arvoparametrienkin tapauksessa, olioiden valittaminen pa-luuarvona onnistuu C++:ssa ilman erityisia ongelmia, kunhan luokankopiorakentaja toimii oikein. Paluuarvon kopiointi ja tuhoaminen ai-heuttavat kuitenkin jalleen sen, etta olion palauttaminen saattaa ollahyvin raskas operaatio. Myos viipaloituminen on vaarana, jos kanta-luokkaolion palauttavasta funktiosta yritetaankin palauttaa aliluokanoliota (aliluku 7.1.3). Olioiden palauttamista paluuarvoina kannattaa-kin valttaa, jos mahdollista. Olion palauttamiselle ei kuitenkaan ole

1 Paivays kuukaudenAlkuun(Paivays p)2 3 Paivays tulos(p);4 tulos.asetaPaiva(1); // Kuukauden alkuun5 return tulos;6 78 int main()9

10 Paivays a(13, 10, 1999);11 Paivays b(a); // Kopiorakentaja12 b = kuukaudenAlkuun(a);13

LISTAUS 7.8: Olio arvoparametrina ja paluuarvona

Page 226: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

7.3. Oliot arvoparametreina ja paluuarvoina 226

aivan yhta itsestaanselvaa vaihtoehtoa kuin viitteet parametrinvali-tyksen tapauksessa.

Ei pida koskaan tehda sita virhetta, etta yrittaa valttaa olion pa-lauttamista paluuarvona kayttamalla viitetta samalla tavoin kuin ar-voparametrien yhteydessa. Jos ohjelma palauttaa viitteen paikalli-seen olioon, viitteen paassa oleva olio tuhoutuu funktiosta palattes-sa. Talloin ohjelmaan jaa viite olemattomaan olioon, jonka kayttami-nen todennakoisesti aiheuttaa varsin vinkeita virhetilanteita. Alilu-vussa 11.7 esiteltavat automaattiosoittimet ovat yksi vaihtoehto, mut-ta usein paluuarvon korvaaminen viiteparametrilla on helpoin ratkai-su.

Listauksessa 7.8 edellisella sivulla on funktio, joka kayttaa olioitaseka arvoparametrina etta paluuarvona. Kuva 7.6 nayttaa vaihe ker-rallaan, mita funktiota kutsuttaessa tapahtuu. Funktiokutsun vaiheetovat seuraavat:

1. Funktiokutsun yhteydessa parametrista a tehdaan automaatti-

a : Paivays

1.

4.

5.

b : Paivays

? : Paivays

p : Paivays

2.

3.

tulos : Paivays

main() kuukaudenAlkuun()

<<create>>

<<create>>

kuukaudenAlkuun(a)Paivays(a)

Paivays(p)

asetaPaiva(1)

return "?"

operator =(?)

<<destroy>>

<<destroy>>

<<destroy>>

Paivays(a)

<<create>>

<<create>>

<<destroy>>

<<destroy>>

Paivays(tulos)

<<create>>

KUVA 7.6: Oliot arvoparametreina ja paluuarvoina

Page 227: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

7.4. Tyyppimuunnokset 227

sesti kopiorakentajaa kayttaen kopio kutsuttavan funktion puo-lelle. Nain funktion parametri p saa saman “arvon” kuin a.

2. Funktion sisalla luodaan paluuarvoa varten olio tulos kopioi-malla se parametrista p. Taman jalkeen tulosolion paivays siir-retaan kuukauden alkuun.

3. Funktio palauttaa paluuarvonaan olion tulos. Siita luodaan ko-piorakentajalla automaattisesti nimeton valiaikaiskopio kutsu-jan puolelle. Taman jalkeen funktiosta poistutaan ja seka tulosetta p tuhotaan.

4. Funktion paluuarvo sijoitetaan olioon b. Tassa kaytetaan sijoi-tusoperaattoria, jolle annetaan parametriksi asken saatu valiai-kaisolio. Nain funktion paluuarvo saadaan talteen olioon b.

5. Rivin 12 lopussa valiaikaisoliota ei enaa tarvita, joten se tuhou-tuu automaattisesti.

7.4 Tyyppimuunnokset

Tyyppimuunnos (type cast) on operaatio, jota ohjelmoinnissa tar-vitaan silloin talloin, kun kasiteltava tieto ei ole jotain operaatio-ta varten oikean tyyppista. Olio-ohjelmoinnin kannalta suomenkieli-nen termi “tyyppimuunnos” on harhaanjohtava. Jos tyyppia int olevamuuttuja i “muunnetaan” liukuluvuksi double, muuttujan i tyyppi eitietenkaan muutu miksikaan, vaan se on edelleenkin kokonaisluku-muuttuja. Tarkempaa olisikin sanoa, etta tyyppimuunnoksessa koko-naislukumuuttujan i arvon perusteella tuotetaan uusi liukulukuarvo,joka jollain tavalla vastaa muuttujan i arvoa. Tassa suhteessa tyyppi-muunnos muistuttaa suuresti kopiointia, jossa siinakin tuotetaan ole-massa olevan olion perusteella uusi olio. Kopioinnissa uusi ja vanhaolio ovat samantyyppiset, tyyppimuunnoksessa taas eivat.

Edellisessa esimerkissa vanhan ja uuden arvon vastaavuus tietystion, etta seka i:n etta tuotetun liukuluvun numeerinen arvo on sama.On kuitenkin olemassa tyyppimuunnoksia, joissa tama vastaavuus eiole yksi-yhteen. Esimerkiksi seka liukuluvut 3.0 etta 3.4 tuottavat ko-konaisluvuksi muunnettaessa arvon 3. Vastaavuus voi olla muutakin

Page 228: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

7.4. Tyyppimuunnokset 228

kuin numeerinen. Jos osoitin muunnetaan kokonaisluvuksi, tulokse-na on useimmissa ymparistoissa kokonaisluku, joka ilmoittaa sen ko-neen muistiosoitteen, jossa osoittimen osoittama olio sijaitsee.

Olio-ohjelmoinnissa on tavallista, etta ohjelmoija kirjoittaa omiaabstrakteja tietotyyppejaan, kuten murtolukuja, kompleksilukuja janiin edelleen. Taman vuoksi C++:ssa on mahdollisuus kirjoittaa omiatyyppimuunnosoperaattoreita, joiden avulla on mahdollisuus tehdatyyppimuunnoksia omien tietotyyppien ja muiden tyyppien valilla.Taman lisaksi standardoinnin yhteydessa C++:aan tuli tyyppimuun-noksille uusi syntaksi, jota nykyisin suositellaan kaytettavaksi van-han C:sta periytyvan muunnossyntaksin sijaan.

7.4.1 C++:n tyyppimuunnosoperaattorit

C-kielessa tyyppimuunnokset tehtiin syntaksilla(uusiTyyppi)vanhaArvo. Taman syntaksin ongelma on, etta su-lut ovat hieman epaloogisessa paikassa — eivat parametrin vaan itseoperaation ymparilla. Tasta johtuen C++-kieleen lisattiin vaihtoehtoi-nen syntaksi uusiTyyppi(vanhaArvo).

Kummankin naiden tyyppimuunnoksen ongelma on, etta niitavoidaan kayttaa suorittamaan kaikentyyppisia muunnoksia — niinmuunnoksia kokonaisluvusta liukuluvuksi kuin muunnoksia olio-osoittimesta kokonaisluvuksi. Kaantaja ei tarkasta lahes ollenkaanmuunnoksen mielekkyytta vaan luottaa siihen, etta ohjelmoija tietaamita on tekemassa. Kaytannossa tama on johtanut siihen, etta tyyp-pimuunnoksiin jaa helposti kirjoitusvirheita. Taman lisaksi tyyppi-muunnoksista johtuvien virheiden loytaminen on usein vaikeaa, kos-ka itse muunnokset nayttavat aivan funktiokutsuilta, joten niiden loy-taminen koodista ei ole aivan helppoa.

ISOC++ on pyrkinyt korjaamaan tata ongelmaa lisaamalla kieleennelja aivan uutta tyyppimuunnosoperaattoria, joiden syntaksi poik-keaa jonkin verran aiemmista:

static cast<uusiTyyppi>(vanhaArvo)const cast<uusiTyyppi>(vanhaArvo)dynamic cast<uusiTyyppi>(vanhaArvo)reinterpret cast<uusiTyyppi>(vanhaArvo)

Page 229: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

7.4. Tyyppimuunnokset 229

Syntaksi saattaa ensikatsomalta tuntua oudolta, mutta se on yhteen-sopiva mallien kayttaman syntaksin kanssa (malleista kerrotaan lu-vussa 9.5).

Jokainen naista operaattoreista on tarkoitettu vain tietynlaistenmielekkaiden muunnosten tekemiseen, ja kaantaja antaa virheilmoi-tuksen, jos niita yritetaan kayttaa vaarin. Myos vanhat tyyppimuun-nossyntaksit on sailytetty kielessa yhteensopivuussyista, mutta nii-den kayttoa tulisi valttaa, ja suosia uusia operaattoreita.

static_cast

Tyyppimuunnoksen static cast tehtavana on suorittaa kaikkia sel-laisia tyyppimuunnoksia, joiden mielekkyydesta kaantaja voi varmis-tua jo kaannosaikana. Tama tarkoittaa esimerkiksi muunnoksia erikokonaislukutyyppien valilla, muunnoksia enum-luettelotyypeista ko-konaisluvuiksi ja takaisin seka muunnoksia kokonaislukutyyppienja liukulukutyyppien valilla. Esimerkiksi kahden kokonaisluvun kes-kiarvon voi laskea liukulukuna seuraavasti:

double ka =(static cast<double>(i1) + static cast<double>(i2)) / 2.0;

Muunnos static cast ei suostu suorittamaan sellaisia muunnok-sia, jotka eivat ole mielekkaita. Esimerkiksi muunnos

Paivays* pvmp = new Paivays();int* ip = static cast<int*>(pvmp); // KAANNOSVIRHE!

antaa kaannosaikana virheilmoituksen, koska paivaysosoittimenpaassa ei voi olla kokonaislukua ja nain ollen muunnos paivaysosoit-timesta kokonaislukuosoittimeksi on jarjeton.

Tavallisten tyyppimuunnosten lisaksi static cast on mahdolli-nen myos silloin, kun kantaluokkaosoittimen paassa tiedetaan ole-van jonkin aliluokan olio, johon halutaan aliluokkaosoitin ilman, ettaolion tyyppia testataan ajoaikana. Aiemmin aliluvussa 6.5.3 kasitel-ty dynamic cast on normaalisti oikea valine tahan, mutta jos kanta-luokkaosoittimen paassa olevan olion tyyppi on todella tiedossa, voinopeuskriittisissa ohjelmissa olla tarve paasta eroon olion tyypin tes-tauksesta. Talloin muunnoksen voi korvata nopeammalla mutta tur-vattomammalla static cast-operaatiolla.

Page 230: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

7.4. Tyyppimuunnokset 230

const_cast

Normaalisti const-sanan kaytto ohjelmissa lisaa ohjelman luotetta-vuutta, koska se pakottaa ohjelmoijan miettimaan, mita olioita muu-tetaan ja mita ei. Vastaavasti se estaa sellaisten olioiden muuttami-sen, jotka on aiemmin maaritelty vakioiksi. Joskus on kuitenkin pak-ko kayttaa sellaista ohjelmakoodia, joka on suunniteltu “vaarin” jajossa const-sanan kaytto tuo ongelmia. Talloin const cast antaa oh-jelmoijalle mahdollisuuden poistaa const-sanan vaikutuksen, eli sillavoi tehda vakio-osoittimesta ja -viitteesta ei-vakio-osoittimen tai -viit-teen.

Yksi tyypillinen esimerkki nakyy listauksessa 7.9. Listauksen oh-jelmassa kaytetaan jonkin toisen tahon ohjelmoimaa kirjastoa kirjo-jen kasittelyyn. Tasta kirjastosta loytyy funktio kauankoPalautukseen,joka laskee annetusta kirjasta, kuinka pitka aika viimeiseen palautus-paivaan on. Ongelmana on, etta taman funktion tekija ei ole kuullut-kaan const-sanasta, joten funktio ottaa parametrinaan normaaliviit-teen kirjaan vakioviitteen sijaan.

Funktio tulostaPalautusaika sen sijaan on koodattu oikein ja ot-taa parametrinaan vakioviitteen. Jos funktiossa nyt yritettaisiin kut-sua virheellista funktiota, kaantaja ei hyvaksyisi tata kutsua, kos-ka vakio-oliota ei voi laittaa ei-vakioviitteen paahan. On kuitenkinselvaa, etta kyseinen virhe ei johdu varsinaisesti virheellisesta oh-jelmasuunnittelusta vaan siita, etta toinen ohjelmoija on unohtanut

1 int kauankoPalautukseen(KirjastonKirja& kirja)2 3 // Funktion toteutus ei muuta kirja-oliota mitenkaan

...14

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .1 void tulostaPalautusaika(KirjastonKirja const& k)2 3 // int paivia = kauankoPalautukseen(k) aiheuttaisi kaannosvirheen!4 int paivia = kauankoPalautukseen(const cast<KirjastonKirja&>(k));5 cout << "Kirjan " << k.annaNimi() << " palautukseen on ";6 cout << paivia << " paivaa." << endl;7

LISTAUS 7.9: Esimerkki const_cast-muunnoksesta

Page 231: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

7.4. Tyyppimuunnokset 231

const-sanan. Taman vuoksi rivilla 4 on kaytetty const cast-muun-nosta tuottamaan ei-vakioviite, joka kelpaa parametriksi huolimatto-masti kirjoitetulle funktiolle.

Koska const cast-muunnoksen kaytto rikkoo C++:n “vakiota ei voimuuttaa” -periaatetta vastaan, sen kaytto on periaatteessa aina osoi-tus siita, etta jokin osa ohjelmasta on suunniteltu huonosti. Tastasyysta sen kayttoa tulisi valttaa aina kun mahdollista ja mieluumminkorjata se ohjelmanosa, jossa const-sanan kaytto on laiminlyoty.

dynamic_cast

Tyyppimuunnosta dynamic cast kaytetaan muuttamaan kantaluokka-osoitin tai -viite aliluokkaosoittimeksi tai -viitteeksi ja samalla tarkas-tamaan, etta osoittimen tai viitteen paassa oleva olio on todella sopi-vaan luokkaan kuuluva. Sen toiminta on jo kasitelty aliluvussa 6.5.3sivulla 164.

Ajoaikaisesta tarkastuksesta johtuen dynamic cast on ainoaISOC++:n uusista tyyppimuunnoksista, jota ei voi mitenkaan toteuttaavanhaa tyyppimuunnossyntaksia kayttaen.

reinterpret_cast

Kaikki tahan saakka esitellyt tyyppimuunnosoperaattorit ovat suos-tuneet tekemaan vain sellaisia muunnoksia, jotka ovat jollain taval-la kaytettyjen tyyppien kannalta mielekkaita (ellei const cast-muun-noksen tekemaa vakioisuuden poistoa katsota “mielettomaksi”). Sil-loin talloin ohjelmissa saattaa kuitenkin joutua kasittelemaan tietoatavalla, joka ei ole sen todellisen tyypin mukainen.

Esimerkiksi osoitinta voi joskus joutua kasittelemaan muistiosoit-teena — siis kokonaislukuna. Vastaavasti monissa kayttoliittymakir-jastoissa painonappeihin voi liittaa “tunnistustietoa”, joka valitetaanohjelmalle nappia painettaessa. Tama tieto voi olla vaikkapa tyyppiavoid*, mutta silti siihen pitaisi saada talletettua (ja myohemmin pa-lautettua) osoitin johonkin olioon.

Tallaisia tiedon esitystavan muuttamiseen liittyvia muunnoksiavarten on operaattori reinterpret cast. Kuten sen nimikin ilmai-see, se “tulkitsee uudelleen” sille annetun tiedon toisen tyyppisena.Muunnoksen lahes ainoa kayttokohde on muuntaa tieto ensin toisen-

Page 232: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

7.4. Tyyppimuunnokset 232

tyyppiseksi ja myohemmin takaisin. ISOC++ -standardi luettelee seu-raavat muunnostyypit, joihin reinterpret cast kelpaa:

• Osoittimen voi muuntaa kokonaisluvuksi, jos kokonaisluku-tyyppi on niin suuri, etta osoitin mahtuu siihen.

• Vastaavasti kokonaisluvun (tai luettelotyypin arvon) voi muun-taa takaisin osoittimeksi.

• Tavallisen osoittimen voi muuntaa toisentyyppiseksi osoitti-meksi.

• Viitteen voi muuntaa toisentyyppiseksi viitteeksi.

• Funktio-osoittimen voi muuntaa toisentyyppiseksi funktio-osoittimeksi (mutta tavallista osoitinta ei tarkasti ottaen voimuuntaa funktio-osoittimeksi tai painvastoin, ainakaan niin et-ta koodi olisi siirrettavaa). Sama patee jasenfunktio- ja -muuttu-jaosoittimille.

Kaikissa naissa muunnoksissa muunnettua arvoa ei pitaisi kayt-taa mihinkaan muuhun kuin tiedon tallettamiseen ja myohemmin ta-kaisin muuntamiseen. Ainoana poikkeuksena on osoittimen muun-taminen kokonaisluvuksi, jonka tuottaman tuloksen pitaisi olla “sel-lainen, etta se ei hammastyta niita, jotka tuntevat kyseisen koneenmuistiosoitteiden rakenteen”.^

Listauksessa 7.10 seuraavalla sivulla on esimerkki tilanteesta, jos-sa kayttoliittymakirjasto tarjoaa funktion luoNappula, jolle annetaannappulan nimi ja siihen liittyva kokonaisluku. Kun nappulaa paine-taan, kayttoliittyma kutsuu funktiota nappulaaPainettu ja antaa sil-le parametriksi nappulaan liittyvan kokonaisluvun. Esimerkki kayt-taa riveilla 4–5 reinterpret cast-muunnosta tallettamaan nappuloi-den sisaltamiin lukuihin osoittimia kirjoihin ja myohemmin rivilla 10muuntamaan saadut kokonaisluvut taas takaisin osoittimiksi.

7.4.2 Ohjelmoijan maarittelemat tyyppimuunnokset

Kun ohjelmoija kirjoittaa oman tietotyyppinsa, on mahdollisesti tar-vittavia tyyppimuunnoksia kahdenlaisia: tyyppimuunnoksia, jotka

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .^“It [the mapping function] is intended to be unsurprising to those who know the address-

ing structure of the underlying machine.” [ISO, 1998, kohta 5.2.10.4]

Page 233: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

7.4. Tyyppimuunnokset 233

...1 void luoNappula(string const& nimi, unsigned long int luku);

.... . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

1 // Tama funktio luo kayttoliittyman2 void luoKayttoliittyma(KirjastonKirja* kirja1, KirjastonKirja* kirja2)3 4 luoNappula("Kirja1", reinterpret cast<unsigned long int>(kirja1));5 luoNappula("Kirja2", reinterpret cast<unsigned long int>(kirja2));6

...7 // Tata funktiota kutsutaan, kun nappulaa painetaan8 void nappulaaPainettu(unsigned long int luku)9

10 KirjastonKirja* kp = reinterpret cast<KirjastonKirja*>(luku);11 cout << "Painettu kirjaa " << kp->annaNimi() << "." << endl;12

LISTAUS 7.10: Tiedon esitystavan muuttaminen jareinterpret_cast

muuntavat muun tyyppisia arvoja ohjelmoijan kirjoittamaksi tyypik-si, ja muunnoksia, jotka muuttavat ohjelmoijan oman tyypin arvo-ja muuntyyppisiksi. C++ antaa ohjelmoijalle mahdollisuuden kirjoit-taa omia tyyppimuunnoksia molempiin suuntiin. Tyyppimuunnok-set kirjoitetaan oman tietotyyppiluokan jasenfunktioina, ja kumpaan-kin suuntaan tapahtuvilla muunnoksilla on oma syntaksinsa.

Rakentaja tyyppimuunnoksena

Oleellisilta osiltaan tyyppimuunnos on uuden erityyppisen arvonluomista olemassa olevan arvon pohjalta. C++:ssa uusia “arvoja” (olioi-ta) luodaan rakentajan avulla, joten on luontevaa yhdistaa tyyppi-muunnokset ja rakentajat. Tama tapahtuu niin, etta kielen mukaanjokainen yksiparametrinen rakentaja on myos tyyppimuunnos raken-tajan parametrin tyypista luokan olioksi.

Esimerkiksi listauksessa 7.11 seuraavalla sivulla on osa luokanMurtoluku maarittelya. Luokalla on rakentaja, jonka avulla uudenmurtoluvun voi luoda kokonaisluvun avulla. Talloin luodaan uusimurtoluku, joka on arvoltaan sama kuin parametrina oleva kokonais-

Page 234: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

7.4. Tyyppimuunnokset 234

1 class Murtoluku2 3 public:4 Murtoluku(int kokonaisarvo); // Myos tyyppimuunnos

...6 private:7 int osoittaja ;8 int nimittaja ;9 ;

...10 Murtoluku::Murtoluku(int kokluku) : osoittaja (kokluku), nimittaja (1)11 12

LISTAUS 7.11: Esimerkki rakentajasta tyyppimuunnoksena

luku. C++ osaa nyt automaattisesti kayttaa tata rakentajaa seka ekspli-siittisena etta implisiittisena tyyppimuunnoksena. Kun tyyppimuun-nos suoritetaan, ohjelma luo uuden nimettoman murtoluvun rakenta-jan avulla ja kayttaa sita tyyppimuunnoksen “lopputuloksena”. Kaan-taja pitaa myos huolen siita, etta tama valiaikaisolio tuhotaan auto-maattisesti, kun sita ei enaa tarvita. Tassa suhteessa tyyppimuunnos-ten valiaikaisoliot kayttaytyvat tasmalleen samoin kuin paluuarvonvalityksessa kaytetyt valiaikaisoliot (aliluku 7.3).

Rakentajamuunnosta voi kayttaa kaikkialla missanormaaliakin tyyppimuunnosta. Esimerkiksi muunnosstatic cast<Murtoluku>(3) tuottaa lukua kolme vastaavan murto-lukuolion luokan rakentajan avulla. Myos vanhat muunnossyntaksitMurtoluku(3) ja (Murtoluku)3 toimivat. Naista ensimmainen muis-tuttaa jo syntaksinsakin puolesta rakentajan kutsumista. Myosimplisiittiset tyyppimuunnokset toimivat normaalisti. Esimerkiksikoodissa

void kasittele(Murtoluku const& m);...

kasittele(5);

kaantaja huomaa, etta funktion kutsumiseksi kokonaisluku 5 pitaamuuttaa murtoluvuksi. Se tuottaa tallaisen murtoluvun rakentajan

Page 235: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

7.4. Tyyppimuunnokset 235

avulla ja valittaa funktiolle viitteen luotuun valiaikaisolioon. Funk-tiokutsun jalkeen valiaikaisolio tuhotaan.

Rakentajatyyppimuunnoksen estaminen

Rakentajan kaytto tyyppimuunnoksena saattaa aluksi tuntua kateval-ta, mutta kun tama ominaisuus lisattiin C++:aan, huomattiin pian, ettaominaisuudella oli epatoivottuja sivuvaikutuksia. Ongelmana on, et-ta kaantajan kannalta kaikki yksiparametriset rakentajat ovat mahdol-lisia tyyppimuunnoksia. On kuitenkin paljon tilanteita, joissa raken-tajan suorittamaa uuden olion luomista ei mitenkaan jarkevasti voitulkita tyyppimuunnokseksi.

Jos ohjelmassa esimerkiksi maaritellaan oma taulukkotyyppi, senrakentaja voi hyvinkin saada parametrinaan tiedon siita, kuinka mo-nen alkion taulukko halutaan luoda. Kuitenkaan ei ole mitenkaanjarkevaa ajatella, etta kyseessa olisi tyyppimuunnos kokonaisluvus-ta taulukoksi. Tassa tapauksessa rakentajan saama parametri on vainolion luomiseen tarvittava lisatieto eika rakentaja maaraa minkaan-laista tyyppimuunnosta. Kaantaja ei kuitenkaan tata tieda ja saattaakayttaa taulukkoluokan rakentajaa myos implisiittisena tyyppimuun-noksena tilanteissa, joissa (esimerkiksi kirjoitusvirheen vuoksi) ko-konaisluvusta taytyisi tehda taulukko. Tama johtaa virheisiin, joidenloytaminen saattaa olla hyvin hankalaa.

Myohemmin C++:ssa on yritetty paikata syntynytta virhelahdettalisaamalla kieleen avainsana explicit. Jos yksiparametrisen rakenta-jan edessa on luokan esittelyssa sana explicit, kyseista rakentajaaei kayteta implisiittisena tyyppimuunnoksena. Kayttaja voi kuitenkinhalutessaan pyytaa tyyppimuunnosta normaalilla tyyppimuunnos-syntaksilla, mutta tama tuskin tapahtuu vahingossa. Edella mainituntaulukkotyypin rakentaja olisi esimerkiksi syyta varustaa explicit-sanalla seuraavaan tapaan:

class Taulukkopublic:explicit Taulukko(int koko); // EI implisiittinen tyyppimuunnos

...;

Page 236: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

7.4. Tyyppimuunnokset 236

Vaikka explicit-sana ratkaiseekin syntyneen ongelman, se onedelleen varsin hankala kayttaa, koska ohjelmoijan taytyy erikseenmuistaa merkita ne rakentajat, joita han ei halua kayttaa tyyppimuun-noksena. Kielen kannalta avainsana “implicit” olisi saattanut olla pa-rempi, mutta se olisi aiheuttanut epayhteensopivuutta kielen vanho-jen ja uusien murteiden valilla.

Muunnosjasenfunktiot

Rakentajien kaytto tyyppimuunnoksena tekee mahdolliseksi sen, et-ta muita tyyppeja voi muuntaa ohjelmoijan kirjoittamaksi tyypiksi.Tyyppimuunnoksen tekeminen toiseen suuntaan ei onnistu samallamenetelmalla, koska ohjelmoija ei tietenkaan voi kirjoittaa uusia ra-kentajia esimerkiksi int-tyypille tai kaupallisista kirjastoista loytyvil-le tyypeille.

Tyyppimuunnos omasta tyypista johonkin toiseen tyyppiin tapah-tuu erityisten muunnosjasenfunktioiden (conversion member func-tion) avulla. Nama ovat parametrittomia vakiojasenfunktioita, joiden“nimi” muodostuu avainsanasta operator ja lisaksi muunnoksen koh-detyypista. Esimerkiksi kokonaisluvuksi muuntavan muunnosjasen-funktion nimi on operator int. Muunnosjasenfunktio palauttaa pa-luuarvonaan muunnoksen lopputuloksen. Jasenfunktion esittelyssaei paluuarvoa merkita nakyviin, koska se kay ilmi jo nimesta.

Listauksessa 7.12 seuraavalla sivulla on esimerkki murtolukuluo-kan muunnosjasenfunktiosta, jonka avulla murtoluvun voi muun-taa liukuluvuksi. Kyseisen maarittelyn jalkeen kaantaja osaa kayttaamuutosta seka implisiittisena etta eksplisiittisena muunnoksena. Jossiis m on murtolukuolio, niin muunnokset static cast<double>(m)ja double d = m; toimivat normaalisti.

Implisiittiset tyyppimuunnokset ovat tietysti mahdollinen vir-helahde muunnosjasenfunktioidenkin tapauksessa. Jostain syystaexplicit-avainsanaa ei voi kayttaa muunnosjasenfunktioiden yhtey-dessa luomaan muunnoksia, joita voisi kayttaa vain eksplisiittisesti.Jos tallaista halutaan, luokkaan taytyy kirjoittaa tavallinen jasenfunk-tio, jota kutsutaan tyyppimuunnoksen sijaan.

Page 237: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

7.5. Rakentajat ja struct 237

1 class Murtoluku2 3 public:

...5 operator double() const; // Muunnosjasenfunktio

...9 ;

...13 Murtoluku::operator double() const14 15 return static cast<double>(osoittaja )/static cast<double>(nimittaja );16

LISTAUS 7.12: Esimerkki muunnosjasenfunktiosta

7.5 Rakentajat ja struct

C++:ssa luokkia voi kayttaa aivan samoissa paikoissa kuin kielensisaanrakennettuja tyyppejakin. Niinpa on taysin normaalia tehdastruct-tietorakenteita, joiden tietokenttina on olioita, kuten paivayk-sia, string-merkkijonoja ja niin edelleen. Listauksessa 7.13 on esi-merkkina tallainen tietorakenne.

Kun struct-tietorakenne luodaan, sisaltyy tahan kaikkienstructin kenttien luominen. Luokkatyyppia olevilla kentilla tamatarkoittaa kenttien rakentajien kutsumista. Jos tietorakenne luodaan“normaaliin tapaan” syntaksilla Henkilotiedot henk;, kutsutaan ken-tille automaattisesti oletusrakentajia, koska mitaan alustamiseen tar-vittavia parametreja ei ole saatavilla.

Jo C-kielessa oli myos mahdollista alustaa struct syntaksilla, jos-

1 struct Henkilotiedot2 3 string nimi;4 Paivays syntymapvm;5 int ika;6 ;

LISTAUS 7.13: struct-tietorakenne, jossa on olioita

Page 238: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

7.5. Rakentajat ja struct 238

sa aaltosuluissa luetellaan luomisen yhteydessa kenttien alkuarvot.Tama on mahdollista myos C++:ssa. Mikali kenttina on luokkia, nai-den alustamisessa on kaksi vaihtoehtoa. Mikali luokalla on yksipara-metrinen rakentaja, joka kelpaa implisiittiseksi tyyppimuunnokseksi,voi aaltosulkulistaan laittaa talle rakentajalle kelpaavan parametrin.Talloin kentan alustus tapahtuu tyyppimuunnosrakentajaa kayttaen.Toinen tapa on kutsua aaltosulkulistassa suoraan jotain luokan raken-tajaa ja antaa sille tarvittavat parametrit.

Alla on esimerkki molemmista tapauksista:

Henkilotiedot henk = "K. Dari", Paivays(1, 1, 1970), 33;

Kentta nimi alustetaan char*-merkkijonoliteraalista string-luokanyksiparametrista rakentajaa kayttaen. Sen sijaan kentta syntymapvmalustetaan alkuarvoonsa erikseen Paivays-luokan rakentajaa kutsu-malla.

Olioiden tapauksessa aaltosulkualustus ei ikava kylla ole valt-tamatta kovin tehokas ratkaisu. C++-standardi nimittain maaritteleealustuksen niin, etta olioiden tapauksessa ensin luodaan aaltosul-kulistassa olevien tietojen perusteella irralliset valiaikaisoliot ja na-ma sitten kopioidaan struct-tietorakenteen sisaan. Tama tapahtuuluomalla kentat kopiorakentajilla, joille annetaan luodut valiaikaiso-liot parametreina. Esimerkissa luodaan ensin valiaikaiset merkkijonostring("K. Dari") ja paivays, ja kentat nimi ja syntymapvm aluste-taan sitten naista kopiorakentajien avulla. Tehokas kaantaja saa opti-moida valiaikaisoliot pois, jos se siihen kykenee, mutta kopiorakenta-jan on silti oltava olemassa.

Aaltosulkualustuksen rajoituksena on myos, etta sita voi kayttaavain, jos ollaan luomassa nimettya paikallista tai globaalia muuttujaa.Sen sijaan syntaksia ei voi kayttaa valiaikaisolion tai new’lla luodunstructin luomiseen. Tallaisia tilanteita varten on joskus katevaa kir-joittaa struct-tietorakenteellekin rakentaja, joka alustaa tietoraken-teen kentat haluttuihin alkuarvoihin.

Listaus 7.14 seuraavalla sivulla nayttaa esimerkin tallaisesta ra-kentajasta. Rakentaja koostuu pelkasta alustuslistasta, joka alustaatietorakenteen kentat annettujen parametrien avulla. Poikkeukselli-sesti koko rakentaja on kirjoitettu struct-maarittelyn sisalle, koskarakentaja ei sisalla mitaan toiminnalisuutta ja talla tavoin sen koo-dia ei tarvitse kirjoittaa erilleen .cc-tiedostoon. Kyseessa on saman-

Page 239: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

7.5. Rakentajat ja struct 239

1 struct Henkilotiedot2 3 string nimi;4 Paivays syntymapvm;5 int ika;6 // Rakentaja, sisaltaa VAIN kenttien alustuksen7 Henkilotiedot(string const& n, int p, int k, int v, int i)8 : nimi(n), syntymapvm(p, k, v), ika(i) 9 ;

LISTAUS 7.14: struct, jolla on rakentaja

lainen tilanne kuin rajapintaluokkien virtuaalipurkajien yhteydessaaliluvussa 6.9.2.

Rakentajan sisaltavan tietorakenteen luominen tapahtuu nyt ai-van kuten olionkin. Se onnistuu milla tahansa seuraavista syntak-seista:

Henkilotiedot henk("K. Dari", 1, 1, 1970, 33);Henkilotiedot* hp =new Henkilotiedot("Dari", 1, 1, 1970, 33);

Henkilotiedot("K. Dari", 1, 1, 1970, 33); // Valiaikaisolio

Vaikka periaatteessa tietorakenteen rakentajan runkoon tai alus-tuslistaan voisi C++:ssa kirjoittaa koodia, ei tata tyylillisesti missaannimessa pitaisi tehda. Ohjelmissa on totuttu pitamaan struct-tietora-kenteita “tyhmina” tietovarastoina, joihin ei sisally ylimaaraista toi-minnalisuutta. Niinpa structin rakentaja onkin syyta pitaa vain syn-taktisena temppuna, jolla helpotetaan kenttien alustamista. Periaat-teessa C++ sallisi myos purkajien ja jasenfunktioiden kirjoittamisenstruct-tietorakenteille, mutta naitakaan ei kannata kayttaa, koska netekisivat ohjelmasta vain vaikealukuisemman tavalliselle ohjelmoi-jalle. Jos tietorakenteeseen on todella tarve upottaa toiminnallisuutta,kannattaa siita yleensa kirjoittaa luokka struct-tietorakenteen sijaan.

Page 240: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

240

Luku 8

Lisaa rajapinnoista

“There are things known and things unknown and in be-tween are The Doors.”

– Jim Morrison [The Doors FAQ, 2002]

Rajapinnan tehtava on kertoa siihen liittyvasta komponentista (mo-duuli tai olio) kayttajalle vastaus kysymykseen “Miten taman on tar-koitus toimia?”. Kun kayttaja tietaa vastauksen tahan kysymykseen,han pystyy hyodyntamaan rajapinnan maarittelemaa palvelua valit-tamatta sen taakse kapseloidusta toteutuksesta. Samalla komponen-tilla voi olla useita erilaisia rajapintoja. Oliolla voi olla rajapinta (taisen osa) erikseen vakio-olioille, “tavallisille” instansseille ja aliluokil-le.

Rajapinnan toteuttajan pitaisi pitaa mielessaan lause “Tekeekototeutus tasmalleen sen, mita rajapinnan maarittely sanoo?”. Taitoja kokemus ovat tassa tyossa korvaamattomia. Ohjelmointikieli sekasuunnittelu- ja tyylisaannot voivat ainakin ohjata kohti oikein toimi-vaa lopputulosta.

8.1 Sopimus rajapinnasta

Sopimussuunnittelu (Design By Contract, [Meyer, 1997]) on rajapin-tasuunnittelun ja -toteutuksen menetelma, jossa palveluiden ominai-suuksia kuvataan matematiikan keinoin. Lakimiesten viilaaman kir-

Page 241: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

8.1. Sopimus rajapinnasta 241

jallisen kahden osapuolen sopimuksen tapaan sopimussuunnittelus-sa ajatellaan syntyvan rajapinnan kayttajan ja sen toteutuksen valillesopimus, jossa kummallakin osapuolella on tarkkaan maaritellyt vas-tuunsa:

• Rajapinnan toteutus lupaa jokaisen rajapinnan palvelun osaltatoimia tietylla tavalla, kun rajapintaa kaytetaan sovitulla taval-la. Maarittelyssa on siis mukana tieto siita, mitka ovat rajapin-nan sallittuja kayttotapoja (funktioiden parametrit, kutsujarjes-tykset, tietotyyppien arvorajat yms.).

• Rajapintaa kayttava ohjelmoija lupaa kayttaa palveluita ai-noastaan niiden maarittelyn mukaisesti.

Sopimussuunnittelu on nimensa mukaisesti ennen kaikkea raja-pintojen suunnittelua auttava ajattelutapa, joka pakottaa ja ohjaa se-ka miettimaan etta dokumentoimaan rajapinnan huolellisesti. Tois-sijaisena hyotyna menetelma yksinkertaistaa komponenttien toteut-tamista. Tama saavutetaan siten, etta rajapinnan toteutuksen ulko-puolelle on sopimuksessa rajattu hankalat ja ei-toivotut kayttotavat,jolloin niihin ei tarvitse toteutuksessa varautua. Voidaan ajatella et-ta “villin lannen” rajapintaa saa kayttaa miten huvittaa ja toteutuk-sen pitaisi osata toimia kaikissa tilanteissa jotenkin “oikein” (mini-missaan laadukas koodi ei ainakaan kaadu kummallisissa tilanteis-sa). Sopimussuunnittelussa jaetaan vastuuta toiminnasta kutsujan jatoteutuksen kesken, jolloin kokonaisohjelmakoodimaara on yleensapienempi kuin kaikkeen varautuneissa toteutuksissa.

8.1.1 Palveluiden esi- ja jalkiehdot

Rajapinnan yksittaisen palvelun sopimus tehdaan maarittelemalla jo-kaiselle palvelulle esiehto (precondition, P) ja jalkiehto (postcondi-tion, Q). Molemmat ovat (predikaattilogiikan) loogisia lausekkeita,joista esiehdon tulee olla totta ennen palvelun kaynnistamista ja jal-kiehdon palvelun suorittamisen jalkeen:

P palvelu() Q

Taman “oikeellisuuskaavan” (correctness formula) mukaisesti pal-velu lupaa, etta kaikki funktion palvelu suoritukset, joiden alkaessaehto P on totta, paattyvat tilaan, jossa ehto Q on totta. Jos ehto P ei

Page 242: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

8.1. Sopimus rajapinnasta 242

toteudu, niin palvelu toimii maarittelemattomasti ja jalkiehdon ei tar-vitse toteutua (haluttua palvelua ei saada).

Yksinkertaisimmillaan ehdoilla voidaan rajata parametrien arvoa-lueita:

p >= kirja.lainauspaiva

kirja.palauta(palautuspaiva p)

kirja.tila = PALAUTETTU

Kaytettaessa predikaattilogiikan ominaisuuksia voidaan kertoaesimerkiksi rutiinista, joka jarjestaa taulukon, joka ei sisalla yhtakaantyhjaa alkiota:

∀ i | 1 ≤ i ≤ n : ARRAY[i] 6= TYHJA

qsort(ARRAY)

∀ i | 1 ≤ i < n : ARRAY[i] ≤ ARRAY[i+ 1]

Sopimussuunnittelun tarkein ominaisuus on palvelun kutsujan jatoteuttajan vastuiden maarittely — samalla suunnittelun suurin vai-keus on paattaa, mitka ominaisuudet kuuluvat vastuurajan millekinpuolelle.

• Kutsuja. Palvelun kutsujan vastuulla on pitaa huoli siita, ettaesiehto toteutuu. Jos kirja yritetaan palauttaa paivamaaralla jo-ka on ennen kirjattua lainauspaivaa, niin palvelun mukainensopimus ei ole voimassa, ja suoritus voi tehda mita tahansa. Oh-jelman testausvaiheessa voidaan esiehtoja tarkastaa, mutta paa-maarana on, etta naita tarkastuksia ei ole enaa lopullisessa oh-jelmistossa mukana (huolellinen testaus on loytanyt esiehdonrikkovat palvelun kutsut).

• Toteuttaja. Palvelun toteuttajan vastuulla on pitaa huoli siita,etta palvelun suorituksen jalkeen jalkiehto on aina voimassa(kunhan esiehto on ollut voimassa). Jos sopimuksessa on maa-ritelty rutiinin parametripaivayksen olevan jotain paivamaaraamyohemman, niin palvelun toteutuksessa ei enaa tehda tar-kastusta, joka on maaritelty kutsujan vastuulle. Jos palvelu eipysty toteuttamaan sopimuksen mukaista palvelua (jalkiehto eitayty) kyseessa on virhetilanne, joka on jollain tavalla ilmaista-va. Tama tehdaan yleensa poikkeusten (luku 11) avulla.

Page 243: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

8.1. Sopimus rajapinnasta 243

Yleiskayttoisten komponenttien rajapinnoissa on usein vaikeapaattaa, minkalainen sopimus halutaan muodostaa kutsujan ja toteu-tuksen valille. Joskus voi olla tarkoituksenmukaista tarjota samastaperustoiminnallisuudesta sopimukseltaan erilaisia versioita.

Yksi esimerkki tallaisesta toiminnasta on vector-taulukon indek-sointi, josta loytyy kaksi versiota. Operaatio at() hyvaksyy parametri-naan minka tahansa kokonaisluvun ja tarkistaa toteutuksessaan osuu-ko tama luku indeksina tulkittuna taulukon sisalle. Jos indeksi onkelvollinen, niin kyseessa oleva alkio palautetaan, muutoin kutsujal-le kerrotaan virheesta (poikkeusmekanismilla). Toinen versio indek-soinnista on hakasulkuoperaattori (v[i]), joka ei suorita indeksin lail-lisuustarkistusta. Sopimussuunnittelun kannalta tama versio on maa-ritellyt kutsujan vastuulle (esiehdoksi), etta operaatiota kutsutaan ai-noastaan laillisilla taulukon indekseilla. Jos kutsuja ei tayta esiehtoa,niin toteutus tekee maarittelemattoman operaation (joka ohjelmassanakyy esimerkiksi sen kaatumisena tai muistin roskaantumisena).

8.1.2 Luokkainvariantti

Luokkien tapauksessa voidaan palveluiden esi- ja jalkiehtojen lisak-si maaritella pysyvaisvaittama eli invariantti (invariant), joka kertooluokan olion yllapitaman ehdon palvelukutsujen valilla. Esimerkiksi:

class KirjastonKirja // Invariantti: sijaintitieto on aina// LAINASSA, PAIKALLA, HUOLTO tai POISTETTU

. . .

Esimerkin KirjastonKirja-luokan olioiden tilan tiedetaan olevan ai-na invariantin mukainen silloin, kun ohjelman suoritus ei ole keskel-la jotain luokan tarjoamaa palvelua.

Luokkainvariantin on oltava voimassa heti olion synnyttya. Josoliota luotaessa kutsutaan alustusrutiinia (kuten C++:n rakentajajasen-funktio), invariantin on oltava voimassa, kun tama rutiini on lopet-tanut suorituksensa. Taman jalkeen invariantin on oltava voimassaaina ennen ja jalkeen jokaista julkisen rajapinnan rajapintafunktionkutsua:

LUOKKAINVARIANTTI∧P olio.palvelu() LUOKKAINVARIANTTI∧Q

Page 244: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

8.1. Sopimus rajapinnasta 244

Kun suoritus on jasenfunktion koodissa olion “sisalla”, puhu-taan epastabiilista tilasta, jossa luokkainvariantti saa valiaikisesti ollaepatosi. Taman saannon noudattaminen ja tarkastaminen tulee han-kalaksi silloin kun jasenfunktio kutsuu toisia jasenfunktioita osanaomaa toiminnallisuuttaan.

8.1.3 Sopimussuunnittelun kaytto

Yksinkertaisissa esimerkeissa kauniilta nayttava sopimussuunnitte-lu ei valitettavasti skaalaudu varsinkaan ohjelmiston ylimman tasonmoduulien rajapintoihin. Esi- ja jalkiehtojen tarkkaan maarittelyynkuuluu usein niin paljon informaatiota, etta sen kuvaaminen mate-maattisen tarkasti on ainakin talla hetkella liian tyolasta. Formaalei-hin maarittely- ja suunnittelumenetelmiin liittyvaa tutkimusta teh-daan paljon, ja sita sovelletaan useissa erityisen suurta varmuuttavaativissa ohjelmistoprojekteissa. Tavallisimmissa ohjelmistoprojek-teissa matemaattista maarittelya ei kuitenkaan yleensa juuri kayteta.Sopimussuunnittelun periaatteiden tunteminen ja jopa osittainenkinnoudattaminen on tietysti pelkkaa sanallista rajapintakuvausta ek-saktimpi menetelma.

Olio-ohjelmointi aiheuttaa myos omat hankaluutensa sopimus-suunnitteluun. Periytymisen yhteydessa rajapinnan sopimuksen pi-taisi usein osata sanoa jotain myos aliluokan rajapinnasta, joka useinon hyvin hankalaa. Joskus kantaluokka ei tieda edes karkeasti minka-laisia versioita siita on tarkoitus periyttaa, jolloin liian tiukasti maa-ritellyt rajapintasopimukset voivat pahimmillaan tehda periytettyjenversioiden tekemisen mahdottomiksi siten, etta kantaluokan rajapin-tasopimus pidetaan voimassa.

8.1.4 C++: luokan sopimusten tarkastus

Ohjelmiston kehitysvaiheessa on hyodyllista tarkastaa, etta rajapin-noille maariteltyja sopimuksia noudatetaan. Vaarinymmarryksistatai huonosta dokumentoinnista johtuvat sopimuksen vastaiset kut-sut saadaan heti nakyville ohjelmiston testausvaiheessa. Ylimaarai-set testit hidastavat ohjelman toimintaa, mutta testausvaiheessa sillaei useinkaan ole merkitysta. Poikkeuksena ovat reaaliaikavaatimuk-sia omaavat ohjelmistot, joissa ei voida kayttaa testeissakaan ylimaa-

Page 245: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

8.1. Sopimus rajapinnasta 245

raisia tarkastuksia, jos niiden aiheuttama ylimaarainen prosessointivaikuttaa ohjelmiston ajoituksiin.

assert-makro

Ohjelmissa olevilla sopimustarkastuksilla on pitkat perinteet jo ajal-ta ennen olio-ohjelmoinnin yleistymista. C-kielessa on maariteltynavarmistusrutiini assert [Kerninghan ja Ritchie, 1988], joka ilmoittaavirheesta ja pysayttaa ohjelman suorituksen, jos sen parametrina ole-va lauseke on arvoltaan epatosi. Kun ohjelmiston julkaisuversio kaan-netaan siten, etta esikaantajasymboli NDEBUG on maariteltyna, assert-makron arvo asettuu tyhjaksi, jolloin nama kehitysvaiheen tarkas-tukset eivat ole mukana lopullisessa ohjelmistossa. Tama kaytanto(assert-testit ovat mukana vain ohjelman testausvaiheessa) noudat-taa sopimussuunnittelun periaatetta, jonka mukaan oikein toimivas-sa ohjelmassa seka rajapinnan kutsu etta toteutus noudattavat ainasopimusta.

Koska assert on maaritelty makroksi, se ei ole C++:n std-nimia-varuuden sisalla. Rajapintafunktion alussa voidaan testauksessa var-mistaa kutsujan noudattavan sopimusta funktion parametreista:

#include <cassert>...

int palvelu(int a, int b)assert( a < 2 && b > 40 );

...

Funktion vaara kaytto voi nakya testaajalle esimerkiksi seuraavas-sa muodossa:

assertkoe.cc:4: failed assertion ‘a < 2 && b > 40’(program aborted)

Tassa assertkoe.cc on lahdekooditiedoston nimi. C++-standardi jat-taa tarkoituksella maarittelematta tulostettavan virheen muodon. Jos-sain ymparistossa tallainen virheilmoitus voidaan ilmaista esimer-kiksi kayttoliittyman virheikkunalla.

Koska assert-tarkastukset eivat ole mukana lopullisessa ohjelma-koodissa, on oltava tarkkana, etta kaytetty varmistusehto ei sisalla

Page 246: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

8.1. Sopimus rajapinnasta 246

ohjelman toiminnallisuutta:

1 Paivays* pNyt = 0;2 assert(pNyt = Paivays::NytOsoitin()); // Sijoitus assertissa!3 pNyt->Tulosta();

Vaikka assert-makron parametrina oleva sijoitus (rivi 2) saakinC++:ssa arvon, joka tulkitaan myos totuusarvoksi, kyseessa on silti vaa-ra tapa kayttaa varmistusrutiinia. Edellinen koodinpatka voi toimiataysin oikein ohjelmiston testausvaiheessa, mutta kun lopullisessaversiossa assert-makro maaritellaan tyhjaksi, rivin 2 sijoitus jaa ko-konaan suorittamatta ja koodin toiminta muuttuu.

C-kielen assert on maaritelty siten, etta ohjelman suoritus kes-keytetaan kutsumalla kielen funktiota abort. Tata funktiota ei pidetaC++:ssa suositeltavana, koska sita kutsuttaessa ei suoriteta mitaan lope-tustoimenpiteita. Erityisesti ohjelmassa kutsuhetkella olevien olioi-den purkajat jaavat suorittamatta (niissa voi olla resurssien vapau-tukseen liittyvia toimintoja). C++:ssa suositeltavampi tapa on kayt-taa poikkeusmekanismia (luku 11) myos “assertiovirheen” ilmaisemi-seen. Listauksessa 8.1 seuraavalla sivulla on esimerkki poikkeuksillatoteutetusta Assert-makrosta C++:lla.

Luokkainvariantti

Koska luokkainvariantin tarkastuksessa on tarkoituksena tarkastaaluokan vastuualueen sopimus jokaisen rajapintafunktion suorituk-sen jalkeen, kannattaa tama tarkastus kirjoittaa omaksi jasenfunktiok-seen, jota kutsutaan aina muiden jasenfunktioiden alussa ja lopussa.(Myos alussa, jotta voidaan varmistua siita, etta invariantin maaraa-ma sopimus on voimassa myos rutiinin suorituksen alkaessa, katsoohjelmalistaus 8.2 sivulla 248).

Tassa esitetty suoraviivainen toteutus ei huomioi mitenkaan sitatilannetta, etta invariantti saa olla epatosi jos tarkistuksen kohteenaolevaa jasenfunktiota on kutsuttu toisesta (saman olion) jasenfunk-tiosta. Tallainen kutsuketjun seuraaminen mutkistaisi ohjelmakoodiajonkin verran, ja sen toteuttamiseen ei C++:ssa valitettavasti ole valmii-ta apukeinoja.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .Hyvin tyypillinen virhe C++:ssa on kayttaa yhtasuuruutta tarkoitettaessa kielen (huonosti

valittua) sijoitusmuotoa [assert( x = 7 ) vs. assert( x == 7 )].

Page 247: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

8.1. Sopimus rajapinnasta 247

1 #ifndef ASSERT HH2 #define ASSERT HH34 #include "varmistuspieleen.hh" /* poikkeusluokan esittely */5 #include <iostream>67 #ifdef NDEBUG8 #define Assert(x) /* tyhja */9 #else

10 #define Assert(x) Assert toteutus( x, #x, FILE , LINE )11 #endif1213 inline void Assert toteutus( bool varmistus, char const* lauseke,14 char const* tiedosto,15 unsigned int rivinumero )16 17 if( varmistus == false ) 18 std::cerr << tiedosto << ":" << rivinumero << ":";19 std::cerr << "Varmistus epaonnistui: " << lauseke << std::endl;20 throw VarmistusPieleen(lauseke, tiedosto, rivinumero);21 22 2324 #endif /* ASSERT HH */

LISTAUS 8.1: Varmistusrutiinin toteutus

Koska C++ ei automaattisesti tue invarianttien tarkastusta, niidenkaytto on taysin ohjelmoijan vastuulla. Tama tulee ottaa huomioonmyos periytymishierarkioissa, joissa on pidettava huoli siita, etta ali-luokan jasenfunktiot tarkastavat myos kantaluokan invariantin saily-misen:

// Kun jarjestetty taulukko on periytetty luokasta Taulukkoinline void JarjestettyTaulukko::Invariantti()#ifndef NDEBUGTaulukko::Invariantti();

...#endif

Page 248: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

8.2. Luokan rajapinta ja olion rajapinta 248

1 inline void JarjestettyTaulukko::Invariantti()2 3 #ifndef NDEBUG4 // Invariantti: alkiot ovat aina suuruusjarjestyksessa, siten etta5 // indeksissa 1 on pienin alkio ja indeksissa KOKO suurin.6 for( int i = 1; i < KOKO; i++ )7 8 if( alkio[ i ] > alkio[ i+1 ] )9 throw JarjestettyTaulukko::InvarianttiRikottu();

10 11 #endif12 1314 void JarjestettyTaulukko::EtsiJaMuutaAlkio(15 Alkio const& etsittava, Alkio const& korvaava )16 17 Invariantti();18 // Jasenfunktion toteutus19 Invariantti();20

LISTAUS 8.2: Esimerkki luokkainvariantin toteutuksestajasenfunktiona

8.2 Luokan rajapinta ja olion rajapinta

Luokkasuunnittelussa tulee silloin talloin vastaan tilanne, jossa jo-kin luokan vastuualueeseen kuuluva asia ei oikeastaan kuulu min-kaan luokan olion tehtaviin vaan ikaan kuin “koko luokalle”, toisaal-ta kaikille, toisaalta ei millekaan oliolle. Esimerkkeja tallaisista luo-kan “yhteisista” asioista ovat esim. paivaysluokalla taulukko, jossapidetaan muistissa kuukausien pituudet, tai merkkijonoluokalla tietosiita, mika on merkkijonojen maksimipituus. Samoin ohjelman tes-tausvaiheessa saattaisi olla mukavaa pitaa kirjaa siita, montako jon-kin luokan oliota ohjelman ajon aikana on luotu, seka tarjota funktio,jolla tuon tiedon saa tulostettua.

Tallaiset luokalle yhteiset asiat pitaisi tietysti jotenkin sitoa itseluokkaan ja kapseloida niin, etta vain kayttajalle tarkoitettu “luokanrajapinta” nakyy ulospain. Taman saavuttamiseen eri oliokielissa onkaytetty erilaisia mekanismeja. Tassa teoksessa tutustutaan lyhyes-ti kahteen eri mekanismiin: Smalltalkin metaluokkiin ja luokkaolioi-hin seka C++:n luokkafunktioihin ja -muuttujiin. Jotkin ohjelmointi-

Page 249: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

8.2. Luokan rajapinta ja olion rajapinta 249

kielet sisaltavat myos kompromisseja naiden kahden valilla, esimer-kiksi Javan luokkaolioiden kasite on yhdistelma Smalltalkin ja C++:nmekanismeja.

8.2.1 Metaluokat ja luokkaoliot

Puhtaasti oliopohjainen lahestymistapa luokan yhteisiin asioihin on,etta luokan yhteisen datan ja operaatioiden taytyy olla jonkin olionattribuutteja ja operaatioita. Tama johtaa luontevasti ajatukseen, ettajokaista luokkaa vastaa yksikasitteinen olio, jonka vastuulla luokanyhteiset asiat ovat. Viela vahan pidemmalle vietyna saadaan aikaanoliomalli, jossa jokainen luokka on olio, luokkaolio (class object), jokahoitaa kaikki koko luokan vastuulla olevat asiat.

Smalltalkissa on otettu juuri tama lahestymistapa luokkiin, ja se onitse asiassa oleellinen osa kielen toteutusta. Uusien olioiden luomi-nen on tietysti yksi asia, joka kuuluu luokan vastuualueeseen. Niin-pa Smalltalkin operaatio new, jolla luodaan uusi olio, on itse asiassaluokkaa vastaavan luokkaolion jasenfunktio, joka luo uuden olion japalauttaa sen paluuarvonaan.

Smalltalkissa luokkaolion nimi on sama kuin itse luokan nimija luokkaoliota voi kayttaa kaikkialla missa normaaleitakin olioita.Luokkaolion voi esimerkiksi antaa parametrina toiselle oliolle, jokakayttaa sita luomaan luokasta uusia olioita.

Ongelmaksi jaa, etta jokaisen olion pitaa kuulua johonkin luok-kaan. Jos kerran luokkakin on vain olio, mihin luokkaan se sittenkuuluu? Smalltalkin ratkaisu on sanoa, etta jokainen luokkaolio kuu-luu omaan metaluokkaansa (metaclass), jonka ainoa olio se on. Kaik-ki metaluokat puolestaan on periytetty luokasta Class, joka sisaltaakaikki kaikille luokille yhteiset ominaisuudet. Nain metaluokat muo-dostavat keskenaan luokkahierarkian, joka on rakenteeltaan tasmal-leen samanlainen kuin tavallisten luokkien muodostama hierarkia.Kuvassa 8.1 seuraavalla sivulla on esimerkkina osa metaluokkien jatavallisten luokkien muodostamaa hierarkiaa.

Smalltalkin metaluokkahierarkia antaa mahdollisuuden varsinmielenkiintoisiin olioratkaisuihin, ja sen avulla voidaan saada aikaanmm. samanlaisia rakenteita kuin C++:n mallien (aliluku 9.5) avulla.Tassa teoksessa ei aiheeseen kuitenkaan keskityta enempaa, muttaasiasta kiinnostuneelle sopivaa kirjallisuutta on mm. “An Introduc-

Page 250: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

8.2. Luokan rajapinta ja olion rajapinta 250

Object

Eläin

Nisäkäs

"MetaEläin"

"MetaNisäkäs"

Class

1

1

Olion tiedot ja jäsenfunktiot

kuuluu

kuuluu

kuuluu

Jyke

Metaluokkahierarkia Luokkahierarkia

Olio

Luokkaolio

Luokan yhteiset tiedot ja palvelut

KUVA 8.1: Luokka- ja metaluokkahierarkiaa Smalltalkissa

tion to Object-oriented Programming, 3rd edition” [Budd, 2002, luku25].

8.2.2 C++: Luokkamuuttujat

C++:n kehittajat paattivat, etta metaluokista ja luokkaolioista saatavahyoty oli turhan pieni niiden toteutusvaivaan nahden. Taman vuoksiC++:ssa on kaytossa erilainen mekanismi luokan yhteisia asioita var-ten.

Jos jokin muuttuja on luonteeltaan sellainen, etta se kuuluu kokoluokalle eika vain yhdelle oliolle (siis muuttujan halutaan olevan luo-kan kaikille olioille yhteinen), muuttuja esitellaan luokan esittelyssaluokkamuuttujana (static data member). Luokkamuuttujan esittelyon muuten samanlainen kuin jasenmuuttujankin, mutta esittely al-

Page 251: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

8.2. Luokan rajapinta ja olion rajapinta 251

kaa avainsanalla static:

class X

...int jasenmuuttuja ;static int luokkamuuttuja ;

;

Luokkamuuttuja on luonteeltaan hyvin lahella normaalia globaa-lia muuttujaa: sen elinkaari on ohjelman alusta ohjelman loppuunsaakka. Luokkamuuttuja on siis olemassa, vaikka luokasta ei olisiviela luotu ainuttakaan oliota. Koska luokkamuuttuja ei ole minkaanolion oma, sen alustamista ei voi tehda rakentajan alustuslistassa ja-senmuuttujien tapaan, vaan jossain kooditiedostossa taytyy olla erik-seen luokkamuuttujan maarittely, jonka yhteydessa muuttuja aluste-taan:

// Yleensa samassa kooditiedostossa kuin jasenfunktioiden koodiint X::luokkamuuttuja = 2;

Luokan omassa koodissa luokkamuuttujaan voi viitata aivan ku-ten jasenmuuttujaankin, suoraan sen nimella. Luokan ulkopuo-lelta niihin voi viitata syntaksilla Luokka::lmuuttuja (esimerkissaX::luokkamuuttuja ). Toinen (harvemmin kaytetty) tapa on viitataluokkamuuttujiin luokan olion kautta ikaan kuin luokkamuuttuja oli-si jasenmuuttuja:

X xolio;int arvo = xolio.luokkamuuttuja ;

Luokkamuuttujille patevat hyvin pitkalle samat tyylisaannot kuinjasenmuuttujillekin, mm. luokkamuuttujat olisi hyva pitaa luokanprivate-puolella. Tahan on kuitenkin yksi yleinen poikkeus — luok-kavakiot. Varsin usein luokkaan liittyy epalukuinen maara rajoituk-sia, maksimiarvoja yms. lukuja, joihin perinteisesti olisi kaytetty#define-vakioita tai globaaleja vakioita. Huomattavasti parempi kay-tanto on kuitenkin upottaa tallaiset vakiot luokan sisaan, jolloin onselvaa, mita osaa ohjelmasta ne koskevat. Samalla myos nimikonflik-tien vaara pienenee.

Page 252: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

8.2. Luokan rajapinta ja olion rajapinta 252

Luokkavakioita saa tehtya yksinkertaisesti luomalla luokkamuut-tuja, joka on maaritelty vakioksi const-maareella. Ongelmaksi jaa-vat vain tilanteet, joissa tallaisen vakion tulee olla kaannosaikainenvakio. Mikali vakion alustaminen jatetaan mielivaltaiseen kooditie-dostoon, kaantaja ei loyda sita eika pysty kayttamaan vakion arvoakaannosaikana. Tama ratkaisemiseksi C++-standardiin otettiin mu-kaan mahdollisuus alustaa kokonaislukutyyppiset luokkavakiot suo-raan luokan esittelyssa. Tyypillisesti tallaiset vakiot sijoitetaan luo-kan public-osaan, koska niiden kayttotarkoituksena on juuri nakyaluokasta ulospain:

class Ypublic:static int const MAX PITUUS = 80;...

;

Vaikka tallaiset luokkavakiot alustetaankin luokan esittelyssa, netaytyy silti edelleen maaritella jossain kooditiedostossa (mutta alus-tusta ei enaa toisteta):

// Yleensa samassa kooditiedostossa kuin jasenfunktioiden koodiint const Y::MAX PITUUS;

Luokkavakioita voi kayttaa luokan omassa koodissa tavallistenluokkamuuttujien tapaan.

8.2.3 C++: Luokkafunktiot

Luokkafunktiot (static member function) muistuttavat jasenfunktioitasamalla tavalla kuin luokkamuuttujat jasenmuuttujia. Luokkafunktiotedustavat sellaisia luokan palveluja ja operaatioita, jotka eivat koh-distu mihinkaan yksittaiseen olioon, vaan “koko luokkaan”. Tallaisiaoperaatioita ovat mm. luokkamuuttujien kasittely, mutta on tietystimahdollista etta luokan vastuualueeseen kuuluu muitakin “luokan-laajuisia” operaatioita. Luokkafunktiot esitellaan luokan esittelyssa

Page 253: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

8.3. Tyypit osana rajapintaa 253

lisaamalla niiden eteen sana static:

class Xpublic:int jasenfunktio();static int luokkafunktio();...

;

Luokkafunktioiden maarittely kooditiedostossa puolestaan ei eroajasenfunktion maarittelysta. Ainoat rajoitukset ovat, etta koska luok-kafunktio ei kohdistu mihinkaan olioon, sen koodissa ei voi viitatajasenmuuttujiin, jasenfunktioihin eika this-osoittimeen. Sen sijaanluokkafunktion koodissa voi vapaasti kayttaa luokkamuuttujia ja toi-sia luokkafunktioita.

Listaus 8.3 seuraavalla sivulla sisaltaa esimerkkina luokan, jokapitaa kirjaa siita, montako luokan oliota ohjelman aikana on luotu jatuhottu. Laskurit on talletettu luokkamuuttujina luotu ja tuhottu jane pystyy tulostamaan luokkafunktion tulosta tilasto avulla.

8.3 Tyypit osana rajapintaa

Listauksessa 8.4 sivulla 255 on osa tyypillista paivaysluokan rajapin-taa. Aiemmin tallaista rajapintaa mainostettiin erityisesti sen vuoksi,etta sen sanottiin piilottavan luokan toteutuksen kayttajalta, jolloinluokan toteutusperiaatteen muuttaminen on helppoa.

Kieltamatta listauksen paivaysluokassa on nyt mahdollista esittaapaivays sisaisesti mita erilaisimmilla tavoilla, mutta luokan rajapintaon myos lupaus. Rajapinnalla luokka lupaa, etta sita voi kayttaa raja-pinnan mukaan, ja tasta lupauksesta on pidettava kiinni. Toisaalta ta-ma lupaus sitoo myos luokan mahdollisia toteutuksia, toisin sanoenmuutettiinpa luokan toteutusta milla tavoin hyvansa, niin rajapinnanon pysyttava tasmalleen samana.

Kuvitellaanpa esimerkiksi, etta paivaysluokkaa on onnistuneestikaytetty jo pitkaan sukupuuohjelmassa ja kaikki on toiminut mai-niosti. Myohemmin huomataan, etta periaatteessa mikaan ei esta pai-vayksen vuosiluvun menemista negatiiviselle puolelle, jos suvun jo-

Page 254: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

8.3. Tyypit osana rajapintaa 254

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . oliolaskuri.hh . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .1 #ifndef LASKIJA HH2 #define LASKIJA HH34 class Laskija5 6 public:7 Laskija(int luku);8 ~Laskija();

...9 static void tulosta tilasto();

10 private:11 int luku ;

...12 static unsigned int luotu ;13 static unsigned int tuhottu ;14 ;1516 #endif

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . oliolaskuri.cc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .1 #include "oliolaskuri.hh"23 #include <iostream>4 using std::cout;5 using std::endl;67 Laskija::Laskija(int luku) : luku (luku)8 9 ++luotu ;

10 1112 Laskija::~Laskija()13 14 ++tuhottu ;15 1617 void Laskija::tulosta tilasto()18 19 cout << "Olioita luotu: " << luotu << endl;20 cout << "Olioita tuhottu: " << tuhottu << endl;21 2223 unsigned int Laskija::luotu = 0;24 unsigned int Laskija::tuhottu = 0;

LISTAUS 8.3: Esimerkki luokkamuuttujien ja -funktioiden kaytosta

Page 255: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

8.3. Tyypit osana rajapintaa 255

1 class Paivays2 3 public:4 Paivays(unsigned int p, unsigned int k, unsigned int v);5 ~Paivays();67 void asetaPaiva(unsigned int paiva);8 void asetaKk(unsigned int kuukausi);9 void asetaVuosi(unsigned int vuosi);

1011 unsigned int annaPaiva() const;12 unsigned int annaKk() const;13 unsigned int annaVuosi() const;1415 void etene(int n);16 int paljonkoEdella(Paivays const& p) const;1718 private:19 unsigned int paiva ;20 unsigned int kuukausi ;21 unsigned int vuosi ;22 ;

LISTAUS 8.4: Tyypillinen paivaysluokan esittely

takin haaraa voidaan seurata todella pitkaan (todellisuudessa tallai-nen mahdollisuus lienee korkeintaan joillain kuningassuvuilla). Eipahataa, ohjelma on tehty olio-ohjelmointia kayttaen, joten paivaysluo-kan sisaisen toteutuksen muuttaminen on helppoa. Ohjelman tekijalupaa puhelimessa korjata ohjelman tunnissa ja alkaa tutkia koodia.

On kylla totta, etta luokan sisaisen toteutuksen muuttaminenon todella helppoa — ainoa muutos on muuttaa rivi 21 muo-toon “int vuosi ;”. Tama ei kuitenkaan ratkaise ongelmaa, kos-ka luokan rajapinnassa kaytetaan vuosilukujen kasittelyyn tyyppiaunsigned int.

Seuraava ratkaisuyritys on luonnollisesti muuttaa uusi tyyppimyos rajapintaan. Tama vaatii jonkin verran tarkkuutta (jotta kaikistarajapinnan unsigned int -tyypeista muutetaan vain ja ainostaan tar-peelliset), mutta on suhteellisen helppo huomata, etta rakentajan jaasetaVuosi-jasenfunktion vuosi-parametrin tyyppi pitaa korjata, sa-moin annaVuosi-jasenfunktion paluutyyppi. Helpotuksesta huokais-ten ohjelmoija kaantaa ohjelman uudelleen ja aloittaa testauksen.

Page 256: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

8.3. Tyypit osana rajapintaa 256

Testaus osoittaa, etta ohjelma toimii vaarin!Vikana on, etta alkuperainen paivaysluokan rajapinta vihjaa kayt-

tajalle varsin voimakkaasti, etta paivayksista saatavia vuosilukuja saaja tuleekin tallettaa unsigned int -tyyppisiin muuttujiin. Niinpa oh-jelmasta saattaa loytya paljonkin koodia, joka nayttaa vaikkapa seu-raavalta:

unsigned int syntymaVuosi = henkilo.syntymapvm.annaVuosi();if (syntymaVuosi % 10 == 0)

cout << "Syntynyt tasakymmenluvulla" << endl;

Kun nyt vuodet muutetussa luokassa palautetaankin int-tyyppisina, tehdaan ensimmaisella rivilla tyyppimuunnosint ⇒ unsigned int. Mikali syntymavuosi sattuu olemaan ne-gatiivinen, sita ei voi esittaa etumerkittomalla luvulla, jotensyntymaVuosi-muuttujaan tallettuu vaara arvo!]

Esimerkin kaltaisten tapausten vuoksi on tarkea ymmartaa, ettamyos rajapinnan tyypit ovat osa rajapintaa. Tama koskee niin pa-rametrien tyyppeja kuin paluutyyppejakin. Mikali rajapinta maaraatyypin tietyksi, alkaa rajapintaa kayttava koodi helposti luottaa tyy-pin sailymiseen samana. Mikali alkuperainen paivaysluokka olisi “ti-laa saastaakseen” kayttanyt unsigned char-tyyppia vuosiluvun valit-tamiseen, olisi vuosi 2000 -ongelma paassyt syntymaan olio-ohjel-moinnista huolimatta.

Ratkaisu ongelmaan on toistaa tuttua kapselointiperiaatetta uu-delleen: kapseloidaan rajapinnan tyypit omien tyyppinimien taakse,ja vaaditaan kayttajia kayttamaan tarjottuja tyyppeja. Listaus 8.5 seu-raavalla sivulla sisaltaa parannellun paivaysluokan esittelyn. Riveilla5–8 esitellaan kaikki luokan rajapinnassa kaytetyt tyypit ja annetaanniille nimi. Taman jalkeen kaikkialla luokassa kaytetaan paivanume-ron yhteydessa tyyppia PaivaNro jne.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .]C++-standardissa sanotaan, etta negatiivinen luku muuntuu etumerkittomaksi luvuksi niin,

etta uusiluku ≡ vanhaluku (mod 2n), missa n on etumerkittoman luvun bittien luku-maara.

Page 257: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

8.3. Tyypit osana rajapintaa 257

1 class Paivays2 3 public:4 // Luokan kayttamat tyypit5 typedef unsigned char PaivaNro;6 typedef unsigned char KkNro;7 typedef unsigned long int VuosiNro;8 typedef long int Erotus;9

10 Paivays(PaivaNro p, KkNro k, VuosiNro v);11 ~Paivays();1213 void asetaPaiva(PaivaNro paiva);14 void asetaKk(KkNro kuukausi);15 void asetaVuosi(VuosiNro vuosi);1617 PaivaNro annaPaiva() const;18 KkNro annaKk() const;19 VuosiNro annaVuosi() const;2021 void etene(Erotus n);22 Erotus paljonkoEdella(Paivays p) const;2324 private:25 PaivaNro paiva ;26 KkNro kuukausi ;27 VuosiNro vuosi ;28 ;

LISTAUS 8.5: Parannettu paivaysluokan esittely

Luokan kayttajan tulee nyt kayttaa nimettyja tyyppeja:

Paivays::VuosiNro syntymaVuosi =henkilo.syntymapvm.annaVuosi();

if (syntymaVuosi % 10 == 0)

cout << "Syntynyt tasakymmenluvulla" << endl;

Jos nyt taman paivaysluokan kanssa tulee tarve sallia negatiivisetvuodet, on muutoksen tekeminen helppoa: muutetaan rivi 7 muotoon

typedef long int VuosiNro;

Tama vaihtaa vuosinumeroiden kasittelyn kaikkialla:

Page 258: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

8.4. Ohjelmakomponentin sisaiset rajapinnat 258

• Luokan rajapinnassa kaikki vuosinumerot muuttuvat etumer-killisiksi.

• Mikali luokan kayttajat ovat kayttaneet tyyppiaPaivays::VuosiNro omassa koodissaan, heidankin koodin-sa muuttuu muutoksen mukaiseksi.

• Mikali luokan toteutuksessa on kaytetty VuosiNro-tyyppia, par-haassa tapauksessa toteutuksen koodiin ei tarvitse koskea ollen-kaan, koska muutos tapahtuu automaattisesti.

Luokan sisalla maariteltyihin tyyppeihin liittyy yksi C++:n omi-tuisuus, joka on hyva tietaa. Luokan jasenfunktioiden toteutuksissa(siis itse koodissa) kaantaja tietaa vasta jasenfunktion nimen luet-tuaan, minka luokan jasenfunktiosta on kysymys. Niinpa kaantajaei jasenfunktion paluutyyppia lukiessaan viela tieda, mita luokkaaollaan kasittelemassa, eika se nain ollen myoskaan osaa automaat-tisesti hyvaksya luokan maarittelemia tyyppeja. Taman vuoksi tay-tyy jasenfunktioiden toteutuksissa paluutyypit kirjoittaa muodossaLuokkanimi::Tyyppi, jos ne ovat luokan sisaisia tyyppeja. Sen sijaanjasenfunktioiden parametrilistassa ja itse koodissa voi luokan omiatyyppeja kayttaa suoraan ilman luokan nimea. Listaus 8.6 nayttaa esi-merkkina luokan Paivays jasenfunktion annaPaiva toteutuksen.

8.4 Ohjelmakomponentin sisaiset rajapinnat

Joskus ohjelmiston rakenteesta tulee kaikesta yrittamisesta huolimat-ta sellainen, etta kaikkea luokan sisaiseen toteutukseen liittyvaa toi-minnallisuutta ei saada luokan sisaan kapseloiduksi, vaan osa siitajoudutaan — usein ohjelmointikielen syntaksista johtuvista syista —toteuttamaan luokan ulkopuolisissa funktioissa. Talloin ongelmaksitulee, etta vain luokan omilla funktioilla on paasy luokan sisaiseen

1 Paivays::PaivaNro Paivays::annaPaiva() const2 3 return paiva ;4

LISTAUS 8.6: Luokan maarittelema tyyppi paluutyyppina

Page 259: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

8.4. Ohjelmakomponentin sisaiset rajapinnat 259

toteutukseen, joten luokan ulkopuolinen funktio joutuu periaatteessakayttamaan luokkaa sen julkisen rajapinnan kautta.

Yleensa tilanne on sellainen, etta vaikka ongelmallinen funktioonkin luokan ulkopuolinen, se kuitenkin kuuluu kasitteellisesti sa-maan moduuliin tai komponenttiin kuin luokkakin. Taten ongelmanaei niinkaan ole se, etta ohjelmassa haluttaisiin rikkoa luokan tarjoa-maa kapselointia vaan se, etta ohjelmassa yhdelle funktiolle haluttai-siin sallia muuta ohjelmaa laajempi rajapinta luokan olioiden kaytta-miseen. Vastaavasti komponentin sisalla olevilla luokilla saattaa ollatarve kayttaa toisiaan tavoilla, joita ei haluta sallia komponentin ulko-puolella. Komponentin luokat tarvitsisivat talloin toisiinsa normaaliajulkista rajapintaa laajemman rajapinnan.

Mekanismit tallaisille komponentin sisaisille rajapinnoille vaih-televat suuresti ohjelmointikielesta toiseen. Joissain ohjelmointikie-lissa (kuten C) tukea ei ole juuri lainkaan, jolloin ohjelmoija joutuuturvautumaan dokumentointiin, sopimuksiin ja koodauskaytantoihintarjotakseen moduulin sisalle ulkomaailmalle nakyvaa laajemman ra-japinnan.

Sen sijaan Java tarjoaa pakkauksen (package)kasitteen, joka ontarkoitettu moduulien kirjoittamiseen (tata kasiteltiin lyhyesti ali-luvussa 1.6.2). Javan luokissa on puolestaan rajapintojen public,protected ja private lisaksi mahdollisuus “pakkauksen sisaiseen” ra-japintaan, joka saadaan aikaan kirjoittamalla jasenfunktio tai -muut-tuja ilman nakyvyysmaaretta. Naihin jasenfunktioihin ja -muuttujiinpaasee kasiksi myos muista saman pakkauksen luokista.

C++:ssa vastaava rakenne saadaan aikaan ystavafunktioiden (friendfunction) ja ystavaluokkien (friend class) avulla. Niita kasitellaan tar-kemmin seuraavissa aliluvuissa.

8.4.1 C++: Ystavafunktiot

C++ tarjoaa varsin karkean mekanismin laajemman rajapinnan tarjoa-miseen: luokka voi julistaa joukon funktioita “ystavikseen”. Luokanystavafunktioiden koodi paasee kasiksi myos luokan olioiden private-osiin, eli kaytannossa silla on samat oikeudet luokan olioihin kuin

Page 260: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

8.4. Ohjelmakomponentin sisaiset rajapinnat 260

luokan omilla jasenfunktioilla. Esittelyn syntaksi on

class Luokka

...friend paluutyyppi funktionnimi(parametrit);

;

On huomattava, etta kyseinen esittely ei tee ystavafunktiosta luokanjasenfunktiota vaan kyseessa on taysin erillinen normaali funktio, jol-le vain sallitaan paasy luokan olioiden private-osaan.

Listauksessa 8.7 seuraavalla sivulla on esimerkki luokasta, jonkarakentaja on private-puolella. Tama tarkoittaa sita, etta luokan nor-maali kayttaja ei paase ollenkaan luomaan luokan olioita. Sen sijaanluokka maaraa funktion luoLukko ystavakseen, jolloin funktio voi luo-da olioita ja palauttaa niita osoittimen paassa paluuarvonaan. NainluoLukko-funktiolla on kaytossaan laajempi rajapinta kuin tavallisel-la kayttajalla.

Vaikka ystavamekanismi salliikin periaatteessa ystavafunktioilleoikeuden kapaloida olioiden private-osia aivan miten tahansa, kan-nattaa kuitenkin muistaa kapselointiperiaate. Yleensa koodin selkeyt-ta parantaa, jos ystavafunktiotkaan eivat kayta suoraan luokan sisais-ta toteutusta (jasenmuuttujia), vaan luokan private-puolelle kirjoite-taan sopivat jasenfunktiot, joiden kautta ystavafunktio saa kayttoon-sa laajemmat oikeudet. Talla tavoin luokan rajapinta sailyy edelleenfunktioista koostuvana.

8.4.2 C++: Ystavaluokat

Ystavafunktioiden avulla voidaan yhdelle funktiolle sallia normaaliavapaampi paasy tietyn luokan olioon. Jos ohjelmakomponentissa onkaksi toisiinsa kiinteasti liittyvaa luokkaa, voi kayda niin, etta luo-kassa on useita jasenfunktioita, joiden taytyy saada vapaampi paasytoisen luokan sisalle. Tama olisi mahdollista toteuttaa luettelemal-la kaikki luokan jasenfunktiot toisen luokan ystavafunktioiksi, muttatama on kompeloa. C++ antaa myos luokalle mahdollisuuden julistaatoinen luokka ystavakseen. Tama tarkoittaa sita, etta kaikki kyseisenluokan jasenfunktiot ovat automaattisesti ystavafunktioita.

Page 261: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

8.4. Ohjelmakomponentin sisaiset rajapinnat 261

1 class Lukko2 3 public:

...4 private:5 Lukko(); // Rakentaja private-puolella: olioiden luominen mahdotonta!6 ~Lukko(); // Purkaja private-puolella: olioiden tuhoaminen mahdotonta!

...7 friend Lukko* luoLukko(); // luo Lukko-olioita8 friend void poistaLukko(Lukko* lp); // tuhoaa Lukko-olioita9 ;

1011 Lukko* luoLukko()12 13 Lukko* lp = new Lukko; // Mahdollista, koska ystava

...14 return lp;15 16 void poistaLukko(Lukko* lp)17 18 delete lp; lp = 0; // Mahdollista, koska ystava19 20

LISTAUS 8.7: Laajemman rajapinnan salliminen ystavafunktioille

Luokkaystavyys saadaan aikaan maareellafriend class Luokkanimi sen luokan esittelyssa, joka haluaa sal-lia toisen luokan jasenfunktioille vapaan paasyn omien olioidensasisalle. Listaus 8.8 seuraavalla sivulla nayttaa esimerkin tallaisestaesittelysta.

Page 262: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

8.4. Ohjelmakomponentin sisaiset rajapinnat 262

1 class LainausJarjestelma2

...3 ;45 class KirjastonKirja6 7 public:

...8 Paivays const& annaPalautusPvm() const;9 private:

10 void asetaPalautusPvm(Paivays const& uusiPvm);11 Paivays palautusPvm ;

...12 friend class LainausJarjestelma;13 // LainausJarjestelman oliot kutsuvat funktiota asetaPalautusPvm14 ;

LISTAUS 8.8: Ystavaluokat

Page 263: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

263

Luku 9

Geneerisyys

ISOC++ 14.7.3/7: The placement of explicit specialization dec-larations for function templates, class templates, member func-tions of class templates, static data members of class templates, mem-

ber classes of class templates, member class templates of class templates,

member function templates of class templates, member functions of mem-

ber templates of class templates, member functions of member templates

of non-template classes, member function templates of member classes of

class templates, etc., and the placement of partial specialization declara-

tions of class templates, member class templates of non-template classes, member

class templates of class templates, etc., can affect whether a program is well-formed

according to the relative positioning of the explicit specialization declarations and

their points of instantiation in the translation unit as specified above and below.

When writing a specialization,be careful about its location;or to make it compilewill be such a trialas to kindle its self-immolation.

– International Standard 14882 [ISO, 1998]

Mallit kertovat meille, millainen jonkin asian pitaisi olla, milta jonkintulisi nayttaa tai miten jokin saadaan rakennetuksi. Jokaisella suoma-laisella on kasitys siita, mika sauna on. Saunan mallinen rakennussisaltaa loylytilan, pukuhuoneen ja vilvoittelualueen mielellaan ve-den aarella. Saunojen rakentamiseen on paljon perinnetietoa, ohjeita

Page 264: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

9.1. Yleiskayttoisyys, pysyvyys ja vaihtelevuus 264

ja jopa tutkimustuloksia. Voidaan sanoa, etta suomalaisten saunojenrakentaminen on dokumentoitu (mallinnettu) huolellisesti vuosisato-jen saatossa.

Vaikka ohjelmistojen tekeminen on hyvin nuori ala saunojen ra-kentamiseen verrattuna, tallakin alalla pyritaan hyodyntamaan aikai-sempia kokemuksia. Ohjelmistomallit pyrkivat kertomaan yleisia lin-joja (geneerisen mallin) siita, minkalainen ratkaisu parhaiten (koke-mukseen perustuen) sopisi kasilla olevaan ongelmaan.

9.1 Yleiskayttoisyys, pysyvyys ja vaihtelevuus

Uudelleenkayttoa mainostetaan yhtena olio-ohjelmoinnin suurenaetuna. Sen saavuttaminen ei kuitenkaan ole niin helppoa kuin pinta-puolisesti voisi luulla. Kun luokkaa tai ohjelmakomponenttia suunni-tellaan, se raataloidaan ja kehitetaan usein enemman tai vahemmantietoisesti juuri tiettyyn kayttoymparistoon. Talloin on todennakois-ta, ettei tuo komponentti kuitenkaan sovi tasmalleen samanlaisenatoiseen kayttotarkoitukseen — uudelleenkaytto ei onnistukaan puh-taassa muodossaan.

Syyna tahan on, etta vaikka yleisella tasolla jokin luokka tai kom-ponentti vaikuttaisikin “uudelleenkaytettavalta”, on sen kayttokoh-teilla kuitenkin hieman erilaisia tarpeita, jotka sitten vaikuttavat luo-kan rajapintaan tai toteutukseen. Taman vuoksi ollaan yha enemmansita mielta, etta uudelleenkayttoa on erittain vaikea saada aikaan “sat-tumalta”. Uudelleenkaytto vaatii sen, etta luokkaa tai komponenttiaensimmaista kertaa suunniteltaessa on edes jonkinlainen kasitys sii-ta, missa kaikissa ymparistoissa luokkaa tai moduulia tarvitaan jakaytetaan. Tassa mielessa olisikin ehka paras puhua yleiskayttoisyy-desta (genericity) mielummin kuin uudelleenkaytosta (re-usability).

Yleiskayttoisyyden suunnittelussa oleellinen asia on loytaa kom-ponentin mahdolliset kayttokohteet (tai edes arvata “todennakoises-ti kattava” osa niista) ja analysoida naiden kayttokohteiden tarpei-ta. Talloin saadaan kuva siita, missa osissa komponenttia eri kayt-tokohteiden tarpeet pysyvat tasmalleen samoina ja missa asioissa nevaihtelevat. Tallainen pysyvyys- ja vaihtelevuusanalyysi (common-ality and variability analysis) on tarkea osa yleiskayttoisen kompo-nentin suunnittelua, koska se kertoo, missa maarin mahdollisuutta

Page 265: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

9.1. Yleiskayttoisyys, pysyvyys ja vaihtelevuus 265

yleiskayttoisyyteen sovellusalueessa on ja missa osissa komponent-tia se ilmenee. [Coplien, 1999]

Aarimmaisessa tapauksessa pysyvyys- ja vaihtelevuusanalyysisaattaa antaa tulokseksi, etta kaikkien kayttokohteiden tarpeet suun-niteltavan komponentin kannalta ovat tasmalleen samat. Talloinyleiskayttoisyys on erittain helppoa saada aikaan, koska tasmalleensamanlainen komponentti kelpaa kaikkialle, ja tehtavaksi jaa enaakomponentin toteuttaminen perinteisin keinoin. Tama on tietystiihannetilanne, joka esiintyy varsin harvoin. Yleensa kayttokohteidentarpeissa on jonkinlaisia eroja, ainakin jos suunniteltava komponenttiei ole todella pieni.

Toisessa aaripaassa saatetaan huomata, etta vaikka aluksi eri kayt-tokohteet nayttivatkin tarvitsevan samanlaista ohjelmakomponenttia,ovat niiden tarpeet niin erilaiset, ettei taydellisia yhtalaisyyksia kayt-tokohteiden valilla kuitenkaan ole. Tallaisessa tapauksessa vaihtele-vuus on siis niin suurta, etta kaytannossa suunniteltava komponent-ti jouduttaisiin raataloimaan kokonaan jokaista kayttokohdetta koh-ti. Talloin yleiskayttoisyytta ei tietenkaan voida saavuttaa ollenkaan.Onneksi tallaiset tapaukset ovat myos erittain harvinaisia.

Tyypillinen tulos pysyvyys- ja vaihtelevuusanalyysista on, ettaosa komponentista voidaan pitaa tasmalleen samanlaisena, osa taastaytyy toteuttaa eri tavalla eri kayttokohteisiin. Haasteena on toteut-taa yleiskayttoinen komponentti niin, etta pysyvat osat ja vaihtelevatosat saadaan kokonaan eriytettya toisistaan.

Talloin komponentin kayttaminen helpottuu, koska ohjelmoijalleon selvaa, mitka osat hanen taytyy raataloida tai kirjoittaa uudelleenjuuri tiettya kayttokohdetta varten, mitka osat taas kaytetaan aina sel-laisinaan. Mikali analyysi on tehty kunnolla, on viela lisaksi toden-nakoista, etta kun tulevaisuudessa kayttokohteita tulee lisaa, pysy-vat osat kelpaavat sellaisenaan niihinkin. Kuva 9.1 seuraavalla sivul-la esittaa pysyvyys- ja vaihtelevuusanalyysin toimintaa graafisesti.

Oman kiemuransa tallaiseen yleiskayttoisen komponentin suun-nitteluun tuo kaiken lisaksi se, etta varsin usein “kayttokohteiden”valinta on varsin mielivaltaista. Tyypillisesti mita suppeammaksikomponentin kayttokohdevalikoima maaritellaan, sita enemman py-syvyytta komponenttiin saadaan, koska kayttokohteiden keskinaisiaeroja on vahemman. Talloin suuri osa komponentin koodista on tay-dellisesti uudelleenkaytettavaa, mutta sen kayttoalue on suppea.

Page 266: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

9.1. Yleiskayttoisyys, pysyvyys ja vaihtelevuus 266

Sovelluskohde 1 Sovelluskohde 2 (Tuleva sovelluskohde)

Pysyvä osa

Vaihtelevat osat

(räätälöinti)

Pysyvyys−

ja vaihtelevuus−

analyysi

KUVA 9.1: Pysyvyys- ja vaihtelevuusanalyysi ja yleiskayttoisyys

Toisaalta, jos kayttokohteiden kirjo on suuri, vaihtelevuuden maa-ra kasvaa suureksi. Tuloksena on komponentti, joka on kylla todellayleiskayttoinen, mutta jossa samanlaisena uudelleenkaytettavan koo-din maara on kuitenkin vahainen. Kuten niin usein ohjelmistoteknii-kassa tassakin taitoa vaatii sopivan kompromissin loytaminen, jottahyodyt saadaan maksimoitua.

Olennaiseksi kysymykseksi yleiskayttoisyydessa muodostuuyleensa se, miten komponentin pysyvyys ja vaihtelevuus saadaanerotettua toisistaan selkeasti. Tahan on olemassa lukematon maaraerilaisia vaihtoehtoja tilanteesta riippuen. Niita kasitellaan tarkem-min esimerkiksi kirjassa “Generative Programming” [Czarnecki jaEisenecker, 2000]. Aliluvussa 9.2 esiteltavat suunnittelumallit ovat

Page 267: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

9.2. Suunnittelun geneerisyys: suunnittelumallit 267

eraanlainen esimerkki suunnittelutason pysyvyyden ja vaihtelevuu-den hallinnasta. Aliluvussa 9.4 kuvataan lyhyesti pysyvyyden javaihtelevuuden hallintaa periytymisen avulla. Aliluvussa 9.5 taaskasitellaan C++:n mallit (template), jotka antavat mahdollisuudenperiytymisesta poikkeavaan yleiskayttoisyyteen.

9.2 Suunnittelun geneerisyys: suunnittelumallit

Olio-ohjelmassa useat oliot toteuttavat yhdessa ohjelmiston toimin-nallisuuden. Hyvin suunniteltu luokka mahdollistaa sen toteutta-man rakenteen uudelleenkayton myos muualla kuin alkuperaisessakayttokohteessa. Vastaavasti voimme uudelleenkayttaa usean luokanmuodostaman toiminnallisen kokonaisuuden ja jopa yleistaa tamanuudelleenkayton saannostoksi, joka kertoo, miten jokin yleisempi on-gelma voidaan ratkaista kyseisen oliojoukon avulla. Tallaisia saan-nostoja nimitetaan olio-ohjelmoinnin suunnittelumalleiksi (designpattern).

Suunnittelumalleja voi esiintya usealla tasolla. Yleisen tason mal-li (arkkitehtuurimalli) voi kertoa periaatteen koko ohjelmiston raken-teesta (esimerkiksi WWW-sovelluksen osien jako http-palvelimen,“cgi-skriptien” ja tietokannan ohjauksen kesken). Keskitaso on mo-duulisuunnittelussa, jossa suunnittelumalli antaa mallin muutamanluokan (tyypillisesti 3–7) muodostamasta toiminnallisesta kokonai-suudesta. Lahinna toteutusta ovat mallit (toteutusmalli, idiomi), jot-ka liittyvat tietyn ongelman ratkaisemiseen ohjelmointikielen tasol-la (esimerkiksi funktionaalisten kielten rakenne map, joka kohdis-taa maaratyn funktion kutsun yksitellen listan jokaiselle alkiolle)[Haikala ja Marijarvi, 2002]. Puhuttaessa suunnittelumalleista ilmanlisamaareita tarkoitetaan yleensa keskitason oliosuunnitteluun liitty-via malleja.

9.2.1 Suunnittelumallien edut ja haitat

Edella mainitusta seuraa helposti, etta suunnittelumalliksi voidaanjulistaa melkein mika tahansa “ei-triviaali” ohjelmakoodin tai suun-nitelman osa. Tama ei kuitenkaan ole tarkoitus. Yleisiksi suunnit-telumalleiksi on tarkoitus kerata kaytannossa usein kaytettyja mal-liratkaisuja. Ensimmainen suunnittelumallien kokoelma oli niin sa-

Page 268: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

9.2. Suunnittelun geneerisyys: suunnittelumallit 268

nottu “Gang of Four” (GoF) -kirja [Gamma ja muut, 1995], johon onvalittu ainoastaan sellaisia suunnittelumalleja, joille on loytynyt va-hintaan kaksi toisistaan riippumatonta kayttajakuntaa (ohjelmistota-loa tai -projektia). Tallaiset yleiset suunnittelumallit tulisi pikkuhil-jaa saada osaksi jokaisen ohjelmistosuunnittelijan tietamysta — tallahetkella on vain hyvin vaikea arvioida, mitka mallit ohjelmistosuun-nittelijan yleissivistykseen tulisi kuulua.

Parhaimmillaan suunnittelumalli on abstrakti suunnitteludoku-mentti, joka voidaan ottaa kayttoon ohjelmiston suunnittelussa he-ti, kun suunnittelija tunnistaa ongelmasta kohdan, johon malli sopii.Yleista tietamysta (osa ohjelmoijan yleisivistysta) olevien mallien li-saksi suunnittelumallit ovat erinomainen tapa kerata talteen organi-saatiossa olevaa sovellusaluekohtaista tietoa, joka nain sailyy, vaikkaihmiset vaihtuvatkin.

Jotta suunnittelumalleista olisi hyotya, ne tulisi dokumentoidahuolellisesti ja niiden tulisi olla helposti omaksuttavissa. Dokumen-tointiin on maaramuotoisten dokumentointipohjien lisaksi kehitettymuodollisempia menetelmia. Mallikieli (pattern language) on nimi-tys kokoelmalle saman sovellusalueen toisiinsa liittyvia suunnittelu-malleja ja tiedolle siita, miten naita malleja voidaan sovellusalueellahyodyntaa ja yhdistella.

Suunnittelumallit itsessaan eivat auta mitaan, elleivat niita kayt-tavat suunnittelijat tieda, mita mikin malli tarkoittaa. Nykyisinuusien suunnittelumallien loytaminen ja julkaiseminen vaikuttaahieman riistaytyneen kasista, eika kukaan yksittainen ohjelmoija eh-di opetella kaikkia uusia malleja. Mallien muistamista ja opetteluahaittaa myos usein niiden sidonnaisuus englannin kieleen — vaa-tii erinomaista kielitaitoa saada oikea mielleyhtyma, jos suunnittelu-mallin nimena on esimerkiksi Memento [Gamma ja muut, 1995, s.283–291] tai The Percolation Pattern [Binder, 1999].

9.2.2 Suunnittelumallin rakenne

Yksi olio-ohjelmien perustoimenpiteista on kasitella joukkoa olioitavaikkapa kutsumalla joukon jokaiselle oliolle jotain palvelurutiinia.Polymorfismin yhteydessa (aliluku 6.5.2) naimme esimerkin oliojou-kon kasittelysta luokkahierarkian avulla siten, etta kasittely tapahtuiyhteisen kantaluokan kautta. Joukkojen kasittelyssa seuraava luon-teva askel on tarve maaritella alijoukkoja. Naita uusia oliokokoelmia

Page 269: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

9.2. Suunnittelun geneerisyys: suunnittelumallit 269

olisi luontevaa kasitella samoissa paikoissa kuin kantaluokan olioita,koska muutoin olioita kasittelevan asiakasohjelmakoodin tulisi toi-mia aina eri tavoin sen mukaan, onko silla kasittelyssa hierarkian pe-rusolio vai kokoelmaolio. Ratkaisuna maarittelemme hierarkiaan uu-den luokan Kokoelma, joka osaa kasitella joukkoa alkuperaisia olioitakantaluokan rajapinnan mukaisesti (katso kuva 9.2).

Kokoelman avulla voidaan luoda esimerkiksi kuvassa 9.3 seuraa-valla sivulla esitetty rakenne. Kun luokan Kokoelma palvelu “piirra”maaritellaan kutsumaan samaa palvelua jokaiselle kokoelmassa ole-valle oliolle, saamme sailytetyksi myos alkuperaisen toiminnallisuu-den, jossa taulukon kaikki grafiikkaoliot piirtyvat nayttolaitteelle kas-kettaessa. Nyt silmukka, joka kutsuu palvelua “piirra” taulukon nel-jalle alkiolle, aiheuttaa palvelun kutsumisen kokoelman kautta kai-kille rakenteessa oleville perusolioille (kuvassa id-tunnisteet 1, 2, 5,6, 7, 8 ja 9).

Kuvan 9.2 rakenne on vasta tiettyyn tarkoitukseen laadittu suun-nitelma. Siita saadaan kuitenkin yleiskayttoisempi, kun jatammerakenteesta pois grafiikkaesimerkkimme ja keskitymme ainoastaan

piirrä()

piilota()

Viiva

Näkyvyys

piirrä() abstract

onkoNäkyvissä: bool

piilota() abstract

piirrä()

piilota()

poista( index )

palauta_jäsen( index )

Kokoelma

lisää( obj : Näkyvyys )piirrä()

piilota()

Ympyrä

KUVA 9.2: Kokoelman toteuttava luokka

Page 270: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

9.2. Suunnittelun geneerisyys: suunnittelumallit 270

id01: Viiva id02: Ympyrä id03: Kokoelma

id04: Kokoelma id07: Ympyrä

id05: Viiva id06: Viiva

id08: Viiva

id09: Ympyrä

taulukko[4] : Näkyvyys

KUVA 9.3: Taulukko joka sisaltaa kokoelmia

olioiden kokoelmaan luokkahierarkiassa ja kantaluokan rajapinnansailyttamiseen. Rakenteen tarkoituksena on tarjota saman kantaluo-kan kaksi eri variaatiota: maaratyilla operaatioilla toimivat oliot (kan-taluokka on rajapintakuvaus) ja kokoelma naita olioita. Rakenteennormaali kayttotarkoitus on ainoastaan rajapinnan kaytto ilman tie-toa siita, onko kaytossa kokoelma vaiko perusolio. Nailla ehdoillasaamme kuvassa 9.5 sivulla 272 nakyvan rakenteen. Kyseessa onsuunnittelumalli nimelta Kokoelma (Composite) [Gamma ja muut,1995, s. 163–173], joka on tarkemmin kuvattu aliluvussa 9.3.1.

UML maarittelee suunnittelumalleja varten oman piirrossymbo-linsa, jossa kerrotaan ainoastaan mallin nimi (tama erityisesti koros-taa sita, etta suunnittelijan ja toteuttajan oletetaan tietavan heti mal-lin nimesta kaiken siihen liittyvan). Malliin liitetaan tavallaan pa-rametreina olioita, jotka toteuttavat suunnittelumallin maarittelematosat. Naiden todellisten ohjelman olioiden sanotaan esiintyvan suun-nittelumallin maarittelemassa roolissa (role). Esimerkki suunnittelu-mallin kuvaamisesta UML:lla on kuvassa 9.4 seuraavalla sivulla, jos-sa luokka Nakyvyys on roolissa Kantaluokka, Ympyra on Perusolio jaNakyvyysLista on Kokoelma.

Page 271: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

9.3. Valikoituja esimerkkeja suunnittelumalleista 271

Kokoelma

Kokoelma

Perusolio

Kantaluokka

(a) Suunnittelumalli

NäkyvyysLista

Ympyrä

Kokoelma

Näkyvyys abstract

Kokoelma

Perusolio

Kantaluokka

(b) Mallin kaytto

KUVA 9.4: Suunnittelumallin UML-symboli ja sen kaytto

9.3 Valikoituja esimerkkeja suunnittelumalleis-ta

Tassa aliluvussa esitellaan kolme melko yksinkertaista mutta hyvinyleisessa kaytossa olevaa suunnittelumallia. Niista nakyy GoF-kirjanensimmaisena kayttoon ottama suunnittelumallien dokumentointi-muoto, jossa esitellaan mallin kayttotarkoitus, perustelu sille miksimalli on yleiskayttoinen ratkaisu useaan ongelmaan ja mallin luok-karakenteen kuvaus UML:n avulla.

9.3.1 Kokoelma (Composite)

Tarkoitus: Esittaa olioita hierarkkisesti toisistaan koostuvina siten,etta koosteolioita ja niiden osia voidaan kayttaa samalla tavalla.[Gamma ja muut, 1995, s. 163–173]

Page 272: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

9.3. Valikoituja esimerkkeja suunnittelumalleista 272

Perustelu: Oliokokoelmissa tarvitaan usein ominaisuutta tallettaakokoelmaan toisia kokoelmia. Talloin ongelmaksi tulee kokoelmienja perusolioiden erilainen kayttaytyminen. Kun kokoelmaoliot ja pe-rusoliot sisaltavat saman toiminnallisen rajapinnan, tata ongelmaa eisynny.

Soveltuvuus: Tarvitaan sisakkaisia kokoelmia tai halutaan, ettaolioita kasittelevien asiakkaiden ei tarvitse valittaa perusolioiden jakokoelmien valisista eroista.

Rakenne: Katso kuva 9.5.

Osallistujat:

• Asiakas: Kayttaa olioita kantaluokan tarjoaman rajapinnankautta.

• Kantaluokka: Maarittelee (abstraktin) rajapinnan, jonka operaa-tiot aliluokkien on toteutettava.

• Perusolio: Primitiiviolio (joita voi olla useita), joka toteuttaakantaluokan rajapinnan.

palvelu()

abstract Kantaluokka

palvelu()

Perusolio Kokoelma

lisää()

poista()

palvelu()

palauta_jäsen()

Toteutus:

kutsu palvelu():a

jokaiselle kokoelman

oliolle.

Asiakas

Käyttää

KUVA 9.5: Suunnittelumalli Kokoelma

Page 273: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

9.3. Valikoituja esimerkkeja suunnittelumalleista 273

• Kokoelma: Koosteluokka, joka tallettaa viittaukset osaolioihin.Kantaluokan rajapinta toteutetaan siten, etta kukin kutsu valite-taan jokaiselle koosteessa mukana olevalle oliolle.

Seuraukset: Asiakkaan ohjelmakoodi yksinkertaistuu, koska eroaperusolioiden ja koosteiden valille ei tarvitse tehda. Uusien kooste-ja perusluokkien lisaaminen on helppoa.

9.3.2 Iteraattori (Iterator)

Tarkoitus: Tarjoaa kokoelman alkioihin viittaamiseen rajapinnan,joka on erillaan itse kokoelman toteutuksesta. [Gamma ja muut, 1995,s. 257–271]

Tunnetaan myos nimella: Kohdistin (Cursor).

Perustelu: Listan tai muun alkiokokoelman rajapinta kasvaa helpos-ti hyvin suureksi, jos siihen liitetaan seka rakenteen muokkaukseenetta lapikayntiin kuuluvat operaatiot. Yksi joukko lapikaynnin ope-raatioita (alkuun, seuraava, edellinen ynna muut) mahdollistaa vainyhden lapikaynnin olemassaolon kerrallaan, koska alkiokokoelmantoteuttava olio sailyttaa itse operaatioiden vaatiman tilatiedon. Kunvastuu alkiokokoelman lapikaynnista irrotetaan erilliseksi (mutta ko-koelmaan laheisesti liittyvaksi) olioksi, saadaan ratkaistuksi molem-mat ongelmat.

Soveltuvuus: Tarvitaan useita yhtaaikaisia lapikaynteja kokoelmaantietoa tai halutaan tarjota yhtenainen rajapinta useiden eri tavalla to-teutettujen kokoelmien lapikayntiin.

Rakenne: Katso kuva 9.6 seuraavalla sivulla.

Osallistujat:

• Asiakas: Kayttaa rajapintaa Iteraattori luokan Kokoelma sisallaolevien alkioiden osoittamiseen ja lapikayntiin.

Page 274: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

9.3. Valikoituja esimerkkeja suunnittelumalleista 274

Kokoelma abstract

LuoIteraattori()

Asiakas

Iteraattori abstract

Ensimmäinen()Seuraava()bool OnkoLopussa()

OsoitettuAlkio()

Käyttää Käyttää

KokoelmaToteutus

LuoIteraattori()

IteraattoriToteutus

Ensimmäinen()Seuraava()bool OnkoLopussa()

OsoitettuAlkio()

Luo uuden

tähän kokoelmaan

osoittavan

iteraattorin

KUVA 9.6: Suunnittelumalli Iteraattori

• Kokoelma: Maarittelee alkiokokoelman rajapinnan (erityisestitavan, jolla kokoelmaan saadaan luoduksi iteraattori).

• KokoelmaToteutus: Toteuttaa edella mainitun rajapinnan. Tar-vitsee keinon luoda iteraattoriolion.

• Iteraattori: Maarittelee rajapinnan alkiokokoelman lapikayn-tiin.

• IteraattoriToteutus: Toteuttaa edella mainitun rajapinnan. (Ko-koelmaToteutus palauttaa taman luokan instanssin.)

Seuraukset: Kokoelma pystyy tarjoamaan useita erilaisia tapoja al-kioiden lapikayntiin tarjoamalla useita iteraattoreita. Erillaan olevaiteraattori yksinkertaistaa kokoelman rajapintaa. Kokoelman kayttajapystyy pitamaan tarvittaessa useita osoituksia eli lapikaynteja yhta-aikaa kaynnissa.

9.3.3 Silta (Bridge)

Tarkoitus: Erottaa rajapinnan toteutuksesta siten, etta toteutuk-sen muutokset eivat vaikuta mitenkaan rajapinnan ohjelmakoodiin[Gamma ja muut, 1995, s. 151–161].

Page 275: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

9.3. Valikoituja esimerkkeja suunnittelumalleista 275

Tunnetaan myos nimella: Handle/Body, Envelope/Letter [Coplien,1992, luku 5.5] ja myos hieman omituisella nimella Cheshire Cat

[Meyers, 1998, s.148]. Lahes samasta rakenteesta on useita eri vari-aatioita, joita on esitelty artikkelissa “C++ Idioms Patterns” [Coplien,2000].

Perustelu: Rajapinnan ja toteutuksen erotteleminen toisistaan oh-jelmakoodin tasolla on joissain ohjelmointikielissa toteutettuna kie-len rakenteilla (katso Modula-3, aliluku 1.6.1). C++:n luokkaesittelyssaon myos puhtaasti toteutukseen liittyvia osia (private-osuus), jolloinniissa tehdyt muutokset nakyvat myos julkisen rajapinnan kayttajil-le (esimerkiksi kaannosriippuvuuksina). Silta on toteutustekniikka,jolla myos C++:n tapaisissa kielissa saadaan rajapinta ja toteutus ero-telluksi paremmin toisistaan (ortogonaalisempi yhteys osien valille).

Soveltuvuus: Halutaan poistaa kiintea yhteys rajapinnan ja toteu-tuksen valilta vaikkapa siksi, etta toteutus voi muuttua ohjelman suo-rituksen aikana. Seka rajapinnasta etta toteutuksesta halutaan periyt-taa toisistaan riippumatta uusia versioita. Toteutuksen muutosten eihaluta aiheuttavan mitaan muutoksia rajapintaa kayttavissa asiakas-koodeissa (niita ei haluta kaantaa uudelleen).

Rakenne: Katso kuva 9.7 seuraavalla sivulla.

Osallistujat:

• Asiakas: Kayttaa palveluita luokasta Rajapinta tehdyn olionkautta.

• Rajapinta: Maarittelee julkisen rajapinnan ja viittaa olioon, jokatoteuttaa taman rajapinnan.

• Toteutus: Kantaluokka, josta periytetyt oliot ovat rajapinnan to-teutusten eri versioita.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .Viittaa Lewis Carrollin teoksessa “Alice’s Adventures in Wonderland” [Carroll, 1865] ole-

vaan kissaan, joka suomennoksessa “Liisa ihmemaassa” esiintyy nimella Irvikissa. Tama kissapystyi erottamaan irvistyksensa muusta ruumiistaan niin, etta vain irvistys nakyi.

Page 276: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

9.3. Valikoituja esimerkkeja suunnittelumalleista 276

Asiakas

Käyttää

Rajapinta

palvelu()

palvelu()

Toteutus abstract

palvelu()

Toteutus_A

palvelu()

Toteutus_B

imp−>palvelu()imp

1

KUVA 9.7: Suunnittelumalli Silta

Seuraukset: Toteutus ei ole pysyvasti kytketty rajapintaan, minkavuoksi toteutus voi maaraytya ajoaikana ohjelman konfiguraatiostatai jopa muuttua kesken ohjelman suorituksen. Rajapinnan ja toteu-tusten laajentaminen periyttamalla on yksinkertaisempaa. Rajapin-nan kayttajalle ei nay epaoleellista tietoa toteutusyksityiskohdista(C++).

9.3.4 C++: Esimerkki suunnittelumallin toteutuksesta

Tyypillinen esimerkki ajoaikaisesta toteutuksen valinnasta on ny-kyaikainen graafinen kayttoliittyma. Erityisesti UNIX-jarjestelmissakayttajalla on valittavana useita erityylisia kayttoliittymia, jotka tar-joavat erilaisia vaihtoehtoja kaytettavyydessa ja ulkoasussa. Jos ha-luamme tehda ohjelman, joka tarjoaa todella joustavan kayttoliitty-man, voimme valita kayttoon erilaisella kayttoliittymakirjastolla teh-dyn toteutuksen kayttajan mieltymysten mukaan. Valinta voi olla etu-kateen merkittyna ohjelman alustustiedoissa, jolloin kayttoliittyma-kirjasto valitaan ohjelman kaynnistyessa, tai erityisen joustava oh-jelma voi pystya kaynnistamaan koko kayttoliittymansa uudelleen

Page 277: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

9.4. Geneerisyys ja periytymisen rajoitukset 277

ajoaikana tehdyn valinnan perusteella (joitain tallaisia jarjestelmiaon olemassa). Tallaisessa tilanteessa voimme hyodyntaa ohjelman ra-kenteessa suunnittelumallia silta (aliluku 9.3.3 sivulla 274).

Otamme esimerkiksi kayttoliittyman komponentin, jonka avul-la voidaan nayttaa kayttajalle virhetiedotteita avaamalla naytolle ik-kuna, jossa ilmoitusteksti sijaitsee. Tiedotteita kayttaville ohjelmanosille tarjotaan abstrakti virheikkunan rajapinta, joka on esitetty lis-tausessa 9.1 seuraavalla sivulla. Luokka VirheIkkuna on suunnit-telumallin roolissa Rajapinta. Koska virheikkunan julkisen rajapin-nan esittely ei tarvitse toteutuksen luokkaesittelya, siita on olemas-sa vain luokan ennakkoesittely (VirheIkkunaToteutus). Virheikkunanrajapinnan toteutuksessa ainoastaan delegoidaan eli valitetaan raja-pintakutsut varsinaiselle toteutusoliolle, joka on rajapinnan ainoanjasenmuuttujan (imp ) paassa (tiedoston virheikkuna.cc rivit 11–14).

Toteutukselle maaritellaan kantaluokka (listaus 9.2 sivulla 279),josta periyttamalla tehdaan todellisen toiminnallisuuden toteutta-via versioita (listaus 9.3 sivulla 279). Luokka VirheIkkunaToteutuson suunnittelumallin roolissa Toteutus. Nyt taman luokkarakenteenavulla voidaan tehda luokan VirheIkkuna instansseja erilaisilla toteu-tuksilla jarjestelman konfiguraation mukaan (listaus 9.4 sivulla 279).

9.4 Geneerisyys ja periytymisen rajoitukset

Aliluvussa 9.1 todettiin, etta geneerisen ja yleiskayttoisen komponen-tin suunnittelussa oleellinen asia on pysyvyyden ja vaihtelevuudenerottaminen toisistaan, jotta pysyvat osat voidaan toteuttaa vain ker-ran. Taman tavoitteen saavuttamiseen loytyy ohjelmointikielitasoltaerilaisia mekanismeja. Olio-ohjelmoinnissa periytyminen on meka-nismi, jolla luokkien yhteiset ominaisuudet voidaan toteuttaa kertaal-leen kantaluokassa. Niinpa onkin luonnollista etta periytyminen an-taa mahdollisuuden yleiskayttoisyyteen. Tassa aliluvussa tutkitaansita, millaiseen yleiskayttoisyyteen periytyminen antaa mahdollisuu-den ja missa sen rajat tulevat vastaan.

On varsin helppoa keksia tilanteita, joissa periytyminen on ihan-teellinen mekanismi pysyvien ja vaihtelevien osien erottamiseen toi-sistaan. Jokainen periytymishierarkia vastaa tilannetta, jossa kanta-luokkien toteutus periytyy aliluokille — kantaluokat ovat hierarkian

Page 278: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

9.4. Geneerisyys ja periytymisen rajoitukset 278

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . virheikkuna.hh . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .1 #ifndef VIRHEIKKUNA HH2 #define VIRHEIKKUNA HH34 #include <string>56 class VirheIkkunaToteutus; // ennakkoesittely78 class VirheIkkuna9

10 public:11 VirheIkkuna( VirheIkkunaToteutus const* p );12 virtual ~VirheIkkuna();1314 // Nayttaa viestin ikkunassa ja odottaa kayttajan kuittausta15 void NaytaIlmoitus( const std::string& viesti ) const;1617 private:18 VirheIkkunaToteutus const* imp ; // osoitin toteutukseen19 ;20 #endif

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . virheikkuna.cc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .1 // Julkisen rajapinnan esittely2 #include "virheikkuna.hh"34 // Esittely rajapinnan toteutusten kantaluokasta5 #include "virheikkunatoteutus.hh"67 VirheIkkuna::VirheIkkuna( VirheIkkunaToteutus const* p ) : imp (p) ;8 VirheIkkuna::~VirheIkkuna() imp = 0; 9

10 // Palveluiden siirto toteutukselle:

11 void VirheIkkuna::NaytaIlmoitus( string const& viesti ) const12 13 imp ->NaytaIlmoitus( viesti );14

LISTAUS 9.1: Virhetiedoterajapinnan esittely ja toteutus

Page 279: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

9.4. Geneerisyys ja periytymisen rajoitukset 279

1 #ifndef VIRHEIKKUNATOTEUTUS HH2 #define VIRHEIKKUNATOTEUTUS HH34 #include <string>56 class VirheIkkunaToteutus7 8 public:9 VirheIkkunaToteutus();

10 virtual ~VirheIkkunaToteutus();1112 virtual void NaytaIlmoitus( const std::string& viesti ) const = 0;13 ;14 #endif

LISTAUS 9.2: Toteutusten kantaluokka, virheikkunatoteutus.hh

1 // virheikkunaversiot.hh2 #include "virheikkunatoteutus.hh"34 class Gtk Virheikkuna : public VirheIkkunaToteutus

...56 class Motif Virheikkuna : public VirheIkkunaToteutus

...LISTAUS 9.3: Toteutus virheikkunoista, virheikkunaversiot.hh

1 #include "virheikkuna.hh"2 #include "virheikkunaversiot.hh"

...3 VirheIkkuna* UusiVirheIkkuna()4 5 VirheIkkunaToteutus* w;6 if( konfiguraatio.kaytossaMotif() ) 7 w = new Motif VirheIkkuna();8 else // GTK9 w = new Gtk VirheIkkuna();

10 11 return new VirheIkkuna( w );12

LISTAUS 9.4: Eri virheikkunatoteutusten valinta

Page 280: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

9.4. Geneerisyys ja periytymisen rajoitukset 280

pysyva osa, kun taas aliluokat voivat jokainen edustaa rajapinnal-taan tai toteutukseltaan erilaista “erikoistettua” versiota kantaluokankuvaamasta asiasta. Tyypillisesti talla tavalla toteutettu yleiskayttoi-nen kirjasto tarjoaa valmiina luokkahierarkian ylaosan (pysyvat osat),joista jokainen sovelluskohde sitten periyttaa itselleen sopivat aliluo-kat, joissa toteutetaan vaihtelevat osat. Aliluvussa 6.11 mainitut so-velluskehykset ovat hyva esimerkki periytymisen kayttamisesta tallatavoin. Monissa oliokielissa periytyminen onkin paaasiallinen yleis-kayttoisyyden mekanismi.

Periytymisella on kuitenkin muutama ominaisuus, joka rajoittaasen kayttokelpoisuutta yleiskayttoisyydessa. Tarkein niista on se, et-ta kaikki aliluokat perivat kantaluokan rajapinnan ja toteutukset ko-konaisuudessaan ja tasmalleen samanlaisena. Tama edellyttaa, ettayleiskayttoisen komponentin pysyvyys koskee kokonaisia palveluita(jasenfunktioita) parametreineen ja paluuarvoineen. Nain ei kuiten-kaan aina ole.

Ongelmaa on ehka helpoin kuvata esimerkin avulla. Oletetaan, et-ta halutaan koodata periytymista kayttaen yleiskayttoinen taulukko-tyyppi, suunnilleen samanlainen kuin C++:n vector-tyyppi. Tallaises-sa taulukkotyypissa pysyvina osina ovat itse vektorin toteutus ja ka-sittely, vaihtelevana osana puolestaan on taulukon alkioiden tyyppi,joka luonnollisesti riippuu kayttokohteesta. Periytymista kayttaen ta-ma tarkoittaa, etta suunnitellaan kaikille taulukoille yhteinen kanta-luokka Taulukko, johon koodataan kaikille taulukoille yhteiset asiat.Tasta kantaluokasta sitten tarvittaessa periytetaan kaikki erilaiset tau-lukot omiksi aliluokikseen, joihin sitten koodataan taulukon alkiotyy-pista riippuvat asiat. Kuva 9.8 seuraavalla sivulla nayttaa nain synty-van luokkahierarkian.

Seuraavaksi tahan luokkahierarkiaan pitaisi lisata tarvittavat jul-kisen rajapinnan palvelut. Kantaluokkaan Taulukko kuuluvat kaikil-le taulukoille tasmalleen samanlaiset palvelut, kun taas toisistaaneroavat jasenfunktiot toteutetaan kussakin aliluokassa. Yksinkertai-suuden vuoksi tassa keskitytaan pelkastaan palveluihin annaKoko,lisaaLoppuun, poistaLopusta ja haeAlkio. Nama vastaavat vector-tyypin operaatioita size, push back, pop back ja at.

Naista palveluista annaKoko ei selvasti millaan tavalla riipu alkioi-den tyypista, se kun vain palauttaa niiden lukumaaran. Sen voisi siislaittaa yhteiseen kantaluokkaan. Samalla tavoin poistaLopusta pois-taa taulukon viimeisen alkion tyypista riippumatta, joten sen voisi ai-

Page 281: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

9.4. Geneerisyys ja periytymisen rajoitukset 281

Taulukko

abstract

TaulukkoInt TaulukkoString ...

KUVA 9.8: Yleiskayttoisen taulukon toteutus periyttamalla

nakin rajapintansa puolesta maaritella kantaluokassa. Sen sijaan pal-velu lisaaLoppuun ottaa parametrikseen lisattavan alkion, joten pal-velun parametrin tyyppi riippuu alkion tyypista. Vastaavasti palveluhaeAlkio palauttaa paluuarvonaan halutun alkion, joten sen paluuar-von tyyppi riippuu alkiotyypista. Nama palvelut (ja kaikki muut vas-taavat palvelut) pitaisi toteuttaa erikseen jokaisessa aliluokassa, jottarajapinnan tyypit voivat erota toisistaan.

Kun tata jaottelua jatketaan, huomataan etta varsin suuressa osas-sa taulukon rajapintaa esiintyy alkion tyyppi jossain muodossa, jotenvarsin suuri osa palveluista siirtyisi aliluokkiin ja kantaluokan Tau-lukko pysyva osa jaisi varsin pieneksi. Yleiskayttoisyyden kannaltatama on tietysti huono asia, koska uuden taulukkotyypin luomisessataytyisi maaritella suuri joukko alkiotyypista riippuvia palveluja, javalmiina periytyvan toiminnallisuuden maara jaisi pieneksi.

Yksi ratkaisu tahan ongelmaan on lisata itse sovellusalueen py-syvyytta. Vaaditaan, etta kaikkien mahdollisten alkiotyyppien on pe-riydyttava yhteisesta kantaluokasta Alkio. Tama sinansa mitattomaltatuntuva lisavaatimus muuttaa tilannetta palveluiden kannalta oleelli-sesti. Nyt palvelun lisaaLoppuun parametrin tyyppi voikin olla Alkio,jolloin sama palvelu voi ottaa vastaan minka tahansa luokasta Alkioperiytetyn alkion.

Samoin palvelun haeAlkio paluuarvon tyyppi voi olla Alkio, jol-loin sama palvelu pystyy palauttamaan minka tahansa tyyppisen al-kion. Talla tavoin ajateltuna kaikki (tai ainakin lahes kaikki) taulukonpalvelut saadaan toteutettua tasmalleen samalla rajapinnalla, ja ne

Page 282: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

9.4. Geneerisyys ja periytymisen rajoitukset 282

voidaan toteuttaa yhteisessa kantaluokassa. Talloin periytettyja tau-lukkoluokkia ei edes tarvita, koska vaihtelevuutta ei enaa ole jaljella!

Taman tyyppista ratkaisua kaytetaan esimerkiksi Java-kielen alku-peraisissa sailioluokissa. Javassa kaikki luokat ovat periytettyina yh-dessa suuressa luokkahierarkiassa, jonka huipulla on kaikkien luok-kien kantaluokka Object. Nain on luonnollista, etta Javan taulukko-luokassa alkioiden tyyppi on aina Object, joten sama taulukkotyyp-pi kelpaa minka tahansa tyyppisten alkioiden tallettamiseen.] Yleis-kayttoisyyden kannalta tallainen tilanne on hyva, koska sama luokkakelpaa sellaisenaan kaikkiin taulukoihin.

Yhdessa yleiskayttoisessa taulukkoluokassa on myos huonot puo-lensa. Taulukkoa luotaessa ei enaa ilmoiteta, minka tyyppisia alkioitataulukkoon on tarkoitus tallettaa. Koska palvelun lisaaLoppuun para-metriksi kelpaa mika tahansa yhteisesta kantaluokasta periytetty olio,voidaan samaan taulukkoon tallettaa sekaisin erityyppisia alkioita,vaikkapa kokonaislukuja, merkkijonoja ja paivayksia. Joskus tallainenheterogeeninen useantyyppisia alkioita sisaltava taulukko on kateva,mutta silla on haittapuolensa.

Samoin kuin lisaaLoppuun-palvelun parametri, palvelun haeAlkiopaluutyyppi on myos yhteista kantaluokkatyyppia. Tama tarkoittaa,etta kun taulukosta haetaan alkio, ei kaantaja pysty paluutyypin pe-rusteella sanomaan, minka tyyppinen alkio taulukosta saatiin, koskapaluuarvona on osoitin tai viite yhteiseen kantaluokkaan. Kaytannos-sa taulukon kayttajan taytyy itse tietaa, minka tyyppinen alkio taulu-kosta olisi pitanyt tulla, ja tehda tarvittava tyyppimuunnos, jotta alkiosaadaan oikeantyyppisen osoittimen tai viitteen paahan.

Yhteisesta kantaluokasta aiheutuvat ominaisuudet johtavat sii-hen, etta kaantaja ei pysty tekemaan taulukon kaytosta tarvitta-via tyyppitarkastuksia, vaan ne jaavat taulukon kayttajan vastuulle.Javan tyyppimuunnoksissa kantaluokasta aliluokkaan tehdaan tarkas-tus siita, etta kantaluokkaviitteen paassa on oikeantyyppinen olio. Sa-moin olisi mahdollista lisata taulukkoluokkaan tarkastus siita, ettakaikki taulukkoon lisattavat alkiot ovat keskenaan samaa tyyppia.

Tallaiset tarkastukset ovat kuitenkin vaistamatta ajoaikaisia eivat-ka kaannosaikaisia. Niinpa helposta yleiskayttoisyydesta on joudut-tu maksamaan se hinta, etta entista suurempi osa ohjelman virheis-ta huomataan vasta testausvaiheessa (jos silloinkaan) sen sijaan, etta

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .]Tarkalleen ottaen tama ei ole aivan totta, koska Javan perustyypit kuten int eivat ole

olioita eivatka nain ollen Object-luokasta periyttamalla luotuja.

Page 283: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

9.5. C++: Toteutuksen geneerisyys: mallit (template) 283

kaantaja ilmoittaisi niista jo ohjelmaa kaannettaessa. Samoin tauluk-kotyypin kaytosta on tullut tyolaampaa tarvittavien tyyppimuunnos-ten takia.

On syyta korostaa, etta edella mainitut ongelmat tulevat esil-le etupaassa kaannosaikaisesti tyypitetyssa kielessa kuten Javassa.Smalltalk-kielen taulukkotyyppi on samantapainen kuin Javan. Kos-ka kielessa ei kuitenkaan ole ollenkaan kaannosaikaista tyypitysta,ovat kaikki tyyppitarkastukset joka tapauksessa aina ajoaikaisia, jotenSmalltalkissa taulukkoluokka ei millaan lailla huononna kielen tyyp-piturvallisuutta (tai sen puutetta).

Pohjimmiltaan koko ongelman syy on se, etta vaikka erityyppisillataulukoilla onkin todella paljon yhteista, ei tama pysyvyys muodos-ta yhtenaista kokonaisuutta, jonka saisi luontevasti toteutettua kanta-luokkana. Taulukon alkioiden tyyppi vaikuttaa taulukon rajapintaanja toteutukseen kauttaaltaan, eika sen vaikutusta pysty eliminoimaanperiytymista kayttaen ilman, etta tyyppiturvallisuudesta ja kayttomu-kavuudesta joudutaan tinkimaan.

Tallaisten ongelmien vuoksi pysyvyyden ja vaihtelevuuden hal-lintaan tarvitaan jokin toinenkin mekanismi, joka antaa perinteises-ta periytymisesta poikkeavan tavan yleiskayttoisyyden hallintaan.C++:ssa tama mekanismi on mallit (templates), jota kasitellaan seu-raavassa aliluvussa. Enemman tai vahemman samantapaisia meka-nismeja on joissain muissakin kielissa. Muun muassa Java-kieleen tu-li template-mekanismia muistuttava generics-mekanismi version 5.0myota.

9.5 C++: Toteutuksen geneerisyys: mallit (tem-plate)

Suunnittelumallien tarkoituksena on tarjota yleisia suunnittelurat-kaisuja, joista ohjelmiston suunnittelija sitten tuottaa oman suunni-telmansa korvaamalla mallissa esiintyvat roolit ja luokat oman oh-jelmistonsa kasitteilla. Samaan tapaan myos ohjelmiston toteutusvai-heessa on usein tilanteita, joissa sama geneerinen koodirunko olisikaytannollinen useassa kohtaa ohjelmaa.

Periaatteessa jo parametreja saavat funktiot ovat aarimmaisen yk-sinkertainen esimerkki tallaisesta geneerisyydesta. Funktioissa “aukijatettyja” tietoja edustavat parametrit, joita funktion kirjoittaja kayttaa

Page 284: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

9.5. C++: Toteutuksen geneerisyys: mallit (template) 284

funktion koodissa varsinaisten arvojen sijaan. Funktiota kutsuttaessanaille parametreille sitten annetaan varsinaiset arvot.

Funktioiden tarjoama toteutuksen geneerisyys rajoittuu C++:ssakuitenkin vain parametrien arvoihin. Kaikki muut asiat, kuten muut-tujien tyypit ja taulukoiden koot, on kiinnitettava jo koodausvaihees-sa. Usein periaatteessa yleiskayttoisessa koodissa kuitenkin myosmuuttujien, parametrien yms. tyypit vaihtelevat. Esimerkiksi luku-jen keskiarvo lasketaan samalla tavalla riippumatta siita, ovatko luvuttyypiltaan int, double vai kenties jokin itse luokkana maaritelty luku-tyyppi. Samoin itse tehty taulukkotyyppi on rakenteeltaan samanlai-nen riippumatta siita, millaisia otuksia taulukkoon on tarkoitus tal-lettaa.

Niissa ohjelmointikielissa, joissa ei ole vahvaa (kaannosaikaista)tyypitysta, ei ole myoskaan naita ongelmia. Ilman tyypitysta samaohjelmakoodi pystyy laskemaan keskiarvon minka tahansa tyyppisil-le arvoille ja taulukossa voi olla mita asioita tahansa. Samalla tietystijaadaan ilman kaannosaikaisia tyyppitarkastuksia ja mahdolliset vir-heet havaitaan vasta ajoaikana. Esimerkkeja tallaisista kielista ovatSmalltalk, Python ja Scheme.

9.5.1 Mallit ja tyyppiparametrit

Kaannosaikaisesti tyypitetyissa kielissa, kuten C++:ssa, taytyy kaikkienparametrien ja muuttujien tyyppien olla selvilla jo ohjelmaa kaannet-taessa, joten tyyppien geneerisyys ei onnistu ilman lisakikkoja. Mallit(template) ovat C++:n tapa kirjoittaa yleiskayttoisia funktioita (funktio-mallit, function template) ja luokkia (luokkamallit, class template).Malleissa tyyppeja ja joitain muitakin asioita voidaan jattaa maaraa-matta (tasta tarkemmin aliluvussa 9.5.5). Auki jatetyt asiat “sidotaan”vasta myohemmin kayton yhteydessa. Talla tavoin saadaan sailyte-tyksi C++:n vahva kaannosaikainen tyypitys, mutta annetaan silti mah-dollisuus kirjoittaa geneerista koodia.

C++:n kirjaston vector on esimerkki luokkamallista. C++:ssa jokaisentaulukon sisaltamien alkioiden tyyppi taytyy tietaa jo kaannosaikana.Kuitenkin jokaisen taulukon toteutus on alkioiden tyyppia lukuunot-tamatta sama. Niinpa vector itsessaan on vasta luokkamalli, joka ker-too taulukoiden yleisen toteutuksen ja jattaa auki alkioiden tyypin.Vektoreita kaytettaessa ohjelmoija sitten kiinnittaa alkioiden tyypinja luo kokonaislukuvektoreita (vector<int>), liukulukuvektoreita

Page 285: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

9.5. C++: Toteutuksen geneerisyys: mallit (template) 285

(vector<double>) tai paivaysosoitinvektoreita (vector<Paivays*>).Auki jatetyt asiat selviavat jo kaannosaikana, mutta kuitenkin vastavektoreiden kayttotilanteessa, ei itse vektorin ohjelmakoodissa.

Funktioissa parametrien nimia kaytetaan koodissa korvaamaanparametrien “puuttuvia” arvoja, jotka selviavat kutsun yhteydessa.Samalla tavoin malleissa auki jatetyt tyypit nimetaan ja ohjelmakoo-dissa kaytetaan naita nimia auki jatettyjen tyyppien tilalla. Yhdes-sa mallissa voi auki jatettyja tyyppeja olla useita, aivan samoin kuinfunktio voi saada useita parametreja. Koska funktion parametrit jamallin auki jatetyt tyypit muistuttavat suuresti toisiaan, puhutaankinusein mallien tyyppiparametreista.

Koska mallien koodissa on auki jatettyja asioita, kaantaja ei pystymallin tavatessaan tuottamaan siita koodia. Vasta kun kyseista malliakaytetaan ohjelmassa ja annetaan tyyppiparametreille arvot, kaantajakaantaa mallista koodin, jossa mallin auki jatetyt tyypit on korvattuannetuilla tyypeilla. Tata kutsutaan mallin instantioimiseksi (instan-tiation). Kaantaja kaantaa mallista oman versionsa jokaista sellaistakayttokertaa kohti, jossa tyyppiparametrien arvot ovat erilaiset. Nainmallien kayttaminen ei yleensa vahenna tuotetun konekoodin maa-raa, vaikka mallit tietysti vahentavatkin ohjelmoijan koodausurakkaa.

Mallien syntaksi C++:ssa on yksinkertainen:

template<typename tyyppiparam1, typename tyyppiparam2, . . .>// Tahan normaali funktion tai luokan maarittely

Malli alkaa avainsanalla template, jonka jalkeen nimetaan kulmasul-keissa kaikki mallissa auki jatetyt tyyppiparametrit. Taman jalkeenseuraa itse mallin koodi, joka funktiomallin tapauksessa on tavalli-nen funktion maarittely ja luokkamallin tapauksessa luokan. Koodis-sa tyyppiparametreja voi kayttaa aivan kuin normaaleja C++:n tyyp-peja. C++ sallii myos syntaksin, jossa avainsanan typename tilalla kay-tetaan tyyppiparametrilistassa avainsanaa class. Tama “vanha” syn-taksi on taysin identtinen yllamainitun kanssa, mutta sita tuskin kan-nattaa kayttaa, koska auki jatetyt tyypit voivat malleissa aina olla mitatahansa tyyppeja, eivat valttamatta luokkia.

Seuraavassa esitellaan C++:n template-mekanismin perusperiaat-teet ja niita kaytetaan jonkin verran hyvaksi mm. luvussa 10. Tem-plate-mekanismi on kuitenkin varsin monimutkainen ja -puolinenniin syntaksiltaan kuin kayttotavoiltaan ja rajoituksiltaankin. C++:n

Page 286: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

9.5. C++: Toteutuksen geneerisyys: mallit (template) 286

geneerisyydesta ja geneerisesta ohjelmoinnista kiinnostuneen kan-nattaakin tutustua esimerkiksi kirjoihin “C++ Templates — The Com-plete Guide” [Vandevoorde ja Josuttis, 2003] ja “Modern C++ De-sign” [Alexandrescu, 2001].

9.5.2 Funktiomallit

Funktiomallit (function template) ovat geneerisia malleja, joista kaan-taja voi generoida eri tyypeilla toimivia funktioita. Kaikilla mallis-ta generoiduilla funktioilla on sama nimi, mutta ne toimivat yleensaerityyppisilla parametreilla.

Listauksessa 9.5 on funktiomallin min toteutus. Rivi 1 kertoo, ettakyseessa on malli ja etta siina on yksi auki jatetty tyyppi, jota mallinkoodissa kutsutaan nimella T. Riveilla 2–12 on sitten funktion maa-rittely, jossa tyyppiparametria T kaytetaan kuin mita tahansa tyyppiakertomaan, etta mallista luodut funktiot ottavat kaksi samantyyppis-ta parametria ja palauttavat viela samaa tyyppia olevan paluuarvon.Tyyppia T kaytetaan myos funktion rungossa tuloksen sisaltavan pai-kallisen muuttujan luomiseen.

Kun funktiomalli on maaritelty, voidaan siita luoda todellisiafunktioita yksinkertaisesti kutsumalla niita. Mikali kaikki tyyppipa-rametrit esiintyvat funktiomallin parametrilistassa, kaantaja osaa au-tomaattisesti paatella kutsusta tyyppiparametrien arvot. Esimerkiksikutsusta min(1,2) kaantaja paattelee, etta T:n on oltava int. Sen jal-

1 template <typename T> // Tai template <class T> (identtinen)

2 T min(T p1, T p2)3 4 T tulos;5 if (p1 < p2)6 7 tulos = p1;8 else 9 tulos = p2;

10 11 return tulos;12

LISTAUS 9.5: Parametreista pienemman palauttava funktiomalli

Page 287: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

9.5. C++: Toteutuksen geneerisyys: mallit (template) 287

keen kaantaja kaantaa automaattisesti funktiomallista koodin, jossaT on korvattu tyypilla int, ja kutsuu tata koodia. Vastaavasti kutsunmin(2.3, 5.7) nahdessaan kaantaja tuottaa funktiomallista koodin,jossa T on double (liukulukuvakiot ovat C++:ssa tyyppia double).

Vaikka jokaisesta funktiomallin kutsusta periaatteessa tuotetaan-kin oma koodinsa, nykyiset kaantajat ovat niin alykkaita, etta ne osaa-vat samantyyppisissa kutsuissa kayttaa yhteista koodia. Jos kaantajaon jo kutsun min(1,2) yhteydessa tuottanut funktiomallista int-tyyp-pia kayttavan toteutuksen, se kutsuu tata samaa toteutusta myohem-min tavatessaan kutsun min(5,9). Nain mallien kaytto ei kasvata oh-jelmakoodin kokoa tarpeettomasti.

Vaikka kaantaja osaakin paatella tyyppiparametreille arvot kutsunparametrien tyypeista, voidaan kutsun yhteydessa myos antaa tyyp-piparametreille eksplisiittiset arvot kirjoittamalla kyseiset arvot kul-masulkeissa mallin nimen peraan. Esimerkiksi kutsu

float f = min<float>(3.2, 6);

kaskee kaantajaa tuottamaan koodin, jossa T on float, vaikka para-metrien tyypit ovatkin double ja int. Kaantaja tuottaa mallista min-funktion, joka ottaa parametreikseen kaksi float-arvoa ja palauttaamyos float-arvon. Koska kutsussa annetut parametrit ovat eri tyyp-pia, sovelletaan niihin implisiittisia tyyppimuunnoksia aivan kutennormaalistikin funktioita kutsuttaessa. Tallainen tyyppiparametrieneksplisiittinen maaraaminen on joskus hyodyllista, kun halutaan pa-kottaa juuri tietynlainen toteutus mallista. Se on myos valttamatonta,jos funktiomallissa on tyyppiparametreja, jotka eivat kay ilmi mallinparametrilistasta (talloin kaantaja ei tietenkaan voi itse paatella nii-den arvoja).

9.5.3 Luokkamallit

Luokkamalli (class template) toimii mallina luokille, jotka ovat muu-ten samanlaisia, mutta joissa jotkin tyypit voivat erota toisistaan. Lis-taus 9.6 seuraavalla sivulla esittelee luokkamallin Pari, joka kuvaamallin luokille, joihin voi tallettaa kaksi mielivaltaista tyyppia olevaaalkiota ja joista nama alkiot voi lukea kutsuilla annaEka ja annaToka.Mallissa on kaksi auki jatettya tyyppia T1 ja T2, jotka esitellaan mallinalussa ja joita sitten kaytetaan itse luokan maarittelyssa.

Page 288: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

9.5. C++: Toteutuksen geneerisyys: mallit (template) 288

1 template <typename T1, typename T2>2 class Pari3 4 public:5 Pari(T1 eka, T2 toka);6 T1 annaEka() const;7 T2 annaToka() const;

...11 private:12 T1 eka ;13 T2 toka ;14 ;

LISTAUS 9.6: Tietotyypin “pari” maaritteleva luokkamalli

Itse Pari ei viela ole luokka, vaan vasta malli kokonaiselle“perheelle” luokkia. Luokkamallista saadaan instantioitua todellinenluokka maaraamalla tyyppiparametreille arvot. Tama tapahtuu luet-telemalla tyyppiparametrien arvot kulmasulkeissa luokkamallin ni-men jalkeen. Esimerkiksi rivi

Pari<int, double> p(1, 3.2);

tuottaa ensin luokkamallista luokan, jossa T1 on int ja T2 on vastaa-vasti double. Taman jalkeen tasta luokasta luodaan olio p. Tama oliosisaltaa sitten tyyppeja int ja double olevat jasenmuuttujat, sen jasen-funktion annaEka paluutyyppi on int ja niin edelleen.

Olioiden luomisen lisaksi luokkamallista voi instantioida luokanmissa tahansa kohtaa ohjelmaa. Syntaksi Pari<int, double> toimiiluokkamallista luodun luokan nimena, ja sita voi kayttaa missa ta-hansa kuten tavallista luokan nimea. Esimerkiksi funktioesittely

void f(Pari<int, int>& i, Pari<float, int*> d);

esittelee tavallisen funktion, joka ottaa parametreikseen viitteen kaksikokonaislukua sisaltavaan pariin ja parin, johon on talletettu liukulu-ku ja osoitin kokonaislukuun.

Jokainen erilainen luokkamallista luotu luokka on oma erillinenluokkansa, eika silla ole mitaan sukulaisuussuhdetta muihin samas-ta mallista luotuihin luokkiin, joissa tyyppiparametrit ovat erilaiset.Esimerkiksi edella olleet luokat Pari<int,int> ja Pari<float,int*>

Page 289: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

9.5. C++: Toteutuksen geneerisyys: mallit (template) 289

ovat taysin omia luokkiaan, eivatka niista luodut oliot ole keskenaanvaihtokelpoisia. Nain taytyy tietysti ollakin, koska seka luokkien ra-japinta etta sisainen toteutus eroavat toisistaan tyyppien osalta.

Listaus 9.6 vasta esitteli luokan. Taman lisaksi taytyy luokka-mallille kirjoittaa viela jasenfunktioiden toteutukset. Tama tapahtuuC++:ssa niin, etta jasenfunktiot kirjoitetaan ikaan kuin omiksi malleik-seen, ja jokaiseen jasenfunktion toteutukseen tulee oma template-maareensa tyyppiparametreineen kaikkineen. Listauksessa 9.7 on esi-merkkina kaksi Pari-luokkamallin jasenfunktion toteutusta. Ne alka-vat samanlaisella template-maareella kuin itse luokkamallin esittely-kin. Lisaksi niiden koodissa on ennen nakyvyystarkenninta :: “luo-kan nimena” merkinta Pari<T1, T2>, jossa mallin tyyppiparametriton “sijoitettu paikoilleen”.

Luokkamallin jasenfunktiot kayttaytyvat kuten funktiomallitmyos siina mielessa, etta kaantaja instantioi luokkamallista instan-tioidulle luokalle vain ne jasenfunktiot, joita luokan olioille todellakutsutaan. Normaalin luokan tapauksessahan kaantaja kaantaa kaik-ki jasenfunktiot riippumatta siita, kutsutaanko niita. Tassa suhteessaluokkamallit voivat jopa vahentaa tuotetun konekoodin maaraa, josluokkamallissa on paljon jasenfunktioita, joita ei kayteta.

C++:ssa on myos mahdollista kirjoittaa jasenfunktiomalleja (mem-ber function template). Jasenfunktiomalli muistuttaa muuten tavallis-ta funktiomallia, mutta se on maaritelty jonkin luokan jasenfunktiok-si. Niinpa siihen patevat kaikki asiat, jotka on selitetty funktiomal-leista aliluvussa 9.5.2. Kaikki kaantajat eivat viela (kesalla 2000) tuejasenfunktiomalleja, mutta uusimmissa kaantajaversioissa tama omi-naisuus jo yleensa toimii.

1 template <typename T1, typename T2>2 Pari<T1, T2>::Pari(T1 eka, T2 toka) : eka (eka), toka (toka)3 4 56 template <typename T1, typename T2>7 T1 Pari<T1, T2>::annaEka() const8 9 return eka ;

10

LISTAUS 9.7: Esimerkki luokkamallin Pari jasenfunktioista

Page 290: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

9.5. C++: Toteutuksen geneerisyys: mallit (template) 290

Mielenkiintoinen yhdistelma syntyy, kun luokkamallille maaritel-laan jasenfunktiomalli. Talloin siis jo itse luokkamallissa on tiettymaara auki jatettyja tyyppeja ja sen jasenfunktiomallissa on naidentyyppien lisaksi viela joukko omia auki jatettyja tyyppeja, jotka maa-raytyvat jasenfunktiomallin kutsun yhteydessa. Koska tassa yhdistel-massa on ikaan kuin malli mallin sisalla, se on syntaksiltaan varsinerikoinen. Listaus 9.8 sisaltaa esimerkin tallaisesta luokkamallin ja-senfunktiomallista. Taman jasenfunktiomallin avulla mihin tahansapariin voi “summata” minka tahansa tyyppisen toisen parin, kunhanvain parien alkiot voi laskea yhteen keskenaan.

9.5.4 Tyyppiparametreille asetetut vaatimukset

Mallin maarittelyssa tyyppiparametreille ei suoraan aseteta mitaanvaatimuksia vaan auki jatetyt tyypit vain nimetaan mallin alussa, jasen jalkeen niita kaytetaan itse mallin koodissa. On kuitenkin selvaa,ettei mallille voi antaa tyyppiparametreiksi mita tahansa. Eri tyyp-pien ja olioiden rajapinnat eroavat toisistaan, ja nain ollen myos nii-den kaytto on erilaista.

1 template <typename T1, typename T2>2 class Pari3

...9 template <typename T3, typename T4>

10 void summaa(Pari<T3, T4> const& toinenPari);...

14 ;...

27 template <typename T1, typename T2>28 template <typename T3, typename T4>29 void Pari<T1, T2>::summaa(Pari<T3, T4> const& toinenPari)30 31 eka += toinenPari.annaEka();32 toka += toinenPari.annaToka();33

LISTAUS 9.8: Luokkamallin sisalla oleva jasenfunktiomalli

Page 291: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

9.5. C++: Toteutuksen geneerisyys: mallit (template) 291

C++:n malleissa ei millaan tavalla erikseen kerrota, millainen ra-japinta tyyppiparametreihin sijoitettavilla todellisilla tyypeilla tulisiolla. Mallille annetuilta tyypeilta vaaditaan vain, etta mallin koodinon kaannyttava, kun tyyppiparametrien tilalle sijoitetaan instantioin-nissa todelliset tyypit.

Esimerkiksi listauksen 9.5 funktiomalli min vertailee parametre-jaan pienempi kuin -operaattorilla <. Nain ollen min-mallia voi kayt-taa vain tyypeille, joiden arvoja voi vertailla <-operaattorilla. Samoinjos mallin koodi kutsuu auki jatettya tyyppia olevalle oliolle jotainjasenfunktiota, voi kyseista mallia kayttaa vain sellaisten tyyppienkanssa, joista kyseinen jasenfunktio loytyy.

Vaikka edellamainittu periaate on sinansa hyvin yksinkertainen,se aiheuttaa helposti ongelmia mallien kirjoittamisessa, koska tyyp-piparametrien vaatimukset on piilotettu ja siroteltu mallin koodin se-kaan. Taman vuoksi geneerisessa ohjelmoinnissa (generic program-ming) pitaisi ottaa aina huomioon seuraavat seikat:

• Tyyppiparametreihin kohdistuvat vaatimukset tulisi aina doku-mentoida ja kerata yhteen paikkaan, jotta mallin kayttaja pystyyhelposti nakemaan, millaisten tyyppien kanssa mallia voi kayt-taa.

• Jotta malli olisi mahdollisimman yleiskayttoinen, sen suunnit-telussa tulisi kiinnittaa huomiota siihen, ettei malli vaadi tyyp-piparametreiltaan yhtaan enempaa kuin on tarpeen.

Edella mainituista varsinkin jalkimmainen kohta vaatii mallin kir-joittajalta huolellisuutta ja taitoa. C++:ssa implisiittiset tyyppimuun-nokset, arvonvalitys yms. aiheuttavat sen, etta sinansa viattomaltanayttava koodi saattaa kulissien takana tehda varsin monimutkaisiaasioita. Talloin mallin koodista tulee helposti sellaista, etta se vaatiityyppiparametreiltaan ominaisuuksia, joita ohjelmoija ei ole ollen-kaan suunnitellut.

Esimerkiksi listauksen 9.5 sinansa viattoman nakoinen min-mal-li vaatii selvasti tyyppiparametriltaan pienemmyysvertailun. Sen li-saksi kuitenkin auki jatettya tyyppia T olevat parametrit valitetaanfunktion sisaan normaalia arvonvalitysta kayttaen, joka vaatii kopio-rakentajan kayttoa (aliluku 7.3). Samoin paluuarvon palauttaminentehdaan kopiorakentajalla. Koodissa on rivi T tulos;, joten tyypilta Tvaaditaan oletusrakentajaa. Kaiken kukkuraksi tulos-muuttujaan si-

Page 292: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

9.5. C++: Toteutuksen geneerisyys: mallit (template) 292

joitetaan kahdessa kohtaa uusi arvo, joten malli vaatii sijoitusoperaat-torin olemassaolon. Naiden kaikkien vaatiminen rajoittaa min-mallinkayttoa aika lailla, koska oletusrakentaja ja sijoitus ovat operaatioita,joita laheskaan kaikille luokille ei haluta kirjoittaa.

Pienella suunnittelulla min-malli saadaan huomattavasti kayttaja-ystavallisemmaksi. Kayttamalla viitteenvalitysta arvonvalityksen si-jaan paastaan eroon kopiorakentajan vaatimisesta. Poistamalla sinan-sa tarpeeton tulos-olio saadaan lisaksi oletusrakentajan ja sijoitus-operaattorin kaytto poistettua. Listaus 9.9 sisaltaa paremman versionmin-mallista. Sen koodi vaatii tyyppiparametrilta enaa ainostaan pie-nempi kuin -operaattorin olemassaoloa.

9.5.5 Erilaiset mallien parametrit

Tahan mennessa mallien yhteydessa on kasitelty vain yksinkertaisiatyyppiparametreja. Mallien parametreissa on lisaksi joitain lisaomi-naisuuksia, jotka joskus helpottavat mallien kayttoa.

Mallien oletusparametrit

C++:ssa voidaan tavallisten funktioiden parametreille antaa oletusar-voja, jolloin funktiokutsun lopusta alkaen voi jattaa parametreja an-tamatta. Esimerkiksi funktioesittely

void f(int i = 1, double j = 3.14;

1 template <typename T>2 T const& min(T const& p1, T const& p2)3 4 if (p1 < p2)5 6 return p1;7 8 else9

10 return p2;11 12

LISTAUS 9.9: Parempi versio listauksen 9.5 funktiomallista

Page 293: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

9.5. C++: Toteutuksen geneerisyys: mallit (template) 293

kertoo, etta funktiolla f on kaksi parametria, joista ensimmaisella onoletusarvo 1 ja toisella 3.14. Funktiota voi kutsua kolmella eri tavalla:

• Kutsussa f(5, 0.0) ei ole mitaan ihmeellista, ja i saa arvon 5 jaj:lle tulee arvo 0.0.

• Kutsu f(3) aiheuttaa sen, etta i saa arvon 3 ja j:lle kaytetaanoletusarvoa 3.14.

• Kutsu f() antaa molemmille parametreille oletusarvon, eli i on1 ja j on 3.14.

Mallien tyyppiparametreille patevat samat saannot. Tyyppipara-metrilla voi olla oletusarvo, jota kaytetaan jos tyyppiparametria eiinstantioinnin yhteydessa anneta. Listaus 9.10 sisaltaa luokkamallinPari2, jonka ensimmainen tyyppiparametri on oletusarvoisesti int jatoisen tyyppiparametrin oletusarvo on sama kuin ensimmainen para-metri. Mallin voi nyt instantioida kolmella eri tavalla:

• Syntaksilla Pari2<double, string> kaikki toimii kuten ennen-kin, eli tuloksena on liukuluku-merkkijono-pari.

• Syntaksi Pari2<double> tuottaa parin, jossa parin molemmatarvot ovat liukulukuja.

• Pari2<> tuottaa kokonaislukuparin.

1 template <typename T1 = int, typename T2 = T1>2 class Pari23 4 public:5 Pari2(T1 eka, T2 toka);6 T1 annaEka() const;7 T2 annaToka() const;

...8 ;

LISTAUS 9.10: Mallin oletusparametrit

Page 294: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

9.5. C++: Toteutuksen geneerisyys: mallit (template) 294

Vakioparametrit

Malleissa voi tyyppien lisaksi jattaa auki myos tiettyja kaannosaikai-sia vakioita, jotka jaavat mallin parametreiksi tyyppiparametrien ta-paan. Auki jatettavaksi kelpaavia vakioita ovat

• kokonaislukuvakiot ja luettelotyyppien arvot (enum)

• osoitin globaaliin olioon tai funktioon

• viite globaaliin olioon tai funktioon

• osoitin jasenfunktioon tai -muuttujaan.

Listauksessa 9.11 on luokkamalli MJono. Siita luoduilla merkkijo-noluokilla on kiintea maksimipituus, joka annetaan mallin paramet-rina. Mallille on lisaksi maaratty oletusarvoinen maksimipituus 80,jota kaytetaan, jos maksimipituutta ei erikseen anneta. Koska esimer-kissa jokainen eri maksimipituudella luotu merkkijonoluokka on omaerillinen luokkansa, eripituiset merkkijono-oliot eivat kuulu samaanluokkaan eika niita nain ollen normaalisti voi esimerkiksi sijoittaatoisiinsa.

Malliparametrit

Viimeisena mallissa voi jattaa auki myos toisen mallin, joka annetaanvasta instantioinnin yhteydessa. Tama mahdollisuus mallin malli-parametreihin (template template parameter) on varsin harvoin nor-maalikoodissa tarvittava ominaisuus, mutta se tekee mahdolliseksivarsin monimutkaisenkin geneerisen ohjelmoinnin.

1 template <unsigned long SIZE = 80>2 class MJono3 4 public:5 MJono(char const* arvo);6 char const* annaArvo() const;7 private:8 char taulukko[SIZE+1];9 ;

10 MJono<12> s1("Tuli tayteen");

LISTAUS 9.11: Malli, jolla on vakioparametri

Page 295: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

9.5. C++: Toteutuksen geneerisyys: mallit (template) 295

Malliparametreja ei juuri kasitella tassa teoksessa, mutta listauk-sessa 9.12 on esimerkki funktiomallista, jossa on jatetty auki yksi kak-si tyyppiparametria saava malli seka lisaksi yksi tavallinen tyyppipa-rametri. Mallin summaa avulla voi nyt summata mista tahansa kaksi-parametrisesta luokkamallista instantioituja olioita, kunhan luokka-mallin tyyppiparametrit ovat samat, ja se tarjoaa operaatiot annaEkaja annaToka.

9.5.6 Mallien erikoistus

Mallit ovat varsin tehokas tapa kirjoittaa yleista koodia, joka on tar-koitettu toimimaan tyypeista riippumatta. Joskus tulee kuitenkin vas-taan tilanne, jossa juuri tietyn tyypin tapauksessa mallin koodi tu-lisikin toteuttaa eri tavalla. Syyna tahan saattaa olla tehokkuus- taitilaoptimointi, tai kenties kyseinen tyyppi eroaa jollain olennaisellatavalla muista tyypeista.

Mallien erikoistus (template specialization) antaa mahdollisuu-den tallaisiin erikoistapauksiin. Kun malli antaa jollekin asialle ylei-sen toteutuksen, erikoistukset maarittelevat tahan poikkeuksia. Kayt-tajan kannalta kaikki sailyy ennallaan, ja mallia voi kayttaa normaa-listi, mutta mallia instantioidessa kaantaja saattaakin valita normaa-lin mallin koodin sijaan erikoistuksen tarjoaman koodin.

Luokkamallien yhteydessa listauksessa 9.6 ollut malli Pari ontoteutettu siten, etta sen molemmat alkiot on talletettu omiin ja-senmuuttujiinsa eka ja toka . Jos halutaan tehda totuusarvopariPari<bool,bool>, tama toteutustapa tuhlaa muistia. Jokainen jasen-

1 template < template <typename T1, typename T2> class X, typename S>2 S summaa(X<S, S> const& x)3 4 return x.annaEka() + x.annaToka();5 67 void kayta()8 9 Pari<int, int> p(1, 2);

10 int tulos = summaa(p);11

LISTAUS 9.12: Mallin malliparametri

Page 296: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

9.5. C++: Toteutuksen geneerisyys: mallit (template) 296

muuttuja vie valttamatta vahintaan yhden tavun muistia, vaikka pe-riaatteessa kaksi totuusarvoa saisi helposti puristettua yhteenkin ta-vuun. Tama tilaoptimointi voidaan toteuttaa Pari-mallin erikoistuk-sena.

Listaus 9.13 sisaltaa alkuperaisen mallin erikoistuksen, jossa onvain yksi jasenmuuttuja ekaJaToka , johon molemmat totuusarvotvoidaan tallettaa C++:n bittioperaatioita kayttaen.

Luokkamallien erikoistuksen syntaksissa jatetaan template-avain-sanan jalkeen kulmasulkeet tyhjiksi (merkkina siita, etta erikoistuk-sessa ei auki jatettyja tyyppeja enaa ole), ja ne tyypit, joita erikois-tus koskee, merkitaan mallin nimen jalkeen kulmasulkeisiin. Lis-tauksessa on myos yhden jasenfunktion erikoistuksen toteutus. Sii-na template-avainsanaa ei tarvita ollenkaan vaan erikoistuksen tyypitmerkitaan suoraan luokan nimen yhteyteen.

Funktiomallin erikoistus on syntaksiltaan vastaava kuin luokka-mallinkin. Siina template-avainsanan jalkeiset kulmasulkeet ovat jal-leen tyhjat, ja erikoistuksen kohteena olevat tyypit merkitaan kulma-sulkeisiin funktiomallin nimen jalkeen. Jos kaikki erikoistuksen tyyp-piparametrien arvot voi paatella parametrilistan avulla, funktion ni-men jalkeisen tyyppilistan voi jattaa halutessaan pois. Listaus 9.14seuraavalla sivulla sisaltaa min-mallin erikoistuksen paivaysolioille.

Luokkamallien tapauksessa C++ sallii viela luokkamallin osittais-

1 template <>2 class Pari<bool, bool>3 4 public:5 Pari(bool eka, bool toka);6 bool annaEka() const;7 bool annaToka() const;8 // summaa() puuttuu erikoistuksesta!9 private:

10 unsigned char ekaJaToka ; // Saastaa muistia11 ;1213 bool Pari<bool, bool>::annaEka() const14 15 return (ekaJaToka & 1) != 0;16

LISTAUS 9.13: Luokkamallin Pari erikoistus totuusarvoille

Page 297: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

9.5. C++: Toteutuksen geneerisyys: mallit (template) 297

31 template<>32 Paivays const& min<Paivays>(Paivays const& p1, Paivays const& p2)33 34 if (p1.paljonkoEdella(p2) > 0)35 36 return p2;37 else 38 return p1;39 40

LISTAUS 9.14: Funktiomallin min erikoistus paivayksille

erikoistuksen (class template partial specialization). Tassa varsinharvoin tarvitussa mekanismissa osa luokkamallin tyyppiparamet-reista sidotaan tiettyihin arvoihin mutta lopputuloksessa on vielaavoimia tyyppeja. Listaus 9.15 nayttaa Pari-mallin osittaiserikoistuk-sen, jota kaytetaan, kun molemmat parin tyyppiparametrit ovat sa-mat.

9.5.7 Mallien ongelmia ja ratkaisuja

C++:n mallien varsin omalaatuisen syntaksin lisaksi mallien suunnit-teleminen on varsin vaativaa puuhaa. Geneerinen ohjelmointi itses-saan on hankalaa, ja useimpien C++-kaantajien lahes lukukelvottomatvirheilmoitukset eivat mitenkaan auta asiaa. Taman lisaksi itse mal-

1 template <typename T>2 class Pari<T, T>3 4 public:5 Pari(T eka, T toka);6 T annaEka() const;7 T annaToka() const;8 private:9 T alkiot [2];

10 ;11 template <typename T>12 T Pari<T, T>::annaEka() const13 return alkiot [0];

LISTAUS 9.15: Luokkamallin Pari osittaiserikoistus

Page 298: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

9.5. C++: Toteutuksen geneerisyys: mallit (template) 298

lien koodaamisessa on tiettyja C++-riippuvia asioita, jotka on hyva tie-taa. Tahan alilukuun on keratty joitain tallaisia seikkoja.

Mallin koodin sijoittelu ja export

Tavallisista funktioista poiketen kaantaja kaantaa mallin koodin vas-ta instantioinnin yhteydessa, eli kun mallia kaytetaan. Kaytannossatama aiheuttaa vaatimuksen, etta kaantajalla taytyy olla tiedossaanmallin koodi silloin, kun mallia kayttavaa koodia kaannetaan.

Normaalien funktioiden yhteydessa tata rajoitusta ei ole vaanpelkka funktion esittely riittaa funktion kutsumiseen. Tama tekeemahdolliseksi sen, etta funktioista vain esittelyt laitetaan otsikkotie-dostoihin, jotka sitten luetaan #include-komennolla jokaiseen tiedos-toon, jossa funktioita kutsutaan. Funktioiden varsinainen maarittely(koodi) voi sitten olla omassa kooditiedostossaan, joka voidaan kaan-taa erikseen. Funktiota kutsuttaessa pelkka funktion esittely riittaakaantajalle funktiokutsun tekevan koodin tuottamiseen (tata kasitel-tiin jo aiemmin aliluvussa 1.4, katso kuva 1.6 sivulla 41).

Koska mallin koodin on oltava kaantajan tiedossa mallia kaytet-taessa, normaalin mallin koodia ei voi jattaa omaan erikseen kaannet-tavaan kooditiedostoonsa, vaan mallin koodi on luettava sisaan jokapaikassa jossa mallia kaytetaan. Kaytannossa tama tarkoittaa sita, ettayleensa mallin koko koodi kirjoitetaan otsikkotiedostoon.

Ohjelman modulaarisuuden kannalta mallin koodin sijoittaminenotsikkotiedostoon on huono asia. Modulaarisuuden perusperiaattei-tahan on, etta eri ohjelmamoduulien ei tarvitse tietaa toistensa to-teutusta, vaan pelkan rajapinnan (eli esittelyiden) lukeminen riittaa.Taman vuoksi C++:aan lisattiin standardoinnin yhteydessa avainsanaexport, jonka avulla mallien modulaarisuutta voidaan lisata. Ikavakylla tama avainsana on tata kirjoitettaessa (kevaalla 2003) toteutet-tu vain erittain harvoissa kaantajissa. Lisaksi kaytannon kokemukseteraiden lehtiartikkeleiden [Sutter, 2002b] mukaan viittaavat siihen,etta export ei kuitenkaan ratkaise kaikkia kaannosriippuvuusongel-mia toivotulla tavalla, vaikka se mahdollistaakin mallien esittelyn jatoteutuksen kirjoittamisen eri tiedostoihin.

Jos mallista annetaan vain esittely ja sen alussa ennen template-sanaa esiintyy avainsana export, tama kertoo kaantajalle etta mallinkoodi on muualla. Talloin kaantaja ei mallia kaytettaessa viela varsi-naisesti instantioi mallia, vaan pistaa ainoastaan muistiin, millaista

Page 299: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

9.5. C++: Toteutuksen geneerisyys: mallit (template) 299

instantiointia tarvitaan. Mallin koodi kirjoitetaan nyt toiseen koodi-tiedostoon ja varustetaan myos avainsanalla export.

Tata kooditiedostoa kaantaessaan kaantaja pistaa muistiin sen, et-ta mallin koodi loytyy tarvittaessa kyseisesta tiedostosta. Kun lopultakoko ohjelman objektitiedostoja linkitetaan yhteen, linkkeri etsii tar-vittavia mallien instansseja vastaavat mallien koodit sisaltavat koodi-tiedostot ja kaantaa niista tarvittavan koodin.

Talla tavoin mallit voidaan export-avainsanan avulla kirjoittaa sa-maan tapaan kuin muukin koodi ja laittaa otsikkotiedostoihin vainmallien esittelyt. Listauksessa 9.16 on esimerkki export-avainsanankaytosta.

Tyyppi vai arvo — avainsana typename

Mallin koodissa ei auki jatetyista tyypeista tiedeta mitaan. Normaa-listi tama ei haittaa, koska kaantaja tuottaa mallista konekoodia vastamallin kayton yhteydessa, jolloin tyyppiparametreja vastaavat tyypit-

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . max.hh . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .1 export template <typename T>2 T const& max(T const& p1, T const& p2);

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . main.cc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .1 #include "max.hh"2 int main()3 4 int m = max(4, 8);

...5

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . max.cc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .1 export template <typename T>2 T const& max(T const& p1, T const& p2)3 4 if (p1 > p2)5 6 return p1;7 else 8 return p2;9

10

LISTAUS 9.16: Avainsanan export kaytto

Page 300: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

9.5. C++: Toteutuksen geneerisyys: mallit (template) 300

kin ovat jo tiedossa. Tietyissa tilanteissa kaantajalle taytyy kuitenkinkertoa enemman tyyppiparametreista.

C++:ssa luokan sisalla voi maaritella jasenfunktioita, jasenmuuttu-jia ja luokan sisaisia tyyppeja (aliluku 8.3). Ongelmaksi tulee, ettajos T on luokka, voidaan syntaksilla T::x viitata jasenmuuttujaan tai-funktioon nimelta x tai luokan sisalla maariteltyyn tyyppiin nimeltax. Jos nyt T on mallin tyyppiparametri, ei kaantajalla ole mallin koo-dia lukiessaan mitaan tapaa tietaa, onko x jasenfunktio tai -muuttujavai tyyppi. Tama tieto taas on tarpeen jo mallin koodin sisaanluku-vaiheessa, jotta kaantaja ylipaataan pystyy ymmartamaan koodin jaselvittamaan, onko siina syntaksivirheita.

Ongelma on ratkaistu C++:ssa maarittamalla, etta edella mainittuamuotoa olevat luokan sisalle tapahtuvat viittaukset tulkitaan ainaniin, etta ne viittaavat joko jasenfunktioon tai -muuttujaan. Jos mallinkoodissa halutaan, etta T::x onkin tyyppi, sen eteen taytyy kirjoittaaavainsana typename.

Listaus 9.17 valottaa tata esimerkin avulla. Siina funktiomallisummaa olettaa saavansa tyyppiparametrina tietorakenteen, jossa onkentat eka ja toka seka lisaksi sisainen tyyppi arvotyyppi, joka ker-too minka tyyppisia arvoja eka ja toka ovat. Funktiomalli haluaa pa-lauttaa tamantyyppisen arvon, joten sen paluutyypiksi on merkit-

1 template<typename T>2 typename T::arvotyyppi summaa(T const& pari)3 4 return pari.eka + pari.toka;5 67 struct IntPari8 9 typedef int arvotyyppi;

10 arvotyyppi eka;11 arvotyyppi toka;12 ;1314 void kaytto(IntPari const& p)15 16 int summa = summaa(p);17

LISTAUS 9.17: Tyypin maaraaminen avainsanalla typename

Page 301: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

9.5. C++: Toteutuksen geneerisyys: mallit (template) 301

ty T::arvotyyppi. Oletusarvoisesti kaantaja tulkitsee taman T:n ja-senfunktioksi tai -muuttujaksi, joten merkinnan eteen taytyy lisatatypename. Listauksessa nakyy myos esimerkki taman funktiomallinkaytosta.

Page 302: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

302

Luku 10

Geneerinen ohjelmointi:STL ja metaohjelmointi

We define abstraction as selective ignorance — concentrat-ing on the ideas that are relevant to the task at hand, andignoring everything else — and we think that it is the mostimportant idea in modern programming. The key to writinga successful program is knowing which parts of the prob-lem to take into account, and which parts to ignore. Everyprogramming language offers tools for creating useful ab-stractions, and every successful programmer knows how touse those tools.– From the preface of Accelerated C++ [Koenig ja Moo, 2000]

Hyva esimerkki mallien kaytosta ja geneerisesta ohjelmoinnista onC++-standardiin kuuluva kirjasto STL (Standard Template Library).STL maarittelee joukon tavallisimpia tietorakenteita ja niiden kayt-toon tarkoitettuja algoritmeja. Tarkoituksena on, etta ohjelmoijan eitarvitsisi keksia pyoraa uudelleen ja kirjoittaa aina omia tietoraken-teitaan vaan ohjelmissa voisi suoraan kayttaa valmiiksi kirjoitettuja,tehokkaita ja optimoituja vakiokomponentteja.

STL perustuu lahes kokonaan malleihin. Se on toteutettavissakokonaan C++:n vakio-ominaisuuksia kayttaen eika vaadi kaantajaltamitaan “erityisominaisuuksia”. Niinpa STL on hyva esimerkki siita,kuinka geneerisia ratkaisuja C++:lla on mahdollista saada aikaan.

Page 303: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.1. STL:n perusperiaatteita 303

Usein geneerisessa ohjelmoinnissa tulee vastaan tilanteita, jos-sa geneerisen kirjaston olisi tarpeen mukauttaa omaa rakennettaanauki jatetyista tyypeista riippuen, valita parhaiten tilanteeseen sopi-van algoritmi tai muuten toimia “alykkaasti”. Tallaisesta paattelyjakayttavasta geneerisyydesta, jossa geneerinen komponentti vaikuttaaomaan rakenteeseensa, kaytetaan yleensa nimitysta metaohjelmointi(metaprogramming) ja sita kasitellaan jonkin verran aliluvussa 10.6.

10.1 STL:n perusperiaatteita

Kaikkein tarkein geneerisyyden muoto STL:ssa on, etta kaikissa sentietorakenteissa alkioiden tyyppi on jatetty auki eli tietorakenteeton kirjoitettu ottamatta kantaa alkioiden tyyppiin. Alkioiden tyyppimaarataan vasta siina vaiheessa, kun tietorakenteita varsinaisesti luo-daan. Kaytannossa tama tarkoittaa, etta kaikki STL:n tietorakenteetovat luokkamalleja. Esimerkki tasta on vector, jossa vector<int>on kokonaislukuvektori, vector<string> merkkijonovektori ja niinedelleen.

STL:n toteutus mallien avulla antaa mahdollisuuden siihen, ettavaikka itse STL:ssa alkioiden tyyppi on jatetty avoimeksi, kaantajavoi kuitenkin tehda STL:aa kayttavassa koodissa kaikki tarpeellisettyyppitarkastukset ja antaa mahdolliset virheilmoitukset jo kaannos-aikana. Tassa suhteessa STL eroaa esimerkiksi Javan tietorakenteista.Niissa kaytetaan geneerisyyden aikaansaamiseksi periytymista.

Kaikki Javan tietorakenteet sisaltavat luokkaa Object olevia alkioi-ta. Koska kaikki Javan luokat on periytetty tasta luokasta, voi tieto-rakenteeseen tallettaa mita tahansa alkioita. Tama kuitenkin tarkoit-taa, etta Javassa yhteen tietorakenteeseen voi sekoittaa minka tahansatyyppisia alkioita. Kun alkiot myohemmin luetaan ulos, viitteet nii-hin taytyy erikseen tyyppimuunnoksella muuntaa alkioiden todellis-ta tyyppia vastaaviksi. Jos alkion todellinen tyyppi onkin oletetustapoikkeava, tuloksena on ajoaikainen virheilmoitus. Tassa mielessamallit ovat periytymista turvallisempi keino taman tyyppisen genee-risyyden toteuttamiseen.

Page 304: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.1. STL:n perusperiaatteita 304

10.1.1 STL:n rakenne

Alkioiden tyypin auki jattamisen lisaksi STL:ssa on paljon muutakingeneerista. Tietorakenteita kayttavat algoritmit ovat tietorakenteistariippumattomia ja geneerisesti kirjoitettuja, joten niita voi kayttaamyos itse kirjoitettujen tietorakenteiden kanssa, kunhan vain tietytvaatimukset tayttyvat. Samoin tietorakenteiden muistinhallinta (se,miten ja mista tarvittava muisti varataan) on geneerisesti kirjoitet-tu, joten ohjelmoija voi itse vaikuttaa siihen. STL on myos hyva esi-merkki tiettyjen suunnittelumallien, erityisesti iteraattoreiden (alilu-ku 9.3.2) kaytosta.

Taman luvun ei ole tarkoitus olla kattava STL-opas vaan tar-koituksena on kertoa STL:n perusperiaatteet ja kayda niita lapi sii-na laajuudessa, kun ne liittyvat olio-ohjelmointiin ja geneerisyyteen.STL:ssa on sellaisia hyodyllisia ominaisuuksia ja yksityiskohtia, joi-den lapikaymiseen ei tassa kirjassa ole mahdollisuuksia ja jotka eivatmuutenkaan sovi hyvin taman kirjan aihepiiriin. Tarkempaa tietoaSTL:n yksityiskohdista ja sen kaytosta ohjelmointiin loytyy monistaC++-oppikirjoista [Lippman ja Lajoie, 1997], [Stroustrup, 1997]. Lisak-si STL:n ja muun C++:n vakiokirjaston kaytosta loytyy kirja “The C++Standard Library” [Josuttis, 1999].

STL muodostuu seuraavista osista:

• Sailiot (container) ovat STL:n tarjoamia tietorakenteita. Niitakasitellaan aliluvussa 10.2.

• Iteraattorit (iterator) ovat “kirjanmerkkeja” sailioiden lapikay-miseen. Aliluku 10.3 kertoo iteraattoreista tarkemmin.

• Geneeriset algoritmit (generic algorithm) kasittelevat sailioitaiteraattoreiden avulla. Niista kerrotaan aliluvussa 10.4.

• Sailiosovittimet (container adaptor) ovat sailiomalleja, jotka to-teutetaan halutun toisen sailion avulla. Niilla voi muuntaa sai-lion rajapinnan toisenlaiseksi. Sailiosovittimia kasitellaan ly-hyesti aliluvun 10.2.3 lopussa.

• Funktio-oliot (function object) ovat olioita, jotka kayttaytyvatkuten funktiot ja joita voi kayttaa muuan muassa algoritmientoiminnan saatamiseen. Niista kerrotaan aliluvussa 10.5.

Page 305: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.1. STL:n perusperiaatteita 305

• Varaimet (allocator) ovat olioita sailioiden muistinhallinnanraatalointiin. Lyhyesti selitettyna varaimet ovat olioita, jotkaosaavat varata ja vapauttaa muistia. Normaalisti STL:n sailiotvaraavat muistinsa new’lla ja vapauttavat deletella. Jos niille an-netaan ylimaaraisena tyyppiparametrina varainluokka, ne kayt-tavat ko. luokan palveluita tarvitsemansa muistin varaamiseenja vapauttamiseen.

Nama osat eivat muodosta irrallisia kokonaisuuksia, vaan kayt-tavat lahes kaikki toisiaan. Esimerkiksi sailioita voi tietyssa maarinkayttaa ymmartamatta muita STL:n osia, mutta algoritmien kayttovaatii iteraattoreiden ymmartamisen ja iteraattoreiden kaytto puoles-taan sailioiden. Funktio-olioita taas tarvitaan joidenkin algoritmiensaatamiseen. STL:n ehka vahiten kaytetty osa on varaimet, joita tar-vitaan vain jos halutaan pakottaa sailiot varaamaan muistia juuri tie-tylla tavallisuudesta poikkeavalla tavalla.

10.1.2 Algoritmien geneerisyys

Olio-ohjelmoinnissa on totuttu siihen, etta luokan oliot sisaltavat ha-lutut tiedot ja tietoja kasitteleva koodi kirjoitetaan luokan jasenfunk-tioihin. Tasta syysta tuntuisi alkuun loogiselta, etta STL:n tarjoamatalgoritmit olisi toteutettu sailioiden jasenfunktioina. Nain ei kuiten-kaan ole tehty, vaan lahes kaikki STL:n algoritmit on kirjoitettu irral-lisina funktioina, jotka eivat kuulu mihinkaan luokkaan vaan ottavatkaiken tarvittavan tiedon parametreinaan. Mika on syyna tahan olio-vastaiseen toteutukseen?

Syy algoritmien toteuttamiseen tavallisina funktioina on, ettaSTL:n algoritmien geneerisyys ei rajoitu kasiteltavien alkioiden tyy-pin auki jattamiseen. Kaikki algoritmit on toteutettu funktiomalleina.Samalla tavoin kuin sailiot jattavat alkioidensa tyypin auki, STL:n al-goritmit jattavat auki sen, minka tyyppisen tietorakenteen kanssa netoimivat. Nain samaa geneerista find-algoritmia voidaan kayttaa etsi-maan halutunlaista alkiota niin listasta, vektorista kuin joukostakin.Etuna tasta on, etta algoritmeja ei tarvitse kirjoittaa erikseen jokaisellesailiolle vaan yksi geneerinen funktiomalli toimii kaikkien sailioidenkanssa.

Tama algoritmien geneerisyys tarkoittaa myos, etta STL:n algorit-mien kaytto ei rajoitu vain STL:n omiin sailioihin. Ohjelmoija voi

Page 306: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.1. STL:n perusperiaatteita 306

kirjoittaa omia tietorakenteitaan, ja jos ne toteuttavat STL:n algorit-mien asettamat vaatimukset, naita algoritmeja voi kayttaa kasittele-maan myos ohjelmoijan omia tietorakenteita. Jos ohjelmoija esimer-kiksi toteuttaa hajautustaulun tai binaaripuun, STL:n find-algoritmipystyy etsimaan alkioita niistakin. Nain STL:n geneerisyys mahdol-listaa myos STL:n “laajentamisen” omilla sailiotyypeilla.

10.1.3 Tietorakenteiden jaotteluperusteet

Kun kirjoissa ja oppilaitoksissa opetetaan tietorakenteita, niissa kes-kitytaan luonnollisesti erityisesti tietorakenteiden sisaiseen rakentee-seen (tasta johtuu koko nimi “tietorakenne”). Niinpa tiettya tietora-kennetta ajatellessaan suurimmalle osalle ohjelmoijista tulee mieleenjuuri tietorakenteen sisainen rakenne. Kuva 10.1 sisaltaa esimerkke-ja mielikuvista, joita tyypilliselle ohjelmoijalle saattaisi tulla sanoista“vektori” ja “lista”.

Olio-ohjelmoinnin kannalta tama tapa mieltaa tietorakenteet nii-den rakenteen avulla on jarjeton. Koko olio-ohjelmoinnin kantavaideahan on keskittya olioiden kaytossa rajapintoihin ja piilottaa sisai-nen toteutustapa kayttajalta. Niinpa tietorakenteiden kayton kannal-ta oleellista ei saisi olla se, miten tietorakenne toteutetaan vaan mitentietorakennetta on tarkoitus kayttaa. Se, onko vektori toteutettu yhte-naisena muistialueena ja sisaltaako lista linkkiosoittimet seuraavaanja edelliseen alkioon, on osa tietorakenteen sisaista toteutusta, jonkaei pitaisi nakya tietorakenteesta ulospain.

0 1 2 3 4 5

Alkio Alkio Alkio Alkio Alkio Alkio

(a) “vektori”

Alkio

seur.

edell.

Alkio

seur.

edell.

Alkio

seur.

edell.

Alkio

seur.

edell.

ens.

viim.

(b) “lista”

KUVA 10.1: Tietorakenteiden herattamia mielikuvia

Page 307: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.1. STL:n perusperiaatteita 307

Olio-ohjelmoinnin kannalta olisi siis oleellista luokitella tietora-kenteet niiden rajapintojen perusteella. Kun tietorakenteiden tarjoa-mia rajapintoja tarkastelee, huomaa kuitenkin pian, etta varsin mo-nilla tietorakenteilla on periaatteessa aivan samanlainen rajapinta jasamanlaiset operaatiot. Esimerkiksi vektoriin voi lisata alkioita ha-luttuun paikkaan (jos oletetaan, etta vektori kasvattaa kokoaan tarvit-taessa), alkioita voi poistaa halutusta paikasta ja lisaksi halutun al-kion voi hakea sen jarjestysnumeron perusteella. Tasmalleen samatoperaatiot ovat kuitenkin mahdollisia myos listan tapauksessa, ja onhelppo keksia myos muita tietorakenteita, joissa nama operaatiot ovatmahdollisia.

Samanlaisista operaatioista huolimatta on kuitenkin selvaa, ettavektori ja lista ovat jollain lailla oleellisesti erilaisia tietorakenteita.Tama ero on siina, etta eri tietorakenteiden sinansa samanlaisten ope-raatioiden tehokkuus saattaa olla erilainen. Joillekin tietorakenteilleuusien alkioiden lisaaminen on nopeaa, toisille hidasta. Nopeus saat-taa myos riippua siita, mihin kohtaan uusi alkio lisataan. Vastaavastitietyn alkion hakemisen ja muiden operaatioiden tehokkuus riippuutietorakenteesta.

Esimerkiksi alkion lisaaminen listan tietyn alkion peraan on no-pea operaatio, koska listan taytyy vain luoda uusi alkio ja linkittaa seosoittimia muuttamalla mukaan listaan. Vastaava lisaysoperaatio vek-torille on kuitenkin hidas, koska uudelle alkiolle taytyy tehda tilaaesimerkiksi siirtamalla kaikkia lisayskohdan jalkeen tulevia alkioitayhdella eteenpain. Vastaavasti halutun jarjestysnumeron omaavan al-kion haku vektorista on nopeaa, koska vektorin alkiot sijaitsevat pe-rakkaisissa muistiosoitteissa. Listalle vastaava operaatio taas on hi-das, koska ainoa tapa etsia alkio listasta on lahtea sen ensimmaisestaalkiosta ja seurata seuraavaan alkioon osoittavaa linkkia tarvittavanmonta kertaa.

Edella mainitut asiat on otettu STL:ssa huomioon. Sen sailiot onjaettu kahteen paakategoriaan, joihin kuuluvien sailioiden rajapinnatovat keskenaan lahestulkoon samanlaiset. Sen sijaan rajapinnan ope-raatioilta vaadittu tehokkuus vaihtelee sailiosta toiseen. STL pyrkiimyos estamaan sailioiden tehottoman kayton. Jokin operaatio saattaapuuttua kokonaan tietyn tyyppisesta sailiosta, jos sen toteuttaminentehokkaasti tallaiselle sailiolle ei olisi mahdollista. Sailioiden operaa-tioiden tehokkuusvaatimukset on dokumentoitu C++-standardissa var-sin tarkasti, kun taas niiden sisaiseen toteutukseen ei oteta lainkaan

Page 308: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.1. STL:n perusperiaatteita 308

kantaa. STL:n sailioiden nimet on kuitenkin valittu niin, etta niidentehokkuusvaatimukset ovat nimeen nahden “luonnolliset” (esimer-kiksi list-tietorakenteeseen on nopeaa lisata uusia alkioita).

Kun ohjelmassa tulee tarve kayttaa jotain tietorakennetta, ohjel-moijan tulisi ensimmaisena miettia ohjelman tehokkuusvaatimuksia.Niiden operaatioiden, joita tietorakenteelle suoritetaan usein, tulisiolla nopeita. Sen sijaan harvoin suoritettavien operaatioiden tehok-kuudella ei yleensa ole mitaan valia. Naiden tietojen perusteella oh-jelmoija voi sitten valita STL:sta tietorakenteen, jonka tehokkuusomi-naisuudet ovat ohjelman tarpeita vastaavat.

10.1.4 Tehokkuuskategoriat

Erilaisten operaatioiden tehokkuuden maarittely ja luokittelu ei oleyksinkertainen asia. Tehokkuuden mittaaminen milli- tai nanosekun-neissa ei ole jarkevaa, koska tallainen “absoluuttinen” tehokkuus tie-tysti riippuu operaation toteutuksen tehokkuuden lisaksi myos kayte-tyn tietokoneen nopeudesta. Todellinen nopeus riippuu myos esimer-kiksi tietorakenteisiin talletettujen alkioiden lukumaarasta ja koosta— on hitaampaa siirtaa paljon tai suuria alkioita. Kuitenkin STL:ntapaisessa geneerisessa kirjastossa on tarve vertailla operaatioiden te-hokkuuksia keskenaan.

Tietojenkasittelytieteessa yleisesti kaytetty tehokkuuden mittarion se, miten tietyn operaation nopeus muuttuu, kun tietorakenteenkoko kasvaa. Talla tavoin ajateltuna ei ole valia silla, kuinka suuriaalkiot ovat, kuinka monta niita tasmalleen on tai kuinka nopea itsetietokone on. Oleellista on se, miten operaatio hidastuu, kun tietora-kenteen alkioiden maara kasvaa.

Esimerkkina voidaan jalleen pitaa vektoria ja listaa. Alkion lisaa-minen vektorin alkuun kestaa sita kauemmin, mita suurempi vektorion. Tarkasti ottaen lisaamiseen kuluva aika on suoraan verrannolli-nen vektorin kokoon. Sen sijaan listan alkuun lisaaminen kestaa tas-malleen yhta kauan, oli lista kuinka suuri tahansa. Vastaavasti vekto-rin alkion hakeminen jarjestysnumeron perusteella on vakioaikainenoperaatio kun taas listalle saman operaation aika on suoraan verran-nollinen haetun alkion jarjestysnumeroon, jota taas rajoittaa listankoko.

Operaation tehokkuus saattaa tietysti riippua myos tietorakenteenalkioiden sisallosta, keskinaisesta jarjestyksesta ja muista seikoista.

Page 309: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.1. STL:n perusperiaatteita 309

Operaation suoritusajalle voidaan naiden perusteella arvioida sekayla- etta alaraja. Ohjelman tehokkuuden varmistamisen kannalta onkuitenkin usein oleellista, kuinka operaatio kayttaytyy pahimmassamahdollisessa tilanteessa.

Usein tehokkuudessa kiinnostaa vain suoritusajan yleinen kayt-taytyminen. Talloin puhutaan usein operaation tehokkuuden ker-taluokasta (order of growth). Sille voi laskea useita erilaisia tehok-kuusmittoja, joille tietojenkasittelytieteessa on omat merkintatapan-sa. Naista merkintatavoista yleisimmat ovat

• Θ-notaatio, joka kertoo suoritusajan kertaluokan

• O-notaatio, joka kertoo suoritusajan “asymptoottisen ylarajan”— kertaluokan, jota suoritusaika ei varmasti ylita, kun alkioidenmaara kasvaa tietyn rajan yli

• Ω-notaatio, joka kertoo suoritusajan “asymptoottisen alarajan”— kertaluokan, jota suoritusaika ei varmasta alita, kun tietty al-kiomaara ylitetaan.

Naiden tehokkuuden merkintatapojen tarkka kuvaus jaa taman kirjanaihepiirin ulkopuolelle, mutta niista loytyy tietoa lahes kaikista algo-ritmeja kasittelevista kirjoista ja muista lahteista (esimerkiksi “Intro-duction to Algorithms” [Cormen ja muut, 1990]).

On huomattava, etta kertaluokkanotaatio kertoo vain, kuinka ope-raation suoritusaika kayttaytyy alkioiden maaran kasvaessa. Se eikuitenkaan anna mitaan tietoa varsinaisesta suoritusajasta. Esimer-kiksi kahdesta lineaarisesta operaatiosta toinen voi olla paljon toistahitaampi — lineaarisuus kertoo vain, etta molemmissa suoritusaikakasvaa suoraan suhteessa alkioiden maaraan.

STL:n kannalta tarkein tehokkuuden mittari on ylarajan mittariO.Se kertoo, kuinka tehokas operaatio ainakin on. Esimerkiksi vakio-aikainen tehokkuus O(1) kertoo, ettei nopeus riipu alkioiden luku-maarasta. Lineaarinen tehokkuus O(n) taas kertoo, etta suoritusaikaon suoraan verrannollinen alkioiden lukumaaraan tai pienempi. Ku-vaan 10.2 seuraavalla sivulla on keratty tarkeimmat STL:ssa kaytetyttehokkuusluokat selityksineen ja O-merkintoineen.

Toinen STL:ssa jonkin verran kaytetty mittari on keskimaarainentehokkuus. Sita kaytetaan joskus O-tehokkuuden rinnalla, jos huo-noimman mahdollisen tapauksen tehokkuus eroaa oleellisesti kes-kimaaraisesta tehokkuudesta. Esimerkiksi jarjestysoperaatio sort on

Page 310: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.2. STL:n sailiot 310

Nimitys O-notaatio SelitysKaannosaikainen(compile-time)

O(0)a Operaatio suoritetaan kaannosaikanaeli se ei vaikuta suoritusaikaan.

Vakioaikainen(constant)

O(1) Suoritusaika ei riipu alkioiden maaras-ta.

Amortisoidustivakioaikainen(amortized constant)

O(1) Operaatio on “kaytannossa” vakioai-kainen, yksittaistapauksissa kenties hi-taampi.

Logaritminen(logarithmic)

O(logn) Suoritusaika on verrannollinen alkioi-den maaran logaritmiin.

Lineaarinen(linear)

O(n) Suoritusaika on suoraan verrannollinenalkioiden maaraan.

O(n logn) Suoritus hidastuu enemman kuin line-aarisesti, ei kuitenkaan viela neliollises-ti.

Neliollinen(quadratic)

O(n2) Suoritusaika on verrannollinen alkioi-den maaran nelioon.

aVaikka merkinta O(0) onkin teoreettisesti oikea, sita ei yleensa kayteta.

KUVA 10.2: Erilaisia tehokkuuskategorioita

keskimaaraiselta tehokkuudeltaan n logn, vaikka se pahimmassa ta-pauksessa voi olla esimerkiksi O(n2).

10.2 STL:n sailiot

Kuten jo aiemmin tassa luvussa on todettu, STL:n sailioiden rajapin-nat muistuttavat suuresti toisiaan. Rajapintojensa puolesta STL:n sai-liot muutamaa poikkeusta lukuun ottamatta jakautuvat kahteen kate-goriaan:

• Sarjat (sequence) ovat sailioita, joiden alkioita pystyy hake-maan niiden jarjestysnumeron perusteella. Samoin alkioita voilisata haluttuun paikkaan ja poistaa siita. Esimerkiksi taulukko-tyyppi vector on tallainen sarja.

Page 311: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.2. STL:n sailiot 311

• Assosiatiiviset sailiot (associative container) puolestaan perus-tuvat siihen, etta alkioita haetaan sailiosta avaimen (key) pe-rusteella. Esimerkiksi puhelinluettelo muistuttaa assosiatiivistasailiota — siina numeron pystyy etsimaan nopeasti nimen pe-rusteella.

Vaikka naiden kahden kategorian sailiot eroavatkin rajapinnoil-taan, niilla on myos yhteisia rajapintaoperaatioita. Kaikilta sailioiltavoi esimerkiksi kysya jasenfunktiolla empty, ovatko ne tyhjia. Lisaksiniiden tarkan koon voi selvittaa jasenfunktiolla size.

Kuten mallit yleensakin, myos STL:n sailiot asettavat joitakin vaa-timuksia tyyppiparametreilleen eli alkioidensa tyypille. Jotta tietyn-tyyppisia alkioita varten voisi luoda sailion, alkioiden tyypin taytyytoteuttaa kaksi ehtoa. Tyypilla tulee olla

• kopiorakentaja, joka luo alkuperaisen olion kanssa samanlaisenolion

• sijoitusoperaattori, jonka tuloksena sijoituksen kohteena olevas-ta oliosta tulee samanlainen sijoitetun olion kanssa.

Lisaksi STL:n assosiatiiviset sailiot vaativat, etta alkioiden avai-mia voi myos kopioida ja sijoittaa ja lisaksi kahta avainta taytyy pys-tya vertailemaan, jotta avaimet voidaan panna jarjestykseen (oletusar-voisesti vertailu tehdaan operaattorilla <).

Naista vaatimuksista seuraa se, etta viitteet eivat kelpaa STL:n sai-lioiden alkioiksi tai avaimiksi. Viitteiden tapauksessahan viitteeseensijoittaminen ei muuta viitetta viittaamaan toiseen olioon, vaan sijoit-taa viitteen paassa olevat oliot. Riippuu kaytetysta kaantajasta, osaa-ko se antaa virheilmoituksen, jos ohjelmassa yritetaan luoda viitesai-lioita.

Kaikki STL:n sailiot varaavat itse lisaa muistia tarvittaessa, kunniihin lisataan uusia alkioita. Kun sailio tuhotaan, se vapauttaa kai-ken varaamansa muistin. Sen sijaan ei ole varmaa, vapauttaako sailiovaraamaansa muistia heti, kun alkio poistetaan sailiosta. Monet sai-liot nimittain saattavat pitaa muistia “varastossa” ja ottaa sen uudel-leen kayttoon, kun sailioon myohemmin lisataan uusia alkioita.

Page 312: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.2. STL:n sailiot 312

10.2.1 Sarjat (“perakkaissailiot”)

Sarjat (sequence) ovat sailioita, joissa alkiot sijaitsevat “perakkain” jajoissa jokaisella alkiolla on jarjestysnumero. Alkioita voi selata jarjes-tyksessa, ja halutun alkion voi hakea sen jarjestysnumeron perusteel-la. Uusia alkioita voi lisata sailiossa haluttuun paikkaan, ja vanhojavoi poistaa. STL tarjoaa kolme erilaista sarjasailiota: vector, deque jalist.

Kaikissa sarjoissa annetaan sarjan alkioiden tyyppi mallin tyyp-piparametrina, siis esimerkiksi vector<float>, deque<int> jalist<string>. Taman lisaksi ylimaaraisena tyyppiparametrina voiantaa sarjan muistinhallintaan kaytettavan varaimen, mutta tatamahdollisuutta ei kasitella tassa enempaa.

Sarjojen rajapinta on suurelta osin yhtenainen. Uuden alkion voikaikissa sarjoissa lisata jasenfunktiolla insert ja vanhoja alkioita voipoistaa jasenfunktiolla erase. Lisaksi koko sarjan voi tyhjentaa ja-senfunktiolla clear. Lisaksi osasta sarjoja loytyy viela “ylimaaraisia”jasenfunktioita sellaisia toimintoja varten, jotka kyseisessa sarjassaovat erityisen nopeita. Esimerkiksi vector-tyypista loytyy jasenfunk-tio push back, joka lisaa uuden alkion taulukon loppuun. Saman toi-minnon saisi tehtya myos insert-jasenfunktiolla, mutta silloin sillepitaisi erikseen kertoa, etta lisays tehdaan taulukon loppuun.

Varsinaisten sarjojen lisaksi useimmat STL:n algoritmit suostuvatkasittelemaan myos C++:n perustaulukoita (esimerkiksi int t[10]) ku-ten sarjoja, vaikka niiden ulkoinen rajapinta ei olekaan varsinaisestisarjojen rajapintavaatimusten mukainen. Samoin merkkijonotyyppiastring voi kasitella kuten merkeista muodostuvaa vektoria.

Koska suurimmat erot eri sarjasailioiden valilla ovat tehokkuudes-sa, kuvaan 10.3 seuraavalla sivulla on keratty tyypillisimpia sarjojentarjoamia operaatioita ja niiden tehokkuuksia.

Vektori — vector

Vektori (vector) on STL:n vastine taulukoille. Vektoreita voi indek-soida taulukoiden tapaan, ja tama alkioiden haku jarjestysnumeronperusteella on vakioaikainen. Uusien alkioiden lisaaminen vektoriinon lineaarinen operaatio, paitsi jos uusi alkio lisataan vektorin lop-puun, jolloin lisays on amortisoidusti vakioaikainen. Samoin alkioi-

Page 313: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.2. STL:n sailiot 313

Operaatio vector deque list1./viim. alkio (front/back) vakio vakio vakioMielivaltainen indeksointi ([ ], at) vakio vakio —1. alkion lisays (push front) —a vakio vakio1. alkion poisto (pop front) —b vakio vakioViim. alkion lisays (push back) amort. vakio vakio vakioViim. alkion poisto (pop back) vakio vakio vakioMieliv. lisays/poisto (insert/erase) lineaar. lineaar. vakioSarjan koko/tyhjyys (size/empty) vakio vakio lin./vakioSarjan tyhjentaminen (clear) lineaar. lineaar. lineaar.

aOperaation voi suorittaa jasenfunktiolla insert.bOperaation voi suorittaa jasenfunktiolla erase.

KUVA 10.3: Sarjojen operaatioiden tehokkuuksia

den poisto on lineaarinen operaatio, paitsi viimeisen alkion poisto onvakioaikainen.

Vektori saadaan kayttoon komennolla #include <vector>. Senrajapinnassa on sarjojen perusrajapinnan lisaksi edella mainittu in-deksointi. Indeksoinnin voi tehda joko normaaleilla hakasulkeilla [ ],jolloin mahdollista yli-indeksointia ei valttamatta tarkasteta, tai ja-senfunktiolla at, joka heittaa poikkeuksen, jos annettu indeksi on lii-an suuri. Vektorin loppuun lisaysta ja poistoa varten ovat jasenfunk-tiot push back ja pop back. Taman lisaksi vektorin ensimmaisen javiimeisen alkion voi lukea jasenfunktioiden front ja back avulla.

Normaalin C++:n taulukon tapaan vektorin alkiot sijaitsevat muis-tissa perakkain. Kun vektoriin lisataan uusi alkio, sen kokoa tay-tyy kasvattaa. Tama puolestaan tarkoittaa, etta vektorin taytyy varatauusi suurempi muistialue, kopioida vanhat alkiot sinne ja vapauttaavanha muistialue. Koska tama operaatio on hidas (tarkasti ottaen li-neaarinen), vektori ei varaa uutta muistia joka lisayksella. Sen sijaanvektori varaa aina kerralla tarvittavaa suuremman muistialueen, jon-ka alkuun se kopioi olemassa olevat alkiot. Kun uusia alkioita lisataan

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .Tarkasti ottaen tallaista vaatimusta ei loydy C++-standardista. Tama on kuulemma kuiten-

kin ollut standardin kirjoittajien tarkoitus, ja kaikissa kaantajissa vektori on toteutettu tallatavalla. Lisaksi vaatimus lisataan standardiin seuraavan paivityksen yhteydessa.

Page 314: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.2. STL:n sailiot 314

vektorin loppuun, niille on jo valmiiksi muistia, joten uutta muistin-varausta ei tarvita. Kun varalla oleva muisti on kulutettu loppuun,vektorin taytyy taas varata suurempi muistialue ja niin edelleen.

Talla tavoin uuden alkion lisaaminen vektorin loppuun saadaan“kaytannossa” vakioaikaiseksi, vaikka aina silloin talloin muistinva-raus ja vanhojen alkioiden kopiointi hidastavatkin operaatiota. Vek-torista loytyy myos jasenfunktio reserve, jolla vektoria voi kaskea va-rautumaan annettuun maaraan alkioita. Talloin vektori varaa kerral-la riittavasti muistia, jolloin uudelleenvarausta ei tarvita niin kauan,kuin alkioiden maara pysyy annetuissa rajoissa.

Vektori on sopiva tietorakenne tilanteisiin, joissa tietorakennettaindeksoidaan paljon mutta uusia alkioita lisataan korkeintaan van-hojen peraan ja vanhoja alkioita poistetaan vain lopusta. Kaytannos-sa tama tarkoittaa, etta vektori kelpaa korvaamaan C++:n perustaulukotlahes kaikkialla. Listaus 10.1 sisaltaa esimerkin vektoreiden kaytosta.Funktio laskeFibonacci tayttaa parametrina annetun vektorin anne-tulla maaralla Fibonaccin lukuja. (Fibonaccin luvuista kaksi ensim-maista on 1 ja seuraavat alkiot saadaan aina laskemalla yhteen kaksi

1 #include <vector>2 #include <algorithm>3 using std::vector;4 using std::min;56 void laskeFibonacci(unsigned long lkm, vector<unsigned long>& taulukko)7 8 taulukko.clear(); // Tyhjenna varmuuden vuoksi9 taulukko.reserve(lkm); // Varaudu nain moneen alkioon

1011 for (unsigned long i = 0; i < min<unsigned long>(lkm, 2); ++i)12 13 // Ensimmaiset kaksi alkiota ovat 114 taulukko.push back(1);15 16 for (unsigned long i = 2; i < lkm; ++i)17 18 // Loput alkiot kahden edellisen summa19 taulukko.push back(taulukko[i-2] + taulukko[i-1]);20 21

LISTAUS 10.1: Fibonaccin luvut vectorilla

Page 315: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.2. STL:n sailiot 315

edellista alkiota.)

Pakka / kaksipainen jono — deque

Kaksipainen jono (double-ended queue) on taulukko, jossa molem-piin paihin tehtavat lisaykset ja poistot ovat vakioaikaisia. Muualletehtavat lisaykset ja poistot ovat lineaarisia. Kaksipaisen jonon nimideque aantyy “dek” samoin kuin englannin sana “deck”, joka tarkoit-taa korttipakkaa. Koska deque muistuttaa toiminnallisuudeltaankinjonkin verran korttipakkaa (kortteja on helppo lisata ja poistaa pakanmolemmista paista), tassa teoksessa kaytetaan kaksipaisesta jonostatastedes lyhempaa nimitysta pakka.

Pakan rajapinta tarjoaa sarjojen perusrajapinnan lisaksi viela vek-torin rajapinnan eli operaatiot [ ], at, front, back, push back japop back. Naiden operaatioiden tehokkuuskin on kertaluokaltaan sa-ma kuin vektorissa. Koska myos pakan alkuun lisaaminen ja siel-ta poistaminen on nopeaa, pakassa on tata varten viela operaa-tiot push front ja pop front. Pakka saadaan kayttoon komennolla#include <deque>.

Koska pakan rajapinnassa on kaikki samat operaatiot kuin vekto-rissakin, ja operaatioiden tehokkuuskin on sama, pakkaa voi kayttaakaikkialla missa vektoriakin. Pakan indeksointi on kuitenkin kaytan-nossa aika lailla vektorin indeksointia hitaampaa (vaikka molemmatovatkin vakioaikaisia), joten suoritustehoa vaativissa tilanteissa vek-tori on parempi vaihtoehto.

Pakka eroaa vektorista edukseen siina, etta pakan alkuun lisays jasiita poisto ovat vakioaikaisia, kun ne vektorissa ovat lineaarisia. Pak-ka onkin kateva tietorakenne toteuttamaan esimerkiksi puskureita,joissa tietoa lisataan toiseen paahan ja luetaan toisesta. Listaus 10.2seuraavalla sivulla sisaltaa pakalla toteutetun luokan LukuPuskuri, jo-hon voi lisata rajoittamattoman maaran kokonaislukuja ja josta ne voilukea pois samassa jarjestyksessa. Lisaksi puskurista voi lukea anne-tussa kohdassa olevan alkion arvon.

Lista — list

Lista (list) on viimeinen STL:n sarjoista. Nimensa mukaisesti lista ontietorakenne, joka tehokkuudeltaan vastaa kahteen suuntaan linkitet-tya listarakennetta. Alkioiden lisaaminen ja poistaminen ovat vakio-

Page 316: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.2. STL:n sailiot 316

1 #include <deque>2 using std::deque;34 class LukuPuskuri5 6 public:7 // (Rakentajat ja purkaja tyhjia)8 void lisaa(int luku); // Lisaa loppuun9 int lue(); // Lukee ja poistaa alusta

10 int katso(int paikka); // Lukee annetusta paikasta11 bool onkoTyhja() const;1213 private:14 deque<int> puskuri;15 ;1617 void LukuPuskuri::lisaa(int luku)18 19 puskuri.push back(luku);20 2122 int LukuPuskuri::lue()23 24 int luku = puskuri.front();25 puskuri.pop front();26 return luku;27 2829 bool LukuPuskuri::onkoTyhja() const30 31 return puskuri.empty();32 3334 int LukuPuskuri::katso(int paikka)35 36 return puskuri.at(paikka); // Ilman tarkistuksia: puskuri[paikka]37

LISTAUS 10.2: Puskurin toteutus dequella

Page 317: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.2. STL:n sailiot 317

aikaisia riippumatta siita, mihin kohtaan listalla lisays tai poisto koh-distuu. Listasta voi myos lukea ensimmaisen tai viimeisen alkion va-kioajassa mutta mielivaltaisen alkion lukeminen indeksoimalla puut-tuu kokonaan, koska sita ei saisi toteutetuksi tehokkaasti. Aliluvus-sa 10.3 esiteltavat iteraattorit antavat kuitenkin mahdollisuuden se-lata listaa lapi alkio kerrallaan.

Listan esittely luetaan komennolla #include <list>. Listan raja-pinta tarjoaa sarjojen perusrajapinnan palvelut seka operaatiot back,push back, pop back, front, push front ja pop front. Indeksointiope-raatiot [ ] ja at puuttuvat.

Lista tarjoaa lisaksi erityisoperaatioita, jotka listan linkitetty ra-kenne tekee mahdolliseksi toteuttaa tehokkaasti. Jasenfunktiollasplice voi vakioajassa siirtaa yhden listan alkiot haluttuun paikkaantoisessa listassa. Samoin silla voi vakioajassa siirtaa osan listasta toi-seen paikkaan samassa listassa. Lisaksi lista tarjoaa sita varten opti-moidut erikoisversiot eraista STL:n algoritmeista.

Listan etuna vektoriin ja pakkaan verrattuna on se, etta listojenyhdistaminen ja pilkkominen seka alkioiden lisays keskelle listaa japoisto sielta ovat vakioaikaisia. Vektorille ja pakalle kaikki nama ope-raatiot ovat lineaarisia. Sen sijaan listaa ei voi indeksoida, joten sitaei voi kayttaa taulukkotyyppina. Lista onkin parhaimmillaan pusku-reiden ja pinojen toteutuksessa seka tilanteissa, joissa tietorakenteitatulee pystya yhdistelemaan.

10.2.2 Assosiatiiviset sailiot

Assosiatiiviset sailiot (associative container) eroavat sarjoista siina,etta alkioita ei lueta, lisata tai poisteta niiden “sijainnin” tai jarjestys-numeron perusteella vaan jokaiseen alkioon liittyy avain (key), jonkaperusteella alkion voi myohemmin hakea. Tasta tulee myos tamansailiotyypin nimi: assosiatiiviset sailiot maaraavat assosiaation (“yh-teyden”) avaimen ja alkion valille. Osassa assosiatiivisia sailioita al-kio itse toimii myos avaimena, osassa avain ja alkio ovat erillisia. STLtarjoaa nelja assosiatiivista sailiota: set, multiset, map ja multimap.

Assosiatiivisille sailioille annetaan tyyppiparametreina seka avai-men etta alkion tyyppi. Ylimaaraisina tyyppiparametreina on lisaksimahdollista antaa avainten suuruusvertailuun kaytettava funktio se-ka muistinhallintaan kaytettava varain. Jalleen nama lisaominaisuu-det jatetaan tassa teoksessa lapikaymatta.

Page 318: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.2. STL:n sailiot 318

Rajapinnaltaan assosiatiiviset sailiot muistuttavat toisiaan. Al-kioita etsitaan jasenfunktiolla find, lisaataan jasenfunktiolla insertja poistetaan jasenfunktiolla erase. Taman lisaksi jotkut sailiotyypittarjoavat lisaoperaatioita.

Kaikissa assosiatiivisissa sailioissa alkioiden lisaaminen, poista-minen ja hakeminen avaimen perusteella ovat tehokkuudeltaan loga-ritmisia operaatioita. Tama onkin niiden etu sarjoihin verrattuna. Josjohonkin sarjasailioon talletettaisiin seka avain etta alkio, ainoa tapatietylla avaimella varustetun alkion etsimiseen olisi kayda lapi kokosarja alusta alkaen, ja tama olisi tehokkuudeltaan lineaarinen operaa-tio.

Kuten kaikki muutkin sailiot, assosiatiivisen sailion alkiot voi se-lata lapi yksi kerrallaan aliluvussa 10.3 esiteltavia iteraattoreita kayt-taen. Talloin alkiot kaydaan lapi avainten suuruusjarjestyksessa.

Joukko — set

Joukko (set) on yksinkertaisin assosiatiivisista sailioista. Joukossa al-kio itse toimii avaimena, mista johtuen joukolle annetaan tyyppi-parametrina vain alkion tyyppi samoin kuin sarjoille. Esimerkiksiset<char> maarittelee joukon, johon talletetaan merkkeja. Joukototetaan kayttoon komennolla #include <set>.

Kaytannossa joukko vastaa aika hyvin “matemaattisen joukon” ka-sitetta. Siihen voi lisata alkioita, niita voi poistaa, ja joukolta voidaankysya, onko annettu alkio jo joukossa. Kaikki nama operaatiot teh-daan logaritmisessa ajassa. Samanarvoisia alkioita voi joukossa ollavain yksi kerrallaan kuten matematiikan joukossakin.

Joukko on kaytannollinen tietorakenne silloin, kun ohjelmassataytyy yllapitaa jonkinlaista rekisteria, johon lisataan alkioita seka-laisessa jarjestyksessa, ja kun pitaa pystya nopeasti testaamaan, onkoannettu alkio rekisterissa. Listaus 10.3 seuraavalla sivulla maaritteleeesimerkkina funktion rekisterointi, jolla voidaan rekisteroida nimia(merkkijonoja). Funktio yllapitaa nimijoukkoa rekisteri ja palauttaatrue, jos rekisteroitava nimi oli uusi eli sita ei viela ollut rekisterissa.

Assosiatiivisten sailioiden operaatioilla on eraita erityispiirteita,jotka helpottavat niiden tehokasta kayttoa. STL:n optimointimahdol-lisuuksien yksityiskohtainen lapikaynti ei kyllakaan ole mahdollis-ta tassa teoksessa, mutta seuraava esimerkki havainnollistaa, millai-sia mahdollisuuksia STL tarjoaa. Listauksen 10.3 rekisterointifunktio

Page 319: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.2. STL:n sailiot 319

1 #include <set>2 #include <string>3 using std::set;4 using std::string;56 // Palauttaa true, jos rekisteroidaan uusi nimi, muuten false7 bool rekisterointi(string const& nimi)8 9 static set<string> rekisteri; // Staattinen, sailyy kutsujen valilla

1011 if (rekisteri.find(nimi) == rekisteri.end())12 13 // Ei ole ollut aiemmin14 rekisteri.insert(nimi); // Lisaa rekisteriin15 return true;16 17 else18 19 // Nimi loytyi jo20 return false;21 22

LISTAUS 10.3: Nimien rekisterointi setilla

toimii kylla logaritmisessa ajassa mutta on siina mielessa tehoton, et-ta ensin rivilla 11 tutkitaan, loytyyko annettua merkkijonoa, ja sittenrivilla 14 sama merkkijono lisataan joukkoon, jos se ei jo ollut siella.Nain alkion paikkaa joudutaan etsimaan kaksi kertaa perajalkeen.

Tilanne, jossa alkiota ensin etsitaan ja sitten mahdollisesti lisataanse, on erittain yleinen. Niinpa joukon insert-operaatio itse asiassa li-saa annetun alkion vain, jos alkio ei jo ollut sailiossa. Lisaksi operaa-tio palauttaa paluuarvonaan std::pair-tyyppisen struct-tietoraken-teen, jossa on myos tieto siita, suoritettiinko lisaysta vai ei. Nain samarekisterointifunktio voidaan toteuttaa tehokkaammin yhdella ainoal-la insert-kutsulla. Listaus 10.4 seuraavalla sivulla nayttaa tallaisentehokkaamman toteutuksen.

Monijoukko — multiset

Monijoukko (multiset) eroaa tavallisesta joukosta siina, etta samanar-voisia alkioita voi olla monijoukossa useita. Monijoukolta voi alkionolemassaolon lisaksi kysya, montako annetun arvoista alkiota moni-

Page 320: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.2. STL:n sailiot 320

1 #include <set>2 #include <string>3 using std::set;4 using std::string;56 // Palauttaa true, jos rekisteroidaan uusi nimi, muuten false7 bool rekisterointi optimoitu(string const& nimi)8 9 static set<string> rekisteri; // Staattinen, sailyy kutsujen valilla

1011 // insert palauttaa parin, jonka jalkimmainen osa second ilmoittaa,12 // tehtiinko lisays vai loytyiko alkio jo joukosta13 return rekisteri.insert(nimi).second;14

LISTAUS 10.4: Tehokkaampi versio listauksesta 10.3

joukossa on. Tama tehdaan jasenfunktiolla count (itse asiassa counton myos tavallisen joukon rajapinnassa, jossa se palauttaa aina arvon1). Monijoukon kayttoonotto tehdaan komennolla #include <set>samoin kuin joukonkin.

Monijoukon kayttotarkoitus on lahes sama kuin joukon, muttamonijoukko mahdollistaa samanarvoisten alkioiden lisaamisen ja las-kemisen. Listaus 10.5 sisaltaa uuden rekisterointifunktion, joka lisaanimen rekisteriin ja ilmoittaa paluuarvonaan, kuinka monta kertaanimi on lisatty rekisteriin.

1 #include <set>2 #include <string>3 using std::multiset;4 using std::string;56 // Palauttaa nimelle suoritettujen rekisterointien lukumaaran7 unsigned long int monirekisterointi(string const& nimi)8 9 static multiset<string> rekisteri; // Staattinen, sailyy kutsujen valilla

10 rekisteri.insert(nimi);11 return rekisteri.count(nimi);12

LISTAUS 10.5: Nimirekisterointi multisetilla

Page 321: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.2. STL:n sailiot 321

Assosiaatiotaulu — map

Assosiaatiotaulu (map) on tietorakenne, jossa avain ja alkio ovat eril-lisia ja jossa avaimen perusteella voidaan hakea haluttu alkio. Assosi-aatiotaulua voi myos ajatella “taulukkona”, jossa indeksina kaytetaanhalutun tyyppista avainta kokonaisluvun sijaan. Yhta avainta kohdenvoi assosiaatiotaulussa olla vain yksi alkio.

Assosiaatiotaulu ottaa kaksi tyyppiparametria, jotka maaraavatavaimen ja alkion tyypit. Esimerkiksi map<string, double> ontaulu, josta voi etsia liukulukuja avaimina toimivien merkkijono-jen avulla. Vastaavasti map<int,string> antaa mahdollisuuden liit-taa merkkijonoihin kokonaisluvun, jonka perusteella merkkijononvoi myohemmin hakea. Assosiaatiotaulut saa kayttoon komennolla#include <map>.

Assosiaatiotaulun rajapinnassa on kaikki assosiatiivisten sailioi-den operaatiot. Kuten muissakin assosiatiivisissa sailioissa, alkioidenetsiminen, lisaaminen ja poistaminen kestavat logaritmisen ajan. Kos-ka assosiaatiotaulu muistuttaa taulukkoa, siihen on lisatty myos in-deksointi [ ]. Tama etsii alkion avaimen perusteella kuten find, mut-ta jos alkiota ei loydy, indeksointi lisaa automaattisesti tauluun uu-den alkion, joka luodaan alkiotyypin oletusrakentajalla. Indeksointipalauttaa sitten joko loytyneen alkion tai taman uuden alkion. Nainassosiaatiotaulua voi kayttaa taulukkona, joka tayttyy tyhjilla alkioil-la samalla, kun sita indeksoidaan.

Listauksessa 10.6 on toteutettu nimien rekisterointi assosiaatio-taululla. Nyt rekisteri onkin assosiaatiotaulu, joka liittaa avaimenatoimivaan merkkijonoon kokonaisluvun, joka kertoo montako kertaa

1 #include <map>2 #include <string>3 using std::map;4 using std::string;56 unsigned long monirekisterointi2(string const& nimi)7 8 static map<string, unsigned long> rekisteri; // Sailyy kutsujen valilla9 return ++rekisteri[nimi]; // Jos uusi nimi, lisataan autom. arvolla 0

10

LISTAUS 10.6: Nimirekisterointi mapilla

Page 322: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.2. STL:n sailiot 322

merkkijono on rekisteroity. Funktio kayttaa hyvakseen assosiaatiotau-lujen indeksointia. Kun funktiolle annetaan uusi merkkijono, indek-sointi ei loyda sita taulusta. Talloin tauluun lisataan uusi alkio, jokaalustetaan oletusarvoonsa eli nollaksi. Jos taas merkkijono on jo rekis-teroity, indeksointi palauttaa tiedon siita, montako kertaa rekisteroin-ti on jo tehty. Rekisterointilaskurin kasvattaminen saadaan nyt teh-dyksi helposti kasvattamalla indeksoinnilla loydettya arvoa yhdella.

Assosiaatiomonitaulu — multimap

Assosiaatiomonitaulu (multimap) eroaa assosiaatiotaulusta samal-la tavoin kuin monijoukko joukosta. Assosiaatiomonitaulussa yhtaavainta kohden voi olla useita alkioita. Tilanne vastaa esimerkiksipuhelinluetteloa, jossa yhta nimea kohti voi olla useita puhelinnu-meroita.

Assosiaatiomonitaulun esittely luetaan komennolla#include <map> kuten tavallisenkin assosiaatiotaulun. Rajapin-nan osalta assosiaatiomonitaulu muistuttaa assosiaatiotaulua, paitsietta indeksointia ei ole toteutettu. Syyna tahan on, etta enaa yh-ta avainta kohden ei valttamatta loydy vain yhta arvoa, jonkavoisi palauttaa. Jasenfunktio find palauttaa jonkin annettua avain-ta vastaavan alkion. Sopivista alkioista ensimmaisen voi hakeajasenfunktiolla lower bound ja viimeista sopivaa seuraavan jasen-funktiolla upper bound, ja molemmat saa tietoonsa yhdella kutsullaequal range. Naita ja aliluvussa 10.3 kasiteltavia iteraattoreita kayt-taen voi monitaulusta kayda lapi kaikki tiettya avainta vastaavatalkiot.

Listaus 10.7 seuraavalla sivulla esittelee puhelinluetteloluokan,joka on toteutettu assosiaatiomonitaulua kayttaen. Jasenmuuttujaluettelo on monitaulu, jossa merkkijonon (nimen) perusteella voihakea kokonaisluvun (puhelinnumeron). Koska monitaulun tyyppi-maarittely on varsin pitka, listauksessa rivilla 18 on maaritelty tyyp-pinimi LuetteloMap. Puhelinluettelon jasenfunktioiden toteutus loy-tyy listauksesta 10.8 sivulla 324. Jasenfunktio tulosta kayttaa ite-raattoreita sopivien puhelinnumeroiden tulostamiseen, joten kyseis-ta koodia kannattaa tutkia tarkemmin vasta aliluvun 10.3 lukemisenjalkeen.

Page 323: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.2. STL:n sailiot 323

1 #include <map>2 #include <string>3 #include <iostream>4 using std::multimap;5 using std::pair;6 using std::string;7 using std::cout;8 using std::endl;9

10 class PuhLuettelo11 12 public:13 // Tyhjat rakentajat ja purkaja kelpaavat14 void lisaa(string const& nimi, unsigned long numero);15 unsigned long poista(string const& nimi);16 void tulosta(string const& nimi) const;17 private:18 typedef multimap<string, unsigned long> LuetteloMap;19 LuetteloMap luettelo ;20 ;

LISTAUS 10.7: multimapilla toteutettu puhelinluetteloluokka

10.2.3 Muita sailioita

Sarjojen ja assosiatiivisten sailioiden lisaksi STL sisaltaa muuta-mia muita sailioita, jotka eivat tarkasti ottaen kuulu rajapinnal-taan kumpaankaan kategoriaan. Tallaisia sailioita ovat totuusarvo-vektori vector<bool>, bittivektori bitset seka sailiosovittimet queue,priority queue ja stack. Tama aliluku esittelee lyhyesti naiden sai-lioiden perusominaisuudet.

Totuusarvovektori — vector<bool>

Vektorimalli vector on varsin kateva perustaulukkotyyppina ja kel-paisi periaatteessa sellaisenaan totuusarvovektoriksi eli taulukoksijonka alkiot ovat tyyppia bool. Ongelmaksi muodostuu kuitenkin, et-ta C++:ssa jokainen olio vie muistia vahintaan yhden tavun verran, jatama rajoitus koskee myos perustyyppia bool olevia alkioita. Nain ta-vallista vektoria kayttaen esimerkiksi 800 totuusarvon taulukko veisimuistia vahintaan 800 tavua (todennakoisesti enemman). Jokaisen to-

Page 324: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.2. STL:n sailiot 324

22 void PuhLuettelo::lisaa(string const& nimi, unsigned long numero)23 24 luettelo .insert(make pair(nimi, numero)); // make pair luo “parin”25 2627 unsigned long PuhLuettelo::poista(string const& nimi)28 29 return luettelo .erase(nimi); // Palauttaa poistettujen lukumaaran30 3132 void PuhLuettelo::tulosta(string const& nimi) const33 34 // Etsi nimen perusteella ala- ja ylaraja35 pair<LuetteloMap::const iterator, LuetteloMap::const iterator>36 alkuJaLoppu = luettelo .equal range(nimi);37 // Kay lapi loytyneet alkio ja tulosta38 cout << "Henkilo " << nimi << ":" << endl;39 for (LuetteloMap::const iterator i = alkuJaLoppu.first;40 i != alkuJaLoppu.second; ++i)41 42 cout << " " << i->second << endl;43 44

LISTAUS 10.8: Puhelinluettelon toteutus

tuusarvon esittamiseen riittaisi kuitenkin jo yksi bitti, joten teoriassa800 totuusarvon taulukon voisi saada mahtumaan 100 tavuun.

Tama on esimerkki tilanteesta, jossa luokkamallien erikoistuk-sesta on hyotya. STL:ssa on maaritelty mallille vector erikoistusvector<bool>. Taman erikoistuksen toteutus pakkaa totuusarvotmuistin yksittaisiin bitteihin niin, etta taulukko vie vahemman muis-tia. Koska totuusarvovektori on toteutettu luokkamallin erikoistukse-na, ei kayttajan periaatteessa tarvitse edes tietaa, etta vector<bool>toteutukseltaan eroaa jotenkin tavallisista vektoreista.

Todellisuus ei kuitenkaan ole aivan nain ruusuinen. Koska to-tuusarvovektoria ei ole toteutettu aidosti erillisina alkioina, sen ra-japinnassa on pienia eroavaisuuksia normaalin vektorin rajapintaan.Peruskaytossa nama eroavaisuudet tuskin tulevat koskaan esille, mut-ta geneerisessa ohjelmoinnissa niilla saattaa olla merkitysta. To-tuusarvovektorin eroista tavallisiin sailioihin on kirjoitettu artikkeli“When Is a Container Not a Container?” [Sutter, 1999].

Page 325: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.2. STL:n sailiot 325

Bittivektori — bitset

Bittivektori (bitset) on tarkoitettu binaaristen bittisarjojen kasitte-lyyn. Rajapinnaltaan se ei itse asiassa ole edes sailio, mutta C++-stan-dardissa se jostain syysta esitellaan luvun “assosiatiiviset sailiot” alla.Bittivektorin saa kayttoon komennolla #include <bitset>.

Malliparametrinaan bittivektori saa yhden kokonaisluvun, jokakertoo kuinka monta bittia vektori sisaltaa. Esimerkiksi bitset<64>maarittelee 64 bitin vektorin. Vektorin koko pysyy vakiona koko ajan,ja sen bitit alustetaan nollaksi.

Bittivektorin rajapinnassa on tyypillisia binaarisia operaatioita.Vektorin yksittaisia bitteja voi asettaa ykkosiksi ja nolliksi tai kaantaapainvastaisiksi. Lisaksi kahdelle samankokoiselle vektorille voi teh-da binaariset operaatiot &= (and), |= (or), ^= (xor) ja flip tai ~ (not).Vektorin bitteja voi myos siirtaa halutun maaran oikealle tai vasem-malle operaattoreilla >>= ja <<=. Kaiken kukkuraksi bittivektori tar-joaa mahdollisuuden ykkosbittien laskemiseen, bittivektorin muutta-miseen kokonaisluvuksi ja takaisin seka joitain muita erityisoperaa-tioita.

Sailiosovittimet

Kuten jo aiemmin on mainittu, STL tarjoaa joukon sailiosovittimia(container adaptor), jotka eivat itsessaan ole sailioita, mutta joidenavulla sailion rajapinnan saa “sovitetuksi toiseen muottiin”. Sailioso-vittimia on STL:ssa seuraavat kolme:

• Pino stack on luokkamalli, jonka rajapinnassa on pinon kasit-telyyn tarvittavat operaatiot empty, size, top, push ja pop. Naistapush lisaa uuden alkion pinon paalle, top palauttaa paallimmai-sen alkion ja pop poistaa sen. Pinon esittely luetaan komennolla#include <stack>.

Tyyppiparametrina pinolle annetaan alkioiden tyyp-pi ja sailio, jota kayttaen pino toteutetaan. Esimerkiksistack<int, list<int> > maarittelee pinon, joka on sisaisestitoteutettu listana.] Pinon oletustoteutuksena on deque, jotenpelkka stack<int> tuottaa pakalla toteutetun pinon.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .]Huomaa syntaksissa sanavali kahden loppukulmasulkeen valilla! Ilman sita kaantaja luu-

lee, etta on kyse operaattorista >>, ja antaa usein lahes kasittamattoman virheilmoituksen.

Page 326: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.3. Iteraattorit 326

• Jono queue on samoin luokkamalli, joka tarjoaa rajapinnas-saan jonolle tyypilliset operaatiot empty, size, front, back, pushja pop. Jonossa push lisaa uuden alkion jonon peraan ja poppoistaa alkion jonon alusta. Operaatio front lukee alkion jo-non alusta ja back lopusta. Jonon saa kayttoonsa komennolla#include <queue>.

Jonon luomisen syntaksi on aivan sama kuin pinonkin tapauk-sessa, eli queue<string, list<string> > luo listana toteute-tun merkkijonojonon (,) ja queue<int> kokonaislukujonon, jo-ka on toteutettu pakan avulla.

• Prioriteettijono priority queue on muuten kuin jono, muttaalkiot sijoitetaan suuruusjarjestykseen. Nain prioriteettijonostaluetaan front operaatiolla jonon pienin alkio, pop poistaa pie-nimman alkion ja niin edelleeen. Prioriteettijonon syntaksi onsama kuin muidenkin sailiosovittimien, mutta sen toteutuksenaei voi kayttaa listaa vaan toteutuksen on tuettava mielivaltaistaindeksointia. Oletustoteutuksena prioriteettijonolla on vektori,ja sen esittely luetaan samalla komennolla #include <queue>kuin jononkin.

10.3 Iteraattorit

Ohjelmoinnissa on hyvin tavallista kayda tietorakenteen alkioita la-pi jarjestyksessa yksi kerrallaan. Vektoreiden ja pakkojen tapauksessatama onnistuu helposti indeksoinnin avulla, mutta esimerkiksi lis-toilla ja assosiatiivisilla sailioilla ei ole nopeaa tapaa hakea annetunjarjestysnumeron maaraamaa alkiota. Lisaksi indeksointi on vektorei-den ja pakkojenkin tapauksessa tarpeettoman tehoton operaatio, kos-ka indeksoinnissa alkioiden laskeminen “aloitetaan aina alusta” eliindeksi ilmoittaa halutun alkion sijainnin suhteessa tietorakenteenalkuun.

Tyypillinen ratkaisuyritys ongelmaan on lisata lapikaymiseen tar-vittavat operaatiot itse tietorakenteeseen. Esimerkiksi listaluokastavoisi loytya operaatiot annaEnsimmainen, annaSeuraava ja onkoLoppu,joiden avulla listan alkiot saisi kaydyksi lapi. Talloin lista muistai-si itse, missa alkiossa lapikayminen on silla hetkella menossa. Tassaratkaisuyrityksessa on kuitenkin kaksi ongelmaa:

Page 327: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.3. Iteraattorit 327

• Koska listan taytyy sisaltaa tieto senhetkisesta paikasta, lista-olion tila koostuu seka listan alkioista etta lapikayntipaikasta.Talloin operaatiot annaEnsimmainen ja annaSeuraava muuttavatvaistamatta listan tilaa eivatka nain ollen voi olla vakiojasen-funktioita (aliluku 4.3). Tama puolestaan tarkoittaa sita, etta va-kioviitteen paassa olevaa listaa ei voi selata lapi, koska vakio-viitteen lapi saa kutsua vain sellaisia jasenfunktioita, jotka eivatmuuta oliota. Vakioviitteiden kaytto parametreina on niin yleis-ta, etta tama rajoitus aiheuttaa suuria ongelmia.

• Toinen ongelma on, etta varsin usein listaa pitaisi pystya kay-maan lapi kahdesta kohtaa yhta aikaa. Esimerkiksi voi olla tar-peen lukea listaa samanaikaisesti alusta loppuun ja lopusta al-kuun ja vertailla alkioita keskenaan. Koska listaolio muistaavain yhden paikan listalla, tallaista “limittaista” lapikayntia eivoi tehda.

Ratkaisu tahan lapikayntiongelmaan loytyy kayttamalla olio-oh-jelmoinnin perusperiaatteita. Koska listan alkioiden sailyttaminen jalapikayntipaikan muistaminen ovat selvasti erillisia asioita, voidaanluoda kaksi luokkaa. Toinen on varsinainen lista, joka ei sisalla mi-taan paikkatietoa. Toinen on “kirjanmerkki”, joka vain muistaa, mis-sa kohtaa listaa ollaan lapikaymassa. Tallainen rakenne on aliluvus-sa 9.3.2 esitelty suunnittelumalli Iteraattori.

10.3.1 Iteraattoreiden kayttokohteet

STL:ssa iteraattorin kasite on erittain tarkea. Jokaista sailiotyyppiakohti STL tarjoaa myos iteraattorityypin, jonka avulla sailion alkiotvoi kayda lapi. Iteraattoria voi ajatella kirjanmerkkina, joka muistaatietyn paikan tietyssa sailiossa. Iteraattoria voi siirtaa sailion sisassa,ja sen “lapi” voi myos lukea ja muuttaa sailion alkioita. Kuva 10.4seuraavalla sivulla havainnollistaa iteraattoreiden toimintaa.

Iteraattoreita kaytetaan kaikkialla STL:ssa ilmoittamaan tiettyapaikkaa sailiossa. Esimerkiksi poisto-operaatio erase ottaa paramet-rikseen iteraattorin, joka ilmoittaa missa kohdassa oleva alkio pois-tetaan. Talloin poistettava alkio on iteraattorin osoittaman valin oi-kealla puolella. (Tarkasti ottaen “oikea” ja “vasen” ovat tietysti tieto-koneessa jarjettomia termeja. Sanonnalla “oikealla puolella” tarkoite-

Page 328: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.3. Iteraattorit 328

Alkio2 Alkio3 Alkio4 Alkio5

IteraattoriIteraattori

Iteraattori

Alkio1

Säiliö

lukeminen

muuttaminen

x

q

siirto

p

KUVA 10.4: Iteraattorit ja sailiot

taan tassa jalkimmaista niista alkioista, joiden valiin iteraattori osoit-taa.)

Iteraattoreita kaytetaan paikan ilmaisemiseen myos, kun uusia al-kioita lisataan sailioon. Jos sailiossa on jo aiemmin n alkiota, uudenalkion voi lisata n+1 eri paikkaan. Nain ollen iteraattorilla pitaa myosolla n+1 paikkaa, johon se voi osoittaa. Tama saadaan aikaan, kun ite-raattori voi osoittaa minka tahansa kahden alkion valiin ja lisaksi sai-lion kumpaankin paahan eli ensimmaisen alkion vasemmalle puolel-le ja viimeisen alkion oikealle puolelle. Kuvan 10.4 oikeanpuoleisiniteraattori x havainnollistaa tata. Uusi alkio lisataan aina iteraattorinosoittaman valin oikealle puolelle. Nain sailion loppuun osoittavaniteraattorin avulla lisatty alkio lisataan todella sailion loppuun.

Kun iteraattorin lapi luetaan alkion arvo tai muutetaan sita, ope-raatio kohdistuu aina iteraattorin oikealla puolella olevaan alkioonsamoin kuin alkion poistamisessakin. Sailion loppuun osoittavan ite-raattorin oikealla puolella ei kuitenkaan ole alkiota. Se onkin vainerityinen “loppumerkki”, ja ohjelmoijan on pidettava huoli siita, etteisailion loppuun osoittavan iteraattorin lapi yriteta lukea tai kirjoittaa.

Sailion loppuun osoittavaa iteraattoria kaytetaan myos merkkinasiita, etta operaatio ei onnistunut. Esimerkiksi assosiatiivisten sailioi-den hakuoperaatio find palauttaa paluuarvonaan iteraattorin, jonkaoikealla puolella loytynyt alkio on. Mikali halutunlaista alkiota ei loy-

Page 329: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.3. Iteraattorit 329

tynyt, find palauttaa sailion loppuun osoittavan iteraattorin merkki-na epaonnistumisesta.

Kahta iteraattoria voi myos kayttaa maaraamaan tietyn valin(range) sailiossa. Talla tarkoitetaan niita alkioita, jotka jaavat iteraat-toreiden valiin. Esimerkiksi kuvassa 10.4 iteraattorit p ja q maaraavatvalin, johon kuuluvat alkiot 1, 2 ja 3. Jos iteraattorit osoittavat samaankohtaan, niiden valilla ei ole alkioita ja niiden maaraama vali on tyh-ja. Valien avulla sailiosta voidaan esimerkiksi poistaa useita alkioitakerrallaan. Lisaksi STL:n algoritmit ottavat yleensa parametreikseennimenomaan iteraattorivaleja, jolloin algoritmin voi kohdistaa vainosaan sailiota.

10.3.2 Iteraattorikategoriat

Erilaiset sailiot tarjoavat erilaisia mahdollisuuksia siirtaa iteraattorianopeasti paikasta toiseen. Esimerkiksi yhtenaisella muistialueella to-teutetun vektorin tapauksessa iteraattorin saa vakioajassa siirretyksikuinka paljon tahansa eteen- tai taaksepain. Sen sijaan linkkiketjuillatoteutetussa listassa iteraattorin saa siirretyksi vakioajassa vain yh-den askeleen verran.

STL:n suunnitteluperiaatteena on ollut, etta kaikkien iteraattoreil-le tehtyjen operaatioiden taytyy onnistua vakioajassa. Talla tavoinvoidaan varmistua siita, etta STL:n algoritmit toimivat luvatulla te-hokkuudella siita riippumatta, millaisia iteraattoreita niille annetaanparametreiksi. Iteraattorit voidaan jakaa erilaisiin kategorioihin senmukaan, millaisia vakioaikaisia operaatioita ne pystyvat tarjoamaan.

Kuva 10.5 seuraavalla sivulla nayttaa STL:n iteraattorikategoriat.Kuvaan on merkitty myos, mihin kategoriaan kuuluvia iteraattoreitaeri STL:n sailiot tarjoavat. Vaikka kuva onkin piirretty UML-tyyliinperiytymista kayttaen, C++-standardi ei vaadi, etta erilaiset iteraattoriton todellisuudessa periytetty toisistaan. Riittaa, etta niiden rajapinnatovat kuvan mukaiset. STL:n iteraattorikategoriat ovat

• syottoiteraattori (input iterator). Iteraattorin lapi voi vain lukeaalkioita mutta ei muuttaa. Lisaksi iteraattoria voi siirtaa yhdenaskelen kerrallaan eteenpain. Iteraattorin p tarjoamat operaatiotovat

– *p : Iteraattorin osoittaman alkion arvon lukeminen

Page 330: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.3. Iteraattorit 330

input iterator

* (luku), ==, !=, ++, =, −>

syöttöiteraattorioutput iterator

tulostusiteraattori

* (kirjoitus), ==, !=, ++, =, −>

forward iterator

* (luku/kirjoitus)

eteenpäin−iteraattori

bidirectional iterator

−−

kaksisuuntainen iteraattori

random access iterator

+=, −=, +, −, [], <, >, <=, >=

hajasaanti−iteraattorideque

vector

set

multiset

map

multimap

list

T[]

KUVA 10.5: Iteraattorikategoriat

– p-> : Iteraattorin osoittaman alkion jasenfunktion kutsumi-nen tai struct-kentan lukeminen (vertaa osoittimet)

– ++p : Iteraattorin siirtaminen yhdella eteenpain– p++ : Kuten ++p, mutta palauttaa paluuarvonaan iteraatto-

rin, joka osoittaa alkuperaiseen paikkaan^

– p=q : Iteraattorin sijoittaminen toiseen samanlaiseen– p==q, p!=q : Sen vertailu, osoittavatko iteraattorit samaan

paikkaan.

• tulostusiteraattori (output iterator). Iteraattori on muuten kuinlukuiteraattori, mutta sen lapi voi vain muuttaa alkioita, ei lu-kea. Muuttaminen tapahtuu syntaksilla *p=x.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .^Iteraattoreissa syntaksi ++p on suositeltavampi kuin p++ silloin, kun paluuarvolla ei ole

merkitysta. Tahan on syyna tehokkuus: p++ joutuu palauttamaan kopion iteraattorin alkupe-raisesta arvosta, kun taas ++p voi palauttaa vain viitteen iteraattoriin itseensa, jolloin kopiointiaei tarvita. Tallaisia tehokkuusasioita kasiteltiin aiemmin aliluvuissa 7.3 ja 7.1.

Page 331: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.3. Iteraattorit 331

• eteenpain-iteraattori (forward iterator). Iteraattorin lapi voi lu-kea ja muuttaa alkioita, ja lisaksi iteraattoria voi siirtaa yhdellaeteenpain. Eteenpain-iteraattorin rajapinta on yhdistelma luku-ja tulostusiteraattorin rajapinnoista.

• kaksisuuntainen iteraattori (bidirectional iterator). Iteraattorion muuten kuin eteenpain-iteraattori, mutta se voi myos siirtyayhden askelen kerrallaan taaksepain. Uudet operaatiot ovat

– --p : Iteraattorin siirtaminen yhdella taaksepain

– p-- : Kuten --p, mutta palauttaa paluuarvonaan iteraatorin,joka osoittaa alkuperaiseen paikkaan.

• hajasaanti-iteraattori (random access iterator). Iteraattori onkuin kaksisuuntainen iteraattori, mutta sita voi siirtaa kerrallamielivaltaisen maaran eteen- tai taaksepain. Lisaksi iteraattoriaindeksoimalla voi lukea ja muuttaa muitakin alkioita kuin ite-raattorin oikealla puolella olevaa. Hajasaanti-iteraattoreita voivertailla keskenaan sen selvittamiseksi, kumpi on sailiossa en-simmaisena. Lisaksi kahdesta hajasaanti-iteraattorista voi las-kea, montako alkiota niiden valissa on. Naihin tarvittavat ope-raatiot ovat

– p+=n : Iteraattorin siirtaminen n askelta eteenpain (taakse-pain, jos n on negatiivinen)

– p-=n : Iteraattorin siirtaminen n askelta taaksepain (eteen-pain, jos n on negatiivinen)

– p+n : Tuottaa uuden iteraattorin, joka osoittaa p:sta n askeltaeteenpain (taaksepain, jos n on negatiivinen)

– p-n : Tuottaa uuden iteraattorin, joka osoittaa p:sta n askeltataaksepain (eteenpain, jos n on negatiivinen)

– p[n] : Sen alkion lukeminen tai muuttaminen, joka on p:nosoittamasta n askelta eteenpain (taaksepain, jos n on ne-gatiivinen)

– p-q : Kahden iteraattorin erotus ilmoittaa, kuinka montaaskelta p:sta eteenpain q osoittaa (jos q osoittaa paikkaanennen p:ta, tulos ilmoitetaan negatiivisena)

Page 332: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.3. Iteraattorit 332

– p<q, p<=q, p>q, p>=q : Iteraattori on toista iteraattoria “pie-nempi”, jos sen osoittama paikka on sailiossa ennen toisenosoittamaa paikkaa.

Iteraattoreiden operaatioiden syntaksi on tarkoituksella valittusellaiseksi, etta se vastaa C++:n ja C:n osoitinaritmetiikkaa, jota voi suo-rittaa taulukkoihin osoittavilla osoittimilla. Iteraattorit ovat itse asias-sa vain osoitinaritmetiikan yleistys mielivaltaisille tietorakenteille.Vastaavasti C++:n perustaulukoihin osoittavat osoittimet kelpaavat ite-raattoreiksi STL:n algoritmeissa.

10.3.3 Iteraattorit ja sailiot

Iteraattorit liittyvat kiinteasti sailioihin, joten STL:ssa itse iteraattori-luokat ja iteraattoreiden luominen on siirretty sailioluokkien sisalle.Jokainen STL:n sailioluokka maarittelee kaksi luokan sisaista tyyp-pia iterator ja const iterator. Naista iterator maarittelee kyseisel-le sailiolle sopivan iteraattoriluokan. Tyyppi const iterator puoles-taan maarittelee vakio-iteraattorin, joka on muuten samanlainen kuiniterator, mutta sen lapi ei voi muuttaa sailion sisaltoa. Vakio-iteraat-toreiden kaytto vastaa vakio-osoittimien ja vakioviitteiden kayttoa.Nama iteraattoriluokat kuuluvat kuvan 10.5 nayttamiin iteraattorika-tegorioihin sailiotyypin mukaan.

Sailioiden maarittelemia iteraattorityyppeja kaytetaan kuin mitatahansa aliluvussa 8.3 kasiteltyja rajapintatyyppeja. Esimerkiksi ko-konaislukuvektoriin osoittava iteraattori saadaan luoduksi syntaksil-la

vector<int>::iterator p;

Tallaisista tyyppinimista tulee usein pitkia, joten niille kannattaa an-taa paikallisesti lyhyempi nimi typedef-maarittelylla:

typedef vector<int> Taulu;typedef Taulu::iterator TauluIter;TauluIter p;

Kun uusi iteraattori luodaan, se on oletusarvoisesti “tyhja” eikaosoita minnekaan. Talloin sita ei myoskaan saa siirtaa eika sen lapi

Page 333: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.3. Iteraattorit 333

saa yrittaa lukea tai kirjoittaa. Jokaisen sailion rajapinnassa ovat ja-senfunktiot begin ja end, joista begin palauttaa sailion alkuun osoitta-van “alkuiteraattorin”. Vastaavasti end palauttaa “loppuiteraattorin”,joka osoittaa sailion loppuun. Nama paluuarvot voi sitten sijoittaatalteen iteraattorimuuttujiin.

Erittain tyypillinen iteraattoreiden kayttokohde on kayda sailionalkioit lapi yksi kerrallaan. Tasta on esimerkki listauksessa 10.9.Riveilla 3–8 nollataan vektorin alkiot while-silmukassa. Talloin luo-daan iteraattori ja sijoitetaan siihen beginin palauttama iteraattori sai-lion alkuun. Ensimmaisen alkion voi nyt lukea, tai sita voi muuttaa*-operaattorin avulla. Seuraavaan alkioon paasee ++-operaatiolla. Jo-ka kierroksella iteraattoria verrataan jasenfunktion end palauttamaanloppuiteraattoriin, jotta tiedetaan, milloin kaikki alkiot on kayty lapi.Talloin on muistettava, etta loppuiteraattori osoittaa paikkaan viimei-sen alkion jalkeen.

Riveilla 13–17 puolestaan tulostetaan sailion alkiot for-silmukas-sa. Koska alkioita ei ole tarkoitus muuttaa, kannattaa tassa kayttaa va-kio-iteraattoria. Listauksen esimerkissa on itse asiassa pakko kayttaavakio-iteraattoria, koska funktio saa parametrinaan vakioviitteen vek-

1 void nollaaAlkiot(vector<int>& vektori)2 3 vector<int>::iterator i = vektori.begin(); // Alkuun4 while (i != vektori.end()) // Toistetaan kunnes ollaan lopussa5 6 *i = 0; // Nollataan alkio7 ++i; // Siirrytaan seuraavaan alkioon8 9

1011 void tulostaAlkiot(vector<int> const& vektori)12 13 for (vector<int>::const iterator i = vektori.begin();14 i != vektori.end(); ++i)15 16 cout << *i << " ";17 18 cout << endl;19

LISTAUS 10.9: Sailion lapikayminen iteraattoreilla

Page 334: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.3. Iteraattorit 334

toriin. Taman viitteen lapi vektoria ei saa muuttaa. Tama on varmis-tettu STL:ssa niin, etta vakioviitteen ja -osoittimen kautta kutsuttunabegin ja end palauttavat automaattisesti vakio-iteraattorin, jonka voisijoittaa vain toiseen vakio-iteraattoriin.

Sailioiden omat jasenfunktiot kayttavat yksinomaan iteraattorei-ta paikan ilmaisemiseen (ainoana poikkeuksena on vektorin ja pakanindeksointi, jossa kaytetaan jarjestysnumeroa). Vaikka iteraattoreidenkaytto vaatii totuttelua, niiden avulla sailioiden kaytto kay varsin ka-tevasti. Esimerkiksi uuden alkion 3 lisaaminen kokonaislukuvekto-rin v alkuun tehdaan syntaksilla v.insert(v.begin(), 3). Vastaavas-ti vektorin viidennen alkion poisto onnistuu iteraattoriaritmetiikkaakayttaen kutsulla v.erase(v.begin()+5).

10.3.4 Iteraattoreiden kelvollisuus

Periaatteessa iteraattorit osoittavat tiettya paikkaa sailiossa eivatkanain ollen ole sidoksissa itse sailion alkioihin. Iteraattoreiden sisai-nen toteutus riippuu kuitenkin kaytannossa sailioiden sisaisesta ra-kenteesta, ja iteraattorit sisaltavat todennakoisesti osoittimia sailioi-den sisaiseen toteutukseen tai alkioihin.

Jos nyt sailiosta poistetaan alkioita tai sinne lisataan uusia alkioi-ta, voi sailiosta riippuen sen sisainen rakenne muuttua. Esimerkiksivektori saattaa varata lisaa tilaa ja siirtaa alkiot sinne. Tama voi puo-lestaan aiheuttaa sen, etta iteraattorin sisainen tieto ei enaa olekaanajan tasalla.

STL:ssa sanotaan, etta iteraattori on kelvollinen (valid) niin kau-an, kun se on kayttokelpoinen. Muutokset sailiossa saattavat aiheut-taa sen, etta iteraattorista tulee kelvoton (invalid). Talloin sanotaan,etta jokin sailion operaatio mitatoi (invalidate) tietyt iteraattorit. Tal-laiselle kelvottomalle iteraattorille ainoat sallitut operaatiot ovat tu-hoaminen ja uuden arvon sijoittaminen. Kaikki muut operaatiot ai-heuttavat sen, etta ohjelman kayttaytyminen on maarittelematon. Ite-raattoreiden lisaksi kelvollisuus koskee myos osoittimia ja viitteitasailion alkioihin. Jos muutos sailiossa siirtaa esimerkiksi alkion toi-seen paikkaan muistissa, myos kaikki osoittimet ja viitteet kyseiseenalkioon muuttuvat kelvottomiksi.

Iteraattoreiden muuttuminen kelvottomiksi riippuu sailiosta jasiita, millainen muutos siihen tehdaan. Seuraavassa luettelossa on

Page 335: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.3. Iteraattorit 335

lueteltu STL:n sailiot ja sailioon kohdistuvien muutosten vaikutusiteraattoreihin, viitteisiin ja osoittimiin.

• Vektori: Jos uuden alkion lisaamisessa ei tarvita muistin uudel-leenvarausta (vektorille on varattu riittavasti tilaa reservella),mitatoityvat kaikki iteraattorit, osoittimet ja viitteet, jotka osoit-tavat lisayspaikan jalkeisiin alkioihin. Jos uudelleenvaraus suo-ritetaan, mitatoityvat kaikki vektoriin osoittavat iteraattorit,osoittimet ja viitteet. Alkion poisto vektorista mitatoi kaikkiiteraattorit, osoittimet ja viitteet poistopaikasta alkaen vektorinloppuun saakka.

• Pakka: Uuden alkion lisays pakan alkuun tai loppuun mitatoikaikki pakkaan osoittavat iteraattorit. Osoittimet ja viitteet sensijaan sailyvat kelvollisina. Alkion poisto pakan alusta tai lo-pusta mitatoi vain heti poistettavan alkion vasemmalla puolellaolevan iteraattorin seka tietysti osoittimet ja viitteet poistettuunalkioon. Lisays pakan keskelle tai poisto sielta mitatoi kaikkipakkaan osoittavat iteraattorit, osoittimet ja viitteet.

• Lista: Alkion lisaaminen ei mitatoi mitaan. Alkion poisto mi-tatoi heti alkion vasemmalla puolella olevan iteraattorin sekaosoittimet ja viitteet poistettuun alkioon.

• Assosiatiiviset sailiot: Lisays ja poisto vaikuttavat samalla ta-voin kuin listaan.

Iteraattoreiden kelvollisuuden huomioon ottaminen on aarimmai-sen tarkeaa ohjelmoinnissa. Koska kelvottomaan iteraattoriin kohdis-tetut operaatiot aiheuttavat ohjelman maarittelemattoman kayttay-tymisen, kelvottomista iteraattoreista aiheutuvia virheita on erittainvaikea loytaa. Moniin C++-ymparistoihin on saatavilla STL-toteutuk-sia, jotka osaavat “testitilassa” ollessaan antaa kelvottomien iteraat-toreiden kaytosta valittomasti jarkevan virheilmoituksen. Yksi tallai-nen STL-toteutus on STLport [Fomitchev, 2001].

Eri sailiot mitatoivat iteraattoreitaan eri tavalla. Niinpa sailioidentehokkuuden lisaksi myos sailion operaatioiden mitatoimisvaikutuk-set kannattaa ottaa huomioon ohjelmaan sopivaa sailiotyyppia valit-taessa.

Page 336: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.3. Iteraattorit 336

10.3.5 Iteraattorisovittimet

Tavallisten iteraattoreiden lisaksi STL tarjoaa myos joukon iteraatto-risovittimia (iterator adaptor). Nama ovat “erikoisiteraattoreita”, jot-ka kayttaytyvat jollain lailla tavallisista iteraattoreista poiketen. Ite-raattorisovittimien perinpohjainen lapikaynti ei ole mahdollista tas-sa teoksessa, mutta tassa aliluvussa ne esitellaan kuitenkin lyhyes-ti. Iteraattorisovittimet ovat hyvia esimerkkeja siita, etta iteraattorei-den kayttoalue on paljon laajempi kuin vain sailioiden yksinkertai-nen lapikaynti. Iteraattorisovittimien avulla voidaan myos muunnel-la STL:n algoritmien toiminnallisuutta. Iteraattorisovittimet otetaankayttoon komennolla include <iterator>.

• Kaanteisiteraattorit (reverse iterator) ovat tavallisten iteraatto-reiden “peilikuvia”. Kaanteisiteraattoreilla ++ siirtaa iteraattoriataaksepain ja vastaavasti -- eteenpain. Myos muut siirto-ope-raatiot on peilattu. Lisaksi luku ja kirjoitus kaanteisiteraattorinkautta kohdistuvat iteraattorin osoittaman paikan vasemmallepuolelle. Jokaisesta STL:n sailiosta saa kaanteisiteraattorin ja-senfunktioilla rbegin, joka palauttaa kaanteisiteraattorin sailionloppuun (siis peilattuna alkuun), ja rend, joka antaa vastaavastikaanteisiteraattorin sailion alkuun.

• Lisaysiteraattorit/lisaajat (insert iterator/inserter) ovat tulos-tusiteraattoreita, joiden lapi kirjoittaminen lisaa sailioon uu-den alkion vanhan alkion muuttamisen sijasta. Niiden avul-la saadaan STL:n algoritmit lisaamaan alkioita sailioihin. Uu-den alkion lisaaminen kay yksinkertaisesti syntaksilla *p=x.Taman jalkeen lisaysiteraattoria taytyy viela siirtaa eteen-pain ++-operaattorilla. Sailion alkuun lisaavan lisaysiteraat-torin saa funktiokutsulla front inserter(sailio) ja loppuunlisaavan kutsulla back inserter(sailio). Annetun iteraatto-rin kohdalle alkioita lisaavan iteraattorin saa aikaan kutsullainserter(sailio, paikka).

• Virtaiteraattorit (stream iterator) ovat luku- tai tulostusi-teraattoreita, jotka sailioiden sijaan lukevat ja kirjoittavatC++:n tiedostovirtoihin. Nain esimerkiksi tiedostoja voi kayt-taa STL:n algoritmeissa sailioiden tapaan. Esimerkiksi cin-virrasta kokonaislukuja lukevan lukuiteraattorin saa syntak-silla istream iterator<int>(cin) ja merkkijonoja cout-vir-

Page 337: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.4. STL:n algoritmit 337

taan pilkuilla erotettuina tulostava iteraattori luodaan kutsullaostream iterator<string>(cout, ’,’).

STL:n valmiina tarjoamien iteraattorisovittimien lisaksi ohjelmoi-ja voi kirjoittaa myos omia iteraattorityyppejaan. Nain iteraattorit an-tavat varsin monipuolisen tyokalun sailioiden kasittelyyn.

10.4 STL:n algoritmit

STL tarjoaa monenlaisia algoritmeja sailioiden kasittelyyn. Jo aiem-min on todettu, etta nama algoritmit on toteutettu irrallisina funk-tiomalleina jasenfunktioiden sijaan. Tama tarkoittaa sita, etta algorit-mien taytyy saada kaikki tarvitsemansa tieto parametrien avulla. Yh-dellekaan STL:n algoritmille ei kuitenkaan anneta parametreina itsesailioita, vaan kaikki algoritmit ottavat parametreinaan iteraattorei-ta. Syita tahan ehka vahan yllattavaan suunnitteluperiaatteeseen onuseita:

• Iteraattoreiden avulla algoritmi saadaan toimimaan vain osal-le sailiota. Suurin osa STL:n algoritmeista ottaa parametreinaankahden iteraattorin maaraaman valin. Tama voi olla joko kokosailio (jos parametreina annetaan begin- ja end-kutsujen tuotta-mat iteraattorit) tai vain osa siita.

• Iteraattorit mahdollistavat sen, etta sama algoritmi tekee toimin-tonsa erilaisten sailioiden kesken, koska itse sailioiden tyyp-pia ei tarvitse kertoa algoritmille. Esimerkiksi merge-algoritminavulla voi yhdistaa listan ja pakan sisallot vektoriin.

• Iteraattorisovittimien avulla voi vaikuttaa algoritmin toimin-taan. Esimerkiksi find-algoritmi etsii normaalisti ensimmaisenhalutun arvoisen alkion. Kun sille annetaan normaalien iteraat-toreiden sijaan kaanteisiteraattoreita, se etsiikin viimeisen so-pivan alkion. Samoin normaalisti copy-algoritmi korvaa sailionalkiot toisen sailion alkioilla. Jos kaytetaan lisaysiteraattoreita,copy kuitenkin lisaa kopioitavat alkiot korvaamisen sijasta.

• Mikaan ei esta ohjelmoijaa kirjoittamasta omia iteraattorityyp-pejaan. Talloin STL:n algoritmit toimivat myos niiden kanssa.

Page 338: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.4. STL:n algoritmit 338

Lahes mihin tahansa tietorakenneluokkaan on helppo kirjoit-taa siihen sopivat iteraattoriluokat, joten STL:n algoritmit saavahalla vaivalla sovitetuksi lahes mihin tahansa tietorakentee-seen. Iteraattoreiden avulla tama onnistuu, vaikka itse tietora-kenteen rajapinta ei olisikaan yhtenainen STL:n sailioiden raja-pintojen kanssa.

Voidaan sanoa, etta iteraattorit ovat ikaan kuin liima algoritmienja sailioiden valissa. Koska algoritmit kayttavat iteraattoreita sailioi-den kasittelyyn, niiden tehokkuus riippuu myos iteraattoreiden te-hokkuudesta. Tama ei kuitenkaan ole ongelma, koska iteraattorit tar-joavat vain sellaisia operaatioita, jotka ovat vakioaikaisia. Eri iteraat-torikategoriat tarjoavat naita operaatioita eri maaran. Toimiakseen te-hokkaasti algoritmit saattavat vaatia, etta niille annettavien iteraatto-reiden taytyy kuulua vahintaan tiettyyn kategoriaan.

Esimerkiksi sailion alkiot jarjestava sort-algoritmi vaatii, etta sil-le annettujen iteraattoreiden taytyy olla hajasaanti-iteraattoreita. Mi-kali nain ei ole, annetaan kaannosaikainen virheilmoitus. Tasta ra-joituksesta seuraa, etta listaa ei voi jarjestaa sort-algoritmilla, koskase tarjoaa vain kaksisuuntaiset iteraattorit (taman vuoksi lista tarjoaajarjestamisen jasenfunktionaan). Sen sijaan find-algoritmille riittaa,etta annetut iteraattorit ovat vahintaan lukuiteraattoreita.

Iteraattorikategorioiden avulla kaantaja voi jo kaannosaikana var-mistua, etta algoritmit pystyvat toteuttamaan tehokkuuslupauksensa.Mikali algoritmille yritetaan antaa iteraattori sellaiseen sailioon, jon-ka operaatiot eivat ole algoritmille riittavan tehokkaita, algoritmi eiyksinkertaisesti kaanny. Saatu virheilmoitus saattaa kyllakin kaanta-jasta riippuen olla varsin kryptinen, kuten mallien yhteydessa ikavakylla usein tapahtuu.

Jotta STL:n algoritmeja pystyisi kayttamaan, niiden esittelyt tay-tyy ensin ottaa kayttoon komennolla #include <algorithm>. Seuraa-vassa listassa on esitelty lyhyesti joitain STL:n algoritmeja. Listantarkoituksena on antaa yleiskuva siita, millaisia asioita algoritmeil-la pystyy tekemaan. Algoritmien tarkemman syntaksin ja kuvauksenvoi lukea monista C++-kirjoista [Lippman ja Lajoie, 1997], [Stroustrup,1997] tai erityisesti C++:n kirjastoja kasittelevista kirjoista [Josuttis,1999].

• copy(alku, loppu, kohde) kopioi valilla alku–loppu olevat al-kiot iteraattorin kohde paahan. Se korvaa vanhat alkiot kopioi-

Page 339: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.4. STL:n algoritmit 339

duilla, joten alkioiden lisaamiseen tarvitaan lisaysiteraattoreita.Operaatio on lineaarinen, alku ja loppu lukuiteraattoreita, kohdetulostusiteraattori.

• find(alku, loppu, arvo) etsii valilta alku–loppu ensimmaisenalkion, jonka arvo on arvo, ja palauttaa iteraattorin siihen. Jossopivaa alkiota ei loydy, palautetaan loppu. Tehokkuudeltaanoperaatio on lineaarinen, alku ja loppu ovat lukuiteraattoreita.Algoritmille voi arvon sijaan antaa parametrina totuusarvon pa-lauttavan funktion, joka kertoo onko sille annettu parametri ha-lutunlainen.

• sort(alku, loppu) jarjestaa valilla alku–loppu olevat alkiotsuuruusjarjestykseen. Algoritmin tehokkuus on keskimaarinn logn, ja iteraattoreiden on oltava hajasaanti-iteraattoreita.sort-algoritmille voi myos antaa vertailufunktion ylimaaraise-na parametrina.

• merge(alku1, loppu1, alku2, loppu2, kohde) edellyttaa, ettavalien alku1–loppu1 ja alku2–loppu2 alkiot ovat suuruusjarjes-tyksessa. Algoritmi yhdistaa kyseisten valien alkiot ja kopioine suuruusjarjestyksessa iteraattorin kohde paahan aivan kutencopy. Operaatio on lineaarinen, alku- ja loppu-iteraattorit ovatlukuiteraattoreita, kohde on tulostusiteraattori.

• for each(alku, loppu, funktio) antaa jokaisen valilla alku–loppu olevan alkion vuorollaan funktion parametriksi ja kutsuufunktiota. Tehokkuudeltaan for each on lineaarinen. Jos alku jaloppu ovat lukuiteraattoreita, funktio ei saa muuttaa parametri-naan saamansa alkion arvoa. Jos iteraattorit ovat eteenpain-ite-raattoreita, alkioita saa muuttaa.

• partition(alku, loppu, ehtofunktio) jarjestaa valilla alku–loppu olevat alkiot niin, etta ensin tulevat ne alkiot, joillaehtofunktio palauttaa true, ja sitten ne, joilla se palauttaafalse. Tehokkuus algoritmissa on lineaarinen, iteraattoreidentulee olla kaksisuuntaisia iteraattoreita.

• random shuffle(alku, loppu) sekoittaa valilla alku–loppu ole-vat alkiot satunnaiseen jarjestykseen. Sekoituksen tehokkuus

Page 340: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.5. Funktio-oliot 340

on lineaarinen, alku ja loppu hajasaanti-iteraattoreita. Algorit-mille voi ylimaaraisena parametrina antaa oman satunnaislu-kugeneraattorin.

Listauksessa 10.10 on lyhyt esimerkki STL:n algoritmien kaytos-ta. Listauksen funktio ottaa ensin muistiin vektorin ensimmaisen javiimeisen alkion arvot. Sen jalkeen se jarjestaa vektorin suuruusjar-jestykseen. Se etsii viela jarjestetysta vektorista muistiin otettuja ar-voja vastaavat alkiot. Lopuksi se poistaa naiden valiin jaavat alkioteli alkiot, jotka ovat suurempia tai yhta suuria kuin alkuperainen en-simmainen alkio mutta pienempia kuin alkuperainen viimeinen al-kio. Operaatioista find ja erase ovat lineaarisia ja sort keskimaa-rin n logn, joten koko funktion keskimaaraiseksi tehokkuudeksi tuleen logn.

10.5 Funktio-oliot

Monissa tapauksissa STL:n algoritmeille pitaa iteraattoreiden lisaksivalittaa myos tietoa siita, miten algoritmin tulisi toimia. Esimerkiksialgoritmille sort voi antaa tarvittaessa tiedon siita, miten alkioiden

1 #include <algorithm>2 using std::find;3 using std::sort;4 #include <vector>5 using std::vector;67 void jarjestaJaPoista(vector<int>& vektori)8 9 int eka = vektori.front(); // Ensimmainen alkio talteen

10 int vika = vektori.back(); // Viimeinen alkio talteen11 sort(vektori.begin(), vektori.end()); // Jarjesta12 vector<int>::iterator ekanpaikka =13 find(vektori.begin(), vektori.end(), eka); // Etsi eka14 vector<int>::iterator vikanpaikka =15 find(vektori.begin(), vektori.end(), vika); // Etsi vika16 // Poista alkiot eka ≤ alkio < vika17 vektori.erase(ekanpaikka, vikanpaikka);18

LISTAUS 10.10: Esimerkki STL:n algoritmien kaytosta

Page 341: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.5. Funktio-oliot 341

“suuruutta” vertaillaan keskenaan. Vastaavasti algoritmi find if etsiiensimmaisen alkion, joka toteuttaa algoritmille valitetyn ehdon.

STL:ssa on yleensa kaksi tapaa valittaa algoritmien (ja assosia-tiivisten sailioiden) sisaan tallaista “toiminnallisuutta”. Toinen tapa,funktio-osoittimet (function pointers), on jo C-kielesta peraisin. Nii-den lisaksi STL:ssa kaytetaan usein funktio-olioita (function objects),joilla voidaan saada hieman yleiskayttoisempia ratkaisuja. Seuraavataliluvut esittelevat naiden kahden tavan perusteet.

10.5.1 Toiminnallisuuden valittaminen algoritmille

Oletetaan, etta ohjelmassa halutaan tulostaa kaikki kokonaislukuvek-torin alkiot, jotka ovat arvoltaan alle 5. Tama olisi tietysti mahdollis-ta tehda suhteellisen helposti for-silmukalla, mutta toisaalta STL:staloytyy valmiina algoritmeja halutunlaisten alkioiden etsimiseen. Jostarkoituksena olisi hakea alkiot, joiden arvo on yhta suuri kuin 5, loy-tyisi ensimmainen tallainen alkio yksinkertaisesti kutsulla

find(vektori.begin(), vektori.end(), 5)

Sen sijaan 5:tta pienempien alkioiden etsiminen on hieman vaa-tivampaa. STL:sta ei loydy valmista algoritmia find less, koska eri-laisia tallaisia hakualgoritmeja olisi niin monta erilaista, ettei niidenkoodaaminen erikseen olisi jarkevaa. Sen sijaan STL:ssa on algorit-mi find if, jolle voidaan kertoa, millaista alkiota halutaan. Helpointapa kayttaa tata algoritmia on valittaa sille kolmantena parametrinafunktio-osoitin (function pointer), joka osoittaa funktioon jota kayte-taan alkioiden testaamiseen.

Funktio-osoitinta voi ajatella tavallisena osoittimena, joka muut-tujaan tai olioon osoittamisen sijaan osoittaakin johonkin ohjelmanfunktioon. Tallaisen osoittimen saa luotua normaalilla syntaksilla&funktionimi, ja funktio-osoittimen lapi funktiota kutsutaan ikaankuin osoitin itse olisi kyseinen funktio. Funktio-osoittimen tyyppimaaraa millaisia parametreja ottaviin funktioihin osoitin voi osoit-taa. Samoin tyyppi maaraa myos osoitettavan funktion paluutyypin.Tassa teoksessa ei menna funktio-osoittimien kayton yksityiskohtiin,mutta mainittakoon, etta funktio-osoittimien lisaksi C++ tarjoaa myosmahdollisuuden jasenfunktio-osoittimiin (member function pointer),jotka voi laittaa osoittamaan tietyn luokan tietynlaisiin jasenfunktioi-

Page 342: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.5. Funktio-oliot 342

hin. Sen sijaan C++ ei tunne kasitetta “viite (jasen)funktioon”, vaikkaosoittimet ovatkin mahdollisia.

Listaus 10.11 nayttaa esimerkin funktio-osoittimen kaytosta. Siinaon ensin maaritelty funktio onkoAlle5, joka ottaa kokonaislukupara-metrin ja palauttaa totuusarvon, joka kertoo oliko parametri 5:tta pie-nempi. Rivilla 10 STL:n algoritmille find if valitetaan osoitin tahanfunktioon. Algoritmi kay lapi jarjestyksessa vektorin alkiot ja kutsuuosoittimen paassa olevaa funktiota antaen kunkin alkion sille para-metrina. Algoritmi jatkaa tata niin kauan, kunnes kaikki alkiot on kay-ty lapi tai osoittimen paassa oleva funktio on palauttanut arvon tosi.Talla tavoin find if tassa tapauksessa etsii vektorista ensimmaisen5:tta pienemman alkion.

Samalla tavalla monet muutkin STL:n algoritmit ottavat para-metreikseen funktio-osoittimia, joilla alkioita voi testata tai kasitella.Nain samaa algoritmia voi kayttaa useaan eri tarkoitukseen antamal-la sille osoitin sopivaan funktioon. Funktio-osoittimien kaytossa onkuitenkin rajoituksensa. Jos ohjelmassa haluttaisiin myos etsia vek-torista alkioita, jotka ovat arvoltaan pienempia kuin 7, pitaisi ohjel-maan kirjoittaa uusi testausfunktio onkoAlle7. Tama ei tietenkaan olejarkeva ratkaisu, jos testausfunktioiden maara kasvaisi suureksi.

Viela ongelmallisemmaksi tilanne tulee, jos testauksessa kaytetta-va raja ei olekaan kaannosaikana tiedossa, vaan se saadaan funktioon

1 bool onkoAlle5(int i)2 3 return i < 5;4 56 void tulostaAlle5(vector<int> const& v)7 8 vector<int>::const iterator i = v.begin();9

10 while ((i = find if(i, v.end(), &onkoAlle5)) != v.end())11 12 cout << *i << ’ ’;13 ++i;14 15 cout << endl;16

LISTAUS 10.11: Funktio-osoittimen valittaminen parametrina

Page 343: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.5. Funktio-oliot 343

parametrina, kysytaan kayttajalta tai vaikkapa lasketaan ajoaikana.Ongelmana on, etta kaikki funktion tarvitsemat tiedot taytyy antaasille parametreina silloin, kun funktiota kutsutaan (globaalit muuttu-jat antavat tahan pienen porsaanreian, mutta niiden kayton ongelmatovat yleensa suuremmat kuin hyodyt). Tassa tapauksessa haluttaisiintestauksessa kaytettava raja kiinnittaa jo silloin, kun vertailufunktioannetaan find if-algoritmille parametrina, ja find if:n sisalla sittenvarsinaisesti kutsuttaisiin funktiota ja annettaisiin sille testattava al-kio. C++:n funktiot ja funktio-osoittimet eivat kuitenkaan taivu tallai-seen kayttoon, vaan niiden sijaan taytyy kayttaa seuraavassa esitelta-via funktio-oliota.

10.5.2 Funktio-olioiden periaate

Funktio-oliot (function object) ovat olioita, joille on maaritelty funk-tiokutsuoperaattori (). Taman avulla naita olioita voi “kutsua” aivankuin ne olisivat funktioita. Verrattuna tavallisiin funktioihin funktio-olioilla on se hyva puoli, etta ne voivat muistaa asioita kutsukerto-jen valilla. Funktio-olioille voi esimerkiksi antaa luomisen yhteydes-sa tietoja, jotka olio panee talteen. Kun olio sitten valitetaan paramet-rina jollekin algoritmille, se voi kutsujen yhteydessa kayttaa naita si-saansa talletettuja tietoja hyvakseen.

Joissain C++-teoksissa funktio-olioista kaytetaan myos nimitystafunktori (functor). Tata nimitysta olisi lyhyydestaan huolimatta eh-ka syyta valttaa, koska matematiikassa termi funktori on jo kaytossa,ja silla tarkoitetaan varsin eri asiaa. Funktio-olioilla voidaan sen si-jaan saada aikaan samanlaisia vaikutuksia kuin funktionaalisen oh-jelmoinnin sulkeumilla (closure) [Wikstrom, 1987].

C++:ssa funktio-olioita saadaan aikaan kirjoittamalla luokkia, jois-sa on maaritelty (yksi tai useampi) jasenfunktio nimelta operator().Listaus 10.12 seuraavalla sivulla nayttaa esimerkin tallaisesta. Kunluokasta luodaan olio, voi tata oliota kutsua ikaan kuin se oli-si oikea funktio. Syntaksi olio(parametrit) aiheuttaa funktiokut-suoperaattorin kutsumisen ikaan kuin ohjelmaan olisi kirjoitettuolio.operator()(parametrit).

Hyotyna funktio-olioissa on, etta niilla voi normaalien olioiden ta-paan olla jasenmuuttujia, joihin talletetaan tietoja. Tyypillisin tapauson, etta oliota luotaessa luokan rakentajalle valitetaan parametreja,jotka talletetaan olion jasenmuuttujiin. Taman jalkeen oliota kutsut-

Page 344: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.5. Funktio-oliot 344

1 class OnkoAlle2 3 public:4 OnkoAlle(int raja);5 // Funktiokutsuoperaattori6 inline bool operator()(int verrattava) const;7 private:8 int raja ;9 ;

...10 OnkoAlle::OnkoAlle(int raja) : raja (raja)11 12 1314 inline bool OnkoAlle::operator()(int verrattava) const15 16 return verrattava < raja ;17

LISTAUS 10.12: Esimerkki funktio-olioluokasta

taessa (sen funktiokutsuoperaattoria kutsuttaessa) olio voi kutsun yh-teydessa valitettyjen parametrien lisaksi kayttaa hyvakseen myos ja-senmuuttujiin talletettuja tietoja. Periaatteessa olio voi tietysti myosmuuttaa jasenmuuttujiensa arvoja kutsujen yhteydessa, mutta tataei yleensa suositella STL:n yhteydessa muutamaa poikkeusta lukuu-nottamatta. Kuva 10.6 seuraavalla sivulla havainnollistaa tavallisenfunktion ja funktio-olion eroja.

Ohjelmassa funktio-olioita voi kayttaa monella eri tavalla. Lis-tauksessa 10.13 seuraavalla sivulla esitellaan niista muutama. Funk-tiossa kaytto1 luodaan rivilla 3 funktio-olio aivan tavallisen olion ta-paan, ja sille annetaan vertailun rajaksi luku 5. Rivilla 4 funktio-oliotasitten kutsutaan ja sille annetaan vertailtavaksi luvuksi 8. Tama esi-merkki on tarkoitettu vain havainnollistamaan funktio-olioiden syn-taksia, silla nain yksinkertaisessa esimerkissa funktio-oliosta ei vielaole mitaan varsinaista hyotya.

Listauksen 10.13 toinen esimerkki on jo kayttokelpoisempi. Ri-veilla 7–13 maaritellaan funktiomalli kysyJaTestaa, joka ottaa para-metrinaan funktio-olion. Funktiomalli lukee syotteesta luvun ja kut-suu funktio-oliota antaen luetun luvun parametriksi. Jos funktio-oliopalauttaa arvon tosi, tulostetaan teksti. Tallainen funktiomalli on var-

Page 345: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.5. Funktio-oliot 345

funktio

param1

param2

tulos

(a) Normaali funktio

Funktio−olio

Luominen

tieto

Funktio−olio

tietoKutsuminen

parametri

tulos

operator()

operator()

(b) Funktio-olio

KUVA 10.6: Funktio-olioiden idea

1 void kaytto1()2 3 OnkoAlle fo(5);

...4 if (fo(8)) cout << "8 < 5!" << endl; 5 67 template <typename FunktioOlio>8 void kysyJaTestaa(FunktioOlio const& fo)9

10 int i;11 cin >> i; // Virhetarkastelu puuttu12 if (fo(i)) cout << "Ehto toteutui!" << endl; 13 1415 void kaytto2(int raja)16 17 kysyJaTestaa(OnkoAlle(raja));18 kysyJaTestaa(OnkoAlle(2*raja));19

LISTAUS 10.13: Funktio-olion kayttoesimerkkeja

Page 346: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.5. Funktio-oliot 346

sin yleiskayttoinen, koska sen ei tarvitse tietaa miten luettua lukuatestataan, koska testaus tapahtuu parametrina saadussa funktio-olios-sa.

Lopuksi funktiossa kaytto2 kutsutaan tata funktiomallia. Kutsunyhteydessa luodaan suoraan valiaikainen funktio-olio luokan raken-tajaa kutsumalla, ja olioon tallentuu testauksen ylaraja. Kun kutsu onohi, tuhotaan valiaikainen funktio-olio automaattisesti.

Funktioiden kaytto2 ja kysyJaTestaa kuvaamaa toiminnallisuut-ta ei voisi saada aikaan esimerkiksi funktio-osoittimia kayttaen. Lis-tauksessahan kaytto2 tallettaa oman parametrinsa valitettavan funk-tio-olion sisaan, ja funktio-oliota puolestaan kutsutaan toisessa funk-tiossa. Tallaisessa kaytossa funktio-oliot ovat lahes valttamattomia.

Kaikki funktio-osoittimia hyvaksyvat STL:n algoritmit hyvaksy-vat parametreikseen myos funktio-olioita (lisaksi tietyissa tilanteissaSTL:ssa funktio-oliot ovat ainoa vaihtoehto). Niinpa aiemmin esite-tyn find if-esimerkin voi kirjoittaa yleiskayttoisemmin myos funk-tio-olioita kayttamalla. Tama on tehty listauksessa 10.14, jossa tulos-tettavien arvojen ylaraja saadaan funktioon parametrina.

Funktio-olioiden yhteydessa on syyta huomata, etta niita kayt-tavat algoritmit saattavat tyypillisesti joskus kopioida kayttamiaanfunktio-olioita. Niinpa jokaisessa funktio-olioluokassa tulisi olla toi-miva kopiorakentaja (aliluku 7.1.2). Lisaksi kaikki STL:n algoritmiteivat takaa, etta ne kayttaisivat jatkuvasta samaa kopiota funktio-oliosta. Nain kaikissa STL:n algoritmeissa funktio-olio ei voi tallet-taa sisaansa kutsukertojen valilla muuttuvaa tietoa, koska seuraavakutsukerta voikin kayttaa eri kopiota oliosta. Kaikkein turvallisinta

1 void tulostaAlle(vector<int> const& v, int raja)2 3 vector<int>::const iterator i = v.begin();45 while ((i = find if(i, v.end(), OnkoAlle(raja))) != v.end())6 7 cout << *i << ’ ’;8 ++i;9

10 cout << endl;11

LISTAUS 10.14: Funktio-olion kaytto STL:ssa

Page 347: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.5. Funktio-oliot 347

onkin yleensa tehda funktiokutsuoperaattorista vakiojasenfunktio japitaa funktio-olion jasenmuuttujien arvot muuttumattomina.

10.5.3 STL:n valmiit funktio-oliot

C++:n standardikirjasto tarjoaa valmiina joukon funktio-olioita, jottakaikkein tavallisimmissa tapauksissa ohjelmoijan ei aina tarvitsisikirjoittaa omia funktio-olioluokkiaan. Kaikkia kirjaston funktio-olio-tyyppeja ei esitella tassa, mutta seuraavassa pyritaan antamaan sup-pea yleiskuva siita, miten kirjaston yleiskayttoisia funktio-olioita ontarkoitus kayttaa.

Koska on varsin katevaa pystya valittamaan kaikkia C++:n perus-operaatioita myos funktio-olioparametreina, STL maarittelee laheskaikkia tallaisia operaatioita varten luokkamallit, joista funktio-olioi-ta pystyy luomaan. Tallaisia malleja ovat muun muassa plus, minus,multiplies, divides, equal to, less ja greater. Kaikki nama ottavattyyppiparametrinaan funktio-olion parametrien tyypin, joten esimer-kiksi kaksi liukulukua yhteenlaskeva funktio-olio olisi plus<double>ja kahden kokonaisluvun pienemmyytta vertailevan funktio-oliontyyppi olisi vastaavasti less<int>. Nama tassa mainitut funktio-olioteivat sisalla jasenmuuttujia, vaan ne matkivat aivan tavallisia funk-tioita ja niilla on vain oletusrakentaja. Taman vuoksi niita paramet-reina valitettaessa funktio-oliot luodaan oletusrakentajaa kutsumalla,siis esimerkiksi less<int>().

Tavallisia funktioita matkivien funktio-olioiden lisaksi STL tarjo-aa funktio-oliosovittimia (function object adaptor), jotka muuntavatolemassa olevia funktio-olioita toisenlaisiksi. Sovittimista tarkeim-mat ovat bind1st ja bind2nd, joiden avulla kaksi parametria ottavistafunktio-olioista kumman tahansa parametrin voi “kiinnittaa” halut-tuun arvoon funktio-olioita luotaessa. Kuva 10.7 seuraavalla sivullanayttaa sovittimen bind2nd rakenteen.

Sovittimet ovat itsekin funktio-olioita, joiden funktiokutsuope-raattori ottaa vain yhden parametrin. Jasenmuuttujissaan sovitin pi-taa muistissa varsinaisen kaksiparametrisen funktio-olion ja lisaksikiinnitetyn parametrin arvon. Nama annetaan sovittimelle sita luo-taessa. Kun sovittimen funktiokutsuoperaattoria kutsutaan yhdellaparametrilla, kutsuu sovitin jasenmuuttujassaan olevaa funktio-olio-ta ja antaa sille saamansa parametrin seka jasenmuuttujaansa talle-

Page 348: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.6. C++: Template-metaohjelmointi 348

bind2nd

less<int>

3

param

operator()

tulos

tulos

operator()

KUVA 10.7: Funktio-olio bind2nd(less<int>(), 3)

tetun kiintean parametrin. Tuloksena saadun paluuarvon sovitin pa-lauttaa itse edelleen kutsujalle.

Funktio-oliosovittimien avulla pystyy muista funktio-olioistamuodostamaan katevasti halutun testauksen tai operaation suoritta-via versioita. Listauksessa 10.15 seuraavalla sivulla naytetaan, mitenaiemmin esimerkkina ollut annettua arvoa pienempien alkioiden tes-taus voidaan koodata STL:n omia funktio-olioita ja sovittimia kayt-tamalla. Listauksen lauseke bind2nd(less<int>(), arvo) luo yhdenparametrin ottavan funktio-olion, joka palauttaa toden, jos parametrion pienempi kuin arvo. Tama funktio-olio valitetaan sitten paramet-rina find if-algoritmille.

10.6 C++: Template-metaohjelmointi

Normaalisti tietokoneohjelmia ajettaessa ohjelmat kasittelevat kayt-tokohteeseensa liittyvia tietoja, jotka niille ajoaikana syotetaan (taijotka on upotettu osaksi itse ohjelman rakennetta). Toisaalta voidaanmyos ajatella, etta itse tietokoneohjelmakin on vain dataa, jota tieto-kone kasittelee (suorittamalla sita). Tama tulee selkeasti esille siina,

Page 349: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.6. C++: Template-metaohjelmointi 349

1 #include <functional>2 using std::less;3 using std::bind2nd;45 void tulostaAlle2(vector<int> const& v, int raja)6 7 vector<int>::const iterator i = v.begin();89 while ((i = find if(i, v.end(), bind2nd(less<int>(),raja))) != v.end())

10 11 cout << *i << ’ ’;12 ++i;13 14 cout << endl;15

LISTAUS 10.15: C++:n funktio-olioiden less ja bind2nd kaytto

etta jokainen C++-ohjelmahan on vain ajoaikaista tietoa C++-kaantajalle,joka muuntaa C++-koodin konekieliseksi objektitiedostoksi.

10.6.1 Metaohjelmoinnin kasite

Ohjelmia, jotka kasittelevat toisia ohjelmia ja kenties tuottavat lop-putuloksenaan uusia ohjelmia, sanotaan metaohjelmiksi_ (metapro-gram). Tallaisia ohjelmia ovat varsinaisten kaantajien lisaksi myoserilaiset ohjelmageneraattorit, esikaantajat, kayttoliittymageneraatto-rit ja niin edelleen. Vastaavasti metaohjelmien kirjoittamista kutsu-taan yleisesti metaohjelmoinniksi (metaprogramming). Kirjan “Gen-erative Programming” [Czarnecki ja Eisenecker, 2000] luvussa 10 onvarsin hyva yleiskatsaus metaohjelmoinnin kasitteisiin.

Metaohjelmointi muuttuu huomattavasti mielenkiintoisemmak-si, jos ohjelma pystyy tutkimaan omaa rakennettaan ja kenties vai-kuttamaan omaan koodiinsa. Tallaista ohjelman kykya “itsetutkiske-luun” kutsutaan nimella reflektio (reflection). Joissain kielissa kutenSmalltalk:ssa tuki reflektiolle on varsin laaja, ja Smalltalk-ohjelmat pys-tyvat tutkimaan omaa luokkahierarkiaansa, luomaan uusia aliluokkia

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ._Kreikan sana “meta” tarkoittaa suunnilleen samaa kuin “jalkeen”. Sita kaytetaan yleisesti

etuliitteena, jolla kuvataan alkuperaisen kasitteen suhteen “ylemmalla tasolla” olevaa asiaa.Niinpa metaohjelmointi on ohjelmointia, jossa metaohjelma kasittelee varsinaista ohjelma-koodia.

Page 350: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.6. C++: Template-metaohjelmointi 350

tai jopa ajoaikana muuttamaan olion tyypin toiseksi. Aliluvun 8.2.1metaluokat ovat yksi esimerkki Smalltalk:n reflektio-ominaisuuksista.

C++:n ja Javan tapaisissa kaannettavissa kielissa mahdollisuusreflektioon on yleensa paljon rajoitetumpi. Javan Reflection-rajapin-nan avulla ohjelma voi jonkin verran tarkastella omaa rakennettaan jaesim. kysya ajoaikana, mita jasenfunktioita luokasta loytyy ja vaikka-pa kutsua jasenfunktiota, jonka nimi loytyy merkkijonomuuttujasta.Sen sijaan helppoa mahdollisuutta ohjelmankoodin muuttamiseentai tuottamiseen ei ole. C++:n mahdollisuudet reflektioon ajoaikanaovat viela rajoitetummat. Aliluvussa 6.5.3 esitellyt dynamic cast jatypeid ovat rajoitettuja yksinkertaisia esimerkkeja tilanteista, joissaohjelma pystyy tutkimaan omaan rakenteeseensa liittyvia asioita (tas-sa olion sijaintia luokkahierarkiassa). Samalla tavoin sizeof-operaat-torin avulla ohjelma voi kysya, montako tavua muistia jonkin tyyppivie.

Vaikka C++:n ajoaikaiset metaohjelmointimahdollisuudet ovat var-sin rajoitetut, tekevat kielen funktio- ja luokkamallit mahdollisek-si kaannosaikaisen metaohjelmoinnin (static metaprogramming), jo-ta C++:ssa usein kutsutaan myos template-metaohjelmoinniksi (tem-plate metaprogramming). Siina template-mekanismin avulla voidaankirjoittaa metaohjelmia, jotka tutkivat tyyppiparametreina annettujatyyppeja ja vakioita ja tekevat niiden perusteella paatoksia tuotetta-vasta koodista. Tata mahdollisuutta ei missaan vaiheessa suunniteltutarkoituksellisesti C++-kieleen, vaan se havaittiin “vahingossa” 90-lu-vun puolivalissa. Taman vuoksi C++:n template-metaohjelmointi onkayttokelpoisuudestaan huolimatta usein tyolasta ja kirjoitettu koodivaikealukuista.

Kaannosaikaista metaohjelmointia voi ajatella myos niin, etta kir-joitettu ohjelma jakautuu ikaan kuin kahteen osaan: kaannosaikanasuoritettavaan metakoodiin, joka voi tutkia rajoitetusti ohjelman ra-kennetta ja vaikuttaa sen perusteella kaannettavaan C++-koodiin. Ta-ma kaannetty koodi suoritetaan sitten ajoaikana, ja siina kaikki “meta-tason” asiat on jo kiinnitetty eika niihin enaa voi vaikuttaa. Seuraavis-sa aliluvuissa tutustutaan lyhyesti C++:n template-metaohjelmoinninmahdollisuuksiin.

Page 351: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.6. C++: Template-metaohjelmointi 351

10.6.2 Metaohjelmointi ja geneerisyys

Perinteista ohjelmaa kirjoitettaessa tarvetta ohjelman “itsetutkiske-luun” ja reflektioon harvemmin esiintyy, mutta tilanne muuttuu no-peasti, kun aletaan suunnitella yleiskayttoisia ohjelmakomponentte-ja, joissa (kuten aliluvussa 9.1 todettiin) on yleensa seka samanlaise-na pysyvia osia etta kayttokohteesta riippuvaa koodia. C++:n template-mekanismi antaa tahan erotteluun mahdollisuuden, kun itse funktio-tai luokkamalli edustaa pysyvaa yleiskayttoista koodia ja auki jatetyttyyppiparametrit kayttokohteen mukaan maarattavia asioita.

Varsin helposti tormataan tilanteeseen, jossa pelkka auki jatetyntyypin kayttaminen yleiskayttoisessa template-koodissa ei riita, vaankoodin pitaisi pystya tutkimaan auki jatettyjen tyyppien ominaisuuk-sia ja tehda niiden perusteella paatoksia esim. toisten tyyppien taikaytettavan algoritmin valinnasta. C++:n tapauksessa metaohjelmoin-ti tapahtuu lahes yksinomaan template-mekanismin avulla, ja lisak-si sita tyypillisesti kaytetaan nimenomaan funktio- ja luokkamallienavulla toteutetuissa yleiskayttoisissa ohjelmakomponenteissa.

C++:ssa ja muissa suoraan konekielelle kaannettavissa kielissa me-taohjelmointia rajoittaa se, etta lopullinen konekielinen ohjelmabi-naari on kiintea, eika siina enaa voi olla auki jatettyna ohjelman ra-kenteeseen liittyvia asioita. Samasta syysta kaikki funktio- ja luokka-mallitkin instantioidaan jo kaannosaikana. Niinpa C++:n template-me-taohjelmointi rajoittuukin kaannosaikaisiin paatoksiin kaannettavanohjelman rakenteesta. Tilannetta on ehka helpointa ajatella niin, ettametaohjelmoinnilla kirjoitettava “metakoodi” suoritetaan jo ohjelmaakaannettaessa, ja se vaikuttaa ainoastaan siihen, millainen lopullises-ta ohjelmabinaarista tulee.

Tyypillinen kayttokohde metaohjelmoinnille yleiskayttoisissa oh-jelmakirjastoissa on optimointi. Metaohjelmoinnin avulla yleiskayt-toinen kirjasto voi kaannosaikana valita kayttokohteeseen sopivan al-goritmin tai saataa ja virittaa algoritmia kohteeseen parhaiten sopi-vaksi. Talloin puhutaan usein mukautuvasta jarjestelmasta (adap-tive system). Yksinkertainen esimerkki tasta on aliluvussa 10.2.1vector-luokkamallin yhteydessa mainittu vector<bool>, jossa STLvalitsee bool-tyypin tapauksessa vektorille muistinkulutuksen kan-nalta tehokkaamman toteutuksen. Kaannosaikainen metaohjelmointitekee kuitenkin mahdolliseksi myos monimutkaisemman mukautu-misen.

Page 352: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.6. C++: Template-metaohjelmointi 352

10.6.3 Metafunktiot

Perinteisessa ohjelmoinnissa funktiot ovat ohjelman perusosia. Mah-dollisimman abstraktilla tasolla ajatellen funktiot ovat ohjelman ra-kenteita, jotka ajoaikana tuottavat niille annettujen parametrien pe-rusteella paluuarvon (paluuarvon lisaksi funktioilla voi olla myos si-vuvaikutuksia, mutta niista ei tassa valiteta). Samalla tavoin meta-funktioilla (metafunction) tarkoitetaan metaohjelman rakenteita, jot-ka tuottavat niille annetun ohjelman rakenteeseen liittyvan tiedon(esim. tyypin) perusteella jotain muuta ohjelman rakenteeseen liitty-vaa. C++:n kaannosaikaisessa metaohjelmoinnissa on monia eri tapojasaada aikaan metafunktioina toimivia rakenteita. Tyypillisesti niidenparametreina on ohjelman tyyppeja tai kaannosaikaisia vakioita, joi-den perusteella metafunktio tuottaa toisia tyyppeja, valitsee tuotetta-vaa koodia tai laskee uusia kaannosaikaisia vakioita. On huomatta-va, etta funktiomallit ja metafunktiot eivat ole alkuunkaan sama asia,vaikka tallaista vaarinkasitysta joskus esiintyy ja vaikka funktiomal-leja voidaan metaohjelmointiin kayttaakin.

Ehka yksinkertaisin esimerkki C++:n metafunktiosta on kielen si-saanrakennettu operaattori sizeof. Sille annetaan parametrina jokinkielen tyyppi, ja sizeof laskee kaannosaikana, montako tavua muis-tia kyseisen tyyppinen muuttuja tarvitsisi. Esimerkiksi sizeof(int)palauttaa ohjelmaa kaannettaessa arvon 4, jos int-tyyppi vaatii kysei-sessa ymparistossa 4 tavua muistia. Olennaista sizeof-operaattorissaon, ettei siita aiheudu minkaanlaista ajoaikaista suoritettavaa koodia,vaan koko operaatio suoritetaan jo ohjelmaa kaannettaessa.

Toinen jopa hamaavan yksinkertainen tapa saada aikaan meta-funktioita ovat luokkien sisalla maaritellyt vakiot ja tyypit. Ole-tetaan, etta meilla on ohjelmassa STL:n sailio vector<int>, jo-hon ohjelmakoodissa halutaan iteraattori. Kuten aiemmin on todet-tu, loytyy iteraattorin tyyppi sailion sisaisena tyyppina syntaksillavector<int>::iterator. Tarkemmin ajatellen tassakin on kyse me-tafunktiosta — sailion tyypin perusteella saadaan kaannosaikana sel-ville sailioon sopivan iteraattorin tyyppi. Samalla tavoin sailion al-kiotyypin saa selville metafunktiolla ::value type. Selkeammin me-

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .Vaihtoehtoisesti sizeof-operaattorille voi antaa parametriksi minka tahansa C++:n lausek-

keen. Talloin sizeof kertoo, kuinka monta tavua muistia lausekkeen lopputulos vaatii. Itselausekkeen arvoa ei kuitenkaan lasketa missaan vaiheessa, muistinkulutuksen kun voi paatellajo itse lausekkeen rakenteesta.

Page 353: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.6. C++: Template-metaohjelmointi 353

tataso nakyy listauksen 10.16 funktiomallista, joka etsii annetusta sai-liosta pienimman alkion ja palauttaa sen. Seka funktiomallin paluu-arvon etta iteraattoreiden tyypin paatteleminen sailion tyypin perus-teella on yksinkertaista metaohjelmointia.

Luokkien sisalle upotetut tyypit kuten ::value type ja ::iteratoreivat ole kovin yleiskayttoinen tapa metafunktioiden kirjoittamiseen,koska niiden kaytto vaatii, etta kaikki luokat maarittelevat sisallaansamalla tavoin nimetyt tyypit. Uuden “metafunktion” lisaaminen vaa-tii kaikkien metafunktion parametrina kaytettavien luokkien paivit-tamisen niin, etta niiden sisaan lisataan haluttu uusi tyyppimaaritys.Tama ei tietenkaan ole mahdollista, jos kaytossa ei ole naiden luok-kien lahdekoodia. Esimerkiksi STL:n sailiotyyppeihin ei ohjelmoijavoi itse lisata omia tyyppimaarityksiaan, vaikka tahan tulisikin tar-vetta.

C++:n luokkamallit ja niihin liittyva mahdollisuus mallien eri-koistamiseen antavat kuitenkin mahdollisuuden kirjoittaa “irrallisia”metafunktioita, jotka tuottavat mallin parametrien perusteella uusiatyyppeja tai kaannosaikaisia vakioita. Tama tapahtuu kirjoittamallaerillinen luokkamalli, jonka sisalle on maaritelty sisaisia tyyppeja,joiden arvo riippuu tyyppiparametrista. Tallaisista malleista kayte-taan englanniksi usein nimitysta trait. Listaus 10.17 seuraavalla si-vulla nayttaa yksinkertaisen trait-metafunktion ToistoStruct, jokalaskee struct-tietorakenteita, joissa on useita annetun tyyppisia kent-

1 template <typename Sailio>2 typename Sailio::value type pienin(Sailio const& s)3 // Esimerkin lyhentamiseksi virhetarkastelu puuttuu4 typename Sailio::const iterator iter = s.begin();5 typename Sailio::const iterator pieninIter = iter;67 while (iter != s.end())8 9 if (*iter < *pieninIter) pieninIter = iter;

10 ++iter;11 1213 return *pieninIter;14

LISTAUS 10.16: Esimerkki yksinkertaisesta metaohjelmoinnista

Page 354: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.6. C++: Template-metaohjelmointi 354

tia. Esimerkiksi kolme int-kenttaa sisaltava struct saataisiin syntak-silla ToistoStruct<int>::Kolmikko. Koska tallaisten trait-mallien ai-noa tarkoitus on tarjota sisaisia tyyppimaarittelyja, nakee C++:ssa useinkaytettavan struct-avainsanaa class:n sijaan, koska kyseessa ei var-sinaisesti ole “luokka”, josta tehtaisiin olioita, ja koska struct-raken-teessa oletusnakyvyys on public eika private.

Askeisen esimerkin mukaiset metafunktiot antavat mahdollisuu-den ainoastaan yksinkertaiseen uusien tyyppien luomiseen. Luokka-mallien erikoistus ja osittaiserikoistus antavat kuitenkin mahdolli-suuden huomattavasti monipuolisempiin ja kayttokelpoisempiin me-tafunktioihin. Niiden avulla on template-metaohjelmoinnissa mah-dollista saada aikaan if-lausetta vastaavia ehtorakenteita.

Vastaavasti mallien rekursiivinen instantioiminen antaa mahdol-lisuuden toistorakenteisiin. Naiden kahden ominaisuuden avulla pe-riaatteessa minka tahansa algoritmin saa koodattua kaannosaikanasuoritettavaksi template-metaohjelmaksi. Kaytannossa tallaisten oh-jelmien koodi on kuitenkin helposti vaikealukuista, koska template-mekanismia ei koskaan ole suunniteltuun varsinaiseen metaohjel-

1 template <typename Tyyppi>2 struct ToistoStruct3 4 struct Yksikko5 6 Tyyppi eka;7 ;8 struct Kaksikko9

10 Tyyppi eka;11 Tyyppi toka;12 ;13 struct Kolmikko14 15 Tyyppi eka;16 Tyyppi toka;17 Tyyppi kolmas;18 ;

...19 ;

LISTAUS 10.17: Yksinkertainen trait-metafunktio

Page 355: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.6. C++: Template-metaohjelmointi 355

mointiin.Listaus 10.18 nayttaa esimerkin erikoistamalla tehdysta meta-

funktiosta. Metafunktio IntKorotus ottaa parametrikseen kaksi koko-naislukutyyppia, ja sen sisainen “paluuarvotyyppi” Tulos kertoo, mi-ka kokonaislukutyyppi pystyy pitamaan sisallaan kummankin anne-tun kokonaislukutyypin kaikki mahdolliset arvot. Tata metafunktiotakaytetaan listauksessa paattelemaan kaannosaikana min-funktiomal-lin paluutyyppi, kun min:n parametrit voivat olla keskenaan erityyp-pisia. IntKorotus on toteutettu niin, etta itse “perusversio” mallistaon tyhja, ja kaikki paattely on koodattu erikoistuksiin, joita pitaisi

1 template <typename T1, typename T2>2 struct IntKorotus3 4 // Erikoistamaton versio on tyhja, ei osata tehda mitaan5 ;67 template <>8 struct IntKorotus<char, int>9

10 typedef int Tulos;11 ;1213 template <>14 struct IntKorotus<short int, char>15 16 typedef short int Tulos;17 ;1819 template <>20 struct IntKorotus<short int, unsigned short int>21 22 typedef int Tulos; // Kaantajariippuvaista, riittaako int23 ;

...24 template <typename T1, typename T2>25 typename IntKorotus<T1, T2>::Tulos min(T1 p1, T2 p2)26 27 if (p1 < p2) return p1; 28 else return p2; 29

LISTAUS 10.18: Erikoistamalla tehty template-metafunktio

Page 356: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.6. C++: Template-metaohjelmointi 356

kirjoittaa koodiin yksi jokaista mahdollista kokonaislukutyyppipariavarten.

Metafunktio sisaltaa myos virhetarkastelun siina mielessa, etta josIntKorotus-mallin parametrit eivat ole kokonaislukutyyppeja, valit-see kaantaja mallin erikoistamattoman perustoteutuksen, joka ei maa-rittele tyyppia Tulos ollenkaan. Tama puolestaan aiheuttaa kaannos-virheen, kun tyyppiin Tulos viitataan.

Luokkamallien osittaiserikoistus antaa mahdollisuuden hienova-raisempiinkin metafunktioihin. Listauksen 10.19 metafunktio pois-taa vakiomaareen const tyyppiparametristaan. Jos parametrina annet-tu tyyppi ei ole const, valitaan erikoistamaton versio, jossa on Tulossama kuin tyyppiparametri. Jos sen sijaan tyyppiparametri on vakio-tyyppia, valitaan mallin osittaiserikoistus. Siina tyyppiparametriin Tjaakin enaa vain alkuperainen ei-vakiotyyppi, koska osittaiserikois-tuksen tyyppimaareena oleva <T const> tekee selvaksi, etta T viit-taa tyypin “ei-const”-osaan. Listauksessa kaytetaan tata metafunk-tiota pitamaan huoli siita, etta listauksen min-toteutuksen muuttujapienin ei ole vakio, jotta siihen voi sijoittaa.

1 template <typename T>2 struct PoistaConst // Oletus, kun tyyppi on “normaalityyppi”3 4 typedef T Tulos;5 ;67 template <typename T>8 struct PoistaConst<T const> // Osittaiserikoistus kaikille vakiotyypeille9 // Huomaa, etta tassa T on nyt ilman const-maaretta oleva tyyppi

10 typedef T Tulos;11 ;

...12 template <typename T>13 T min(T* p1, T* p2)14 15 typename PoistaConst<T>::Tulos pienin;16 if (*p1 < *p2) pienin = *p1; // Sijoitus, joten pienin ei saa olla vakio17 else pienin = *p2; 18 return pienin;19

LISTAUS 10.19: Osittaiserikoistuksella tehty metafunktio

Page 357: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.6. C++: Template-metaohjelmointi 357

Edella mainittuja tekniikoita kayttaen on mahdollista kirjoittaametafunktiokirjastoja, jotka helpottavat varsinkin luokka- ja funk-tiomallien avulla tehtya geneerista ohjelmointia. Tallaisia kirjas-toja loytyy myos valmiina. Esimerkiksi kirjassa “Modern C++ De-sign” [Alexandrescu, 2001] esitelty kirjasto Loki tarjoaa valmiita me-tafunktiototeutuksia joihinkin suunnittelumalleihin. Samoin C++-kir-jastokokoelma Boost [Boost, 2003] sisaltaa monia kayttokelpoisia me-tafunktioita, joista osa saattaa paatya seuraaviin C++-standardin ver-sioihin.

10.6.4 Esimerkki metafunktioista: numeric_limits

Template-metaohjelmointi on yleistynyt C++-ohjelmoijien parissa vas-ta viime vuosina. Siita huolimatta jo nykyisessa C++-standardissa onjonkin verran metafunktioiksi laskettavia ominaisuuksia. Tassa alilu-vussa esitellaan standardin metafunktio numeric limits, joka on hy-va esimerkki siita, miten metafunktioiden avulla voidaan saada hyo-dyllista tietoa ohjelman numeerisista tyypeista.

Silloin talloin ohjelmoinnissa tulee tarve saada tietoa esimerkik-si siita, mika on suurin mahdollinen int-tyyppiin mahtuva luku taikuinka monen numeron tarkkuudella double luvut esittaa. C-kielessaja standardia edeltavassa C++:ssa tama ratkaistiin niin, etta nama tie-dot loytyivat sopivan includen jalkeen vakioina. Edelleenkin C++:ssavoi lukea otsikkotiedoston <climits>, jonka jalkeen suurin int loy-tyy nimella INT MAX ja otsikkotiedoston <cfloat> lukemisen jalkeendouble-tyypin tarkkuus numeroina selviaa vakiosta DBL MANT DIG.

Tama vakioihin perustuva jarjestelma ei kuitenkaan ole kayttokel-poinen geneerisessa ohjelmoinnissa. Tyyppiparametrin T perusteellaei millaan voi selvittaa, mika kyseisen tyypin maksimiarvo on, kos-ka tassa C:sta periytyvassa menetelmassa jokaisen tyypin tiedot loy-tyvat eri nimisista vakioista, eika tyyppiparametrin perusteella pystymillaan kaannosaikana muodostamaan vakiolle oikeaa nimea. Tamanvuoksi C++-standardiin lisattiin trait-metafunktio numeric limits, jo-ka kertoo tyyppiparametrinsa perusteella kyseiseen tyyppiin liittyviatietoja.

Metafunktio saadaan kayttoon lukemalla otsikkotiedosto<limits>. Taman jalkeen ohjelma voi kysya minka tahansa numee-risen tyypin tietoja syntaksilla numeric limits<tyyppi>::tieto. Esi-merkiksi int tyypin maksimiarvo on numeric limits<int>::max().

Page 358: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.6. C++: Template-metaohjelmointi 358

Samoin tyypin double tarkkuus 10-jarjestelmassa onnumeric limits<double>::digits10 numeroa.

Vaikka tama uusi tapa on syntaksiltaan pidempi kuin vanha, senetuna on, etta esimerkiksi template-koodin sisalla tyyppiparametrin Tpienin mahdollinen arvo selviaa kutsulla numeric limits<T>::min()tyypista T riippumatta. Taulukko 10.8 luettelee tarkeimmatnumeric limits-metafunktion tarjoamat tiedot. Tarkemmat seli-tykset metafunktion toiminnasta ja yksityiskohdista saa monistaC++-oppaista.

numeric limits<T>:: Selitysmin(), max() Tyypin pienin ja suurin arvo. Liukulukutyypeilla

min() kertoo pienimman positiivisen arvon.radix Tyypin sisainen lukujarjestelma (yleensa 2).digits, digits10 Tyypin tarkkuus numeroina radix-kantaisessa esitys-

muodossa (digits) ja 10-jarjestelmassa (digits10).is signed,is integer,is exact

Totuusarvo true tai false riippuen siita, onko tyyp-pi etumerkillinen, kokonaislukutyyppi ja tarkka (siispyoristysvirheita ei voi sattua).

is bounded Onko lukualue rajoitettu. Tosi kaikille sisaanrakenne-tuille tyypeille, mutta omille rajoittamattoman tark-kuuden tyypeille voisi olla epatosi.

is modulo Onko tyyppi modulo-aritmetiikkaa kayttava, eli pyo-rahtaako lukualue “ympari”. Tosi etumerkittomillekokonaisluvuille ja useimmiten muillekin kokonais-luvuille. Yleensa epatosi liukulukutyypeille.

epsilon(),round error()

Liukulukutyyppien tarkkuuteen liittyvia tietoja. Tar-koilla luvuilla 0.

min/max exponent,min/max exponent10

Liukulukutyyppien eksponentin minimi- ja maksi-miarvot sisaisen lukujarjestelman etta 10-jarjestel-man eksponenteille. Kokonaislukutyypeilla aina 0.

is iec559,has infinity,has denorm . . .

IEC-559-standardin mukaisten liukulukujen tietoja,yksityiskohtainen selittaminen veisi liikaa tilaa.,

KUVA 10.8: Metafunktion numeric_limits palvelut

Page 359: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.6. C++: Template-metaohjelmointi 359

Sen lisaksi, etta numeric limits kertoo yhtenaisella tavalla kaikis-ta C++:n sisaisista tyypeista, se on myos laajennettavissa omille luku-ja esittaville luokille. Toteutukseltaan numeric limits on nimittainjokaiselle perustyypille erikoistettu struct-luokkamalli. Niinpa sitavoi edelleen erikoistaa omille lukutyypeille. Jos ohjelma esimerkik-si maarittelee oman murtolukutyypin, voi sita varten kirjoittaa eri-koistuksen numeric limits-mallille, jolloin numeric limits:a kaytta-vat geneeriset laskenta-algoritmit toimivat myos talle tyypille.

Listaus 10.20 nayttaa funktiomallin, joka vertailee annettujen lu-kujen yhtasuuruutta. Kokonaisluvuille ja muille tarkoille luvuille sekayttaa ==-vertailua, liukuluvuille ja muille pyoristysvirheista karsi-ville luvuille erotuksen itseisarvon suuruusluokan vertaamista mah-dolliseen pyoristysvirheeseen.

1 #include <limits>2 using std::numeric limits;3 #include <cmath>4 using std::abs; // Itseisarvo5 #include <algorithm>6 using std::max;78 template <typename T>9 bool yhtasuuruus(T p1, T p2)

10 11 if (numeric limits<T>::is exact)12 // Pyoristysvirheet eivat ongelma, vertaillaan suoraan13 return p1 == p2;14 15 else

16 // Pyoristysvirhemahdollisuus, kaytetaan kaavaa |p1−p2|

max(|p1|,|p2|)≤ ε

17 return18 abs(p1-p2) / max(abs(p1),abs(p2)) <= numeric limits<T>::epsilon();19 // Tarkasti ottaen tama kaava ei riita aina, mutta kelvatkoon.,20 21

LISTAUS 10.20: Esimerkki numeric_limits-metafunktion kaytosta

Page 360: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.6. C++: Template-metaohjelmointi 360

10.6.5 Esimerkki metaohjelmoinnista: tyypin valinta

Edella esitetyt tavat metafunktioiden kirjoittamiseen tekevat metaoh-jelmoinnin mahdolliseksi, mutta metaohjelmien kirjoittaminen onvarsin tyolasta. Yksinkertaisenkin valinnan suorittaminen kaannosai-kana vaatii erillisen luokkamallin ja sen erikoistuksen kirjoittamista.Olisikin katevaa, jos C++:n metakoodiin saataisiin edes etaisesti nor-maaleja ohjelmointikielen rakenteita muistuttavia mekanismeja.

Tallaisia valinta-, silmukka- ynna muita rakenteita on onneksimahdollista kirjoittaa erillisina metafunktioina, joita voi kutsua me-takoodista. Tama tekee metaohjelmista jonkin verran helppolukui-sempia, vaikka edelleenkaan template-metakoodi ei ylla selkeydessalaheskaan normaalin ohjelmakoodin tasolle. Tassa aliluvussa esite-taan esimerkkina yksinkertaisen ehtolauseen IF toteuttaminen me-tafunktiona. Vastaavalla tavalla voidaan toteuttaa toistolauseet kutenWHILE ja muut kontrollirakenteet, mutta niista kiinnostuneen kannat-taa tutustua esimerkiksi kirjan “Generative Programming” [Czarneckija Eisenecker, 2000] lukuun 10 “Static Metaprogramming in C++”.

Toteutettavan IF-metafunktion ideana on ottaa template-paramet-rina yksi bool-tyyppinen kaannosaikainen vakio, joka edustaa testat-tavaa ehtoa, ja kaksi tyyppia. IF tuottaa paluuarvokseen jommankum-man naista tyypeista riippuen siita onko ehto tosi vai epatosi. Itse me-tafunktion toteutus on kohtalaisen yksinkertainen ja perustuu luok-kamallin osittaiserikoistukseen.

Metafunktion koodi on listauksessa 10.21 seuraavalla sivulla. Senerikoistamaton perustoteutus vastaa tilannetta, jossa ehto on tosi, jo-ten perustoteutuksessa sisainen tyyppi Tulos maaritellaan samaksikuin ensimmainen tyyppiparametri Tosi. Lisaksi mallille maaritel-laan osittaiserikoistus sita tapausta varten, etta ehto on epatosi. Tal-loin tyyppi Tulos maaritellaan samaksi kuin toinen tyyppiparametriEpatosi. Lopputuloksena on metafunktio, jossa Tulos on joko samatyyppi kuin Tosi tai Epatosi totuusarvosta riippuen.

Toteutetun metafunktion avulla voidaan nyt kirjoittaa koodia, jos-sa kaytetty tyyppi valitaan kaannosaikana annetun ehdon perusteel-la. Esimerkiksi halutaan valita muuttujan tyypiksi int, jos tama tyyp-pi on vahintaan neljan tavun kokoinen, muuten valitaan long int.Tama onnistuu seuraavasti:

IF< (sizeof(int)>=4), int, long int >::Tulos muuttuja = 0;

Page 361: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.6. C++: Template-metaohjelmointi 361

4 // Perustapaus, jos ehto on tosi5 template <bool EHTO, typename Tosi, typename Epatosi>6 struct IF7 8 typedef Tosi Tulos;9 ;

1011 // Osittaiserikoistus, jos ehto on epatosi12 template <typename Tosi, typename Epatosi>13 struct IF<false, Tosi, Epatosi>14 15 typedef Epatosi Tulos;16 ;

LISTAUS 10.21: Metafunktion IF toteutus

Listaus 10.22 nayttaa toisen kayttoesimerkin metafunktiolle IF.Kyseessa on sama min-funktiomallin paluutyypin valinta kuin listauk-sessa 10.18, mutta nyt erillisen paluutyyppia varten kirjoitetun meta-funktion sijaan valitaan kahdesta parametrityypista se, joka koko nu-meroina on numeric limits:n mukaan suurempi (tama versio ei otahuomioon esimerkiksi parametrityyppien etumerkkeja vaan olettaaetta molemmat parametrit ovat joko etumerkillisia tai etumerkittomiakokonaislukuja).

1 // Valitaan paluutyypiksi parametreista “laajempi”2 template <typename T1, typename T2>3 typename IF< (std::numeric limits<T1>::digits >=4 std::numeric limits<T2>::digits),5 T1, T2 >::Tulos6 min(T1 const& p1, T2 const& p2)7 8 if (p1 < p2)9

10 return p1;11 12 else13 14 return p2;15 16

LISTAUS 10.22: Metafunktion IF kayttoesimerkki

Page 362: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.6. C++: Template-metaohjelmointi 362

10.6.6 Esimerkki metaohjelmoinnista: optimointi

Tahan mennessa kaikki esitetyt metafunktiot ovat vain tuottaneetuusia tyyppeja tai valinneet kaannosaikana olemassa olevien tyyp-pien valilla. Toinen tarkea kayttokohde metaohjelmoinnille on koo-din optimointi kaannosaikana tehtavien paatelmien perusteella. Kay-tannossa tama tarkoittaa usein, etta useista eri algoritmivaihtoehdois-ta valitaan metaohjelmoinnin avulla tehokkain kayttokohteeseen so-piva, tai algoritmin toimintaa saadetaan kaannosaikaisten laskelmienperusteella. Tallaisesta optimoinnista on usein suuri hyoty, koskase voidaan tehda huomattavasti korkeammalla abstraktiotasolla kuinmihin C++-kaantajien omat optimointialgoritmit pystyvat.

Esimerkkina tamantapaisesta korkean tason optimoinnista kayte-taan tassa STL:n iteraattoreita. Kuten aiemmin on todettu, iteraatto-reita pystyy liikuttelemaan sailion sisalla eri tavoin riippuen siita,mihin iteraattorikategoriaan ne kuuluvat. Eteenpainiteraattorit pys-tyvat liikkumaan vain yhden alkion eteenpain kerrallaan. Kaksisuun-taiset iteraattorit taas pystyvat liikkumaan yhden alkion verran eteen-tai taaksepain. Hajasaanti-iteraattorit puolestaan pystyvat hyppimaanmielivaltaisen hypyn kumpaan suuntaan tahansa. Jaottelu kategorioi-hin STL:ssa perustui siihen, etta kaikkien iteraattoreiden liikkumi-sien taataan tapahtuvan vakioajassa.

Joissain tapauksissa olisi tarkeaa pystya siirtamaan iteraattoria ha-lutun verran eteen- tai taaksepain riippumatta siita, kuinka tehokastaliikkuminen on. Tama on tietysti mahdollista kayttamalla kaikille ite-raattoreille kayttamalla ++-operaattoria silmukassa, mutta silloin siir-taminen tulee tehtya turhan tehottomasti hajasaanti-iteraattoreille,jotka pystyisivat hyppaamaan halutun verran kerrallakin. Olisi ihan-teellista, jos sama siirtymisfunktio pystyisi siirtamaan hajasaanti-ite-raattoreita +=-operaatiolla ja muita iteraattoreita silmukassa ++-ope-raattorilla.

Ilman metaohjelmointia tata ongelmaa ei ole mahdollista ratkais-ta helposti C++:ssa. Pelkka normaali if-lause ei riita, koska muillekuin hajasaanti-iteraattoreille +=-operaattoria kayttava koodi aiheut-taa kaannosvirheen. Taten sama C++-funktio ei pysty ajoaikana paat-telemaan miten iteraattoria tulisi liikuttaa tehokkaasti.

Ratkaisuna ongelmaan on kirjoittaa kuormitettu funktiomalli, jol-la on eri toteutukset eri iteraattorityyppeja varten. Naista toteutuksis-ta valitaan sitten kaannosaikana kutakin kutsua varten sopiva. Tata

Page 363: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.6. C++: Template-metaohjelmointi 363

varten taytyy kuitenkin pystya kaannosaikana selvittamaan mihin ite-raattorikategoriaan tietty iteraattori kuuluu. Onneksi STL tarjoaa juu-ri tata tarkoitusta varten valmiin trait-metafunktion iterator traits.

Metafunktiolle iterator traits annetaan tyyppiparametri-na iteraattori (tai C++:n perustaulukkotyypin tapauksessa nor-maali osoitin, joka toimii iteraattorina taulukkoon). Tamanjalkeen metafunktio kertoo iteraattorin ominaisuuksia. Ta-ta esimerkkia varten meita kiinnostaa ominaisuuksista vaintyyppi iterator traits<T>::iterator category. Tama tyyp-pi on jokin tyypeista input iterator tag, output iterator tag,forward iterator tag, bidirectional iterator tag tairandom access iterator tag. Nama tyypit on periytetty toisistaanniin, etta saadaan iteraattorikategorioita vastaava luokkahierarkia,joka nakyi esimerkiksi kuvassa 10.5 sivulla 330.

Tata iteraattorikategorioihin jakamista voidaan nyt kayttaa hy-vaksi algoritmin valinnassa. Listauksessa 10.23 seuraavalla sivullaon kolme eri toteutusta funktiomallille siirry apu.Nama eroavat toi-sistaan viimeisen parametrin tyypin osalta. Tata viimeista paramet-ria kaytetaan iteraattorikategorian valintaan.` Ensimmainen versiofunktiomallista hyvaksyy kolmanneksi parametriksi lukuiteraattori-kategorian (johon kuuluvat periyttamalla kaikki iteraattorikategoriattulostusiteraattoria lukuun ottamatta). Toisen version lisaparametrintyyppi maaraa kategoriaksi kaksisuuntaisen iteraattorin ja kolmas ha-jasaanti-iteraattorin. Nama eri versiot on toteutettu eri tavoilla niin,etta kutakin iteraattorityyppia liikutetaan niin tehokkaasti kuin mah-dollista.

Varsinainen funktiomalli siirry on toteutettu riveilla 43–48 niin,etta se kutsuu apufunktiota siirry apu ja antaa sille kolmanneksi pa-rametriksi iterator traits-metafunktion avulla saadun iteraattori-kategorian. Taman kategorian perusteella kaantaja sitten kutsuu ky-seiselle iteraattorille sopivaa apufunktiota, joka siirtaa iteraattorin te-hokkaimmalla tavalla halutun verran eteen- tai taaksepain.

Kuten tasta esimerkista huomataan, metaohjelmointi antaa C++:ssavarsin monipuoliset mahdollisuudet koodin korkealla tasolla tapah-tuvaan optimointiin. Ikava kylla metaohjelmointiin tarvittavat “kikat”ovat usein varsin vaikealukuisia ja -selkoisia, joten tallainen metaoh-

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .`Funktiomalleissa saattaa oudoksuttaa se, etta niiden kolmannesta parametrista on para-

metrilistassa mainittu vain tyyppi, eika parametrin nimea. Tama on laillista C++:aa ja kertookaantajalle, ettei kyseista parametria kayteta itse koodissa lainkaan.

Page 364: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.6. C++: Template-metaohjelmointi 364

1 #include <iterator>2 using std::iterator traits;3 using std::input iterator tag;4 using std::bidirectional iterator tag;5 using std::random access iterator tag;67 // Kuormitetut funktiomallit eri tapauksille8 // Perustapaus, vahintaan lukuiteraattori9 template<typename Iteraattori, typename Siirtyma>

10 void siirry apu(Iteraattori& iter, Siirtyma s, input iterator tag)11 12 // Ollaan varmasti menossa eteenpain, lukuiteraattorit eivat osaa muuta13 for (Siirtyma i = 0; i != s; ++i)14 15 ++iter;16 17 1819 // Erikoistapaus 1, vahintaan kaksisuuntainen iteraattori20 template<typename Iteraattori, typename Siirtyma>21 void siirry apu(Iteraattori& iter, Siirtyma s, bidirectional iterator tag)22 23 // Taytyy pystya menemaan eteen- tai taaksepain tilanteesta riippuen24 if (s >= 0)25 26 for (Siirtyma i = 0; i != s; ++i) ++iter; 27 28 else29 30 for (Siirtyma i = 0; i != s; --i) --iter; 31 32 3334 // Erikoistapaus 2, hajasaanti-iteraattori35 template<typename Iteraattori, typename Siirtyma>36 void siirry apu(Iteraattori& iter, Siirtyma s, random access iterator tag)37 38 // Hypataan suoraan oikeaan paikkaan39 iter += s;40 4142 // Varsinainen funktio, joka valitsee apufunktion metakoodilla43 template<typename Iteraattori, typename Siirtyma>44 inline void siirry(Iteraattori& iter, Siirtyma s)45 46 siirry apu(iter, s,47 typename iterator traits<Iteraattori>::iterator category());48

LISTAUS 10.23: Esimerkki metaohjelmoinnista optimoinnissa

Page 365: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

10.6. C++: Template-metaohjelmointi 365

jelmointi vaatii tuekseen selkean dokumentoinnin, jotta myos koodialukevat ymmartavat, mita koodin on tarkoitus tehda. Onneksi genee-risen template-metakoodia sisaltavan kirjaston kayttaminen on kui-tenkin varsin selkeaa ja usein jopa ohjelmoijalle taysin lapinakyvaa,kuten esimerkiksi STL osoittaa.

Page 366: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

366

Luku 11

Virhetilanteet japoikkeukset

Although the source of the Operand Error has been identi-fied, this in itself did not cause the mission to fail. The spec-ification of the exception-handling mechanism also con-tributed to the failure. In the event of any kind of excep-tion, the system specification stated that: the failure shouldbe indicated on the databus, the failure context should bestored in an EEPROM memory (which was recovered andread out for Ariane 501), and finally, the SRI processorshould be shut down.

– ARIANE 5 Flight 501 Failure Report [Lions, 1996]

Virhetilanteisiin varautuminen ja niihin reagoiminen on aina ollutyksi vaikeimpia ohjelmoinnin haasteita. Virhetilanteissa ohjelmantaytyy yleensa toimia normaalista poikkeavalla tavalla, ja naidenuusien ohjelman suoritusreittien koodaaminen tekee ohjelmakoodis-ta helposti sekavaa. Lisaksi useiden erilaisten virhetilanteiden viida-kossa ohjelmoijalta jaa helposti tekematta tarvittavia siivoustoimen-piteita, kuten muistin vapautusta. Jos viela lisaksi vaaditaan, etta oh-jelman taytyy toipua virheista eika vain esimerkiksi lopettaa ohjel-

Page 367: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

11.1. Mika virhe on? 367

man suoritusta virheilmoitukseen, ohjelmakoodin taytyy pystya pe-ruuttamaan virhetilanteen vuoksi kesken jaaneet operaatiot.

Virhetilanteiden kasittely on kokonaisuudessaan niin laaja ai-he, ettei siita taman teoksen puitteissa voida kertoa kovinkaan pal-jon, eika se edes kovin oleellisesti liity taman teoksen aiheeseen-kaan. C++ tarjoaa kuitenkin virheiden kasittelyyn erityisen mekanis-min, poikkeukset (exception), joiden toiminta perustuu luokkahierar-kioihin ja nain ollen sivuaa myos olio-ohjelmointia. Virhekasittelystaja C++:n poikkeuksista loytyy lisatietoa esim. kirjoista “More EffectiveC++” [Meyers, 1996], “Exceptional C++” [Sutter, 2000] ja “More Excep-tional C++” [Sutter, 2002c].

11.1 Mika virhe on?

Useimmat tietokoneen kayttajat sanovat ohjelman toimivan vaarin,jos siina ei ole heidan tarvitsemaansa ominaisuutta tai ohjelma an-taa kayttajan mielesta vaaria vastauksia. Ohjelmiston tekijan kannal-ta syyt naihin vaitteisiin voivat olla maarittelyssa (yritetaan tehda oh-jelmalla jotain mihin sita alunperinkaan ei ole tarkoitettu), suunnit-telussa (toteutukseen ei ole otettu mukaan kaikkia maarittelyssa ol-leita asioita tai niiden toteutus on suunniteltu virheelliseksi) tai oh-jelmoinnissa (ohjelmointityossa on tapahtunut virhe).

Tietokoneohjelmat ovat mutkikkaita ja paraskaan ohjelmisto eiluultavasti pysty varautumaan kaikenlaisiin eri tasoilla oleviin virhe-tilanteisiin etukateen — vahintaankin tallaisen ohjelmiston toteutus-kustannukset nousisivat sietamattomiksi. Ohjelmistoa on kuitenkinhelppo pitaa kilpailijoitaan laadukkaampana, jos siita loytyy muitaenemman virhetilanteisiin varautuvia ominaisuuksia.

Ohjelmointityossa ei pysty vaikuttamaan maarittelyn ja suunnit-telun aikaisiin virheisiin, ne paljastuvat ohjelmiston testauksessa taihuonoimmassa tapauksessa vasta tuotantokaytossa. Ohjelmoinnissavoidaan varautua etukateen pohdittuihin vikatilanteisiin, jotka voi-daan karkeasti jakaa laitteiston ja ohjelmiston aiheuttamiin.

Laitteistovirheet nakyvat ohjelmistolle sen ympariston kayttayty-misena eri tavoin kuin on oletettu. Ohjelmia tehtaessa oletetaan esi-merkiksi, etta muuttujaan kirjoitettu arvo on sailynyt samana, kunsita hetken kuluttua luetaan muuttujasta — viallinen muistipiiri tie-tokoneessa saattaa kuitenkin aiheuttaa tilanteen olevan toinen. Tie-

Page 368: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

11.1. Mika virhe on? 368

dostojen kasittely voi menna vikaan levyn tayttymisen tai vikaantu-misen vuoksi. Ohjelma itsessaan on saattanut osittain muuttua kunse on ladattu suoritettavaksi ja nain ollen toimii vaarin suorittaessaankonekaskyja, joita ohjelmoija ei ole tarkoittanut suoritettavaksi.

Laitteistovirheista saadaan tietoa yleensa kayttojarjestelman kaut-ta. Tiedostojen kasittelyssa tapahtuneet virheet useimmat kayttojar-jestelmat osaavat ilmoittaa ohjelmalle, mutta muut “vakavammat” lai-tevirheet voivat aiheuttaa tiedon muuttumista ilman, etta siita erik-seen tulee ilmoitusta ohjelmalle. Ohjelmoijan on todellisuudessa ainatehtava jonkinlainen kompromissi sen kanssa, minka tyyppisia vir-heita ohjelmassa pyritaan havaitsemaan ja kasittelemaan. Laitteistontapauksessa yleisin linja on varautua kayttojarjestelman ilmoittamiinvikoihin ja jattaa muut huomioimatta luottaen niiden olevan erittainharvinaisia.

Eraan arvion mukaan nykyaikainen henkilokohtainen tietokonetekee virheen laitteiston laskutoimituksissa keskimaarin kolmen tril-joonan (18 nollaa) laskun suorituksen jalkeen. Tama tarkoittaa suun-nilleen sita, etta ajettaessa ohjelmistoa tallaisella koneella tuhat vuot-ta vika esiintyy kerran. Useimmat ohjelmistot jattavat nama vikamah-dollisuudet tarkastamatta, mutta joskus nekin muodostuvat merkit-taviksi. Esimerkiksi massiivista rinnakkaiseksi hajautettua laskentaasuorittava SETI@home-projekti kayttaa edella mainitun ajan proses-soriaikaa paivassa ja tormaa kyseiseen vikaan siis keskimaarin kerranvuorokaudessa — talloin vikamahdollisuus on myos huomioitava oh-jelmistossa. [SETI, 2001]

Ohjelmistossa virheet ovat yksittaisen ohjelmanpatkan (funktio,olio, moduuli) kannalta sisaisia tai ulkoisia. Ulkoisessa virheessa koo-dia pyydetaan tekemaan jotain, mita se ei osaa tai mihin se ei pysty.Esimerkiksi funktion parametrilla on vaara arvo, syotetiedosto ei nou-data maariteltya muotoa, tai kayttaja on valinnut toimintosekvenssinjossa ei ole “jarkea”. Sisaisessa virheessa toteutus ajautuu itse tilan-teeseen jossa jotain menee pieleen (esimerkiksi muisti loppuu tai to-teutusalgoritmissa tulee jokin aariraja vastaan).

Virhetilanteita huomioivassa ohjelmoinnissa on usein kaikistahelpoin vaihe havaita virhetilanne. Tahan toimintaan kayttojarjestel-mat, ohjelmakirjastot ja ohjelmointikielet tarjoavat lahes aina keinoja.Havaitsemista paljon vaikeampaa on suunnitella ja toteuttaa se, mitavikatilanteessa tehdaan.

Page 369: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

11.2. Mita tehda virhetilanteessa? 369

11.2 Mita tehda virhetilanteessa?

Varautuva ohjelmointi (defensive programming, [McConnell, 1993])on ohjelmointityyli, jota voisi verrata autolla ajossa ennakoivaan ajo-tapaan. Vaikka oma toiminta (koodi) olisi taysin oikein ja sovittujensaantojen mukaista, kannattaa silti varautua siihen, etta muut osallis-tujat voivat toimia vaarin. Usein ajoissa tapahtunut virheiden ja on-gelmien havaitseminen mahdollistaa niihin sopeutumisen jopa siten,etta ohjelmissa kayttajan ei tarvitse huomata mitaan erityistilannettaedes syntyneen.

Seuraavassa on listattu muutamia tapoja, joilla ohjelman osa voitoimia havaitessaan virhetilanteen. Sopiva suhtautuminen virhee-seen on vahintaankin ohjelmakomponentin suunnitteluun kuuluvaasia. Ei ole olemassa yhta ainoata oikeata tai vaaraa tapaa — hyvinsuunniteltu komponentti voi ottaa virheisiin reagoinnin omalle vas-tuulleen, mutta hyvana ratkaisuna voidaan pitaa myos sellaista, joka“ainoastaan” ilmoittaa havaitsemansa virheet komponentin kayttajal-le.

• Suorituksen keskeytys (abrupt termination) on aarimmainen ta-pa toimia kun ohjelmassa kohdataan virhe. Jarjestelman suorit-taminen keskeytetaan valittomasti ja usein ilman, etta virheti-lannetta yritetaan edes mitenkaan kirjata myohempaa tarkas-telua varten. Taman tavan kayttoa tulisi valttaa, silla pysahty-neesta ohjelmasta ei edes aina tiedeta miksi pysahtyminen ta-pahtui. Valitettavan useassa kayttojarjestelmassa ja ohjelmointi-kielten ajoymparistoissa tama on oletustoiminta silloin kun jo-kin virhe on havaittu (esimerkiksi kaatuminen muistin loppues-sa).

• Suorituksen hallittu lopetus (abort, exit) on edellista lievempitapa, jossa yritetaan siivota ohjelmiston tila vapauttamalla kaik-ki sen varaamat resurssit ja kirjaamalla virhetilanne pysyvaantalletuspaikkaan seka ilmoittamalla virheesta kayttajalle ennensuorituksen lopettamista.

• Jatkaminen (continuation) tarkoittaa havaitun virheen jattamis-ta huomiotta. Maarittelynsa mukaisesti virhe on ohjelman ei-

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .Huom: C++-kielen funktiot abort() ja exit() toteuttavat suorituksen keskeytyksen, eivat

hallittua lopetusta.

Page 370: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

11.2. Mita tehda virhetilanteessa? 370

toivottu tila, joten sellaisen jattaminen kasittelematta havain-noinnin jalkeen on hyvin hyvin harvinainen toimintamalli. Jos-kus esimerkiksi kayttoliittymassa tapahtumien puuttuminen taikatoaminen voi olla tilanne, jossa jatkaminen tulee kysymyk-seen — esimerkiksi yksittaiset hiirikohdistimen paikkatietoa si-saltavat tapahtumat voivat kadota ilman etta tilanteella on mi-taan vaikutusta ohjelmiston toimintaan.

• Peruuttaminen (rollback) tarkoittaa jarjestelman tilan palautta-mista siihen tilanteeseen, mika se oli ennen virheen aiheutta-neen operaation kaynnistamista. Tama helpottaa huomattavas-ti esimerkiksi operaation yrittamista uudelleen, koska tiedetaantarkasti missa tilassa ohjelmisto on, vaikka virhe onkin tapahtu-nut. Valitettavasti peruuttamisen toteuttaminen on usein mut-kikasta (voi aiheuttaa itsessaan virheita ohjelmistoon) ja resurs-seja kuluttavaa. Yksi yksinkertainen toteutustapa on operaationalussa luoda kopio muutettavasta datasta ja tehda muutoksettahan kopioon. Jos operaatio menee lapi ilman virheita, vaih-detaan kopio alkuperaisen datan tilalle ja virheiden sattuessatuhotaan kopioitu data (alkuperainen pysyy koskemattomana).

• Toipuminen (recovery) on ohjelman osan paikallinen toteutushallitusta lopetuksesta. Osanen ei pysty itse kasittelemaan ha-vaittua virhetta, mutta se pyrkii vapauttamaan kaikki varaa-mansa resurssit ennen kuin virheesta tiedotetaan ohjelmistos-sa toisaalle (usein loogisesti ylemmalle tasolle) jonka toivotaanpystyvan kasittelemaan havaittu virhe paremmin.

Peruuttaminen ja toipuminen antavat mahdollisuuden yrittaa kor-jata tilannetta, joka johti virhetilanteeseen. Pieleen menneen operaa-tion yrittaminen uudelleen on virheisiin reagoinnin suunnittelussahankalinta. Helpoimmassa tapauksessa operaatio vain toistetaan (esi-merkiksi tietoliikenteessa suoritetaan uudelleenlahetys), mutta va-litettavan usein virhetilanne johtuu ongelmista resursseissa, joidenpuuttuessa tilanteen korjaaminen on hankalaa.

Yksi hyva esimerkki on muistin loppuminen. Ohjelmointikieletja -ymparistot tarjoavat lahes aina tavan havaita, etta ohjelman suori-tuksen aikana silta on loppunut muisti (viimeisin dynaamisen muis-tin varausoperaatio on epaonnistunut). Tilanteen voi havaita, mutta

Page 371: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

11.3. Virhehierarkiat 371

mita sille voi tehda? Jos muistia ei ole, niin toipumisoperaatiot eivatmissaan tapauksessa saa kuluttaa lisaa muistia.

Jarjestelmasta voitaisiin yrittaa vapauttaa kaytossa olevaa muis-tia, mutta resursseista jarkevasti huolta pitava ohjelmisto on tietystialunperin toteutettu siten, ettei turhia muistivarauksia ole olemas-sa. Seuraavaksi voidaan etsia “vahemman tarkeita” muistivarauksiaja vapautetaan ne, jolloin virhetilanne hyvin suurella todennakoisyy-della siirtyy toisaalle ohjelmistossa.

Yksi varma tapa saada muistia lisaa kayttoonsa on varautua lop-pumiseen etukateen varaamalla kasa “turhaa” muistia, joka voidaanturvallisesti vapauttaa uusiokayttoon, jos joudutaan tilanteeseen, jos-sa muisti on lopussa [Meyers, 1996]. Muistia jatkuvasti syovan vir-heen tapauksessa tietysti vain pitkitetaan todellisen ongelman koh-taamista, mutta ohimeneviin muistiongelmiin tama on tayden toipu-misen toteuttava tapa.

11.3 Virhehierarkiat

Ohjelmassa tapahtuvat mahdolliset virhetilanteet voidaan usein ja-kaa kategorioihin sen perusteella, mihin virhe liittyy. Tallaista vir-heiden jaottelua voi kuvata luokkakaaviolla niin kuin tavallistenkinolioiden kategorioita. Kuva 11.1 seuraavalla sivulla nayttaa esimerki-na, miten tama jako on tehty C++:n standardikirjastossa. Kuvan hierar-kia ei tietenkaan ole taydellinen, mutta se kattaa C++:n itsensa tuot-tamat poikkeukset. Ohjelmoija voi itse laajentaa tata hierarkiaa taikirjoittaa oman hierarkiansa alusta saakka, jos niin haluaa.

Tallaisten virhehierarkioiden etuna on, etta ne tekevat mahdolli-seksi virhekasittelyn jakamisen eri tasoihin. Esimerkiksi kuvan hie-rarkiassa virheet jakautuvat kahteen paakategoriaan: logiikkavirheet(logic error) ja ajoaikavirheet (runtime error). Logiikkavirheisiinkuuluvat kaikki sellaiset virheet, jotka aiheutuvat ohjelman toiminta-logiikassa havaituista virheista, “bugeista”. Tallaisen virheen tapah-tuminen on merkki siita, etta ohjelmassa on vikaa. Ajoaikavirheetpuolestaan johtuvat siita, etta ohjelman suorituksen aikana ajoym-paristo aiheuttaa tilanteen, jota ohjelma ei pysty hallitsemaan. Esi-merkkeja tasta ovat ylivuodot (ohjelmalle syotetaan liian suuria luku-ja), virheet lukualueissa (kayttaja syottaa kuukauden numeroksi 13)ja vaikkapa tietoliikenneyhteyden katkeaminen.

Page 372: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

11.3. Virhehierarkiat 372

exception

const char* what() const

logic_error runtime_error bad_exceptionbad_typeid

range_error underflow_error

overflow_error

domain_errorlength_error

out_of_range invalid_argument

bad_alloc bad_cast

KUVA 11.1: C++-standardin virhekategoriat

Kun virheet on jaoteltu hierarkiaksi, jotkin ohjelman osat voivatesimerkiksi kasitella ylivuodot ja kenties toipua niista, mutta jattavatmuut virheet ohjelman ylempien tasojen huoleksi. Ylempi ohjelmanosa voi sitten kasitella yhtenaisesti kaikkia ajoaikaisia virheita valit-tamatta siita, mika nimenomainen virhe on kyseessa.

Koska virheiden muodostama hierarkia muistuttaa suuresti luok-kahierarkiaa, olio-ohjelmoinnissa voidaan virheita mallintaa luokil-la, jotka toteutetaan ohjelmassa. Naita luokkia voidaan sitten kayt-taa hyvaksi C++:n poikkeusten kanssa. Listaus 11.1 seuraavalla sivullanayttaa osan kuvan 11.1 virhetyypeista luokkina, jotka loytyvat C++:nstandardikirjastoista <exception> ja <stdexcept>.

Periaatteessa ohjelmoija voi itse vapaasti paattaa, kayttaako itsealusta saakka suunnittelemaansa virhehierarkiaa vai periyttaako tar-vitsemansa poikkeukset kirjaston virhehierarkiasta. C++:n oman vir-hehierarkian laajentaminen tietysti yhtenaistaa virheiden kasittelya,joten se lienee usein tarkoituksenmukaista. Hierarkiaan voi tietysti

Page 373: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

11.3. Virhehierarkiat 373

1 // Nama kaikki ovat std-nimiavaruudessa2 class exception3 4 public:5 exception() throw(); // throw() selitetaan myohemmin6 exception(exception const& e) throw();7 exception& operator =(exception const& e) throw();8 virtual ~exception() throw();9 virtual char const* what() const throw();

...10 ;1112 class runtime error : public exception13 14 public:15 runtime error(std::string const& msg);16 ;1718 class overflow error : public runtime error19 20 public:21 overflow error(std::string const& msg);22 ;

LISTAUS 11.1: Virhetyypit C++:n luokkina

lisata myos uusia alihierarkioita periyttamalla ne standardin kanta-luokista.

Listaus 11.2 seuraavalla sivulla nayttaa esimerkin omasta virhe-luokasta LiianPieniArvo, joka kuvaa virhetta, jossa jokin ohjelmanarvo on liian pieni sallittuun verrattuna. Tama on erikoistapaus C++:nvirhetyypista domain error, joten oma virheluokka on periytetty siita.Listauksesta nakyy myos, kuinka virheluokkaan voi upottaa tietoa it-se virheesta. Tassa tapauksessa jokainen LiianPieniArvo-olio sisaltaatiedon siita, mika liian pieni arvo oli ja mika arvon minimiarvo olisiollut. Nama tiedot annetaan oliolle rakentajan parametrina, kun vir-hetilanne havaitaan ja virheolio luodaan. Virhetta kasitellessa arvojavoi sitten kysya luokan anna-jasenfunktioilla.

Page 374: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

11.4. Poikkeusten heittaminen ja sieppaaminen 374

1 class LiianPieniArvo : public std::domain error2 3 public:4 LiianPieniArvo(std::string const& viesti, int luku, int minimi);5 LiianPieniArvo(LiianPieniArvo const& virhe);6 virtual ~LiianPieniArvo() throw();7 int annaLuku() const;8 int annaMinimi() const;9 private:

10 int luku ;11 int minimi ;12 ;

LISTAUS 11.2: Esimerkki omasta virheluokasta

11.4 Poikkeusten heittaminen ja sieppaaminen

C++:n poikkeusten periaatteena on, etta virheen sattuessa ohjelmaheittaa (throw) “ilmaan” poikkeusolion, joka kuvaa kyseista virhet-ta. Taman jalkeen ohjelma alkaa “peruuttaa” funktioiden kutsuhierar-kiassa ylospain ja yrittaa etsia lahimman poikkeuskasittelijan (excep-tion handler), joka pystyy sieppaamaan (catch) virheolion ja reagoi-maan virheeseen. Jokaisella poikkeuskasittelijalla on oma koodiloh-konsa, valvontalohko (try-block), jonka sisalla syntyvat virheet ovatsen vastuulla. Virhekasittelyn yhteydessa poikkeusoliosta tehdaan ko-pio, joten on tarkeaa, etta poikkeusluokilla on toimiva kopiorakentaja.

Poikkeuksen heittaminen ja poikkeuskasittelijan etsiminen onkohtalaisen raskas operaatio verrattuna useimpiin muihin C++:n ope-raatioihin. Niinpa onkin tarkeaa, etta poikkeuksia kaytetaan vainpoikkeuksellisten tilanteiden kasittelyyn eika esimerkiksi uutenamuodikkaana hyppykaskyna.

11.4.1 Poikkeushierarkian hyvaksikaytto

Listaus 11.3 sivulla 376 sisaltaa esimerkin virhekasittelysta keskiar-von laskennassa. Keskiarvoa laskettaessa on kaksi virhemahdollisuut-ta: lukujen lukumaara saattaa olla nolla tai niiden summa saattaa kas-vaa liian suureksi. Lukujen summaa laskeva funktio heittaa ylivuoto-

Page 375: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

11.4. Poikkeusten heittaminen ja sieppaaminen 375

tapauksessa riveilla 10 ja 15 poikkeuksen komennolla throw.] Vastaa-vasti keskiarvon laskeva funktio heittaa poikkeuksen rivilla 27, joslukujen lukumaara on nolla.

Funktio keskiarvo1 sisaltaa kaksi poikkeuskasittelijaa riveilla 40–47. Kasittelija merkitaan avainsanalla catch, jonka jalkeen anne-taan parametrilistan omaisesti kasittelijan hyvaksyman poikkeus-olion tyyppi. Poikkeuskasittelijan parametrit on syyta merkita vakio-viitteiksi samasta syysta kuin tavallisetkin parametrit olioita valitet-taessa (aliluku 4.3.3). Poikkeuskasittelijat pystyvat sieppaamaan vir-hetilanteet, jotka syntyvat sina aikana, kun ohjelman suoritus on ri-veilla 34–39 olevan try-avainsanalla merkityn valvontalohkon sisal-la. Ensimmainen poikkeuskasittelija sieppaa nollallajakovirheet, jal-kimmainen ylivuodot.

Normaalissa tapauksessa ohjelman suoritus siirtyy valvontaloh-koon, suorittaa siella koodin ja hyppaa sen jalkeen poikkeuskasitte-lijoiden yli riville 48. Talla tavoin virhekasittely ei millaan tavallavaikuta ohjelman normaaliin toimintaan.

Jos lukujen summa kasvaa liian suureksi, rivi 10 heittaa poikkeuk-sen. Ohjelma alkaa talloin etsia lahinta sopivaa poikkeuskasittelijaa.Funktiossa summaaLuvut sellaista ei ole, joten virhe “vuotaa” ulos tas-ta funktiosta ja ohjelma peruuttaa takaisin funktioon laskeKeskiarvo.Siellakaan ei ole poikkeuskasittelijaa, joten ohjelma palaa funktioonkeskiarvo1. Siella on vihdoin ylivuotovirheen hyvaksyva poikkeus-kasittelija, ja ohjelman suoritus jatkuu poikkeuskasittelijan koodistarivilta 46.

Virhekasittelyn paatyttya ohjelma ei palaa virhekohtaan vaanjatkuu koko virhekasittelyrakenteen jalkeen rivilta 48. Virhekasittelyaei siis suoraan voi kayttaa taydelliseen virheesta toipumiseen, jossaohjelman suoritus palaisi virhekasittelyn jalkeen takaisin valvonta-lohkoon jatkamaan sen suoritusta.

Listauksen 11.3 molemmat poikkeuskasittelijat sisaltavat lahes sa-man koodin, koska molemmat virheet ovat luonteeltaan samanlaisia.Joskus onkin jarkevaa tehda poikkeuskasittelija, joka sieppaa kaikkitiettyyn virhekategoriaan kuuluvat poikkeukset. Tama tehdaan laitta-malla poikkeuskasittelijan parametriksi viite virhehierarkian halut-tuun kantaluokkaan. Koska jokainen virhekantaluokasta peritty vir-he on olio-ohjelmoinnin mukaan myos kantaluokan olio, poikkeus-

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .]Koodissa numeric limits<double>::max() palauttaa suurimman mahdollisen liukulu-

vun. numeric limits-mallia kasiteltiin aliluvussa 10.6.4.

Page 376: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

11.4. Poikkeusten heittaminen ja sieppaaminen 376

1 void lueLuvutTaulukkoon(vector<double>& taulu);23 double summaaLuvut(vector<double> const& luvut)4 5 double summa = 0.0;6 for (unsigned int i = 0; i < luvut.size(); ++i)7 8 if (summa >= 0 && luvut[i] > numeric limits<double>::max()-summa)9

10 throw std::overflow error("Summa liian suuri");11 12 else if (summa < 0 &&13 luvut[i] < -numeric limits<double>::max()-summa)14 15 throw std::overflow error("Summa liian pieni");16 17 summa += luvut[i];18 19 return summa;20 2122 double laskeKeskiarvo(vector<double> const& luvut)23 24 unsigned int lukumaara = luvut.size();25 if (lukumaara == 0)26 27 throw std::range error("Lukumaara keskiarvossa 0");28 29 return summaaLuvut(luvut) / static cast<double>(lukumaara);30 3132 void keskiarvo1(vector<double>& lukutaulu)33 34 try35 36 lueLuvutTaulukkoon(lukutaulu);37 double keskiarvo = laskeKeskiarvo(lukutaulu);38 cout << "Keskiarvo: " << keskiarvo << endl;39 40 catch (std::range error const& virhe)41 42 cerr << "Lukualuevirhe: " << virhe.what() << endl;43 44 catch (std::overflow error const& virhe)45 46 cerr << "Ylivuoto: " << virhe.what() << endl;47 48 cout << "Loppu" << endl;49

LISTAUS 11.3: Esimerkki C++:n poikkeuskasittelijasta

Page 377: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

11.4. Poikkeusten heittaminen ja sieppaaminen 377

kasittelija sieppaa myos kaikki kantaluokasta periytetyt poikkeukset.Listauksessa 11.4 on uusi keskiarvofunktio, jonka poikkeuskasittelijasieppaa kaikki ajoaikaiset virheet.

11.4.2 Poikkeukset, joita ei oteta kiinni

Ohjelmassa voi tietysti tapahtua myos poikkeus, jota mikaan poik-keuskasittelija ei sieppaa. Esimerkissa on mahdollista, etta lukujataulukkoon luettaessa muisti loppuu. Talloin vector-luokka vuotaaulos poikkeuksen std::bad alloc (katso aliluku 3.5.1). Jos nyt funk-tio lueLuvutTaulukkoon ei itse sieppaa tata virhetta ja toivu siita, vuo-taa virhe ulos tastakin funktiosta.

Mikali virhe paasee vuotamaan ulos paaohjelmastakin eli jos oh-jelmassa ei yksinkertaisesti ole sopivaa poikkeuskasittelijaa, ohjelmakutsuu funktiota terminate. Oletusarvoisesti tama funktio vain lopet-taa ohjelman suorituksen (ja kenties tulostaa virheilmoituksen). Oh-jelma voi itse tarjota oman toteutuksensa talle funktiolle, mutta jokatapauksessa ohjelman suoritus loppuu taman funktion suoritukseen.

Koska poikkeukset, joihin ei ole varauduttu, aiheuttavat ohjelmankaatumisen, on tarkeaa, etta ohjelmassa otetaan jollain tasolla kiinnikaikki aiheutetut poikkeukset. Joissain tapauksissa voi tietysti olla,etta ohjelman kaatuminen on hyvaksyttava reaktio virhetilanteeseen.

1 void keskiarvo2(vector<double>& lukutaulu)2 3 try4 5 lueLuvutTaulukkoon(lukutaulu);6 double keskiarvo = laskeKeskiarvo(lukutaulu);7 cout << "Keskiarvo: " << keskiarvo << endl;8 9 catch (std::runtime error const& virhe)

10 11 // Tanne tullaan minka tahansa ajoaikaisen virheen seurauksena12 cerr << "Ajoaikainen virhe: " << virhe.what() << endl;13 1415 cout << "Loppu" << endl;16

LISTAUS 11.4: Virhekategorioiden kaytto poikkeuksissa

Page 378: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

11.4. Poikkeusten heittaminen ja sieppaaminen 378

Ohjelmaan voi myos lisata “yleispoikkeuskasittelijoita”, jotka otta-vat vastaan kaikki valvontalohkossaan tapahtuvat poikkeukset. Yleis-poikkeuskasittelijan syntaksi on

catch (. . .) // Todellakin . . . eli kolme pistetta

// Tama poikkeuskasittelija sieppaa kaikki poikkeukset

Yleensa tallaisia “kaikkivoipia” yleispoikkeuskasittelijoita ei kan-nata kirjoittaa kuin korkeintaan paaohjelmaan, ellei sitten ole aivanvarma, etta poikkeuskasittelijan koodi todella pystyy reagoimaan oi-kein kaikkiin mahdollisiin poikkeuksiin, joita valvontalohkossa voisattua. Yleispoikkeuskasittelijahan sieppaa myos sellaiset virheet,joihin kenties voitaisiin paremmin reagoida ylemmalla tasolla ohjel-massa!

Poikkeuksen (,) tasta muodostavat tilanteet, joissa virheesta riip-pumatta taytyy suorittaa tietty siivouskoodi, jonka jalkeen virhe heite-taan uudelleen komennolla throw; ylemman tason poikkeuskasitteli-joiden hoidettavaksi. Talloin yleispoikkeuskasittelija on varsin kayt-tokelpoinen. Poikkeuksista ja siivoustoimenpiteista kerrotaan enem-man aliluvussa 11.5.

11.4.3 Sisakkaiset valvontalohkot

Listauksien 11.3 ja 11.4 keskiarvofunktiot eivat ota mitaan kantaamuistin loppumiseen, joten niissa mahdollisesti syntyvat muistinloppumisesta aiheutuvat poikkeukset — tai mitka tahansa poikkeuk-set ajoaikavirheita lukuun ottamatta — vuotavat funktioista ulosylempiin funktioihin. Niissa voi puolestaan olla omia poikkeuskasit-telijoitaan, joista jotkin voivat sitten siepata muistin loppumisesta ai-heutuneet poikkeukset. Jos poikkeus heitetaan usean sisakkaisen val-vontalohkon sisalla, etsitaan “lahin” poikkeuskasittelija, joka pystyysieppaamaan poikkeuksen.

Listaus 11.5 seuraavalla sivulla nayttaa paaohjelman, joka kut-suu keskiarvofunktiota ja sisaltaa lisaksi oman poikkeuskasittelijan-sa. Keskiarvofunktio kasittelee itse ajoaikavirheet, mutta muut vir-heet vuotavat keskiarvofunktiosta ulos paaohjelmaan. Naista paaoh-jelma kasittelee itse muistin loppumisen ja kaikki ohjelman Virhe-luokasta periytetyt poikkeukset.

Page 379: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

11.4. Poikkeusten heittaminen ja sieppaaminen 379

1 int main()2 3 vector<double> taulu;4 try5 6 keskiarvo2(taulu); // Lue luvut ja laske keskiarvo7 8 catch (std::bad alloc&)9

10 cerr << "Muisti loppui!" << endl;11 return EXIT FAILURE;12 13 catch (std::exception const& virhe)14 15 cerr << "Virhe paaohjelmassa: " << virhe.what() << endl;16 return EXIT FAILURE;17 1819 return EXIT SUCCESS;20

LISTAUS 11.5: Sisakkaiset valvontalohkot

Tama mahdollisuus sisakkaisiin valvontalohkoihin on erittainhyodyllinen ominaisuus, varsinkin kun se yhdistetaan virheluokka-hierarkioihin. Talloin ohjelman alemmissa osissa voidaan kasitel-la yksityiskohtaisesti tietyt virheet, kuten esimerkissa keskiarvostajohtuvat ylivuodot ja nollalla jakaminen. Ohjelman ylemmat osattaas voivat kasitella yleisemmalla tasolla laajempia virhekategorioita.Nain jokainen ohjelman osa voi reagoida virheisiin omalla abstraktio-tasollaan. Triviaalit pikkuvirheet siepataan alemmilla tasoilla ja yla-tasoille vuotavat suuremmat virheet voivat puolestaan aiheuttaa dra-maattisempia toimia.

Poikkeuskasittelija voi myos halutessaan heittaa virheen edelleen,jos se ei pysty toipumaan virheesta. Tama saadaan aikaan poik-keuskasittelijan koodissa komennolla throw; ilman mitaan paramet-ria. Tallaista osittaista poikkeuskasittelya kaytetaan hyvaksi funktionsiivoustoimenpiteissa seuraavassa aliluvussa. Poikkeuskasittelija voimyos muuttaa poikkeuksen toiseksi heittamalla omasta koodistaanuuden poikkeuksen.

Page 380: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

11.5. Poikkeukset ja olioiden tuhoaminen 380

11.5 Poikkeukset ja olioiden tuhoaminen

Poikkeuksen sattuessa ohjelma palaa takaisin koodilohkoista ja funk-tioista, kunnes se loytaa sopivan poikkeuskasittelijan. Taman peruut-tamisen tuloksena saatetaan poistua usean olion ja muuttujan naky-vyysalueelta. C++ pitaa huolen siita, etta kaikki oliot ja muuttujat, joi-den nakyvyysalue loppuu poikkeuksen tuloksena, tuhotaan normaa-listi. Olioiden purkajia kutsutaan, joten niiden siivoustoimenpiteetsuoritetaan kuten pitaakin. Nain poikkeukset eivat aiheuta mitaanongelmia olioille, joiden elinkaari on staattisesti maaratty.

Java-kielessa ei ole purkajia samalla tavoin kuin C++:ssa, joten sii-na kielen muuten C++:aa muistuttavaan poikkeuskasittelyyn on lisattyerityinen poikkeuskasittelijoiden jalkeen tuleva finally-lohko, jossaoleva koodi suoritetaan aina lopuksi, tapahtui valvontalohkossa poik-keus tai ei. Tahan lohkoon voi kirjoittaa siivoustoimenpiteita, jotkasuoritetaan aina valvontalohkosta poistumisen jalkeen.

Joskus vastaavasta siivouslohkosta olisi hyotya myos C++-kielessa,mutta sen poikkeusmekanismista ei tallaista loydy. Yleisesti kaytettyratkaisu on upottaa mahdollisimman moni siivousta vaativa asia so-pivan luokan sisaan, jolloin luokan purkaja suorittaa tarvittavan sii-vouksen.

11.5.1 Poikkeukset ja purkajat

C++ pystyy kasittelemaan samassa lohkossa vain yhta poikkeusta ker-rallaan. Poikkeuksen heittaminen aiheuttaa tarvittavien staattisenelinkaaren olioiden purkajien suorittamisen ennen kuin poikkeuson kasitelty. Jos jo heitetyn poikkeuksen tuloksena kutsutaan pur-kajaa, joka puolestaan vuotaa ulos oman poikkeuksensa, tulisi sa-maan aikaan voimaan kaksi poikkeusta. C++:n poikkeuskasittely eipysty tahan, joten se kutsuu tallaisesssa tapauksessa suoraan funktio-ta terminate ja lopettaa ohjelman suorituksen. Poikkeuksia purkajienyhteydessa kasitellaan tarkemmin aliluvussa 11.8.3.

11.5.2 Poikkeukset ja dynaamisesti luodut oliot

Dynaamisen elinkaaren oliot ovat ongelmallisia. Kuten jo luvussa 3kerrottiin, new’lla varattuja olioita ei koskaan tuhota automaattises-ti, ja tama patee myos poikkeuksen sattuessa. Tilanteen tekee erittain

Page 381: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

11.5. Poikkeukset ja olioiden tuhoaminen 381

vaikeaksi se, etta dynaamisesti luotuihin olioihin osoittavat osoittimetovat todennakoisesti normaaleja paikallisia muuttujia, joten ne tuho-taan poikkeuksen seurauksena. Nain muistiin jaa helposti tuhoamat-tomia olioita, joita on mahdoton tuhota, koska niihin ei enaa paastakasiksi.

Ainoa tapa valttaa edella mainitun kaltaisia muistivuotoja on ym-paroida kaikki tarvittavat dynaamisia olioita kasittelevat koodiloh-kot omalla poikkeuskasittelijallaan. Poikkeuskasittelija tuhoaa oliondeletella ja sen jalkeen heittaa viela tarvittaessa virheen edelleenylemmalla tasolla kasiteltavaksi. Listauksessa 11.6 on funktio, jokaluo olion dynaamisesti ja tuhoaa sen virhetilanteessa. Huomaa, ettakoodissa ei riita varautuminen pelkastaan muistin loppumiseen vaanolio taytyy tuhota minka tahansa muunkin virheen sattuessa.

Jos funktiossa luodaan dynaamisesti useita olioita perakkain, ontarkeaa, etta koodissa varaudutaan siihen, etta muisti loppuu, kun vas-ta osa olioista on saatu luoduksi. Jos olioiden luomisen valissa vie-la suoritetaan koodia, jossa voi tapahtua virheita, kannattaa koodiinyleensa kirjoittaa useita sisakkaisia valvontalohkoja. Listaus 11.7 seu-raavalla sivulla sisaltaa esimerkin tallaisesta funktiosta.

1 void siivousfunktio1()2 3 vector<double>* taulup = new vector<double>();45 try6 // Jos taalla sattuu virhe, vektori pitaa tuhota7 keskiarvo2(*taulup);8 9 catch (. . .)

10 // Otetaan kiinni kaikki virheet ja tuhotaan vektori11 delete taulup; taulup = 0;12 throw; // Heitetaan poikkeus edelleen kasiteltavaksi13 1415 delete taulup; taulup = 0; // Tanne paastaan, jos virheita ei satu16

LISTAUS 11.6: Esimerkki dynaamisen olion siivoamisesta

Page 382: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

11.6. Poikkeusmaareet 382

1 void siivousfunktio2()2 3 vector<double>* taulup = new vector<double>();4 try5 6 // Jos taalla sattuu virhe, vektori pitaa tuhota7 keskiarvo2(*taulup);8 vector<double>* taulu2p = new vector<double>();9 try

10 // Jos taalla sattuu virhe, myos uusi vektori pitaa tuhota11 for (unsigned int i = 0; i < taulup->size(); ++i)12 // Lasketaan taulukon neliot13 taulu2p->push back((*taulup)[i] * (*taulup)[i]);14 15 cout << "Nelioiden k.a.=" << laskeKeskiarvo(*taulu2p) << endl;16 17 catch (. . .)18 // Otetaan kiinni kaikki virheet ja tuhotaan vektori19 delete taulu2p; taulu2p = 0;20 throw; // Heitetaan virhe ylemmalle tasolle21 22 delete taulu2p; taulu2p = 0; // Tanne tullaan, jos virheita ei satu23 24 catch (. . .)25 // Otetaan kiinni kaikki virheet ja tuhotaan vektori26 delete taulup; taulup = 0;27 throw; // Heitetaan poikkeus edelleen kasiteltavaksi28 29 delete taulup; taulup = 0; // Tanne paastaan, jos virheita ei satu30

LISTAUS 11.7: Virheisiin varautuminen ja monta dynaamista oliota

11.6 Poikkeusmaareet

Funktioista ulos vuotavat poikkeukset ovat olennainen osa funktiondokumentaatiota. Ilman niita funktion kutsuja ei tieda, mihin kaik-kiin poikkeuksiin tulee varautua. C++ antaa mahdollisuuden merki-ta funktioon, minka tyyppiset poikkeukset saavat vuotaa funktios-ta ulos. Tama tapahtuu poikkeusmaareiden (exception specification)avulla.

Ikava kylla kaytanto on standardoinnin jalkeen osoittanut, et-ta hyvasta tarkoituksesta huolimatta poikkeusmaareet eivat C++:ssaole kovinkaan kayttokelpoinen mekanismi, koska ne suureksi osak-

Page 383: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

11.7. Muistivuotojen valttaminen: auto_ptr 383

si pohjautuvat ajoaikaisiin tarkastuksiin siita, millaisia poikkeuksiafunktioista saa vuotaa ulos. Niinpa nykyisin monet C++-asiantuntijatsuosittelevatkin, ettei poikkeusmaareita kaytettaisi, vaan funktioistamahdollisesti vuotavat poikkeukset dokumentoitaisiin muuten raja-pintadokumentaatioon.

Lisaa tietoa poikkeusmaareista ja syista niiden valttamaiseen loy-tyy esimerkiksi Herb Sutterin artikkeleista “A Pragmatic Look at Ex-ception Specifications — The C++ feature that wasn’t” [Sutter, 2002a]ja “Exception Safety and Exception Specifications: Are They WorthIt?” [Sutter, 2003].

Olio-ohjelmoinnissa on lisaksi useita tilanteita, joissa funktio eitieda, mita kaikkia poikkeuksia voi vuotaa ulos. Tallainen tilanne voisattua erityisesti polymorfismia ja malleja (geneerisyytta) kaytettaes-sa, jolloin funktio ei valttamatta tarkalleen tieda, millaisia olioita sekasittelee, joten se ei myoskaan tieda mahdollisia poikkeuksia. Onkuitenkin vaikeaa ohjelmoida niin, etta varautuu kaikkiin mahdol-lisiin — jopa tuntemattomiin — poikkeuksiin, joten ohjelmoinnissatulisi aina pyrkia dokumentoimaan mahdolliset ulos vuotavat poik-keukset.

Jos funktiosta voi vuotaa ulos jotkin tunnetut poikkeukset ja lisak-si mahdollisesti muitakin, poikkeusmaareita ei voi kayttaa, koska needellyttavat, etta poikkeuslistaan merkitaan kaikki mahdolliset poik-keukset. Tallaisissa tapauksissa kannattaakin vain dokumentoida so-pivaan ohjelmakommenttiin tunnetut poikkeukset ja lisaksi mainita,etta muitakin poikkeuksia saattaa vuotaa ulos.

11.7 Muistivuotojen valttaminen: auto_ptr

Dynaamisen elinkaaren oliot tuottavat poikkeuskasittelyssa paljonongelmia. Niita tarvitaan valttamatta, jos olion elinkaari ei osu yksiinminkaan koodilohkon nakyvyysalueen kanssa. Toisaalta dynaamises-ti luotujen olioiden tuhoaminen kaikissa mahdollisissa virhetilan-teissa lisaa tarvittavan koodin maaraa ja tekee koodista vaikealukui-semman. Taman vuoksi ISOC++:aan lisattiin luokka nimelta auto ptr,joka ratkaisee osan dynaamisten olioiden ongelmista. Tata auto ptr-luokkaa ei ikava kylla aina ole vanhahkojen kaantajaversioiden kir-jastoissa. Sen sijaan uudemmista kaantajista sen pitaisi loytya.

Page 384: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

11.7. Muistivuotojen valttaminen: auto_ptr 384

11.7.1 Automaattiosoittimet ja muistinhallinta

Automaattiosoittimen saa kayttoonsa komennolla#include <memory>. Se kayttaytyy ulkoisesti lahes tasmalleensamoin kuin tavallinen osoitinkin: sen saa alustaa osoittamaan dy-naamisesti luotuun olioon, siihen voi sijoittaa uuden olion, ja senpaassa olevaan olioon paasee kasiksi normaalisti operaattoreilla *ja ->. Erona automaattiosoittimien ja tavallisten osoittimien valillaon, etta automaattiosoitin omistaa paassaan olevan olion. Lisaksiautomaattiosoittimilla ei voi tehda osoitinaritmetiikkaa eika niitavoi kayttaa osoittamaan taulukoihin. Huomaa, etta automaattio-soittimen paahan saa sijoittaa vain dynaamisesti new’lla luotujaolioita!

Omistaminen tarkoittaa sita, etta kun automaattiosoitin tuhou-tuu, se suorittaa automaattisesti delete-operaation paassaan olevalleoliolle. Automaattiosoittimien hyodyllisyys piilee juuri omistamises-sa. Sen ansiosta ohjelmoijan ei tarvitse valittaa dynaamisesti luotujenolioiden tuhoamisesta. Jos ne on alunperin pantu automaattiosoit-timen paahan, olion tuhoamisvastuu siirtyy osoittimelle. Koska itseautomaattiosoittimen elinkaari on staattinen, kaantaja pitaa huolenolion tuhoamisesta.

Listaus 11.8 seuraavalla sivulla sisaltaa esimerkin automaattio-soittimien kaytosta. Siita kay ilmi automaattiosoittimien samankal-taisuus tavallisten osoittimien kanssa ja se, miten automaattiosoit-timet helpottavat virhekasittelya verrattuna listauksen 11.7 koodiin.Koska automaattiosoitin pitaa itse huolen paassaan olevan olion tu-hoamisesta, virhetilanteissa ei tarvitse ryhtya mihinkaan erikoistoi-menpiteisiin — poikkeuksen sattuessa paikallisena muuttujana olevaautomaattiosoitin tuhotaan automaattisesti, ja se puolestaan tuhoaadynaamisesti luodun olion.

Automaattiosoittimesta saa halutessaan ulos tavallisen “ei-omistavan” osoittimen jasenfunktiokutsulla osoitin.get(). Tata voikayttaa, jos ohjelmassa tulee joskus tarve viitata olioon myos taval-lisen osoittimen lapi. Talloin on kuitenkin syyta muistaa, etta olionomistus sailyy automaattiosoittimella, joten olio tuhoutuu edelleenautomaattisesti automaattiosoittimen tuhoutuessa.

Page 385: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

11.7. Muistivuotojen valttaminen: auto_ptr 385

1 #include <memory>2 using std::auto ptr;

...3 void siivousfunktio2()4 5 auto ptr< vector<double> > taulup(new vector<double>());6 keskiarvo2(*taulup);78 auto ptr < vector<double> > taulu2p(new vector<double>());9 for (unsigned int i = 0; i < taulup->size(); ++i)

10 // Lasketaan taulukon neliot11 taulu2p->push back((*taulup)[i] * (*taulup)[i]);12 13 cout << "Nelioiden keskiarvo: " << laskeKeskiarvo(*taulu2p) << endl;14

LISTAUS 11.8: Esimerkki automaattiosoittimen auto_ptr kaytosta

11.7.2 Automaattiosoittimien sijoitus ja kopiointi

Ohjelmoinnissa tulee usein vastaan tilanne, jossa monta osoitintaosoittaa samaan olioon. Automaattiosoittimien tapauksessa tama eikuitenkaan ole mahdollista, koska olion voi omistaa vain yksi au-tomaattiosoitin kerrallaan. Jos automaattiosoittimia voisi osoittaa sa-maan olioon useita, tama olio tulisi tuhotuksi useaan kertaan. Tamanvuoksi automaattiosoittimien kopiointi ja sijoittaminen on maariteltyniin, etta operaatiot siirtavat olion osoittimesta toiseen.

Siirtaminen tarkoittaa sita, etta kopioinnin jalkeen uusi osoitinosoittaa olioon ja omistaa sen kun taas vanha osoitin on “tyhjenty-nyt” eika enaa osoita minnekaan. Vastaavasti sijoituksen yhteydes-sa alkuperainen osoitin menettaa olion eika osoita enaa minnekaankun taas sijoituksen kohteena oleva osoitin tuhoaa vanhan olionsa jasiirtyy omistamaan uutta oliota. Tallainen sijoituksen ja kopioinninsemantiikka, jossa myos alkuperainen osoitin muuttuu, eroaa oleelli-sesti normaalista sijoituksesta ja kopioinnista, joten sen kanssa kan-nattaa olla tarkkana.

Omistuksen siirtyminen on kuitenkin myos erittain hyodyllinenpiirre. Sen avulla funktiossa voi luoda automaattiosoittimen paahandynaamisesti olion ja palauttaa funktiosta auto ptr-tyyppisen paluu-arvon. Talla tavoin olion omistus siirtyy paluuarvon mukana auto-maattisesti funktiosta kutsujan puolelle eika kummankaan tarvitse

Page 386: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

11.7. Muistivuotojen valttaminen: auto_ptr 386

valittaa olion tuhoamisesta edes virhetilanteissa. Listaus 11.9 nayt-taa, kuinka automaattiosoitinta voi kayttaa hyvaksi olion omistuksensiirtamiseen paikasta toiseen.

Olion omistuksen ja sen siirtymisen vuoksi automaattiosoitintavoi kayttaa myos dokumentoimaan omistusta. Ohjelman voi kirjoit-taa niin, etta kaikki dynaamisesti luodut oliot ovat aina jonkin au-tomaattiosoittimen paassa. Jos funktiolle taytyy valittaa tieto oliosta,mutta ei omistusvastuuta, kaytetaan joko viitetta tai tavallista osoi-tinta. Jos taas funktiolle halutaan siirtaa myos omistusvastuu, valite-taan automaattiosoitin. Tallaisessa ohjelmoinnissa dynaamisesti luo-tuja olioita ei tarvitse koskaan tuhota ja ainakin periaatteessa muis-tivuodot ovat mahdottomia. Kaytannossa ohjelmoijan taytyy kuiten-kin varmistua siita, etteivat tavalliset osoittimet tai viitteet jaa viittaa-maan jo tuhottuun olioon ja ettei jo siirrettyyn olioon yriteta paasta

12 typedef auto ptr< vector<double> > AutoTauluPtr;1314 AutoTauluPtr tuotaTaulukko()15 16 AutoTauluPtr taulup(new vector<double>());17 lueLuvutTaulukkoon(*taulup);18 return taulup;19 20 AutoTauluPtr tuotaNeliotaulukko(vector<double> const* taulup)21 // Parempi vaihtoehto: tuotaNeliotaulukko(vector<double> const& taulu)22 AutoTauluPtr neliop(new vector<double>());23 for (unsigned int i = 0; i < taulup->size(); ++i)24 // Lasketaan taulukon neliot25 neliop->push back((*taulup)[i] * (*taulup)[i]);26 27 return neliop;28 2930 void siivousfunktio3()31 32 AutoTauluPtr taulu1p(tuotaTaulukko()); // Taulukon omistus siirtyy tanne33 AutoTauluPtr taulu2p(tuotaNeliotaulukko(taulu1p.get())); // Samoin tassa3435 cout << "Keskiarvo: " << laskeKeskiarvo(*taulu1p) << endl;36 cout << "Nelioiden keskiarvo: " << laskeKeskiarvo(*taulu2p) << endl;37

LISTAUS 11.9: Automaattiosoitin ja omistuksen siirto

Page 387: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

11.7. Muistivuotojen valttaminen: auto_ptr 387

kasiksi vanhan automaattiosoittimen kautta.

11.7.3 Automaattiosoittimien kayton rajoitukset

Aiemmin tassa luvussa on jo tullut esille joitain automaattiosoittimiakoskevia rajoituksia. Seuraavaan luetteloon on keratty tarkeimmat ra-joitukset.

• Automaattiosoittimen paahan saa laittaa vain dynaamisestinew’lla luotuja olioita.

• Vain yksi automaattiosoitin voi osoittaa samaan olioon kerral-laan. Automaattiosoittimien sijoitus ja kopiointi siirtavat olionomistuksen automaattiosoittimelta toiselle.

• Sijoituksen ja kopioinnin jalkeen olioon ei paase kasiksi vanhanautomaattiosoittimen kautta.

• Automaattiosoittimelle ei voi tehda osoitinaritmetiikkaa (++, --,indeksointi ynna muut).

• Automaattiosoittimen paahan ei voi panna tavallisia taulukoita(sen sijaan vector ei tuota ongelmia).

• Automaattiosoittimia ei voi laittaa STL:n tietora-kenteiden sisalle. Tama tarkoittaa, etta esimerkiksivector< auto ptr<int> > ei toimi.

Nama rajoitukset huomioon ottamalla automaattiosoittimet hel-pottavat huomattavasti dynaamisten olioiden hallintaa. Rajoituksis-ta ehka ikavin on se, ettei automaattiosoittimia voi panna suoraanSTL:n tietorakenteisiin. Syy tahan on se, etta STL vaatii etta tieto-rakenteiden alkioita voi kopioida ja sijoittaa normaalisti. Automaat-tiosoittimien omistuksen siirto aiheuttaa sen, etta sijoituksessa ja ko-pioinnissa alkuperainen olio muuttuu, joten automaattiosoittimet ei-vat ole yhteensopivia STL:n kanssa.

Vaihtoehtoja automaattiosoittimille ovat “alykkaat osoittimet”(smart pointer), jotka kayttavat viitelaskureita olion elinkaaren maa-raamiseen. Niiden idea on, etta alykkaat osoittimet pitavat ylla las-kuria siita, kuinka monta osoitinta olioon osoittaa. Siina vaihees-sa, kun viimeinen alykas osoitin tuhoutuu tai lakkaa muuten osoit-tamasta olioon, se tuhoaa olion. Alykkaat osoittimet eivat kuu-lu ISOC++ -kieleen, mutta verkosta saa useita toimivia toteutuksia.

Page 388: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

11.8. Olio-ohjelmointi ja poikkeusturvallisuus 388

Tallaisia ovat esim. Boost-kirjaston shared ptr [Boost, 2003] taiAndrei Alexandrescun Loki-kirjaston uskomattoman monipuolinenSmartPtr [Alexandrescu, 2001] [Alexandrescu, 2003].

11.8 Olio-ohjelmointi ja poikkeusturvallisuus

Tahan mennessa tassa luvussa on kasitelty C++:n poikkeusmekanis-min perusteet. Vaikka itse mekanismi ei olekaan kovin monimutkai-nen, on vikasietoisen ja poikkeuksiin varautuvan ohjelman kirjoitta-minen kuitenkin yleensa erittain monimutkaista ja tarkkuutta vaati-vaa tyota. Syyna tahan on, etta virhetilanteita – ja nain ollen myospoikkeuksia – voi tapahtua lahes missa tahansa kohdassa ohjelmaa.

Olio-ohjelmoinnin kapselointi piilottaa luokkien sisaisen toteu-tuksen, joten luokan kayttaja ei voi nahda, miten luokka on toteutettuja millaisia virheita koodissa voi syntya. Kapselointi tekee myos mah-dolliseksi sen, etta luokan toteutusta muutetaan myohemmin, jolloinuuden toteutuksen reagointi virheisiin voi poiketa aiemmasta. Kai-ken kukkuraksi periytyminen ja polymorfismi aiheuttavat sen, etteiluokkahierarkian kayttaja edes valttamatta tarkasti tieda, minka luo-kan oliota kayttaa (kun olio on kantaluokkaosoittimen paassa).

Kaikki tama tekee entista tarkeammaksi sen, etta kaikki mahdolli-set luokasta tai moduulista ulos vuotavat virhetilanteet ja poikkeuk-set dokumentoidaan rajapinnan dokumentaatiossa. Nain luokan kayt-taja voi varautua kaikkiin tarpeellisiin poikkeustilanteisiin ilman, et-ta han tietaa luokan tai moduulin sisaista toteutusta.

Samoin periytymishierarkiassa on tarkeaa, etta aliluokan uudel-leen maarittelemat virtuaalifunktiot eivat aiheuta sellaisia virhetilan-teita ja poikkeuksia, joita ei ole dokumentoitu jo kantaluokan rajapin-tadokumentaatiossa. Luokan rajapinnasta vuotavat poikkeustilanteetja luokan reagoiminen niihin kuuluvat suoraan periytymisen “aliluo-kan olio on myos kantaluokan olio” -suhteeseen, joten aliluokan tu-lee noudattaa kantaluokan kayttaytymista myos poikkeustilanteissa.Toisaalta tama tarkoittaa myos sita, etta kantaluokka ei saa omassa ra-japintadokumentaatiossaan tarjota liian suuria lupauksia poikkeusti-lanteissa, koska talloin saattaa pahimmassa tapauksessa kayda niin,etta on mahdotonta kirjoittaa aliluokkaa, jonka laajennettu ja muutet-tu toiminnallisuus edelleen pitaisi kaikista kantaluokan lupauksistakiinni.

Page 389: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

11.8. Olio-ohjelmointi ja poikkeusturvallisuus 389

Virhetilanteissa jarkevasti toimivien ja vikasietoisten luokkien kir-joittaminen tulee helpommaksi, jos ensin maaritellaan joukko peli-saantoja, joita olioiden tulee virhetilanteissa noudattaa. Lisaksi olioi-den kayttaytyminen virhetilanteissa voidaan jakaa selkeisiin katego-rioihin, jolloin rajapintadokumentaation kirjoittaminen ja ymmarta-minen tulee helpommaksi. Tassa aliluvussa esitellaan C++:ssa useinkaytettavat poikkeusturvallisuuden tasot seka muutamia yleisia poik-keuksiin ja C++:n luokkiin liittyvia mekanismeja.

11.8.1 Poikkeustakuut

C++:n poikkeuksista ja niiden kaytosta on jo ehtinyt kertya kaytannonkokemusta, vaikka poikkeusmekanismi tulikin kieleen varsin myo-haisessa vaiheessa. Lisaksi vikasietoisuudesta on tietysti paljon tieta-mysta myos ajalta ennen C++:aa. Mistaan loppuunkalutusta aiheesta eikuitenkaan ole kysymys, vaan uusia poikkeuksiin liittyvia mekanis-meja ja koodaustapoja kehitetaan jatkuvasti.

Periaatteessa luokan pitaisi erikseen dokumentoida jokaisesta pal-velustaan, mita virhetilanteita palvelussa voi sattua, ja miten palve-lu reagoi niihin. Tama ei kuitenkaan ole aina jarkevaa, koska talloinrajapintadokumentti saattaa helposti kasvaa niin suureksi, etta senkayttokelpoisuus vaarantuu. Lisaksi luokka ei aina voi edes tarkastimaaritella kaikia mahdollisia virhetilanteita. Tata esiintyy sita enem-man, mita geneerisempi ja yleiskayttoisempi luokka on.

Esimerkiksi C++:n vector ei voi millaan luetella push back-operaa-tiosta ulos vuotavia poikkeuksia. Kyseinen operaatiohan aiheuttaauuden alkion luomisen ja lisaamisen vektoriin, ja vektorilla ei ole mi-taan kasitysta siita, millaisia virhetilanteita uuden alkion luomiseenvoi liittya.

Tallaisten ongelmien ratkaisemiseksi voidaan antaa yksinkertai-nen jaottelu siita, miten luokka voi tyypillisesti virhetilanteisiin suh-tautua. Tassa esitellaan taman jaottelun perusteet, tarkemmin asiaanvoi tutustua esim. kirjoista “Exceptional C++” [Sutter, 2000] ja “MoreExceptional C++” [Sutter, 2002c].

Alla olevan jaottelun poikkeuksiin suhtautumisesta on ilmeisestiensimmaisena julkaissut David Abrahams. Hanen mukaansa luokkavoi tarjota operaatioilleen erilaisia poikkeustakuita (exception guar-antee). [Abrahams, 2003]

Page 390: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

11.8. Olio-ohjelmointi ja poikkeusturvallisuus 390

Minimitakuu (minimal guarantee)

Vahin, mita luokka voi tehda, on taata, etta mikali olion palvelu kes-keytyy virhetilanteen vuoksi (ja poikkeus vuotaa ulos), niin olio eihukkaa resursseja ja on edelleen sellaisessa tilassa, etta sen voi tuho-ta. Tama tarkoittaa, etta virhetilanteenkaan sattuessa olio ei aiheutamuistivuotoja eika muitakaan resurssivuotoja. Olion ei tarvitse sisai-sesti olla “jarkevassa” tilassa (luokkainvariantin ei tarvitse olla voi-massa), mutta sen purkajan tulee pystyy hoitamaan tarvittavat sii-voustoimenpiteet.

Minimitakuu takaa siis vain, etta olion voi tuhota ilman ongelmia.Mahdollisesti lisaksi olion “resetoiminen” sopivalla jasenfunktiollavoi olla mahdollista, tai uuden arvon sijoittaminen olioon.

On varsin selvaa, etta minimitakuuta loyhempaa lupausta ei olekaytannollista antaa. Jos oliota ei voi edes turvallisesti tuhota virheenjalkeen, ja se voi vuotaa muistia ja resursseja, ei olion kayttamista saaturvalliseksi millaan keinoin.

Perustakuu (basic guarantee)

Perustakuu takaa kaiken minka minimitakuukin, mutta lisaksi se ta-kaa, etta olion luokkainvarianttia ei ole rikottu. Olio on siis poik-keuksen jalkeenkin kayttokelpoisessa tilassa, ja sen jasenfunktioitavoi kutsua. On kuitenkin huomattava, ettei perustakuu tarkoita sita,etta olion taytyisi olla ennustettavassa tilassa virhetilanteen jalkeen.Perustakuu takaa vain, etta olio ei ole mennyt rikki poikkeuksen joh-dosta. Olion tila voi olla ennallaan, puolivalissa kohti onnistunuttasuoritusta tai olio voi olla muuttunut johonkin aivan toiseen lailli-seen tilaan.

Jos esimerkiksi vektorin insert-operaatiolla vektoriin lisataanuseita alkioita, ja operaation aikana tapahtuu virhe (esim. alkion ko-pioiminen vuotaa poikkeuksen), ei vektorin alkioista enaa ole var-muutta. Saattaa olla, etta osa alkioista jaa vaaraan paikkaan vekto-rissa tai jotkin alkiot saattavat jopa olla vektorissa kahteen kertaantai puuttua kokonaan. Siita huolimatta tiedetaan, etta vektorin voiedelleen tuhota normaalisti, sen voi tyhjentaa, ja sen alkiot voi edel-leen kayda lapi, vaikka alkioiden arvoista ei olekaan varmuutta. C++:nstandardikirjaston luokat antavat perustakuun lahes kaikista operaa-

Page 391: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

11.8. Olio-ohjelmointi ja poikkeusturvallisuus 391

tioistaan. Joistain operaatioista luvataan lisaksi viela enemman kuinperustakuu vaatii.

Vahva takuu (strong guarantee)

Vahva takuu takaa, etta kun luokan oliolle suoritetaan jokin operaa-tio, niin operaatio saadaan joko suoritettua loppuun ilman virheita,tai poikkeuksen sattuessa olion tila pysyy alkuperaisena. Tama tar-koittaa sita, etta jos operaatiossa tapahtuu virhe, niin poikkeuksenvuotamisen jalkeen olio on tasmalleen samassa tilassa kuin ennen ko-ko operaatiotakin. Tasta kaytetaan myos usein englanninkielista ter-mia “commit or rollback” – operaatio saa joko toimintansa loppuuntai “kierahtaa takaisin” tilaansa ennen operaatiota.

Vahvan takuun antavat operaatiot ovat luokan kayttajan kannal-ta varsin helppoja, koska virheen sattuessa olion tila on sailynyt en-nallaan. Sen sijaan luokan toteuttajalle vahva takuu tuottaa yleen-sa jonkin verran lisavaivaa. Vahvan takuun voi toteuttaa esimerkiksiniin, etta operaation yhteydessa olion tilan tarvittavat osat kopioi-daan muualle, ja itse operaatio suoritetaankin talle kopiolle. Jos ope-raatio onnistui, voidaan lopputulos sitten siirtaa takaisin olioon. Vir-heen sattuessa varsinaista oliota ei taas olekaan viela muutettu, jotenoperaatio voi yksinkertaisesti tuhota tyokopion ja heittaa poikkeuk-sen. Aliluvussa 11.9 on esimerkki vahvan takuu tarjoavasta sijoitus-operaattorista.

Vahvan takuun toteuttaminen saattaa usein vaatia luokan koodaa-mista siten, etta se kuluttaa hieman enemman muistia ja toimii hie-man hitaammin kuin ilman takuuta. Sen vuoksi vahva takuu ei ole-kaan mikaan “ihanne”, johon tulisi aina pyrkia, mutta joissain tilan-teissa se tekee luokan kayttajan elaman paljon helpommaksi. Aivankaikkia operaatioita ei lisaksi edes voi kirjoittaa niin, etta ne tarjoai-sivat vahvan takuun.

C++:n standardikirjasto pyrkii tarjoamaan vahvan poikkeustakuunsellaisille operaatioille, joissa takuu on mahdollista toteuttaa jarke-valla vaivalla ja joissa vahvasta takuusta on kayttajalle eniten hyotya.Esimerkiksi kaikkien STL:n sailioiden push back-operaatiot antavatvahvan takuun – jos alkion lisaaminen epaonnistuu, sisaltaa sailiopoikkeuksen vuotaessa samat alkiot kuin ennen operaatiota.

Page 392: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

11.8. Olio-ohjelmointi ja poikkeusturvallisuus 392

Nothrow-takuu (nothrow guarantee)

Kaikkein vahvin poikkeustakuu “nothrow” on varsin yksinkertainen.Se takaa, etta operaation suorituksessa ei voi sattua virheita. Mitaanpoikkeuksia ei siis voi vuotaa operaatiosta ulos, ja operaatio onnistuuaina. Nothrow-takuu on kayttajan kannalta ideaalinen, koska virhei-siin ei tarvitse varautua lainkaan. Sen sijaan on tietysti selvaa, ettasuuri osa operaatioista ei millaan voi tarjota nothrow-takuuta, koskaniihin sisaltyy aina jokin virhemahdollisuus.

Nothrow-takuu on kuitenkin hyodyllinen virheturvallisen ohjel-moinnin kannalta. Joissain tapauksissa nimittain ohjelmaa ei voi kir-joittaa virheturvalliseksi, elleivat jotkin tietyt operaatiot tarjoa no-throw-takuuta. Esimerkiksi vahvan takuun tarjoaminen vaatii, et-ta onnistuneen operaation lopussa tyokopion kopioiminen takai-sin itse olioon ei voi epaonnistua – kopioimisen taytyy siis tarjotanothrow-takuu. Samoin aliluvussa 11.7 kasitelty automaattiosoitinauto ptr tarjoaa nothrow-takuun suurimmalle osalle operaatioistaan.Lisaksi nothrow-takuun antavat STL:n sailioiden erase, pop back japop front.

C++:n poikkeusmekanismin kannalta nothrow-takuu vastaa poik-keusmaareen throw() kayttoa. Kyseisen poikkeusmaareen kayttami-nen ei kuitenkaan ole mitenkaan valttamatonta, vaan nothrow-ta-kuun voi tarjota myos perinteisesti dokumentoimalla.

Poikkeusneutraalius (exception neutrality)

Poikkeusneutraalius ei ole vaihtoehtoinen poikkeustakuu perusta-kuun, vahvan takuun ja nothrow-takuun rinnalla, mutta se liittyy kui-tenkin olennaisesti samaan aiheeseen “toisella akselilla”. Poikkeus-neutraaliutta on, etta yleiskayttoisen luokan operaatiot vuotavat sisal-laan olevien komponenttien poikkeukset ulos muuttumattomina. Ta-ma tarkoittaa lahinna sita, etta luokka ei itse muuta poikkeuksia jon-kin toisen tyyppisiksi, vaan paastaa alkuperaisen poikkeuksen kaytta-jalle saakka. Poikkeusneutraaliuden lisaksi luokan pitaisi tietysti tar-jota myos jokin muista poikkeustakuista, lahinna joko perustakuu taivahva takuu. Siten luokka voi tietysti ottaa poikkeuksen valiaikaises-ti kiinni ja reagoida siihen (esim. toteuttaakseen vahvan poikkeusta-kuun).

Page 393: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

11.8. Olio-ohjelmointi ja poikkeusturvallisuus 393

Hyva esimerkki poikkeusneutraaliudesta on vector. Vekto-rin poikkeusneutraalius tarkoittaa, etta jos esimerkiksi vektorinpush back-operaation yhteydessa lisattavan alkion kopioiminen ai-heuttaa poikkeuksen (eli alkiotyypin kopiorakentaja vuotaa poik-keuksen), niin vektori tarvittavan siivouskoodin suorittamisen jal-keen vuotaa taman saman poikkeuksen edelleen ulos kayttajalleen.(Siis kaytannossa tama tapahtuu suorittamalla siivouskoodin lopussakomento throw;.)

Poikkeusneutraalius on vektorin tapaisten yleiskayttoisten luok-kamallien tapauksessa varsin toivottavaa. Vektorin koodihan ei voitietaa mitaan vektorin alkioiden kayttaytymisesta ja niissa mahdolli-sesti sattuvista virhetilanteista, koska vektorin koodi on taysin alkio-tyypista riippumatonta. Sen sijaan vektorin kayttajalla on tavallisestitarkka tieto siita, miten vektorin alkioissa tapahtuviin virheisiin tuli-si reagoida. Taman vuoksi on tarkeaa, etta vektori valittaa alkioidenpoikkeukset kayttajalleen saakka, jotta poikkeus saadaan kasiteltyaasianmukaisesti.

11.8.2 Poikkeukset ja rakentajat

Olion luominen on sen elinkaaren kannalta erikoinen tapahtuma,koska luomisen onnistumisesta riippuu, onko koko olio olemassa vaiei. Taman vuoksi luomiseen liittyy poikkeuksien ja virhetilanteidenkannalta joitain hieman erikoisia piirteita, jotka on syyta kayda lapi.

Olion luomisen vaiheet

Yksi oleellinen kysymys on, milloin olio varsinaisesti syntyy, eli mil-loin alustustoimet ovat niin pitkalla, etta voidaan puhua jo “uudestaoliosta”. C++:n oliomalli maarittelee taman niin, etta olion katsotaanlopullisesti syntyneen, kun kaikki olion luomiseen kuuluvat rakenta-jat on suoritettu onnistuneesti loppuun. Tahan kuuluvat niin kanta-luokkien rakentajat kuin myos olion jasenmuuttujien rakentajat, josjasenmuuttujat ovat olioita.

Oleelliseksi tama “syntymishetki” tulee silloin, kun olion luomi-sen aikana tapahtuu virhe. Jos nimittain olion luomisen jossain osa-vaiheessa tapahtuu poikkeus, jonka kyseinen osa paastaa vuotamaanulos, ei oliota ole saatu luotua onnistuneesti. Osa siita on kuitenkin

Page 394: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

11.8. Olio-ohjelmointi ja poikkeusturvallisuus 394

todennakoisesti saatu luotua loppuun saakka, kuten esimerkiksi jot-kin jasenmuuttujat ja kenties osa olion kantaluokkaosista.

Jos virhe on kuitenkin tapahtunut ennen kuin kaikki olioon liitty-vat rakentajat on saatu suoritettua, ei oliota ole C++:n kannalta saatuluoduksi. Talloin C++ pitaa automaattisesti huolen siita, etta kaikki neolion osat, jotka on jo ehditty luoda, tuhotaan automaattisesti osanapoikkeuskasittelya. Tahan kuuluu niin tarvittavien purkajien kutsu-minen kuin muistin vapauttaminenkin. Poikkeuksen tekevat tuttuuntapaan dynaamisesti luodut oliot, joita ei tassakaan tapauksessa tuho-ta automaattisesti. Lopputulos olion luomisen kannalta joka tapauk-sessa on, etta olio ei koskaan ehtinyt syntya.

Listaus 11.10 nayttaa esimerkin oliosta, jossa on jasenmuuttujinauseita toisia olioita. Jos nyt henkilooliota luotaessa syntymapaivanluominen onnistuu, mutta nimen luomisessa tapahtuu virhe (esim.muisti loppuu), niin henkiloolion luominen keskeytetaan. Lisaksikoska syntymapaiva on jo saatu luotua, niin se tuhotaan automaat-tisesti.

Dynaamisesti luodut osaoliot

Koska jasenmuuttujien ja muiden osaolioiden tuhoaminen poikkeuk-sen sattuessa tapahtuu automaattisesti, ei siita yleensa aiheudu vai-vaa ohjelmoijalle. Sen sijaan dynaamisesti luotujen olioiden kanssatulee olla erittain huolellinen. Niita ei tuhota automaattisesti, jotenon erittain tarkeaa, etta olioita ei luoda dynaamisesti rakentajan alus-

1 class Henkilo2 3 public:4 Henkilo(int p, int k, int v, std::string const& nimi,5 std::string const& hetu);6 ~Henkilo();

...7 private:8 Paivays syntymapvm ;9 std::string nimi ;

10 std::string* hetup ;11 ;

LISTAUS 11.10: Esimerkki luokasta, jossa on useita osaolioita

Page 395: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

11.8. Olio-ohjelmointi ja poikkeusturvallisuus 395

tuslistassa. Jos nain nimittain tehdaan, dynaamisesti luodut oliot jaa-vat tuhoamatta, jos jonkin niiden jalkeen alustettavan jasenmuuttu-jan luominen epaonnistuu. Tallaiseen virheeseen ei voi reagoida edesrakentajan rungon koodissa olevalla virhekasittelijalla, koska raken-tajan koodiin siirrytaan vasta, kun kaikki jasenmuuttujat on onnistu-neesti luotu. Jasenmuuttujan luomisvirheessa rakentajan runkoon eisiis paasta ollenkaan.

Taman vuoksi kannattaa noudattaa periaatetta, jossa jasenmuut-tujaosoittimet alustetaan rakentajan alustuslistassa nolliksi ja oliotluodaan dynaamisesti niiden paahan vasta rakentajan rungossa. Naindynaamisesti luotavat oliot luodaan vasta, kun kaikki normaalit ja-senmuuttujat on saatu onnistuneesti luotua. Jos rakentajan rungos-sa luodaan dynaamisesti useita olioita, taytyy koodissa olla tietys-ti tarvittava virheenkasittely, joka varmistaa etta virheen sattuessajo luodut oliot tuhotaan. Listaus 11.11 nayttaa esimerkkina listauk-sen 11.10 rakentajan toteutuksen.

Automaattiosoittimia kaytettaessa tata ongelmaa ei paase synty-maan, koska virheenkin sattuessa automaattiosoittimen purkaja tu-hoaa dynaamisesti luodun olion. Taman vuoksi automaattiosoittimenpaahan voi alustaa dynaamisesti luodun olion jo alustuslistassa.

1 Henkilo::Henkilo(int p, int k, int v, std::string const& nimi,2 std::string const& hetu)4 : syntymapvm (p, k, v), nimi (nimi), hetup (0)5 6 try7 8 hetup = new std::string(hetu);9

10 catch (. . .)11 // Tanne paastaan, jos hetun luominen epaonnistuu12 // Siivotaan tarvittaessa, olion luominen epaonnistui13 throw; // Heitetaan virhe edelleen kasiteltavaksi14 15

LISTAUS 11.11: Olioiden dynaaminen luominen rakentajassa

Page 396: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

11.8. Olio-ohjelmointi ja poikkeusturvallisuus 396

Luomisvirheisiin reagoiminen

Jos olion luomisen yhteydessa tapahtuu virhe esimerkiksi jasenmuut-tujaa luotaessa, ei luotava olio voi millaan toipua tasta virheesta, vaankoko olion luominen epaonnistuu. Talloin jo luotujen osaolioidenpurkajia kutsutaan ja poikkeus vuotaa koodiin, jossa olio luotiin. Josluokassa kuitenkin on tarve virheen sattuessa yrittaa toipua virhees-ta ja kenties yrittaa epaonnistuneen osaolion luomista uudelleen, ontahan yksi keino. Sellaiset osaoliot, joiden luominen voi epaonnis-tua, voi nimittain luoda tavallisen jasenmuuttujan sijaan dynaami-sesti osoittimen paahan. Talloin osaolion luomisen new’lla voi tehdarakentajan rungossa, jonka virheenkasittelykoodi sitten yrittaa luo-mista tarvittaessa uudelleen.

Vaikka tavallisten jasemuuttujien ja kantaluokkaosien luomisvir-heista ei voikaan toipua, saattaa luokalla olla kuitenkin tarve reagoidatallaisiin virheisiin. Kenties luokan tulee kirjoittaa tieto epaonnistu-misesta virhelokiin tai antaa kayttajalle virheilmoitus. Joskus osao-lion luomisesta aiheutunut poikkeus voi myos olla vaaraa tyyppia, jaluokka haluaisi itse vuotaa ulos toisentyyppisen poikkeuksen.

Standardoinnin myota C++:aan lisattiin naita tarpeita varten erityi-nen funktion valvontalohko (function try block). Sita voi kayttaa ra-kentajissa (ja purkajissa), ja sen virhekasittelijat ottavat kiinni poik-keukset, jotka tapahtuvat jasenmuuttujien tai kantaluokkaosien luo-misen (purkajan tapauksessa tuhoamisen) yhteydessa. Listaus 11.12seuraavalla sivulla nayttaa taman valvontalohkon syntaksin. Avain-sana try tulee jo ennen rakentajan alustuslistaa, eika sen jalkeen tuleaaltosulkuja. Valvontalohko kattaa automaattisesti virheet, jotka syn-tyvat osaolioita luotaessa tai itse rakentajan rungossa. Valvontaloh-koon liittyvat virhekasittelijat tulevat aivan rakentajan loppuun senrungon jalkeen.

Funktion valvontalohkosta on huomattava, etta sen virhekasitteli-joihin paastaessa olion ennen virhetta luodut jasenmuuttujat ja kan-taluokkaosat on jo tuhottu. Virhekasittelijoissa ei siis enaa voi viita-ta jasenmuuttujiin tai kantaluokan palveluihin eika nain ollen voisuorittaa mitaan varsinaisia siivousoperaatioita (kuten dynaamisenmuistin vapauttamista). Samoin virhekasittelija ei voi kasitella vir-hetta loppuun ja nain toipua siita, vaan jokaisen virhekasittelijan onlopuksi joko heitettava sama virhe uudelleen (komennolla throw;) taisitten heitettava kokonaan uusi erityyppinen virhe. Jos virhekasitteli-

Page 397: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

11.8. Olio-ohjelmointi ja poikkeusturvallisuus 397

1 Henkilo::Henkilo(int p, int k, int v, std::string const& nimi,2 std::string const& hetu)3 try // Huomaa try-sanan paikka!4 : syntymapvm (p, k, v), nimi (nimi), hetup (0)5

...15 16 catch (. . .)17 // Tanne paastaan, jos jasenmuuttujien (tai kantaluokan)18 // luominen epaonnistuu. Tehdaan tarvittaessa toimenpiteita.19 throw; // Tai heitetaan jokin toinen poikkeus20

LISTAUS 11.12: Funktion valvontalohko rakentajassa

ja ei tee naista kumpaakaan, heitetaan alkuperainen virhe automaat-tisesti uudelleen.

11.8.3 Poikkeukset ja purkajat

Kuten aliluvussa 11.5 todettiin, poikkeuksen sattuessa kutsutaan au-tomaattisesti kaikkien sellaisten olioiden purkajia, joiden elinkaariloppuu virhekasittelijan etsimisen yhteydessa. Nain olion purkajaasaatetaan kutsua sellaisessa tilanteessa, jossa vireilla on jo yksi poik-keustilanne. Jos purkaja viela vuotaa ulos toisen poikkeuksen, ei C++:npoikkeusmekanismi pysty selviytymaan tilanteesta, vaan ohjelmansuoritus keskeytetaan kutsumalla funktiota terminate.

Taman vuoksi on erittain tarkeaa, etta olioiden purkajista ei vuo-da poikkeuksia ulos. Yleensa tama ei ole ongelma, koska suurin osasiivoustoimenpiteista — kuten esimerkiksi muistin vapauttaminen —on luonteeltaan sellaisia, etta ne eivat voi epaonnistua. Mikali purka-jissa joudutaan kuitenkin tekemaan toimenpiteita, jotka voivat epaon-nistua, niissa mahdollisesti tapahtuvat poikkeukset tulisi ottaa kiinnija kasitella jo purkajassa itsessaan.

Toinen mahdollisuus on kirjoittaa luokalle erillinen siivoa-jasen-funktio, joka suorittaa kaikki sellaiset siivoustoimenpiteet, jotka voi-vat epaonnistua. Luokan kayttajien tulee sitten kutsua tata jasenfunk-tiota aina ennen olion tuhoamista, jolloin mahdolliset virheet voi-daan ottaa kiinni. Tallainen erillinen siivousfunktio on kuitenkinkompelo, eika se edelleenkaan ratkaise kysymysta siita, mita pitaisi

Page 398: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

11.8. Olio-ohjelmointi ja poikkeusturvallisuus 398

tehda, jos yhden virhetilanteen jo vireilla ollessa kutsutaan siivous-funktiota ja havaitaan toinen virhe. Mainittakoon, etta Java-kielessatallaiset erilliset siivousfunktiot ovat C++:aa tavallisempia, koska kie-lessa ei ole C++:n purkajaa vastaavaa mekanismia.

On huomattava, etteivat edella olleet asiat tarkoita sita, etteikoluokan purkajassa saisi sattua poikkeusta. Jos nain tapahtuu, taytyypurkajan vain itse kyeta sieppaamaan syntynyt poikkeus ja toipu-maan siita. Oleellista on, ettei poikkeus vuoda purkajasta ulos. Pe-riaatteessa siis kaikki purkajat tulisi kirjoittaa niin, etta niille voisiantaa poikkeusmaareen throw(). Se, kirjoitetaanko tuo poikkeusmaa-re todella nakyville purkajaan, on sitten makuasia.

Kun oliota tuhotaan, myos sen kaikkien jasenmuuttujien ja kanta-luokkaosien purkajia kutsutaan. Periaatteessa C++ antaa mahdollisuu-den ottaa kiinni naissa osaolioiden purkajissa sattuvat poikkeuksetitse “emo-olion” purkajassa. Tahan tarkoitukseen purkajaan voi kir-joittaa samanlaisen funktion valvontalohkon kuin rakentajaan alilu-vussa 11.8.2 sivulla 396. Talloin taman valvontalohkon virhekasitte-lijaan siirrytaan, jos itse purkajasta tai jonkin jasenmuuttujan tai kan-taluokkaosan purkajasta vuotaa poikkeus ulos. Talle mekanismille eikuitenkaan ole kaytannossa mitaan kayttoa, koska purkajista ei kos-kaan tulisi vuotaa poikkeuksia, joten normaali purkajan sisalla olevavirhekasittely on kaytannossa aina riittava.

Samoin C++ tarjoaa funktion uncaught exception, joka palauttaa ar-von true, jos ohjelmassa on vireilla jokin poikkeus. Joskus tata nakeekaytettavan siihen, etta purkaja vuotaa poikkeuksen vain, jos vireillaei ole toista poikkeusta. Tamakaan tapa ei ole suotava. Se ei nimit-tain vastaa siihen kysymykseen, mita tehdaan jos vireilla tosiaan ontoinen virhetilanne. Purkajassa tapahtuneen toisen virheen huomiot-ta jattaminen tuskin on kovin turvallista, eika ole kovin jarkevaa, et-ta purkaja toimii muutenkaan kahdella eri tavalla riippuen siita, on-ko muualla havaittu virheita. Yleinen mielipide onkin nykyisin, ettauncaught exception-funktio on C++:ssa kayttokelvoton, eika funktionvalvontalohkojakaan pitaisi kayttaa muualla kuin korkeintaan raken-tajissa. [Sutter, 2002c]

Page 399: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

11.9. Esimerkki: poikkeusturvallinen sijoitus 399

11.9 Esimerkki: poikkeusturvallinen sijoitus

Esimerkkina poikkeusturvallisuuden huomioon ottamisesta kasitel-laan seuraavaksi listauksessa 11.13 esitetyn luokan Kirja sijoitusope-raattorin kirjoittamista. Listaus nayttaa myos yhden mahdollisen si-joitusoperaattorin toteutuksen. Tama sijoitusoperaattori on kirjoitet-tu aliluvun 7.2 ohjeiden mukaan, joten enaa taytyy miettia sen poik-keusturvallisuutta.

11.9.1 Ensimmainen versio

Ensimmainen vaihe on miettia, millaisia virhemahdollisuuksia sijoi-tuksessa voi sattua ja mita niista seuraa. Viitteiden ja osoittimien ka-sittelyissa ei virheita voi sattua (ne antavat nothrow-takuun). Sen si-jaan merkkijonojen sijoituksessa voi sattua virhe. Nain jaljelle jaavatseuraavat mahdolliset tapahtumasarjat:

1. Mitaan virheita ei satu, sijoitus saadaan suoritettua onnistu-neesti. Tama on tietysti toivottava lopputulos.

1 class Kirja2 3 public:

...4 Kirja& operator =(Kirja const& kirja);5 private:6 std::string nimi ;7 std::string tekija ;8 ;

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .1 Kirja& Kirja::operator =(Kirja const& kirja)2 3 if (this != &kirja)4 5 nimi = kirja.nimi ;6 tekija = kirja.tekija ;7 8 return *this;9

LISTAUS 11.13: Yksinkertainen luokka, jolla on sijoitusoperaattori

Page 400: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

11.9. Esimerkki: poikkeusturvallinen sijoitus 400

2. On mahdollista, etta heti ensimmaista merkkijonoa nimi sijoi-tettaessa tulee virhe. Talloin sijoitus keskeytyy ja poikkeus vuo-taa ulos sijoitusoperaattorista, jolloin toista merkkijonoa ei eh-dita kasitella lainkaan.

3. Viimeinen mahdollisuus on, etta ensimmainen sijoitus onnis-tuu, mutta toisessa tapahtuu virhe ja poikkeus vuotaa ulos si-joituksesta.

Vaihtoehto 1 ei luonnollisesti vaadi miettimista poikkeusturval-lisuuden kannalta. Sen sijaan tilanne vaihtoehdossa 2 riippuu sii-ta, millaisen poikkeustakuun string antaa omalle sijoitukselleen. C++-standardista (ja esim. teoksesta “The C++ Standard Library” [Josuttis,1999]) kay ilmi, etta suurin osa C++:n vakiokirjaston luokista, stringmukaanlukien, tarjoaa kayttajalleen perustakuun. Tama tarkoittaasiis sita, etta virheen jalkeen olio on jossain kayttokelpoisessa, mutteivalttamatta ennustettavassa tilassa. Kaytannossa tama tarkoittaa, ettavirheen sattuessa string sisaltaa jonkin jarkevan merkkijonoarvon,mutta meilla ei ole tietoa siita, mika se on. Olion voi kuitenkin esim.tuhota onnistuneesti tai siihen voi yrittaa sijoittaa uuden arvon.

Kirja-luokan kannalta tama tarkoittaa, etta vaihtoehdon 2 jalkeenkirjaolio itse on myos jossain kayttokelpoisessa muttei ennustettavas-sa tilassa. Vaikka kirjan tekija on sailynytkin ennallaan, ei kirjan ni-mesta voida sanoa mitaan! Jos sen sijaan string olisi tarjonnut vah-van poikkeustakuun ja luvannut, etta virheen sattuessa sen arvo sai-lyy muuttumattomana, olisi tilanne ollut toinen. Silloin voitaisiin ol-la varmoja, etta vaihtoehdon 2 sattuessa myos kirjaolion sisalto onsailynyt ennallaan, koska epaonnistuneen merkkijonosijoituksen li-saksi ei ehditty tehda mitaan muuta.

Vaihtoehto 3 on hieman ongelmallisempi. Siina kirjan nimen si-joittaminen onnistuu, mutta tekijan sijoituksessa tulee virhe. Koskastring tarjoaa kayttajalleen perustakuun, on tuloksena tilanne, jossakirjan nimi on muutettu, mutta merkkijono tekija on jossain kayt-tokelpoisessa mutta ei ennustettavassa tilassa. Kirjan sijoitus on siisosaksi tapahtunut, osaksi epaonnistunut.

Tassa tapauksessa tilannetta ei muuttuisi, vaikka string olisikintarjonnut vahvan poikkeustakuun. Lopputuloksena olisi talloin kir-ja, jonka nimi olisi muutettu mutta tekija olisi alkuperainen. Kirjantila ei siis olisi alkuperainen eika myoskaan haluttu lopullinen, jo-

Page 401: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

11.9. Esimerkki: poikkeusturvallinen sijoitus 401

ten poikkeusturvallisuuden kannalta tassakin tapauksessa kirjan tilaolisi “kayttokelpoinen muttei jarkeva”.

Kun kaikki vaihtoehdot otetaan huomioon, saadaan tulokseksi, et-ta luokan Kirja sijoitus voi tarjota kayttajalleen perustakuun. Pahinmahdollinen tilanne on, etta virheen sattuessa kirjan tila on tuntema-ton, mutta kirja on kylla muuten kayttokelpoinen, ja sen voi esimer-kiksi tuhota tai siihen voi yrittaa sijoittaa uudelleen.

Kuten tasta esimerkista nakyy, kaikkien mahdollisten virhetilan-teiden analysoiminen on varsin mutkikasta jo nainkin yksinkertai-sessa luokassa. Tilanne muuttuu tietysti helposti erittain hankalaksi,kun luokan rakenne monimutkaistuu.

11.9.2 Tavoitteena vahva takuu

Kuten esimerkki nayttaa, perustakuun tarjoaminen on yleensa koh-talaisen helppoa, jos kaytossa on muita perustakuun tarjoavia ope-raatioita. Perustakuu ei kuitenkaan ole kayttajan kannalta paras mah-dollinen, koska operaation epaonnistuessa ollaan hukattu olion alku-perainen tila, mika tekee esimerkiksi virheesta toipumisen vaikeak-si. Taman vuoksi onkin hyva miettia, miten Kirja-luokan sijoituksensaisi tarjoamaan vahvan poikkeustakuun — virheen sattuessa kirjantila olisi sama kuin ennen koko sijoitusta.

Ensin kannattaa miettia, auttaisiko tilannetta, jos C++ tarjoaisistring-luokan, joka tarjoaisi vahvan poikkeustakuun sijoitukselle.Talloin askeisen listan vaihtoehdot 1 ja 2 olisivat vahvan takuun mu-kaisia. Nimen sijoituksen epaonnistuminen sailyttaa nimen alkupe-raisena, joten joko kirjan sijoitus onnistuu tai sitten se epaonnistuu jakirjan tila sailyy alkuperaisena. Sen sijaan vaihtoehto 3 tuottaa edel-leen ongelmia. Sen tapahtuessa kirjan nimi on saatu sijoitettua, muttatekijan arvo jaa ennalleen sijoituksen epaonnistuttua.

Kirjan tila tassa tapauksessa olisi “kayttokelpoinen mutta ei toi-vottu”, mika ei poikkeusturvallisuuden kannalta eroa olennaises-ti perustakuun lupauksesta “kayttokelpoinen muttei ennustettava”.Kummassakin tapauksessa kirja on menettanyt alkuperaisen arvonsa,mutta ei ole saanut haluttua uutta arvoa. Siis kirja voisi talla sijoi-tusoperaattorilla tarjota vain perustakuun, vaikka string tarjoaisikinvahvan takuun.

Listaus 11.14 seuraavalla sivulla nayttaa seuraavan version sijoi-tusoperaattorista. Siina yritetaan kiertaa askeinen ongelma ottamal-

Page 402: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

11.9. Esimerkki: poikkeusturvallinen sijoitus 402

la talteen kirjan alkuperainen nimi ja tekija. Jos jumpikumpi sijoitusepaonnistuu, palautetaan nimi ja tekija ennalleen. Tassa tapauksessavirhemahdollisuuksia tulee heti kaksi lisaa, koska uusien merkkijo-nomuuttujien luominen voi epaonnistua. Naissa tapauksissa kirjantilaan ei kuitenkaan ole ehditty koskea, joten nama virheet eivat oleristiriidassa vahvan takuun kanssa.

Ikava kylla, tama yritelma ei toimi. Mikaan ei nimittain takaa, ettavirheen jalkeen alkuperaisten arvojen palauttaminen onnistuu ilmanvirheita! Arvojen palauttaminen virhekasittelijassa riveilla 14–15 onsamanlainen merkkijonojen sijoitus kuin muutkin, ja se voi epaon-nistua, jolloin alkuperaisia arvoja ei saadakaan palautettua niin kuinpiti.

Lopputuloksena on, etta Kirja-luokan sijoitus voi edelleen tarjo-ta vain perustakuun, koska virheen jalkeen on mahdollista, etta kir-jan tila on kayttokelpoinen muttei ennustettava. Tallainen tilanne onkaytannossa vaistamaton, jos operaatio on suoritettava useassa osas-sa, ja virhe voi syntya niin, etta vain jotkin osista saadaan suoritettua.

1 Kirja& Kirja::operator =(Kirja const& kirja)2 3 if (this != &kirja)4 5 std::string vanhanimi(nimi );6 std::string vanhatekija(tekija );7 try8 9 nimi = kirja.nimi ;

10 tekija = kirja.tekija ;11 12 catch (. . .)13 14 nimi = vanhanimi;15 tekija = vanhatekija;16 throw;17 18 19 return *this;20

LISTAUS 11.14: Sijoitus, joka pyrkii tarjoamaan vahvan takuun (eitoimi)

Page 403: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

11.9. Esimerkki: poikkeusturvallinen sijoitus 403

11.9.3 Lisataan epasuoruutta

Ohjelmoinnin vanha sananlasku sanoo, etta lahes minka tahansaongelman voi ratkaista lisaamalla ohjelmaan epasuoruutta (yleensaosoittimia). Tama viisaus patee tassakin tapauksessa. Edellisen yri-tyksen ongelmaksi muodostui, etta sijoituksen epaonnistuessa van-hoja arvoja ei saatu palautettua.

Yksi ratkaisukeino on havaita, etta vaikka merkkijonon sijoitta-minen voi epaonnistua, niin merkkijono-osoittimen sijoittaminen on-nistuu aina. Kirjan nimi ja tekija laitetaankin dynaamisesti luotuinaosoittimien paahan. Talloin sijoituksessa uudesta nimesta ja tekijastavoidaan ensin luoda dynaamisesti erilliset kopiot. Jos kopioinnissa eitapahdu virheita, voidaan vanha nimi ja tekija korvata uusilla ja tu-hota vanhat ilman, etta virheita voi enaa sattua. Listaus 11.15 nayttaatallaisen luokan ja listaus 11.16 seuraavalla sivulla sen toteutuksen.

Tama toteutus tarjoaa vihdoin kayttajalleen vahvan poikkeusta-kuun. Kirjan sijoitus joko onnistuu tai sitten tapahtuu virhe ja kirjasailyy alkuperaisessa tilassa. Tasta on varsin helppo varmistua, koskakoodissa kirjan varsinaiseen tilaan kosketaan vasta, kun kaikki mah-dolliset virhepaikat on jo ohitettu. Tama on mahdollista, koska osoit-timien sijoitus ja merkkijonon tuhoaminen eivat voi aiheuttaa poik-keuksia, joten kirjan vanhan tilan korvaaminen uudella ei voi keskey-tya, vaan se saadaan aina suoritettua onnistuneesti loppuun saakka.Samasta syysta sijoitusoperaattorissa normaalisti valttamaton itseensijoituksen testaus ei ole enaa valttamaton, koska olion vanha arvo

1 class Kirja2 3 public:4 Kirja(std::string const& nimi, std::string const& tekija);5 // Tarvitaan myos oma kopiorakentaja (dynaaminen muistinhallinta)!6 ~Kirja();

...7 Kirja& operator =(Kirja const& kirja);8 private:9 std::string* nimip ;

10 std::string* tekijap ;11 ;

LISTAUS 11.15: Kirjaluokka epasuoruuksilla

Page 404: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

11.9. Esimerkki: poikkeusturvallinen sijoitus 404

1 Kirja::Kirja(std::string const& nimi, std::string const& tekija)2 : nimip (0), tekijap (0)3 4 try5 6 nimip = new std::string(nimi);7 tekijap = new std::string(tekija);8 9 catch (. . .)

10 11 delete nimip ; nimip = 0;12 delete tekijap ; tekijap = 0;13 throw;14 15 1617 Kirja::~Kirja()18 19 delete nimip ; nimip = 0;20 delete tekijap ; tekijap = 0;21 2223 Kirja& Kirja::operator =(Kirja const& kirja)24 25 if (this != &kirja) // Periaatteessa tarpeeton!26 27 std::string* uusinimip = 0;28 std::string* uusitekijap = 0;29 try30 31 uusinimip = new std::string(*kirja.nimip );32 uusitekijap = new std::string(*kirja.tekijap );33 // Jos paastiin tanne, ei virheita tullut34 delete nimip ; nimip = uusinimip; // Onnistuvat aina35 delete tekijap ; tekijap = uusitekijap; // Samoin nama36 37 catch (. . .)38 39 delete uusinimip; uusinimip = 0;40 delete uusitekijap; uusitekijap = 0;41 throw;42 43 44 return *this;45

LISTAUS 11.16: Uuden kirjaluokan rakentaja, purkaja ja sijoitus

Page 405: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

11.9. Esimerkki: poikkeusturvallinen sijoitus 405

tuhotaan vasta uuden arvon luomisen jalkeen. Tehokkuusmielessa it-seen sijoittamisen testaus saattaa silti olla paikallaan.

Esimerkista huomaa selvasti, etta tassa versiossa vahva poikkeus-takuu on tehnyt luokasta selvasti kompelomman ja vaikeammin hal-littavan. Lisaksi jokaisen kirjaolion luominen vaatii kaksi uutta dy-naamista olion luomista, mika tuhlaa hieman muistia ja hidastaa oh-jelmaa vahaisessa maarin. Jos merkkijonoja olisi useampi kuin kak-si, muuttuisi tilanne aina vain pahemmaksi. Ratkaisua kannattaa siisviela jalostaa.

11.9.4 Tilan eriyttaminen (“pimpl”-idiomi)

Vahvan poikkeustakuun antava toteutus saadaan tyylikkaammaksi jatehokkaammaksi, jos olion tila (sen jasenmuuttujat) ja itse olio (sen“identiteetti”) erotetaan toisistaan. Helpoimmin tama tapahtuu laitta-malla jasenmuuttujat omaan struct-tietorakenteeseensa, johon olios-sa on sitten osoitin. Tallaisella jarjestelylla voidaan olion tila korva-ta toisella yksinkertaisesti sijoittamalla osoittimen paahan uusi tila-tietorakenne. Tassakin tapauksessa tilan sisaltava struct taytyy luo-da dynaamisesti, mutta luomisia tapahtuu vain yksi jasenmuuttujienmaarasta riippumatta. Lisaksi dynaamista muistinhallintaa voidaanhelpottaa korvaamalla osoittimet aliluvun 11.7 automaattiosoittimil-la.

Listaukset 11.17 seuraavalla sivulla ja 11.18 seuraavalla sivul-la nayttavat esimerkin erillisen tilarakenteen kaytosta. Siina Kirja-luokan sisalla on maaritelty erillinen struct-tietorakenne Tila. Luo-kan esittelyn yhteydessa tasta tietorakenteesta riittaa vain ennak-koesittely (aliluku 4.4), koska luokan esittelyssa tarvitaan vain (au-tomaatti)osoitin tietorakenteeseen. Itse tietorakenteen maarittely voiolla vasta luokan toteuttavassa kooditiedostossa. Talla tavalla rat-kaisu myos lisaa luokan kapselointia, koska jasenmuuttujat eivatenaa nay otsikkotiedostossa. Tallaisesta erillisesta tilatietorakenteestakaytetaan englanninkielisessa C++-kirjallisuudessa yleisesti nimitysta“pimpl” (private implementation)^ ja se on joskus varsin kayttokel-poinen muutenkin kuin poikkeuksien yhteydessa [Sutter, 2000, koh-dat 26–30].

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .^Lisaksi pimpl on ah, niin puujalka-hauskasti lahella sanaa “pimple” = finni.

Page 406: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

11.9. Esimerkki: poikkeusturvallinen sijoitus 406

1 class Kirja2 3 public:4 Kirja(std::string const& nimi, std::string const& tekija);5 // Tarvitaan myos oma kopiorakentaja!6 // Oma purkaja tarvitaan, jotta auto ptr ennakkoesittelylle toimii7 ~Kirja();

...8 Kirja& operator =(Kirja const& kirja);9 private:

10 struct Tila;11 std::auto ptr<Tila> tilap ;12 ;

LISTAUS 11.17: Kirja eriytetylla tilalla ja automaattiosoittimella

1 struct Kirja::Tila2 3 std::string nimi ;4 std::string tekija ;5 Tila(std::string const& nimi, std::string const& tekija)6 : nimi (nimi), tekija (tekija) 7 ;89 Kirja::Kirja(std::string const& nimi, std::string const& tekija)

10 : tilap (new Tila(nimi, tekija))11 12 1314 Kirja::~Kirja()15 // Automaattiosoitin tuhoaa tilan automaattisesti16 1718 Kirja& Kirja::operator =(Kirja const& kirja)19 20 std::auto ptr<Tila> uusitilap(new Tila(*kirja.tilap ));21 tilap = uusitilap; // Ei voi epaonnistua ja tuhoaa vanhan tilan22 return *this;23

LISTAUS 11.18: Eriytetyn tilan rakentajat, purkaja ja sijoitus

Page 407: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

11.9. Esimerkki: poikkeusturvallinen sijoitus 407

Automaattiosoittimen ansiosta luokan rakentajat ja purkajat nayt-tavat jo paljon siistimmilta kuin aiemmin. Samoin sijoitusoperaatto-rissa ei enaa tarvitse reagoida virheisiin, koska automaattiosoitin pi-taa huolen dynaamisesti luotujen tilatietorakenteiden tuhoamisesta.Sijoituksesta on myos jatetty tilasyista pois itseen sijoituksen testaus,koska se ei enaa ole valttamaton. Tama ratkaisu on jo varsin yllapi-dettava ja tehokkuudeltaankin todennakoisesti siedettava.

11.9.5 Tilan vaihtaminen paikseen

Nyt kun vahvan poikkeustakuun antavalle sijoitukselle on loytynytyleispateva ratkaisu, voidaan viela tutkia, eiko nimenomaan esimer-kin merkkijonojen tapauksessa voitaisi paasta tehokkaampaa ratkai-suun. Vahan string-luokan rajapintaa tutkimalla tallainen loytyy-kin. Luokka nimittain tarjoaa jasenfunktion swap, joka vaihtaa kahdenmerkkijonon arvot keskenaan nothrow-poikkeustakuulla. Sama ope-raatio loytyy myos kaikista STL:n sailioista. Vaihto-operaatio antaamahdollisuuden pitaa merkkijonot Kirja-luokan normaaleina jasen-muuttujina, mutta silti saavuttaa vahva poikkeustakuu. Listaus 11.19seuraavalla sivulla nayttaa esimerkin tasta.

Listauksessa luokkaan on lisatty johdonmukaisuuden vuoksi omajasenfunktio vaihda, joka vaihtaa kahden kirjan tilat keskenaan kayt-tamalla string-luokan swap-operaatiota. Koska swap onnistuu aina, eimyoskaan vaihda-jasenfunktiossa voi tapahtua virhetta. Tata kayte-taan hyvaksi sijoitusoperaattorissa. Siella sijoitettavasta oliosta luo-daan ensin kopio uuteen paikalliseen Kirja-olioon. Tama olio edustaasita, mihin sijoituksella halutaan paasta. Jos kopion luominen epaon-nistuu, ei alkuperaiselle oliolle ole tehty mitaan ja vahva poikkeus-takuu patee. Jos kopiointi onnistuu, vaihdetaan sijoituksessa yksin-kertaisesti kopion ja vanhan kirjaolion tilat keskenaan. Nain sijoitustulee tehtya. Sijoitusoperaattorista palattaessa vanhan tilan sisaltavapaikallinen muuttuja lopuksi tuhoutuu.

Erillinen vaihda-jasenfunktio on kateva, koska sita kayttamallamyos Kirja-olioita jasenmuuttujinaan pitavat luokat voivat tarjota si-joitukselle vahvan poikkeustakuun samaa mekanismia kayttamalla.Lisaksi ohjelmassa saattaa muulloinkin olla katevaa pystya vaihta-maan kahden olion tilat keskenaan ilman virhemahdollisuutta.

Vihoviimeisena esimerkkina listaus 11.20 sivulla 409 yhdistaakeskenaan tilan eriyttamisesta saatavan lisakapseloinnin ja tilan vaih-

Page 408: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

11.9. Esimerkki: poikkeusturvallinen sijoitus 408

1 class Kirja2 3 public:

...4 Kirja& operator =(Kirja const& kirja);5 void vaihda(Kirja& kirja) throw();6 private:7 std::string nimi ;8 std::string tekija ;9 ;

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .1 void Kirja::vaihda(Kirja& kirja) throw()2 3 nimi .swap(kirja.nimi ); // Ei voi epaonnistua4 tekija .swap(kirja.tekija ); // Eika tamakaan5 67 Kirja& Kirja::operator =(Kirja const& kirja)8 9 Kirja kirjakopio(kirja); // Kopio sijoitettavasta

10 vaihda(kirjakopio); // Vaihdetaan itsemme siihen, ei epaonnistu11 return *this; // Vanha tila tuhoutuu kirjakopion myota12

LISTAUS 11.19: Kirja, jossa on nothrow-vaihto

tamisen. Se tarjoaa ehka kaikkein tyylikkaimman yleiskayttoisen rat-kaisun, jolla vahva poikkeustakuu saadaan toteutettua lahes luokassakuin luokassa.

Page 409: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

11.9. Esimerkki: poikkeusturvallinen sijoitus 409

1 class Kirja2 3 public:4 Kirja(std::string const& nimi, std::string const& tekija);5 Kirja(Kirja const& kirja);6 // Oma purkaja tarvitaan, jotta auto ptr ennakkoesittelylle toimii7 ~Kirja();

...8 Kirja& operator =(Kirja const& kirja);9 void vaihda(Kirja& kirja) throw();

10 private:11 struct Tila;12 std::auto ptr<Tila> tilap ;13 ;

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .1 struct Kirja::Tila2 3 std::string nimi ;4 std::string tekija ;5 Tila(std::string const& nimi, std::string const& tekija)6 : nimi (nimi), tekija (tekija) 7 ;89 Kirja::Kirja(std::string const& nimi, std::string const& tekija)

10 : tilap (new Tila(nimi, tekija))11 12 1314 Kirja::Kirja(Kirja const& kirja)15 : tilap (new Tila(*kirja.tilap )) // Tilan kopiorakentaja16 17 1819 Kirja::~Kirja()20 // Automaattiosoitin tuhoaa tilan automaattisesti21 2223 void Kirja::vaihda(Kirja& kirja) throw()24 25 std::auto ptr<Tila> p(tilap );26 tilap = kirja.tilap ;27 kirja.tilap = p;28 2930 Kirja& Kirja::operator =(Kirja const& kirja)31 32 Kirja kirjakopio(kirja); // Kopio sijoitettavasta33 vaihda(kirjakopio); // Vaihdetaan itsemme siihen, ei epaonnistu34 return *this; // Vanha tila tuhoutuu kirjakopion myota35

LISTAUS 11.20: Yhdistelma tilan eriyttamisesta ja vaihdosta

Page 410: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

410

Liite A

C++: Ei-olio-ominaisuuksia

Vaikka sana “ei-olio-ominaisuuksia” onkin melkoinen hirvitys, se ku-vaa hyvin sita, miksi nama C++-ohjelmointikielen rakenteet on esiteltylyhyesti erillisessa liitteessa. Kyseessa on joukko kielen ominaisuuk-sia (lahinna parannuksia C-kieleen), jotka C++:n kayttajan on hyva tun-tea, mutta joilla ei ole suoraan mitaan tekemista kirjan aiheen eli olio-ohjelmoinnin kanssa.

Esiteltavista asioista viitteet ovat uusi “osoitustyyppi”, inline onpuhtaasti optimointiominaisuus ja vector seka string ovat uusiahelppokayttoisempia versioita C:ssa olevista ominaisuuksista.

Taman liitteen tarkoituksena on antaa naista C++:n ominaisuuksis-ta sen verran tietoa, etta muun kirjan lukeminen ja ymmartaminenonnistuvat ilman suuria ongelmia. Kattavan kuvauksen naista omi-naisuuksista saa parhaiten jostain C++-oppikirjasta [Lippman ja Lajoie,1997], [Stroustrup, 1997].

A.1 Viitteet

C-kieli kayttaa kahdenlaisia tapoja tiedon kasittelyyn ja valittamiseenesimerkiksi funktioiden parametreina: muuttujat ja osoittimet. Muut-tujat ovat varsinainen datan esitysmuoto, ja osoittimien avulla voi-daan viitata olemassa olevaan dataan (muuttuja). Osoittimet ovat to-teutukseltaan muistiosoitteita, ja niita pystyy kasittelemaan hyvin va-paasti: vaihtamaan arvoa mielivaltaiseksi kokonaisluvuksi ja siirta-

Page 411: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

A.1. Viitteet 411

maan osoittamaan esimerkiksi “seuraavaan” alkioon (++-operaattori).Naista vapauksista johtuen osoittimet ovat myos usein pahojen oh-jelmointivirheiden lahde. Ohjelmointivirheen seurauksena vaaraanpaikkaan osoittava osoitin voi aiheuttaa muutoksia ohjelman datassavaarassa paikassa tai jossain vaiheessa jarkeva osoitinarvo osoittaakinmuistin vapauttamisen jalkeen kielletylle alueelle, jota silti yritetaankayttaa osoittimen lapi. Nama virheet paljastuvat usein vasta hyvinpitkien aikojen kuluttua (ohjelman aivan muut kuin virheen aiheut-taneet osat alkavat kayttaytya virheellisesti).

Osoittimien “vaarallisuuden” takia C++ maarittelee uuden tietotyy-pin viite (reference), joka osoittimien tapaan viittaa olemassa olevaandataan mutta johon liittyen kaantaja tekee enemman kayttotarkistuk-sia kuin osoittimien yhteydessa. Viite on tavallaan synonyymi ole-massa olevalle data-alkiolle, ja jokainen viite viittaa aina johonkindataan (se ei voi olla nolla kuten osoitin).

Aina kun viite luodaan, se taytyy samalla alustaa osoittamaan ai-kaisemmin luotuun dataan. C++ uudelleenkayttaa viitteiden yhteydes-sa hieman hamaavasti osoittimiin liittyvan syntaktisen merkin “&”.Kun muuttujan tietotyypin peraan on lisatty &, kyseessa on viitetyyp-pi kyseista tyyppia olevaan alkioon:

int a = 0;int b = 0; // normaali kokonaislukumuuttujien esittelyint& a r = a; // kokonaislukuviite muuttujaan a

Kun viitetyyppista muuttujaa kaytetaan, se kayttytyy samoin kuinkohdassa esiintyisi alkuperainen muuttuja (viite on toinen muoto elisynonyymi alkuperaiselle muuttujalle).

b = a r + 1; // lukee muuttujan a arvon ⇒ b == 1a r = 2 - b; // kirjoittaa muuttujaan a ⇒ a == 1

Osoittimeen voi tehda viitteen, mutta tyyppia “osoitin viitteeseen”ei ole olemassa(!) (katso kuva A.1 seuraavalla sivulla).

int* a p = &a r; // osoittaa muuttujaan a (ei viitteeseen!)int*& a rp = a p; // synonyymi osoittimelle

*a rp = 7; // muuttujan a arvo on sijoituksen jalkeen 7

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .Tassa on suuri ero Javan viitetyyppeihin, joilla on olemassa tyhja viitearvo null.

Page 412: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

A.1. Viitteet 412

a a_r

a_p a_rp

KUVA A.1: Esimerkkien viittaukset

Viitteiden normaali kayttotarkoitus ei ole luoda turhia uusia nimi-synonyymeja jo olemassa oleville muuttujille vaan valittaa viittauk-sia dataan funktioiden parametreina tai paluuarvoina. Viiteparamet-ri sitoutuu synonyymiksi funktiolle annettuun dataan, kun funktiotakutsutaan:

void tuplaa( int& i ) i = i * 2; ...

int x = 2;tuplaa( x );// x on nyt 4

Viiteparametrin avulla on helpompi kirjoittaa funktioita, jotkamuuttavat parametriensa arvoja. Haittapuoli taas on se, etta funktionkutsusta tuplaa( x ) ei pysty paattelemaan mitenkaan, muuttaakofunktio parametrina annettua arvoa. Tama tieto on tarkistettava funk-tion esittelysta, jossa funktio voi luvata vakioviiteparametrilla, etta seei muuta parametrin arvoa:

struct SuuriData . . . ;

void arvonValitys( SuuriData d );void vakioViiteValitys( SuuriData const& d );

Kumpikaan esitellyista funktioista ei muuta parametrina annettua da-taa. ArvonValitys kopioi tietorakenteen datasta itselleen oman ko-pion, jolloin kutsujan antama data ei muutu. VakioViiteValitys eimyoskaan muuta dataa, koska se on merkitty kaantajalle vakioda-

Page 413: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

A.2. inline 413

taksi. Mahdollisesti raskasta datan kopiointioperaatiota ei kuitenkaantehda, vaan funktiolle valittyy kayttoon viite alkuperaiseen dataan.

Funktio voi palauttaa viitteen olemassa olevaan dataan myos pa-luuarvonaan. Taman ominaisuuden avulla voidaan tehda funktio, jo-ka valitsee palautettavan arvon, joka on jatkokasiteltavissa aivan kuinkaytossa olisi koko ajan ollut alkuperainen arvo:

1 int data[10];2 int& kohdistin( int i ) return data[i];

...3 kohdistin(0) = 7; // sijoittaa taulukon ensimmaiseen alkioon

Funktio kohdistin palauttaa viitteen, joka on sitoutunut siihentaulukon data alkioon, jonka funktion parametri i maaraa. Koska pa-luuarvo kayttaytyy kuten alkuperainen alkio, siihen voidaan tehdamyos rivin 3 mukainen sijoitus.

Viitepaluuarvoihin liittyy myos tilanne, jossa saadaan aikaiseksivirheellinen viite. Koska funktion paikallinen data tuhoutuu funktionsuorituksen paatyttya, tallaiseen dataan palautettava viite ei viittaaolemassa olevaan dataan, ja viitteen kaytto aiheuttaa maarittelemat-toman toiminnon:

int& virheellinen( int a )int b = 2 * a;return b; // VIRHE! paikallinen muuttuja viitepaluuarvona

A.2 inline

Useimmissa prosessoriarkkitehtuureissa funktiokutsu on eniten kay-tetyista operaatioista raskain (parametrien sijoitus esimerkiksi pi-noon ja arvokopioinnit funktion alussa ja lopussa). C++-ohjelmoija voimerkita haluamansa funktiot avainsanalla inline, joka antaa kaanta-jalle luvan optimoida funktion kayttoa.

Optimoinnissa kaantaja yrittaa korvata funktion kutsun sen sisal-tamalla toiminnallisuudella. Tama tapahtuu siten, etta funktion al-kuperainen toiminnallisuus ei saa muuttua mitenkaan. Jos funktio

Page 414: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

A.3. vector 414

on liian mutkikas tallaiseen korvaukseen, kaantaja suorittaa normaa-lin funktiokutsun. Ohjelmoijan ei tarvitse valittaa siita, mita mahdol-lisia optimointimuutoksia inlinen kaytto aiheuttaa syntyvaan kone-koodiin — toiminnallisuus on edelleen sama, kuin jos mitaan opti-mointia ei olisi tehty. C++-standardi ei vaadi kaantajilta optimointiensuorittamista, joten C++-kaantaja, joka jattaa inline-avainsanat otta-matta huomioon, on standardin mukaisesti toimiva. (Tietysti juuritallaiset kohdat standardissa antavat kaantajavalmistajille kilpailu-varaa, joten kaytannossa kaikki C++-kaantajat suorittavat inline-opti-mointeja.)

Esimerkiksi ohjelmakoodissa

1 inline int max( int x, int y )2 3 if( x > y ) return x; 4 else return y; 5

...6 return max( a, max( b, c ) );

rivi 6 voi inline-funktiokutsujen takia muuttua muotoon:

int r1 =(b>c)?b:c;int r2 =(a>r1 )?a:r1 ;return r2 ;

Kaytannossa optimoinnit suoritetaan kaantajan tuottaman konekoo-din tasolla, mutta esimerkista nakyy inlinen idea funktiokutsujenpoistamisessa.

A.3 vector

Taulukko on tietorakenne, joka sisaltaa sarjan samaa tietotyyppia ole-via alkioita. Taulukoiden perusominaisuus on indeksointi, jossa voi-daan viitata (yhta nopeasti) taulukon mihin tahansa alkioon (T[i]).[Sethi, 1996, luku 4.4]

C-kielessa taulukot ja osoittimet ovat “lahisukulaisia”. Taulukonnimi toimii osoittimena taulukon ensimmaiseen alkioon. Erityisesti

Page 415: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

A.3. vector 415

tama piirre korostuu funktioiden parametreissa, joissa valitetaan tau-lukoita osoittimina, vaikka syntaksi saattaisikin viitata taulukon va-littamiseen arvona: [Kerninghan ja Ritchie, 1988, luku 5.3]

void poke( char s[ ] ) s[1] = ’u’; ...

char j[ ] = "Jyke";poke( j );// j on nyt “Juke”;

C++ tarjoaa C-kielen tyyppisten taulukoiden lisaksi uuden tauluk-kotyypin “vektori”, joka ei ole taulukoiden tavoin sitoutunut osoitti-miin.

#include <vector> // taulukkotyypin esittely kayttoon

int main()int vt[10]; // “vanha” taulukko kokonaislukujavector<int> ut(10); // vektoritaulukko kokonaislukuja

C-taulukoihin verrattuna vektori on turvallista valittaa arvopara-metrina (se kopioituu), ja vektoriin voidaan sijoittaa toinen vektori.Vektorin koko kasvaa tarvittaessa (automaattinen tilan kasvatus), mi-ka vahentaa muistinhallinan ongelmia. Koska vektorin tilanvaraustaei ole C-taulukoiden tapaan pakko ilmoittaa luonnin yhteydessa, sevoidaan luoda tyhjana ja tayttaa tarvittavilla alkioilla. Taulukon raja-pinnassa oleva operaatio push back lisaa uuden alkion taulukon lop-puun:

vector<unsigned int> taulukko; // tyhja vektori

for( unsigned int i=1; i<=10; ++i ) // lisataan alkiot 1. .10 taulukko.push back(i);

Vektoritaulukoita indeksoidaan samalla syntaksilla kuin C-taulu-koitakin (ut[i]), ja samoin kuin aikaisemmin, tassa indeksointita-vassa ei ole tarkastuksia indeksoinnin “onnistumisesta” eli osumi-sesta taulukon alueelle. Seka vt[11] etta ut[11] ovat C++-standardin

Page 416: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

A.4. string 416

kannalta maarittelemattomia operaatioita (viittaus tietorakenteen ul-kopuolelle). Vektori tarjoaa rajapinnassaan operaation at, joka suo-rittaa ajoaikaisen indeksoinnin oikeellisuuden tarkastuksen. Lauseut.at(11) tuottaa siis virheen (poikkeuksen avulla, katso luku 11).

Kuvassa A.2 on vertailtu C-taulukon ja vektorin toimintoja.

A.4 string

C-kielen yhteydessa merkkijonoista puhuttaessa tarkoitetaan merk-kitaulukkoa (char[ ] tai unsigned char[ ]), jonka viimeisena (paatty-mismerkkina) on sovittu olevan merkkikoodi nolla (’\0’). C++ tarjo-aa uuden merkkijonotietotyypin string, joka on “taysiverinen” tieto-tyyppi eika enaa kielen alimmalla tasolla oleva yhdistetty taulukko- jaosoitin-rakenne C:n tapaan.

string-tyyppiset muuttujat muun muassa kopioituvat funktioi-

Toiminta C-taulukko vektoriLuominen int t[10] vector<int> t(10)Luominen, tyhja (ei jarkeva) vector<int> tSijoitus (silmukassa) t1 = t2Indeksointi t[i] t[i] tai t.at(i)Lisays loppuun (mahdoton) t.push back(a)Koko (tiedettava muualta) t.size()Tyhjennys (mahdoton) t.clear()Vertailu (silmukassa) t1 == t2 (myos t1 < t2, yms.)Lisays keskelle (mahdoton) t.insert(paikka-iteraattori, a)Etsiminen (silmukassa) find(t.begin(), t.end(), a)Arvojen vaihto (silmukassa) t1.swap( t2 )

KUVA A.2: C-taulukon ja vektorin vertailu

Page 417: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

A.4. string 417

Toiminta merkkijonotaulukko stringAlustus char* s = "jaa"; string s("maa");Sijoitus strcpy(mihin, mista) mihin = mistaIndeksointi s[i] s[i] tai s.at(i)Yhdistaminen strcat(mihin, mika) mihin += mikaAlimerkkijono strncpy(t,&s[alku],koko) t=s.substr(alku,alku+koko)Koko strlen(s) s.length()Tyhjennys s[0] = ’\0’ s.clear()Vertailu strcmp(s, t) s == t (myos s < t yms.)Rivin luku (eri tapoja) getline(cin, s)Lisaaminen (todella hankalaa) s.insert(paikka-iter, t)Etsiminen strstr(mista, mita) mista.find(mita)Arvojen vaihto char* tt=s; s=t; t=tt; s.swap(t)

KUVA A.3: Merkkijonotaulukon ja C++:n stringin vertailu

1 #include <cstdlib>2 #include <string>3 using std::string;4 #include <iostream>5 using std::cin;6 using std::cout;7 using std::endl;89 int main()

10 11 string sana, kokoelma("ALKU");1213 while( cin >> sana ) 14 if( sana.find("ja") != string::npos ) 15 // loytyi16 kokoelma = kokoelma + "," + sana;17 18 19 cout << kokoelma << endl;20 return EXIT SUCCESS;21

LISTAUS A.1: Osamerkkijonon “ja” etsinta (C++)

Page 418: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

A.4. string 418

1 #include <string.h>2 #include <stdlib.h>3 #include <stdio.h>45 int main()6 7 char sana[1024]; /* ei toimi tata pidemmilla sanoilla */8 char *kokoelma, *tmp;9 unsigned int pituus, kaytossa;

10 pituus = kaytossa = 5; /* ALKU + lopetusmerkki */11 kokoelma = (char*)malloc( pituus ); strcpy( kokoelma, "ALKU" );12 while( scanf("%s", sana) == 1) 13 if( strstr(sana, "ja") != 0 ) 14 if( kaytossa + strlen(sana) + 2 > pituus ) 15 /* muisti ei riita -> lisaa */16 tmp = kokoelma;17 kokoelma = (char*)malloc( pituus + 2*strlen(sana) );18 strcpy( kokoelma, tmp );19 free( tmp );20 pituus += 2*strlen(sana);21 22 strcat( kokoelma, "," );23 strcat( kokoelma, sana );24 kaytossa = kaytossa + strlen(sana) + 2;25 26 27 printf("%s\n", kokoelma);28 return EXIT SUCCESS;29

LISTAUS A.2: Osamerkkijonon “ja” etsinta (C)

den parametreina:

#include <string> // merkkijonotyypin esittely kayttoon

void spoke( string s ) s[1] = ’u’; ...

string j = "Jyke";spoke( j );// j:n arvo on edelleen “Jyke”

C-kielen kirjasto maarittelee merkkijonotaulukoiden kasittelyynjoukon str-alkuisia funktioita, joiden yhteydessa ohjelmoijan on jat-

Page 419: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

A.4. string 419

kuvasti itse huolehdittava siita, etta operaatioiden kohdemerkkijo-noissa on tarpeeksi tilaa (merkkitaulukko on tarpeeksi suuri operaa-tion tuloksen sailyttamiseen). C++:n stringin rajapinnassa ovat kaikkivastaavat operaatiot (vertailu kuvassa A.3 sivulla 417), ja niiden yh-teydessa mahdolliset tulosmerkkijonon koon muutokset tapahtuvatautomaattisesti.

Listauksessa A.1 sivulla 417 on esimerkki string-tietorakenteenkaytosta. Ohjelma lukee oletussyotteestaan sanoja ja muodostaa pil-kulla erotellun luettelon (merkkijonoon) kaikista niista sanoista, jotkasisaltavat sanan “ja”. Lopuksi ohjelma tulostaa muodostamansa luet-telon. Listauksessa A.2 edellisella sivulla sama toiminnallisuus ontoteutettu C-kielella merkkijonotaulukoiden avulla, jolloin ohjelmoi-jan on itse huolehdittava muistinhallinnasta: etukateen ei ole tiedos-sa syotteen yksittaisen sanan tai syntyvan luettelon pituutta. Kasitel-tavan sanan maksimipituuden ongelma on ratkaistu “taikavakiolla”1024, ja tuotettavan listan muistinhallinta on ohjelmoitu itse malloc-ja free-rutiineilla.

Page 420: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

420

Liite B

C++-tyyliopas

Opettele saannot, jotta tiedat miten rikkoa niita oikein.– Tuntematon

Koska nykyaikaiset ohjelmointikielet ovat mutkikkaita ja monita-hoisia tyokaluja, tyylioppaalla pyritaan rajaamaan projektin tai orga-nisaation tarpeisiin sopivat tavat kayttaa tata tyokalua. Yhtenaisenohjelmointityylin noudattaminen lisaa lahdekoodin ymmarrettavyyt-ta, yllapidettavyytta, siirrettavyytta, luettavuutta ja uudelleenkaytet-tavyytta. Mutkikkaissa ohjelmointikielissa (kuten C++) tyylioppaan oh-jeet voivat myos lisata koodin tehokkuutta ja auttaa valttamaan ohjel-mointivirheita — tai ainakin auttaa loytamaan virheet nopeammin.Hyva tyyliopas pystyy parantamaan kirjoittamamme ohjelmakoodinlaatua. Tahan tyylioppaaseen on koottu saantoja, joita noudattamallapaasee kohti tuota paamaaraa kaytettaessa C++-ohjelmointikielta.

Opas on kokoelma saantoja ja suosituksia. Saannot ovat pe-rusteltavissa ISOC++ -standardilla tai muilla erityisen painavilla syil-la. Ohjelmakoodin ulkoasuun, muokkaamiseen ja taltiointiin liitty-vat suositukset on tarkoitettu yhdenmukaisiksi sopimuksiksi, joitatarvittaessa muokataan tyyliohjetta sovellettaessa. Suositus “tiedos-tonimi paattyy aina paatteeseen .cc” voidaan tarvittaessa muokatatarkoittamaan esimerkiksi tiedostopaatetta .C, .cxx tai .cpp kaytetykaannosympariston vaatimusten mukaisesti.

1. Yleista

Page 421: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

B.2. Ohjelmiston tiedostot 421

1.1. Kaikkia tyylioppaan saantoja on noudatettava.

1.2. Tyylioppaan saannosta saa aina poiketa hyvalla perusteel-la, joka tulee kirjata ohjelmakoodin kommentteihin.

1.3. Saannot eivat koske ulkopuolista lahdekoodia (muualtahankittu tai koodigeneraattorin tuottama).

1.4. Vanhoja ohjelmia ei muuteta taman tyylioppaan mukaisik-si, vaan niita yllapidetaan niita tehtaessa noudatetun kay-tannon mukaisesti.

2. Ohjelmiston tiedostot

2.1. Tiedostot ovat maaramuotoisia: alussa on koko tiedostoakoskeva kommentti.

2.2. Tiedoston kommentit kirjoitetaan C++-muodossa (// ...).Monen rivin kommentit voidaan sijoittaa C-kielen kom-menttien (/* ... */) sisaan.

2.3. Otsikkotiedostot

2.3.1. Otsikkotiedostojen nimen paate on .hhYhtenaiseen nimeamiseen kuuluvat maaramuotoiset tie-dostonimet. Paatteen valintaan vaikuttaa ensisijaisesti se,mita tiedostoja kaytetty kaannosymparisto pitaa oletusar-voisesti C++-tiedostoina. Paatetta .h ei kannata kayttaa, kos-ka se on jo kaytossa C-kielisissa ohjelmissa.

2.3.2. Yhdessa otsikkotiedostossa esitellaan yksi julkinen ra-japinta (luokka tai nimiavaruus).

Hyvin dokumentoitu julkinen rajapinta on usein sopivankokoinen kokonaisuus sijoitettavaksi yhteen ohjelmakoodi-tiedostoon. (Katso myos kohta 5.1.)

2.3.3. Otsikkotiedosto on selkeasti kommentoitu, jos sen pe-rusteella pystytaan suunnittelemaan ja toteuttamaanrajapintaa koskevat testitapaukset.

2.3.4. Otsikkotiedostoissa on ainoastaan esittelyita. (ISOC++:nominaisuuksien takia otsikkotiedostossa voi olla myoskokonaislukuvakioita, inline-rakenteita [aliluku A.2sivulla 413] ja template-malleja [aliluku 9.5 sivul-la 283].)

Vaikka inline- ja template-rakenteet sisaltavat toteutuksenohjelmakoodia, niin nykyisten C++-kaantajien on nahtavaniiden taysi maarittely, ennen kuin kyseisia rakenteita voi-daan kayttaa (instantioida) muussa ohjelmakoodissa.

Page 422: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

B.2. Ohjelmiston tiedostot 422

2.3.5. Laajat mallit ja inline-koodit tulee kirjoittaa erilli-seen tiedostoon (jolla on yhtenainen paate esimerkiksi.icc). Tama tiedosto luetaan otsikkotiedoston lopussa#include-kaskylla.

Toteutustapa jolla saantoa 2.3.4 voidaan noudattaa sel-keasti.

2.3.6. Otsikkotiedostossa otetaan kayttoon (#include-kas-kyilla) ainoastaan ne muut esittelyt, jotka ovat tarpeentiedoston sisaltaman esittelyn takia.

Ylimaaraiset #include-kaskyt aiheuttavat turhia riippu-vuuksia lahdekooditiedostojen valille vaikeuttaen yllapidet-tavyytta.

2.3.7. Otsikkotiedostoissa ei saa kayttaa using-lauseita. [ali-luku 1.5.7 sivulla 47] (Katso myos kohta 3.3.)

Esittelyiden yhteydessa on suurin vaara nimikonflikteistaeri otsikkotiedostojen sisaltamien nimien valilla. Eksplisiit-tinen nimien kaytto nakyvyystarkentimella ei sotke ohjel-man nimiavaruuksia tai tuota ohjelmakoodia, jonka toi-mivuus riippuu kaytettyjen otsikkotiedostojen keskinaisestakayttoonottojarjestyksesta.

2.3.8. Jos esittelyssa (yleensa otsikkotiedostossa) viitataan jo-honkin luokkatyyppiin vain osoittimella tai viitteella,kaytetaan ennakkoesittelya [aliluku 4.4 sivulla 112].Ennakkoesittelyn saa tehda vain itse tekemilleenluokille.

Vain itse tehdysta ohjelmakoodista voi varmasti tietaa en-nakkoesittelyn olevan sallittua. Esimerkiksi vaikka standar-dikirjaston std::string [aliluku A.4 sivulla 416] vaikuttaaluokkatyypilta, siihen ei saa tehda ennakkoesittelya, koskatoteutus on tyyppialias (typedef).

2.3.9. Otsikkotiedosto on suojattava moninkertaiselta kayt-toonotolta [aliluku 1.4 sivulla 39].

#include-ketjuissa moninkertainen otsikkotiedoston kayt-toonotto on mahdollista ja aiheuttaa kaannosvirheen (C++-rakenteet voidaan esitella kaantajalle vain kerran samankaannoksen yhteydessa). Suojaus toteutetaan ehdollisenkaantamisen esiprosessorikaskyilla otsikkotiedoston alussaja lopussa:

#ifndef LUOKKA A HH#define LUOKKA A HH

...#endif // LUOKKA A HH

Page 423: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

B.3. Nimeaminen 423

2.3.10. #include-kaskyissa ei saa esiintya viittauksia tietyn ko-neen tiettyyn hakemistoon.

Kaikki yhteen kaannosymparistoon sitovat viittaukset vai-keuttavat lahdekoodin yllapitoa ja siirrettavyytta.

2.4. Toteutustiedostot

2.4.1. Toteutukset sisaltavan tiedoston paate on .cc

2.4.2. Toteutustiedostossa otetaan kayttoon vain kyseisenkaannosyksikon toteuttamisen kannalta tarpeelliset ot-sikkotiedostot.

2.4.3. Toteutustiedostossa otetaan ensin kayttoon ohjelmanpaikalliset esittelyt (otsikkotiedostot) ja vasta sittenkaannosympariston ja ISOC++-kielen maarittelemat kir-jastot [aliluku 1.5.7 sivulla 47] (katso myos koh-ta 3.3.5).

Ulkopuolisten esittelyiden keskinaisesta jarjestyksesta riip-puvat otsikkotiedostot ovat vaarallista ohjelmakoodia ja tal-la kaytannolla ne huomataan helpommin kaannoksen yh-teydessa.

2.4.4. Rajapinnan toteutukset esitetaan tiedostossa samassajarjestyksessa kuin ne ovat vastaavassa otsikkotiedos-tossa.

Nain maaratyn rajapintaoperaation toteutuksen loytami-nen helpottuu.

3. Nimeaminen

3.1. Ohjelmakoodin kommentit ja symboliset nimet kirjoite-taan yhtenaisella kielella ja nimeamistavalla.

Esimerkiksi TTY:n opetuskieli on suomi, joten on loogista kayt-taa samaa kielta myos ohjelmoidessa. Vastaavasti jos yrityksenvirallinen kieli on englanti, se lienee parempi vaihtoehto ohjel-makoodin nimien ja kommentoinnin kieleksi.

3.2. Alaviivalla alkavia nimia ei saa kayttaa. Samoin kielletty-ja ovat nimet, joissa on kaksi perakkaista alaviivaa. [alilu-ku 2.3.2 sivulla 61].

3.3. Nimiavaruudet

3.3.1. Moduulin kayttamat nimet pidetaan erillaan muus-ta ohjelmistosta sijoittamalla ne nimiavaruuden(namespace) sisaan [aliluku 1.5.1 sivulla 42].

Page 424: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

B.3. Nimeaminen 424

3.3.2. ISOC++:n uusia otsikkotiedostoja ja niiden kautta std-nimiavaruutta on kaytettava. [aliluku 1.5.4 sivulla 45]ja [aliluku 1.5.5 sivulla 45].

3.3.3. Jos nimiavaruuden sisalta nostetaan nakyville nimia,niin kayttoon otetaan vain todella tarvittavat nimet(lauseen using namespace X kaytto on kiellettya). [ali-luku 1.5.7 sivulla 47]

3.3.4. using-lauseella nostetaan nimet kayttoon lahelle kayt-topaikkaa (koodilohko tai funktio).

3.3.5. Erityisen usein tarvittavat nimet voidaan nostaa naky-ville koko kaannosyksikkoon ja niita koskevat using-lauseet tulee sijoittaa tiedoston kaikkien #include-kas-kyjen jalkeen, jotta “nostetut” nimet eivat paase sotke-maan toisia esittelyita. ISOC++:n otsikkotiedostoja voi-daan kuitenkin pitaa hyvin muotoiltuina ja niidenyhteyteen liitetyt kyseista esittelya koskevat using-lauseet usein parantavat ohjelmakoodin luettavuutta.[aliluku 1.5.7 sivulla 47]

#include "prjlib.hh"#include <iostream>using std::cout;using std::endl;#include <sstream>using std::ostringstream;

using Prjlib::Puskuri;using Prjlib::Paivays;

3.3.6. Kaannosyksikon (tiedosto) sisaisessa kaytossa olevatmaarittelyt sijoitetaan nimeamattoman nimiavaruu-den sisaan. [aliluku 1.5.8 sivulla 49]

Oletuksena kaannosyksikon nimet voivat aiheuttaa nimi-konflikteja muun ohjelmiston osien kanssa linkitysvaihees-sa. Nimeamaton nimiavaruus varmistaa maarittelyjen ole-van uniikkeja (kaannosyksikon sisaisia).

namespace // nimeamaton nimiavaruusunsigned long int viiteLaskuri = 0;void lisaaViiteLaskuria()

...

Page 425: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

B.4. Muuttujat ja vakiot 425

3.4. Luokka- (class) ja tietuetyyppien (struct), nimiavaruuk-sien (namespace) seka tyyppialiaksien (typedef) nimet al-kavat isolla alkukirjaimella.

3.5. Muuttujien, funktioiden ja jasenfunktioiden nimet alka-vat pienella alkukirjaimella. Useammasta sanasta koostu-vat nimet kirjoitetaan yhteen ja loppupaan sanat aloitetaanisolla alkukirjaimella (esimerkiksi: haeRaportinPaivays()).Poikkeus saantoon on luokan rakentaja, jonka nimen onoltava tasmalleen sama kuin luokan nimi.

3.6. Jasenmuuttujien nimen viimeinen merkki on alaviiva (taine erotellaan muista nimista jollain muulla yhtenaisellanimeamistavalla) [aliluku 2.3.2 sivulla 61].

3.7. Luokkamuuttujien nimen viimeinen merkki on alaviiva(tai ne erotellaan muista nimista ja mahdollisesti myos ja-senmuuttujista jollain muulla yhtenaisella tavalla) [alilu-ku 8.2.2 sivulla 250].

Kohdan 3.6 mukainen nimeaminen, joka helpottaa luokka-muuttujien tunnistamista ohjelmakoodissa.

3.8. Vakiot kirjoitetaan kokonaan isoilla kirjaimilla ja sanojenerottimena kaytetaan tarvittaessa alaviivaa. (“Vakion” ar-vo on aina sama. Jos esim. funktion paikallisen const-muuttujan arvo lasketaan ajoaikana ja voi vaihdella funk-tion kutsukerroilla, kannattaa se varmaan nimeta tavalli-sen muuttujan tapaan.)

unsigned int const PUSKURIN SUURIN KOKO = 1024;

4. Muuttujat ja vakiot

4.1. Muuttujien nakyvyysalue tulee suunnitella mahdollisim-man pieneksi (koodilohko, funktio, luokka tai nimiava-ruus).

Lahella kayttopaikkaa maaritellyt ja alustetut muuttujat lisaa-vat ohjelmakoodin selkeytta.

4.2. Jokainen muuttuja on maariteltava eri lauseessa.Muuttujan tyyppi, nimi, alustus ja sita koskeva kommentti ovatusein yhdelle riville sopiva kokonaisuus. Saanto myos varmis-taa, etta kohdan 5.4 mukaiset osoittimien ja viitteiden maaritte-lyt tulevat aina oikein. “char* p1, p2;” olisi luultavasti virhe,silla nyt p1 on osoitin ja p2 merkkimuuttuja.

Page 426: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

B.4. Muuttujat ja vakiot 426

4.3. Ohjelmassa ei saa olla alustamattomia muuttujia.Jos muuttujalla ei ole jarkevaa alkuarvoa, sille annetaan jo-kin “laiton” alkuarvo (nolla, miinus yksi, tms.). Alustamatto-mat muuttujat ovat yksi suurimmista (satunnaisten) virhetoi-mintojen aiheuttajista ohjelmistoissa. Laittomaan arvoon alus-tetut muuttujat eivat korjaa virheita, mutta tuovat ne testeissahelpommin esille.

4.4. Luokan jasenmuuttujat alustetaan aina rakentajan alustus-listassa, jossa jasenmuuttujat luetellaan niiden esittelyjar-jestyksessa [aliluku 3.4.1 sivulla 80].

4.5. #define-kaskya ei saa kayttaa ohjelman vakioiden ja “mak-rofunktioiden” maarittelyyn [aliluku 4.3 sivulla 105].

Nimetyt vakiot toteutetaan C++:ssa const-muuttujina ja makrotkorvataan inline-funktioilla.

4.6. Ohjelmakoodin toimintaa ohjaavat raja-arvot ja muut va-kiot maaritellaan keskitetysti yhdessa paikassa vakiomuut-tujiksi (numeeristen vakioiden kaytto ohjelmassa on kiel-lettya). Lyhyet merkitykseltaan varmasti pysyvat vakiot ku-ten esimerkiksi nollaosoitin (0) tai “kymmenen alkion sil-mukka” (-5 . . . 5) on kuitenkin usein selkeampaa kirjoit-taa suoraan kayttopaikassa nakyviin.

Kaikki numeeriset arvot tulisi maaritella yhdessa keskitetyssapaikassa (otsikkotiedosto) selkeasti nimetyiksi vakiomuuttujik-si, joita kaytetaan muualla ohjelmakoodissa. Vakioiden arvojenmuuttaminen myohemmin on talloin helppoa. Nain tulisi teh-da kaikille arvoille, joita mahdollisesti halutaan muuttaa ohjel-man jatkokehityksessa. Jos tiedetaan hyvin varmaksi, ettei jokinarvo muutu (nolla tai “tassa laiteohjaimessa on kymmenen al-kion alustus”), niin arvon kirjoittaminen ilman vakiomuuttujantuomaa epasuoruutta on perusteltua.

4.7. Totuusarvot ilmaistaan ISOC++:n tietotyypilla bool, jonkamahdolliset arvot ovat false ja true.

C-kielen “totuusarvot” nolla ja nollasta poikkeavat kokonaislu-vut ovat edelleen toimivia, mutta niiden kayttoa ei suositellaISOC++:ssa.

4.8. Kun tyyppi maaritellaan vakioksi, const-sanan paikka tyy-pin maarittelyssa on itse tyyppinimen jalkeen. Tama suo-siteltava tyyli on yleistymassa uusissa ohjelmissa. Aiem-min koodissa on ollut tapana kirjoittaa const ennen tyyp-pinimea. Tarkeinta on, etta kaytanto on yhtenainen kokoohjelmassa. [aliluku 4.3 sivulla 105]

Page 427: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

B.5. Asemointi ja tyyli 427

int const VAKIO = 3; // Kokonaislukuvakioint const* ptr = &VAKIO; // vakio-osoitin eli osoitin vakioonint* const PTR2 = 0; // Osoitinvakio jota ei saa muuttaa

5. Asemointi ja tyyli

5.1. Yli tuhannen rivin tiedostojen kasittely on hankalaa, jotenniita tulisi valttaa.

5.2. Yhden (jasen)funktion toteutuksen pituuden ei tulisi ylit-taa sataa rivia.

5.3. Paatelaitteiden kaytannoksi on muodostunut 80 merkkialevea naytto. Tasta johtuen yli 79 merkin pituiset rivit kan-nattaa jakaa useammalle riville.

std::string const esimerkkijono = "Katenointi ""on ominaisuus jolla kaantaja"" yhdistaa perakkain olevat"" merkkijonoliteraalit yhdeksi.\n";

std::vector<unsigned long int>laskeTaulukkoVastaus( unsigned int pituus,

std::map<unsigned long int,std::string> const& tiedot );

5.4. Osoittimen ja viitteen merkki kuuluvat tyyppinimen yh-teyteen.

Paivays* ptr = NULL;void tulostaPaivays( Paivays& paivaysOlio );

5.5. Primitiiveja if, else, while, for ja do seuraa aina koodiloh-ko, joka on sisennetty johdonmukaisesti valilyontimerkeil-la.

Sarkaimen (tabulator) kaytto ei ole suositeltavaa, koska erilaisettekstieditorit ja katseluohjelmat nayttavat sen sisennyksen eritavoin ja voivat tuottaa lukukelvottoman lopputuloksen koodis-ta, joka alkuperaisessa kirjoitusymparistossa on nayttanyt sel-kealta.

5.6. Koodilohkon alku- () ja loppumerkki () sijoitetaan omallerivilleen samalle sarakkeelle (tai jollakin muulla yhtenai-sella ja selkealla tavalla eroteltuna muusta koodista).

5.7. Jokaisessa switch-lauseessa on default-maare.

Page 428: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

B.6. C-kielen rakenteet C++:ssa 428

5.8. Jokainen switch-lauseen case-maareen toteutus on omakoodilohkonsa ja se paattyy lauseeseen break (samalla to-teutuksella voi olla useita case-maareita).

switch( merkki )

case ’a’:case ’A’:

...break;

default:

break;

6. C-kielen rakenteet C++:ssa

6.1. Ohjelmasta ei saa poistua funktiolla exit() tai abort().Nama funktiot lopettavat koko ohjelman suorituksen ilman, ettaohjelman kaikki oliot tuhoutuvat hallitusti (niiden purkajat jaa-vat suorittamatta). Oikea tapa on aina poistua funktiosta main()normaalisti (return-lauseella).

6.2. main()-funktion paluuarvo on aina int.ISOC++ -standardin maarittelema ainoa mahdollinen paluuar-vo.

6.3. main()-funktiota ei saa kutsua, siita ei saa ottaa funktio-osoitinta, sita (sen nimea) ei saa kuormittaa eika se saa ollainline eika staattinen funktio — toisin sanoen funktioni-mea main ei saa kayttaa kuten tavallista funktiota.

ISOC++ -standardi kasittelee main()-funktion erikoistapauksena(se ei siis ole tavallinen funktio), ja kieltaa nama kayttotavat.

6.4. goto-lausetta ei saa kayttaa.Ohjelman normaalin toiminnan hallintaan on olemassa pa-rempia kontrollirakenteita ja virhetilanteissa “hyppaaminenmuualle” toteutetaan poikkeusten avulla.

6.5. Kayta tietovirtoja (cin, cout, cerr ja clog) C-kielen IO-funk-tioiden sijaan.

Page 429: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

B.7. Tyyppimuunnokset 429

Tietovirtojen operaatiot tekevat tyyppitarkistuksia, joita esimer-kiksi C-funktio printf() ei tee.

6.6. Funktioita malloc(), realloc() ja free() ei saa kayttaa,vaan niiden sijasta kaytetaan operaattoreita new ja delete[aliluku 3.2 sivulla 71] [aliluku 3.5 sivulla 85].

Uudet operaattorit kutsuvat olioiden dynaamisen kasittelyn yh-teydessa rakentajia ja purkajia, joista vanhat muistinvarausru-tiinit eivat tieda mitaan.

6.7. C:n kirjastoja kaytettaessa niiden esittelyt tulee ottaa kayt-toon maareella extern "C".

Vain talla merkinnalla varmistetaan yhteensopivuus C-kielisenkirjaston kutsumekanismin kanssa.

extern "C" #include <gtk/gtk.h>#include <curses.h>

7. Tyyppimuunnokset

7.1. Mikaan osa ohjelmakoodista ei saa luottaa implisiittiseentyyppimuunnokseen.

7.2. Ohjelmassa tulee kayttaa kielen uusia tyyppimuun-nosoperaattoreita (static cast, dynamic cast jareinterpret cast) vanhempien tyyppimuunnosten si-jaan [aliluku 7.4.1 sivulla 228].

7.3. Vakio-ominaisuutta ei saa poistaa tyyppimuunnoksella(Operaattoria const cast ei saa kayttaa) [aliluku 7.4.1 si-vulla 228].

Vakio on suunnitteluvaiheessa maaratty ominaisuus, jota ei pi-taisi olla tarvetta poistaa ohjelmointivaiheessa.

8. Funktiot ja jasenfunktiot

8.1. Funktion parametrien nimet on annettava seka esittelyssaetta maarittelyssa ja niiden on oltava molemmissa samat.

8.2. Funktion olioparametrin valitykseen kaytetaan (va-kio)viitetta aina kun se on mahdollista [aliluku 4.3sivulla 105] [aliluku 7.3 sivulla 224].

8.3. Funktiolla ei saa olla maarittelematonta parametrilistaa (el-lipsis notation).

Page 430: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

B.9. Luokat ja oliot 430

Maarittelematon parametrilista poistaa kaytosta kaikki kaanta-jan tekemat tarkistukset parametrinvalityksessa.

8.4. Funktion mahdolliset oletusparametrit ovat nakyvissa esit-telyssa eika niita saa lisata maarittelyssa.

8.5. Funktion paluuarvo on aina maariteltava.

8.6. Funktio ei saa palauttaa osoitinta tai viitetta sen omaan pai-kalliseen dataan.

Paikallinen data ei ole kielen maarittelyn mukaan enaa kaytet-tavissa kun funktiosta on palattu.

8.7. Julkisen rajapinnan jasenfunktio ei saa palauttaa olion ja-senmuuttujaan viitetta eika osoitinta (vakioviite ja vakio-osoitin kay) [aliluku 4.2 sivulla 101].

8.8. Jasenfunktiosta tehdaan vakiojasenfunktio aina kun se onmahdollista [aliluku 4.3 sivulla 105].

9. Luokat ja oliot

9.1. Oliot toteutetaan C++:n rakenteella class ja tietueet (esimer-kiksi linkitetyn listan yksi alkio) toteutetaan rakenteellastruct.

Nain erotellaan selkeasti tietueet (rakenteinen tietorakenne)olioista.

9.2. Tietueella saa olla rakentajia ja virtuaalinen toteutuksel-taan tyhja purkaja, muttei muita jasenfunktioita.

Tietueissa sailytetaan yhteenkuuluvaa tietoa, jolloin niiden ai-noa sallittu toiminnallisuus liittyy uuden tietueen alustukseenja tietueen tuhoutumiseen (virtuaalipurkaja tarvitaan periyte-tyn tietueen oikean tuhoutumisen varmistamiseksi).

9.3. Luokan osat public, protected ja private esitellaan tassajarjestyksessa [aliluku 4.2 sivulla 101].

Luokan kayttajaa kiinnostaa ainoastaan julkisessa rajapinnas-sa olevat palvelut, joten niille selkein paikka on heti luokkaesit-telyn alussa.

9.4. Luokan jasenmuuttujat ovat aina nakyvyydeltaan private[aliluku 4.2 sivulla 101].

9.5. Luokan esittelyssa ei koskaan ole toteutusta (toteutusmahdollisista inline-jasenfunktioista on otsikkotiedostos-sa luokkaesittelyn jalkeen) [aliluku 2.3.3 sivulla 64].

Page 431: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

B.10. Elinkaari 431

Esittelyssa on nimensa mukaisesti tarkoitus ainoastaan esitellajokin rakenne. Sen toteutusyksityiskohdat ovat muualla eika ra-kenteen kayttajan tarvitse niita nahda. Katso myos kohdat 2.3.4ja 2.3.5.

9.6. Luokalla ei paasaantoisesti saa olla ystavafunktioita ei-ka ystavaluokkia (friend-ominaisuus) [aliluku 8.4 sivul-la 258].

“Ystavyys” rikkoo olioiden kapselointiperiaatetta vastaan. Ylei-nen poikkeus saannolle ovat kiinteasti yhteenkuuluvan luok-karyppaan keskinaiset erioikeudet esimerkiksi saman ohjelma-komponentin sisalla. C++:ssa tulostusoperaattori operator <<on pakko toteuttaa luokasta erillisena funktiona, jolloin siitausein tehdaan ystavafunktio (jos suunnittelussa tulostusta pide-taan luokan julkisen rajapinnan operaationa).

9.7. Luokalle kuormitettavien operaattoreiden semantiikka onsailytettava (esimerkiksi operator + tekee aina yhteenlas-kua “vastaavan” toiminnon).

10. Elinkaari

10.1. Muistinhallinta

10.1.1. Dynaamisesti varatun muistin vapauttaminen on paa-saantoisesti sen komponentin vastuulla (olio tai mo-duuli), joka varasi muistin [aliluku 3.5.4 sivulla 90].

10.1.2. Operaattorilla delete saa vapauttaa ainoastaan muis-tin, joka on varattu operaattorilla new (deleten para-metri saa olla vain new:n palauttama osoitin tai arvonolla) [aliluku 3.5.2 sivulla 89].

10.1.3. Operaattorilla delete[ ] saa vapauttaa ainoastaanmuistin, joka on varattu operaattorilla new[ ] [alilu-ku 3.5.3 sivulla 90].

10.1.4. Jos delete-lauseen parametri on osoitinmuuttuja, jo-hon voi sijoittaa, sen arvoksi sijoitetaan nolla deleteaseuraavassa lauseessa [aliluku 3.5.4 sivulla 90].

10.2. Olion luonti ja tuhoaminen

10.2.1. Dynaamisesti (operaattorilla new) luodun olion tuhou-tuminen varmistetaan auto ptr-rakenteella (tai muillaalykkailla osoitintoteutuksilla) aina kun sen kaytto on

Page 432: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

B.10. Elinkaari 432

mahdollista [aliluku 3.5 sivulla 85] [aliluku 11.7 sivul-la 383]. (Mutta esimerkiksi kohdan 12.3.4 saanto estaaauto ptr:n taltioinnin STL:n sailioihin.)

10.2.2. Jokaiselle luokalle on maariteltava vahintaan yksi ra-kentaja [aliluku 3.4.1 sivulla 80].

10.2.3. Rakentajissa ja purkajissa ei saa kutsua virtuaalifunk-tioita [aliluku 6.5.7 sivulla 170].

10.2.4. Rakentajissa ja purkajissa ei saa kayttaa globaaleja eikastaattisia olioita.

Olio voi itse olla staattinen tai globaali eivatka sen kaytta-mat muut vastaavan tason oliot ole viela valttamatta ole-massa alustettuna kun rakentajan suoritus alkaa. Vastaa-vasti purkajassa voidaan vahingossa viitata jo aiemmin tu-hottuun olioon.

10.2.5. Jos rakentajan toteutuksessa (toteutustiedostossa) tu-lostetaan, ennen luokan esittelya (otsikkotiedosto) pi-taa ottaa kayttoon otsikkotiedosto <iostream>.

Ainoastaan talla tavalla voidaan varmistua siita, etta tu-lostusolio (esim. cout) on olemassa ennen luokasta tehtyaoliota.

10.2.6. Yksiparametrinen rakentaja on merkittava explicit-maareella [aliluku 7.4.2 sivulla 232], ellei se erityi-sesti ole tarkoitettu implisiittiseksi tyyppikonversiok-si. Poikkeuksena kopiorakentaja, jolle tata maaret-ta ei saa laittaa.

10.2.7. Itse tehty kantaluokan purkaja on aina public ja virtu-aalinen tai protected ja ei-virtuaalinen. [aliluku 6.5.5sivulla 168].

10.2.8. Purkajan tulee vapauttaa kaikki tuhoutumishetkellaolion vastuulla olevat resurssit [aliluku 3.4.2 sivul-la 83].

10.3. Olion kopiointi

10.3.1. Luokalle esitellaan aina kopiorakentaja [aliluku 7.1.2sivulla 206].

10.3.2. Tarpeettoman kopiorakentajan kaytto estetaan esittele-malla se ilman toteutusta luokan private-rajapintaan[aliluku 7.1.2 sivulla 206].

Page 433: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

B.11. Periytyminen 433

10.3.3. Periytetyn luokan kopiorakentajan tulee kutsua kanta-luokan kopiorakentajaa alustuslistassaan [aliluku 7.1.2sivulla 206].

10.4. Olion sijoitus

10.4.1. Jokaisessa luokassa esitellaan oma sijoitusoperaattori[aliluku 7.2.2 sivulla 216].

10.4.2. Tarpeettoman sijoitusoperaattorin kaytto estetaan esit-telemalla se ilman toteutusta luokan private-osassa[aliluku 7.2.2 sivulla 216].

10.4.3. Periytetyn luokan sijoitusoperaattorin tulee kutsuakantaluokan sijoitusoperaattoria [aliluku 7.2.2 sivul-la 216].

10.4.4. Sijoitusoperaattorin tulee aina varautua “itseen sijoita-mista” (x = x) vastaan [aliluku 7.2.2 sivulla 216].

10.4.5. Sijoitusoperaattorin paluuarvo on *this [aliluku 7.2.2sivulla 216].

11. Periytyminen

11.1. Ainoastaan public-periytymisen kaytto on sallittua mallin-tamaan olio-ohjelmoinnin periytymista [aliluku 6.3 sivul-la 149].

11.2. Periytyminen on staattinen “is-a”-suhde. “has-a” toteute-taan jasenmuuttujilla ja tyyppiriippumaton koodi malleilla[aliluku 6.3.4 sivulla 155].

11.3. Moniperiytymista saa kayttaa ainoastaan rajapinnan taitoiminnallisen yksikon periyttamiseen (interface ja mix-ing) [aliluku 6.3 sivulla 149].

Moniperiytyminen on monimutkainen ominaisuus, jota harvoinkaytetaan oliosuunnittelussa. Rajapintojen periyttaminen taason paljon kaytetty osa oliosuunnittelua.

11.4. Rajapintaluokan kaikki jasenfunktiot ovat puhtaita virtu-aalifunktioita (pure virtual) [aliluku 6.6 sivulla 172].

11.5. Periytetyn luokan rakentajan alustuslistassa taytyy kutsuakantaluokan rakentajaa [aliluku 6.3.2 sivulla 152].

11.6. Kantaluokan maarittelemiin virtuaalifunktioihin, jotka pe-ritetty luokka maarittelee uudelleen, liitetaan avainsana

Page 434: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

B.12. Mallit 434

virtual myos periytetyssa luokassa [aliluku 6.5.5 sivul-la 168].

11.7. Periytynytta ei-virtuaalista jasenfunktiota ei saa maaritellauudelleen [aliluku 6.5 sivulla 159].

11.8. Periytynytta virtuaalisen jasenfunktion oletusparametria eisaa maaritella uudelleen.

12. Mallit

12.1. Mallin tyyppiparametreilleen asettamat vaatimukset tuleedokumentoida mallin esittelyn yhteydessa [aliluku 9.5.4sivulla 290].

12.2. Mallin kayttajille tulisi tarvittaessa tarjota valmiiksi op-timoituja toteutuksia mallin erikoistusten avulla [alilu-ku 9.5.6 sivulla 295].

12.3. STL

12.3.1. C-kielen vanhan taulukkotyypin sijaan tulee kayttaaSTL:n sarjasailioita vector tai deque [aliluku 10.2 si-vulla 310] [aliluku A.3 sivulla 414].

Uudet rakenteet ovat helppokayttoisempia. Jos jokin (van-han ohjelmakoodin) funktio tarvitsee esimerkiksi paramet-rikseen taulukon, sille voidaan antaa osoitin vektorin al-kuun (&v[0]).

12.3.2. STL:n sisaisesta toiminnasta ei saa tehda oletuksia.Ainoastaan ISOC++:n maarittelemia ominaisuuksia saahyodyntaa.

12.3.3. Kaytetyille STL-sailioille annetaan kuvaavat nimettyyppialiaksen avulla.

typedef std::map<std::string,unsigned int>Puhelinluettelo;

12.3.4. STL:n sailioon saa tallettaa ainoastaan olioita, joillaon kopiorakentaja ja sijoitusoperaattori, jotka tuotta-vat uuden tilaltaan identtisen olion [aliluku 10.2 sivul-la 310].

12.3.5. Kaytettaessa STL:n sailioita on varmistuttava siita, ettakaytettyjen operaatioiden pahimman tapauksen ajan-kaytto on riittavan tehokasta ohjelmiston toiminnankannalta [aliluku 10.1 sivulla 303].

Page 435: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

B.13. Poikkeukset 435

12.3.6. STL:n sailioiden yhteydessa tulee kayttaa STL:n algo-ritmeja omien toteutusten sijaan [aliluku 10.4 sivul-la 337].

STL:n algoritmit ovat luultavasti omia toteutuksia tehok-kaampia ja ne pystyvat tekemaan sisaisia optimointeja.

12.3.7. STL:n sailion alkioden lapikaynti toteutetaan iteraat-toreiden avulla (begin()–end() -vali) [aliluku 10.3 si-vulla 326].

Esimerkki funktiosta, joka kay lapi kaikki sailion alkiot riip-pumatta siita mita STL:n sailioista on kaytetty parametrintoteutukseen:

void tulostaPuhelinluettelo(Puhelinluettelo& tk)

Puhelinluettelo::const iterator alkio;for (alkio = tk.begin(); alkio != tk.end(); ++alkio)// Vertailu “alkio < tk.end()” ei toimi// kaikkilla sailioilla (niiden iteraattoreilla)

std::cout << *alkio << std::endl;

12.3.8. Iteraattorin lisaaminen ja vahentaminen suoritetaanetuliiteoperaattorilla (preincrement ja predecrement)[aliluku 10.3 sivulla 326].

Lausekkeen evaluoinnin jalkeen suoritettava operaatio(postincrement) on tehottomampi, koska se joutuu palaut-tamaan kopion iteraattorin vanhasta arvosta.

12.3.9. Mitatoitynytta iteraattoria ei saa kayttaa (ainoastaantuhoaminen ja uuden arvon sijoittaminen ovat sallit-tuja operaatioita) [aliluku 10.3 sivulla 326].

13. Poikkeukset

13.1. Poikkeuksia saa kayttaa ainoastaan virhetilanteiden ilmai-semiseen [aliluku 11.4 sivulla 374].

Poikkeusten kasittely on muita kontrollirakenteita raskaampaa,joten niita ei kannata kayttaa ohjelmiston normaalin kontrollinohjaamiseen.

13.2. Jos poikkeuskasittelija ei pysty taysin toipumaan sieppaa-mastaan poikkeuksesta, se “heitetaan” uudelleen ylemmal-le tasolle [aliluku 11.4.2 sivulla 377].

Page 436: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

B.13. Poikkeukset 436

13.3. Paaohjelmasta ei saa vuotaa poikkeuksia ulos [alilu-ku 11.4.2 sivulla 377].

Useat ajoymparistot antavat erittain epaselvia ilmoituksia oh-jelmasta ulos vuotavien poikkeusten yhteydessa.

13.4. Luokan purkajasta ei saa vuotaa poikkeuksia [aliluku 11.5sivulla 380].

13.5. Paasaantoisesti funktioiden poikkeusmaarelistaa ei saakayttaa. Vain jos funktion kaikissa kayttotilanteissa var-masti tiedetaan siita mahdollisesti vuotavat poikkeukset,voi poikkeukset luetella funktioesittelyn poikkeuslistassa.[aliluku 11.6 sivulla 382].

Page 437: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

Kirjallisuutta 437

Kirjallisuutta

[Abadi ja Cardelli, 1996] Martın Abadi ja Luca Cardelli. A Theory ofObjects. Monographs in Computer Science. Springer-Verlag, 1996.ISBN 0-387-94775-2.

[Abrahams, 2003] David Abrahams. Exception-Safety in GenericComponents. http://www.boost.org/more/generic_exception_safety.html, toukokuu 2003.

[Adams, 1991] James Adams. Insinoorin maailma. Art House Oy,1991. ISBN 951-884-163-2. Suomentanut Kimmo Pietilainen.

[Aikio ja Vornanen, 1992] Annukka Aikio ja Rauni Vornanen, toim.Uusi sivistyssanakirja. Otava, 11. painos, 1992. ISBN 951-1-11365-8.

[Alexandrescu, 2001] Andrei Alexandrescu. Modern C++ Design. C++In-Depth Series. Addison-Wesley, 2001. ISBN 0-201-70431-5.http://www.moderncppdesign.com/.

[Alexandrescu, 2003] Andrei Alexandrescu. Modern C++ Design,Chapter 7 — Smart Pointers. http://www.aw.com/samplechapter/0201704315.pdf, toukokuu 2003. Kirjan [Alexandrescu, 2001] lu-ku 7 verkossa julkaistuna.

[Arnold ja Gosling, 1996] Ken Arnold ja James Gosling. The Java Pro-gramming Language. Addison-Wesley, 1996. ISBN 0-201-63455-4.

[Beck ja Cunningham, 1989] Kent Beck ja Ward Cunningham. A Lab-oratory for Teaching Object-Oriented Thinking. Teoksessa OOP-SLA’89 – Conference on Object-Oriented Programming Systems,Languages and Applications. ACM SIGPLAN, 1989.

Page 438: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

Kirjallisuutta 438

[Binder, 1999] Robert V. Binder. The Percolation Pattern – Tech-niques for Implementing Design by Contract in C++. C++ Report, sivut38–44, toukokuu 1999.

[Booch ja muut, 1999] Grady Booch, James Rumbaugh ja Ivar Jacob-son. The Unified Modeling Language User Guide. Addison-Wesley,1999. ISBN 0-201-57168-4.

[Booch, 1987] Grady Booch. Software Engineering with Ada, 2nd edi-tion. The Benjamin/Cummings Publishing Company, 1987. ISBN0-8053-0604-8.

[Booch, 1991] Grady Booch. Object-Oriented Design with Applica-tions. The Benjamin/Cummings Publishing Company, 1991. ISBN0-8053-0091-0.

[Boost, 2003] C++ Boost library. http://www.boost.org/, helmikuu2003.

[Boszormenyi ja Weich, 1996] Laszlo Boszormenyi ja Carsten Weich.Programming in Modula-3 → An Introduction in Programming withStyle. Springer-Verlag, 1996. ISBN 3-540-57912-5.

[Budd, 2002] Timothy Budd. An Introduction to Object-oriented Pro-gramming, 3rd edition. Addison-Wesley, 2002. ISBN 0-201-76031-2.

[Carroll, 1865] Lewis Carroll. Alice’s Adventures in Wonderland.Kirjassa The Complete Works of Lewis Carroll [1988].

[Carroll, 1988] Lewis Carroll. The Complete Works of Lewis Carroll.The Penguin Group, 1988. ISBN 0-14-010542-5.

[Clements ja Parnas, 1986] Paul Clements ja David Parnas. A Ratio-nal Design Process: How and Why to Fake It. IEEE Transactions onSoftware Engineering, 12(2):251–257, helmikuu 1986.

[Coplien, 1992] James O. Coplien. Advanced C++ Programming Stylesand Idioms. Addison-Wesley, 1992. ISBN 0-201-54855-0.

[Coplien, 1999] James O. Coplien. Multi-Paradigm Design for C++.Addison-Wesley, 1999. ISBN 0-201-82467-1.

Page 439: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

Kirjallisuutta 439

[Coplien, 2000] James Coplien. C++ idioms patterns. Teoksessa NeilHarrison, Brian Foote ja Hans Rohnert, toim., Pattern Languages ofProgram Design 4, luku 10, sivut 167–197. Addison-Wesley, 2000.ISBN 0-201-43304-4.

[Cormen ja muut, 1990] Thomas H. Cormen, Charles E. Leiserson jaRonald L. Rivest. Introduction to Algorithms. The MIT Press, 1990.ISBN 0-262-53091-0.

[Czarnecki ja Eisenecker, 2000] Krysztof Czarnecki ja Ulrich Eise-necker. Generative Programming: Methods, Tools, and Applications.Addison-Wesley, 2000. ISBN 0-201-30977-7.

[Dijkstra, 1972] Edsger W. Dijkstra. The Humble Programmer (1972Turing Award Lecture). Communications of the ACM (CACM),15(10):859–866, lokakuu 1972.

[Doxygen, 2005] Doxygen homepage. http://www.doxygen.org/,huhtikuu 2005.

[Driesen ja Holzle, 1996] Karel Driesen ja Urz Holzle. The DirectCost of Virtual Function Calls in C++. Teoksessa Carrie Wilpolt,toim., OOPSLA’96 – Conference on Object-Oriented ProgrammingSystems, Languages and Applications. ACM SIGPLAN, Addison-Wesley, lokakuu 1996.

[Egan, 1995] Greg Egan. Axiomatic. Millennium, 1995. ISBN 1-85798-416-1.

[Fomitchev, 2001] Boris Fomitchev. STLport. STLport home pagehttp://www.stlport.org/, elokuu 2001.

[Gaiman ja muut, 1994] Neil Gaiman, Jill Thompson ja Vince Locke.Brief Lives. Sandman-albumi. DC Comics, 1994. ISBN 1-56389-137-9.

[Gamma ja muut, 1995] Erich Gamma, Richard Helm, Ralph Johnsonja John Vlissides. Design Patterns – Elements of Reusable Object-Oriented Software. Addison-Wesley, 1995. ISBN 0-201-63361-2.

[Gillam, 1997] Richard Gillam. The Anatomy of the Assignment Op-erator. C++ Report, sivut 15–23, marraskuu–joulukuu 1997.

Page 440: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

Kirjallisuutta 440

[Haikala ja Marijarvi, 2002] Ilkka Haikala ja Jukka Marijarvi. Ohjel-mistotuotanto. Suomen ATK-kustannus Oy, 2002. ISBN 952-14-0486-8.

[Heller, 1961] Joseph Heller. CATCH-22. Gummerus, 1961. ISBN951-20-4558-3. Suomentanut Markku Lahtela.

[ISO, 1998] ISO/IEC. International Standard 14882 – ProgrammingLanguages – C++, 1. painos, syyskuu 1998.

[Jacobson ja muut, 1994] I. Jacobson, M. Christerson, P. Johnsson jaG. Overgaard. Object Oriented Software Engineering, A Use CaseDriven Approach (Revised Printing). Addison-Wesley, 1994. ISBN0-201-54435-0.

[JavaBeans, 2001] JavaBeans Component Architecture. http://java.sun.com/beans/, elokuu 2001.

[Josuttis, 1999] Nicolai M. Josuttis. The C++ Standard Library.Addison-Wesley, 1999. ISBN 0-201-37926-0.

[Kerninghan ja Ritchie, 1988] Brian W. Kerninghan ja Dennis M.Ritchie. The C Programming Language, 2nd edition. Prentice HallSoftware Series, 1988. ISBN 0-13-110370-9.

[Koenig ja Moo, 2000] Andrew Koenig ja Barbara Moo. AcceleratedC++. Addison-Wesley, 2000. ISBN 0-201-70353-X.

[Koskimies, 2000] Kai Koskimies. Oliokirja. Suomen ATK-kustannusOy, 2000. ISBN 951-762-720-3.

[Krohn, 1992] Leena Krohn. Matemaattisia olioita. WSOY, 1992.ISBN 951-0-18407-1.

[Lem, 1965] Stanislaw Lem. Kyberias, luku Ensimmainen matka (A)eli Trurlin elektrubaduuri. Kirjayhtyma, 1965. ISBN 951-26-2955-0. Suomentanut Matti Kannosto.

[Liberty, 1998] Jesse Liberty. Beginning Object-Oriented Analysis andDesign with C++. Wrox Press Ltd, 1998. ISBN 1-861001-33-9.

[Linnaeus, 1748] Carl Linnaeus. Systema Naturae. Holmiae, 1748.

Page 441: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

Kirjallisuutta 441

[Lions, 1996] Prof. J. L. Lions. ARIANE 5 flight 501 failure. Raport-ti, Inquiry Board, heinakuu 1996. http://www.cafm.sbu.ac.uk/cs/people/jpb/teaching/ethics/ariane5anot.html.

[Lippman ja Lajoie, 1997] Stanley B. Lippman ja Josee Lajoie. C++Primer 3rd edition. Addison-Wesley, 1997. ISBN 0-201-82470-1.

[Lippman, 1996] Stanley B. Lippman. Inside the C++ Object Model.Addison-Wesley, 1996. ISBN 0-201-83454-5.

[McConnell, 1993] Steve McConnell. Code Complete. MicrosoftPress, 1993. ISBN 1-55615-484-4.

[Meyer, 1997] Bertrand Meyer. Object-Oriented Software Construc-tion, 2nd edition. Prentice Hall, 1997. ISBN 0-13-629155-4.

[Meyers, 1996] Scott Meyers. More Effective C++. Addison-Wesley,1996. ISBN 0-201-63371-X. Saatavilla elektronisessa muodossaosana teosta [Meyers, 1999].

[Meyers, 1998] Scott Meyers. Effective C++ 2nd edition. Addison-Wesley, 1998. ISBN 0-201-92488-9. Saatavilla elektronisessa muo-dossa osana teosta [Meyers, 1999].

[Meyers, 1999] Scott Meyers. Effective C++ CD. Addison-Wesley, 1999.ISBN 0-201-31015-5. CD:lla julkaistu elektroninen kirja.

[OMG, 2002a] OMG. UML Resource Page. http://www.omg.org/technology/uml/, syyskuu 2002.

[OMG, 2002b] OMG. What Is OMG-UML and Why Is It Impor-tant? OMG Press Release, http://www.omg.org/gettingstarted/what_is_uml.htm, elokuu 2002.

[Pirsig, 1974] Robert M. Pirsig. ZEN ja moottoripyoran kunnossapito.WSOY, 1974. ISBN 951-0-19300-3. Suomentanut Leena Tammi-nen.

[Pratchett, 1987] Terry Pratchett. Equal Rites. Corgi, 1987. ISBN 0-552-13105-9.

[Roszak, 1992] Theodore Roszak. Konetiedon kritiikki. Art House Oy,1992. ISBN 951-884-085-7. Suomentanut Maarit Tillman.

Page 442: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

Kirjallisuutta 442

[Rumbaugh ja muut, 1991] James Rumbaugh, Michael Blaha, Wil-liam Premerlani, Frederick Eddy ja William Lorensen. Object-Oriented Modeling and Design. Prentice-Hall, 1991. ISBN 0-13-630054-5.

[Rumbaugh ja muut, 1999] James Rumbaugh, Ivar Jacobson ja Gra-dy Booch. The Unified Modeling Language Reference Manual.Addison-Wesley, 1999. ISBN 0-201-30998-X.

[Sethi, 1996] Ravi Sethi. Programming Languages, Concepts & Con-structs, 2nd edition. Addison-Wesley, 1996. ISBN 0-201-59065-4.

[SETI, 2001] SETI Newsletter: Result verification. http://setiathome.ssl.berkeley.edu/newsletters/newsletter8.html,heinakuu 2001.

[Shakespeare, 1593] William Shakespeare. The Tragedy of Titus An-dronicus. Kirjassa The Complete Works of William Shakespeare[1994]. Vuosiluku summittainen.

[Shakespeare, 1994] William Shakespeare. The Complete Works ofWilliam Shakespeare. Wordsworth Editions Ltd, 1994. ISBN 1-85326-810-0.

[Stroustrup, 1994] Bjarne Stroustrup. The Design and Evolution ofC++. Addison-Wesley, 1994. ISBN 0-201-54330-3.

[Stroustrup, 1997] Bjarne Stroustrup. The C++ Programming Language3rd edition. Addison-Wesley, 1997. ISBN 0-201-88954-4. Teoksestaon myos suomennos [Stroustrup, 2000].

[Stroustrup, 2000] Bjarne Stroustrup. C++-ohjelmointi. Teknolit, 2000.ISBN 951-846-026-4. Suomennos teoksesta [Stroustrup, 1997].

[Sudo, 1999] Philip Toshio Sudo. ZEN Computer. Simon & SchusterNew York, 1999. ISBN 0-684-85410-4.

[Sun Microsystems, 2005] Sun Microsystems. Javadoc tool home pa-ge. http://java.sun.com/j2se/javadoc/, huhtikuu 2005.

[Sutter, 1999] Herb Sutter. When Is a Container Not a Container? C++Report, 11(5):60–64, toukokuu 1999.

Page 443: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

Kirjallisuutta 443

[Sutter, 2000] Herb Sutter. Exceptional C++. C++ In-Depth Series.Addison-Wesley, 2000. ISBN 0-201-61562-2.

[Sutter, 2002a] Herb Sutter. A Pragmatic Look at Exception Specifica-tions – The C++ feature that wasn’t. C/C++ Users Journal, 20(7):59–64,heinakuu 2002.

[Sutter, 2002b] Herb Sutter. “Export” Restrictions, Part 1. C/C++ UsersJournal, 20(9):50–55, syyskuu 2002.

[Sutter, 2002c] Herb Sutter. More Exceptional C++. C++ In-Depth Series.Addison-Wesley, 2002. ISBN 0-201-70434-X.

[Sutter, 2003] Herb Sutter. Exception Safety and Exception Speci-fications: Are They Worth It? http://www.gotw.ca/gotw/082.htm,maaliskuu 2003.

[The Doors FAQ, 2002] THE DOORS Frequently Asked Questions(FAQ). http://classicrock.about.com/library/artists/bldoors_faq.htm, marraskuu 2002.

[Vandevoorde ja Josuttis, 2003] David Vandevoorde ja Nicolai M. Jo-suttis. C++ Templates — The Complete Guide. Addison-Wesley,2003. ISBN 0-201-73484-2.

[Wikstrom, 1987] Ake Wikstrom. Functional Programming UsingStandard ML. Prentice Hall, 1987. ISBN 0-13-331661-0.

[Wilkinson, 1995] Nancy M. Wilkinson. Using CRC Cards, An In-formal Approach to Object-Oriented Development. SIGS BOOKS,1995. ISBN 0-13-374679-8.

Page 444: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

Englanninkieliset termit 444

Englanninkieliset termit

abstract base class . . . . . . . . abstrakti kantaluokka, s. 144actor . . . . . . . . . . . . . . . . . . . . . . kayttajarooli, s. 123adaptive system . . . . . . . . . . . mukautuva jarjestelma, s. 351aggregate . . . . . . . . . . . . . . . . . kooste, s. 134allocator . . . . . . . . . . . . . . . . . . varain, s. 305ambiguous . . . . . . . . . . . . . . . . moniselitteinen, s. 181amortized constant com-plexity . . . . . . . . . . . . . . . . . . . .

amortisoidusti vakioaikainen te-hokkuus, s. 310

ancestor . . . . . . . . . . . . . . . . . . esi-isa, s. 144assignment operator . . . . . . sijoitusoperaattori, s. 216associative container . . . . . . assosiatiivinen sailio, s. 317attribute . . . . . . . . . . . . . . . . . . attribuutti, s. 122

base class . . . . . . . . . . . . . . . . . kantaluokka, s. 144basic (exception) guarantee perustakuu, s. 390bidirectional iterator . . . . . . kaksisuuntainen iteraattori, s. 331bitset . . . . . . . . . . . . . . . . . . . . . . bittivektori, s. 325bottom-up . . . . . . . . . . . . . . . . . kokoava jaottelu, s. 118bridge . . . . . . . . . . . . . . . . . . . . . silta, s. 274

call by value . . . . . . . . . . . . . . arvonvalitys, s. 224call-through function . . . . . . lapikutsufunktio, s. 180catch (exceptions) . . . . . . . . . siepata, s. 374class . . . . . . . . . . . . . . . . . . . . . . luokka, s. 60

Page 445: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

Englanninkieliset termit 445

class definition . . . . . . . . . . . . luokan esittely, s. 61class hierarchy . . . . . . . . . . . . luokkahierarkia, s. 144class invariant . . . . . . . . . . . . luokkainvariantti, s. 246class object . . . . . . . . . . . . . . . luokkaolio, s. 249class template . . . . . . . . . . . . . luokkamalli, s. 287class template partial spe-cialization . . . . . . . . . . . . . . . .

luokkamallin osittaiserikoistus,s. 297

closure . . . . . . . . . . . . . . . . . . . . sulkeuma, s. 343commonality and variabil-ity analysis . . . . . . . . . . . . . . . .

pysyvyys- ja vaihtelevuusanalyy-si, s. 264

compile-time complexity . . kaannosaikainen tehokkuus,s. 310

component . . . . . . . . . . . . . . . . komponentti, s. 38composite . . . . . . . . . . . . . . . . . kokoelma, s. 271composite aggregate . . . . . . . muodostuminen (UML), s. 134const . . . . . . . . . . . . . . . . . . . . . . vakio, s. 105constant complexity . . . . . . . vakioaikainen tehokkuus, s. 310constructor . . . . . . . . . . . . . . . . rakentaja, s. 80container . . . . . . . . . . . . . . . . . sailio, s. 304container adaptor . . . . . . . . . sailiosovitin, s. 325conversion member func-tion . . . . . . . . . . . . . . . . . . . . . . .

muunnosjasenfunktio, s. 236

copy constructor . . . . . . . . . . kopiorakentaja, s. 206cursor . . . . . . . . . . . . . . . . . . . . . kohdistin, s. 273

data member . . . . . . . . . . . . . . jasenmuuttuja, s. 61deep copy . . . . . . . . . . . . . . . . . syvakopiointi, s. 205default constructor . . . . . . . . oletusrakentaja, s. 82derived class . . . . . . . . . . . . . . aliluokka, s. 144descendant . . . . . . . . . . . . . . . jalkelainen, s. 144Design By Contract . . . . . . . . sopimussuunnittelu, s. 240design pattern . . . . . . . . . . . . . suunnittelumalli, s. 267destructor . . . . . . . . . . . . . . . . . purkaja, s. 83double-ended queue . . . . . . . pakka, s. 315

Page 446: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

Englanninkieliset termit 446

dynamic binding . . . . . . . . . . dynaaminen sitominen, s. 161

encapsulation . . . . . . . . . . . . . kapselointi, s. 33exception . . . . . . . . . . . . . . . . . poikkeus, s. 367exception guarantee . . . . . . . poikkeustakuu, s. 389exception handler . . . . . . . . . poikkeuskasittelija, s. 374exception neutrality . . . . . . . poikkeusneutraalius, s. 392exception specification . . . . poikkeusmaare, s. 382

forward declaration . . . . . . . ennakkoesittely, s. 112forward iterator . . . . . . . . . . . eteenpain-iteraattori, s. 331framework . . . . . . . . . . . . . . . . sovelluskehys, s. 200function object . . . . . . . . . . . . funktio-olio, s. 340function object adaptor . . . . funktio-oliosovitin, s. 347function pointer . . . . . . . . . . . funktio-osoitin, s. 341function template . . . . . . . . . funktiomalli, s. 286function try block . . . . . . . . . funktion valvontalohko, s. 396functor . . . . . . . . . . . . . . . . . . . . funktio-olio, s. 340

garbage collection . . . . . . . . . roskienkeruu, s. 71generalization . . . . . . . . . . . . . yleistaminen, s. 147generic algorithm . . . . . . . . . geneerinen algoritmi, s. 304generic programming . . . . . geneerinen ohjelmointi, s. 291genericity . . . . . . . . . . . . . . . . . yleiskayttoisyys, s. 264getter . . . . . . . . . . . . . . . . . . . . . anna-jasenfunktio, s. 103

header . . . . . . . . . . . . . . . . . . . . otsikkotiedosto, s. 39hide . . . . . . . . . . . . . . . . . . . . . . . peittaa, s. 168

identity . . . . . . . . . . . . . . . . . . . identiteetti, s. 58inheritance . . . . . . . . . . . . . . . periytyminen, s. 144inheritance hierarchy . . . . . periytymishierarkia, s. 144initialization list . . . . . . . . . . alustuslista, s. 81input iterator . . . . . . . . . . . . . . syottoiteraattori, s. 329

Page 447: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

Englanninkieliset termit 447

insert iterator . . . . . . . . . . . . . lisays-iteraattori, s. 336inserter . . . . . . . . . . . . . . . . . . . lisays-iteraattori, s. 336instantiation . . . . . . . . . . . . . . instantiointi, s. 285interface class . . . . . . . . . . . . . rajapintaluokka, s. 190invalid iterator . . . . . . . . . . . . kelvoton iteraattori, s. 334invalidate (iterator) . . . . . . . mitatoida, s. 334invariant . . . . . . . . . . . . . . . . . . invariantti, s. 243iterator . . . . . . . . . . . . . . . . . . . . iteraattori, s. 304iterator adaptor . . . . . . . . . . . iteraattorisovitin, s. 336

key . . . . . . . . . . . . . . . . . . . . . . . . avain, s. 317

linear complexity . . . . . . . . . lineaarinen tehokkuus, s. 310list . . . . . . . . . . . . . . . . . . . . . . . . lista, s. 315logarithmic complexity . . . . logaritminen tehokkuus, s. 310

map . . . . . . . . . . . . . . . . . . . . . . assosiaatiotaulu, s. 321member function . . . . . . . . . . jasenfunktio, s. 64member function template jasenfunktiomalli, s. 289metaclass . . . . . . . . . . . . . . . . . metaluokka, s. 249metafunction . . . . . . . . . . . . . . metafunktio, s. 352metaprogram . . . . . . . . . . . . . metaohjelma, s. 349metaprogramming . . . . . . . . metaohjelmointi, s. 349method . . . . . . . . . . . . . . . . . . . metodi, s. 64minimal (exception) guar-antee . . . . . . . . . . . . . . . . . . . . .

minimitakuu, s. 390

module . . . . . . . . . . . . . . . . . . . moduuli, s. 32multimap . . . . . . . . . . . . . . . . . assosiaatiomonitaulu, s. 322multiple inheritance . . . . . . moniperiytyminen, s. 175multiset . . . . . . . . . . . . . . . . . . . monijoukko, s. 319

namespace . . . . . . . . . . . . . . . . nimiavaruus, s. 41nothrow (exception) guar-antee . . . . . . . . . . . . . . . . . . . . .

nothrow-takuu, s. 392

Page 448: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

Englanninkieliset termit 448

order of growth . . . . . . . . . . . . kertaluokka, s. 309output iterator . . . . . . . . . . . . tulostus-iteraattori, s. 330overloading . . . . . . . . . . . . . . . kuormittaminen, s. 81

parent class . . . . . . . . . . . . . . . kantaluokka, s. 144pattern language . . . . . . . . . . mallikieli, s. 268postcondition . . . . . . . . . . . . . jalkiehto, s. 241precondition . . . . . . . . . . . . . . esiehto, s. 241pure virtual function . . . . . . puhdas virtuaalifunktio, s. 172

quadratic complexity . . . . . . neliollinen tehokkuus, s. 310

random access iterator . . . . hajasaanti-iteraattori, s. 331range . . . . . . . . . . . . . . . . . . . . . vali, s. 329re-usability . . . . . . . . . . . . . . . . uudelleenkaytettavyys, s. 264reference . . . . . . . . . . . . . . . . . . viite, s. 411reference copy . . . . . . . . . . . . viitekopiointi, s. 203reflection . . . . . . . . . . . . . . . . . . reflektio, s. 349repeated multiple inheri-tance . . . . . . . . . . . . . . . . . . . . .

toistuva moniperiytyminen, s. 186

replicated multiple inheri-tance . . . . . . . . . . . . . . . . . . . . .

erotteleva moniperiytyminen,s. 186

responsibility . . . . . . . . . . . . . vastuualue, s. 38reverse iterator . . . . . . . . . . . . kaanteis-iteraattori, s. 336

scope resolution operator . . nakyvyystarkenninoperaattori,s. 44

sequence . . . . . . . . . . . . . . . . . . sarja, s. 312sequence diagram . . . . . . . . . tapahtumasekvenssi, s. 138set . . . . . . . . . . . . . . . . . . . . . . . . joukko, s. 318setter . . . . . . . . . . . . . . . . . . . . . . aseta-jasenfunktio, s. 103shallow copy . . . . . . . . . . . . . . matalakopiointi, s. 204shared aggregate . . . . . . . . . . jaettu kooste (UML), s. 134

Page 449: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

Englanninkieliset termit 449

shared multiple inheritance yhdistava moniperiytyminen,s. 187

slicing . . . . . . . . . . . . . . . . . . . . viipaloituminen, s. 210smart pointer . . . . . . . . . . . . . alykas osoitin, s. 387software crisis . . . . . . . . . . . . . ohjelmistokriisi, s. 27specialization . . . . . . . . . . . . . erikoistaminen, s. 147state machine . . . . . . . . . . . . . tilakone, s. 139static data member . . . . . . . . luokkamuuttuja, s. 250static member function . . . . luokkafunktio, s. 252static metaprogramming . . kaannosaikainen metaohjelmoin-

ti, s. 350stream iterator . . . . . . . . . . . . virtaiteraattori, s. 336strong (exception) guaran-tee . . . . . . . . . . . . . . . . . . . . . . . .

vahva takuu, s. 391

subclass . . . . . . . . . . . . . . . . . . . aliluokka, s. 144superclass . . . . . . . . . . . . . . . . kantaluokka, s. 144

template . . . . . . . . . . . . . . . . . . malli, s. 283template metaprogramming template-metaohjelmointi, s. 350

template specialization . . . . mallin erikoistus, s. 295temporary object . . . . . . . . . . valiaikaisolio, s. 225throw (exceptions) . . . . . . . . . heittaa, s. 374top-down . . . . . . . . . . . . . . . . . . osittava jaottelu, s. 118try-block . . . . . . . . . . . . . . . . . . valvontalohko, s. 374type cast . . . . . . . . . . . . . . . . . . tyyppimuunnos, s. 227

unnamed namespace . . . . . nimeamaton nimiavaruus, s. 49use case . . . . . . . . . . . . . . . . . . . kayttotapaus, s. 123

valid iterator . . . . . . . . . . . . . . kelvollinen iteraattori, s. 334vector . . . . . . . . . . . . . . . . . . . . . vektori, s. 312virtual function . . . . . . . . . . . virtuaalifunktio, s. 159

Page 450: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

Hakemisto 450

Hakemisto

:: 42::Pari 289::annaEka 289, 296, 297::summaa 290˜Liikkuva 195

abort 369 abstract (UML) 144abstrahoida 29abstrahointi 63abstrakti 29abstrakti kantaluokka 144,

172–175, 193abstraktio 29abstraktiotaso 30, 202, 379Adams, James 116aikaaJaljella 104ajoaikainen

indeksointitarkastus 416jasenfunktion valinta

katso “dynaaminensitominen”

toteutuksen valinta 276tyyppitarkastus 164–167,

231, 284virheilmoitus 146, 303

<algorithm> 338aliluokka 144alkuiteraattori 333

alustaminenjasenmuuttujan 81olion katso “rakentaja”

Alustus 73alustuslista 81

aliluokan rakentajan 153amortisoidusti vakioaikainen

tehokkuus 310, 314analysis paralysis 141ankh 69anna-jasenfunktio 103annaArvo 294annaEka 288, 293, 296, 297annaLuku 374annaNimi 157annaPaiva 107, 255, 257annaPalautusPvm 262arkkitehtuurimalli 267arvonvalitys katso

“arvoparametri”, 224,291

arvoparametri 224–227aseta-jasenfunktio 103Assert 246assert 245Assert_toteutus 247assosiaatio 132–133assosiaatiomonitaulu (STL)

322

Page 451: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

Hakemisto 451

assosiaatiotaulu (STL)321–322

assosiatiivinen sailio (STL)317–318

at 243, 416at 313, 313, 315attribuutti 55, 61, 122, 126,

128auto_ptr katso

“automaattiosoitin”automaattiosoitin 383–388

ja STL 387ja taulukot 387kopiointi 385–387omistaminen 384paluuarvona 385rajoitukset 387–388sijoitus 385–387

avain (STL) 317

back 313, 313, 315, 317, 326back_inserter 336bad_alloc 87bad_cast 165begin 435begin 333<bitset> 325bitset 325bittivektori (STL) 325

Carroll, Lewis 275<cassert> 245catch 87, 395, 397catch 375catch (...) 378CATCH-22 201Cheshire cat 275class 60, 285clear 312, 313

clone 205commit or rollback 391const 105

-sanan paikka 106const_cast 230–231const_iterator 332copy 338copy (Smalltalk) 205correctness formula 241count 320CRC-kortti 124–126<cstdlib> 46<cstring> 46

Death 69deck 315deepCopy 206delegointi 277delete 89–90delete[] 90<deque> 315deque 315Design By Contract 240Dijkstra, Edsger W. 28Dijkstra, Edsger Wybe 20Doors 240double 237Dreaded Diamond of Death

190dualismi 58dynaaminen elinkaari 79

automaattiosoitin 384ja poikkeukset 380ja taulukot 90olion luominen 86–89olion luominen ja

tuhoaminen 85–86olion tuhoaminen 89–90omistusvastuu 91

Page 452: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

Hakemisto 452

vaarat 86virheiden valttaminen

90–94dynaaminen muistinhallinta

85–86dynaaminen sitominen 147,

160–164hinta 169–170

dynamic_cast 164, 231

ekskursioperiytyminen 136poikkeukset 87

Elain::liiku 174elektrubaduuri 26elinkaari

C++ 76–79dynaaminen 79Java 74–75Modula-3 72olion 71–72Smalltalk 74staattinen 77–79

empty 313, 325, 326end 333ennakkoesittely 112–113envelope/letter 275equal_range 322erase 312, 313, 318erikoisiteraattori 336erikoistaminen 147erikoistus

funktiomallin 296luokkamallin 296luokkamallin osittais- 297vektorin 324

erotteleva moniperiytyminen186

esi-isa 144

esiehto 241esim 92esim2 93esittely

ennakko- 112jasenfunktio 64luokka 60

estetiikka 116eteenpain-iteraattori 331exit 428explicit 235export 299

f 293finalize (Java) 74finally (Java) 380find 318, 322, 339flavours 178flip 325for 314, 415for_each 339friend 260front 313, 313, 315, 317, 326front_inserter 336funktio-olio 304, 343,

340–348kopiointi 346

funktio-oliosovitin 347funktio-osoitin 341funktiomalli 286–287

erikoistus 296funktion valvontalohko 396funktori katso “funktio-olio”

Gaiman, Neil 69Gang of Four 268geneerinen algoritmi 304–306,

337–340geneerinen ohjelmointi 291

Page 453: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

Hakemisto 453

geneerisyys 163, 263–348GoF-kirja 268

haeEnsimmainen 167haeRaportinPaivays 425Haikala, Ilkka 20, 28hajasaanti-iteraattori 331hajotin katso “purkaja”hamekangas 110handle/body 275Heller, Joseph 201Henkilo 82, 394Henkilo::Henkilo 82, 395, 397Henkilotiedot 239homesieni 144havitin katso “purkaja”

identiteetti 57–58idiomi 267if 217, 220, 223, 297, 399,

402, 404imeta 192indeksointi (STL) 313, 315,

321inline 413–414insert 312, 313, 318inserter 336instanssimuuttuja katso

“jasenmuuttuja”instantiointi 56instantiointi, mallin 285interface

Java 51Modula-3 51maarittely 31UML 128

« interface » (UML) 128invariantti 243<iostream> 45

<iostream.h> 45is-a 137, 155, 161istream_iterator 336iteraattori 304, 326–329

-kategoriat 329–332-sovitin 336–337, 337alku- 333eteenpain- 331hajasaanti- 331ja algoritmit 337ja osoittimet 332ja sailiot 332–334kaksisuuntainen 331kelvollinen 334kelvoton 334kaanteis- 336lisays- 336loppu- 328, 333mitatoida 334suunnittelumalli 273–274syotto- 329tulostus- 330tyhja 332vakio- 332virta- 336vali 329, 337

<iterator> 336iterator 332

jaettu kooste 134jarjestaJaPoista 340JarjestettyTaulukko::EtsiJaMuutaAlkio

248JarjestettyTaulukko::Invariantti

248Java 51–53, 103, 204, 205, 249,

303, 411jfunktio 66Jim 240, 275

Page 454: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

Hakemisto 454

johdettu luokka katso“aliluokka”

johtaminen katso“periytyminen”

jono (STL) 326joukko (STL) 318–319julkinen rajapinta 32jalkelainen 144jalkiehto 241jasenfunktio 64–66

anna- 103aseta- 103esittely 64katkeminen 103muunnos- 236toteutus 64–66vakio- 107

jasenfunktiomalli 289jasenmuuttuja 61–64

alustaminen 81elinkaari 63katkeminen 103nimeaminen 62olio 63osoitin 63rajapinnassa 102viite 63

kaksipainen jono (STL) katso“pakka”

kaksisuuntainen iteraattori331

Kana::liiku 174kantaluokka 144

abstrakti 144, 172–175,193

kantaluokkaosa 152kantaluokkaosoitin 155kantaluokkaviite 155

kapselointi 33, 63, 113, 152,256

luokkienystavyysominaisuus260

karkauspaiva 110kategorisointi 142kauankoJouluun 68kauankoPalautukseen 230kayta 295kaytakopiota 214kaytto 114, 300kaytto1 345kaytto2 345kelvollinen iteraattori 334kelvoton iteraattori 334kentta katso “jasenmuuttuja”kerroPaivays 48kertaluokka 309keskiarvo1 376keskiarvo2 377ketjusijoitus 217Kirja::˜Kirja 157, 404, 406,

409Kirja::annaNimi 157Kirja::annaPalautusPvm 104Kirja::Kirja 157, 404, 406, 409Kirja::sopiikoHakusana 161Kirja::tulostaTiedot 161Kirja::tulostaVirhe 161Kirja::vaihda 408, 409KirjaApu::tulostaTiedot 185kirjanmerkki katso

“iteraattori”, 327KirjastonKirja 158KirjastonKirja::˜KirjastonKirja

158KirjastonKirja::KirjastonKirja

158

Page 455: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

Hakemisto 455

KirjastonKirja::onkoMyohassa158

KirjastonKirja::tulostaKirjatiedot184, 185

KirjastonKirja::tulostaKTeostiedot184, 185

KirjastonKirja::tulostaTiedot162, 182

KirjastonTeosApu::tulostaTiedot185

kloonaa 214kloonaa-jasenfunktio 213kohdistin 273, 413kokoava jaottelu 118kokoelma 271–273kokoonpanollinen kooste 134komponentti 38, 120, 121kontravarianssi 160kooste 134–136

C++ 135koostuminen 135muodostuminen 134vs. periytyminen 198–199

koostuminen 135kopioinnin estaminen

209–210kopiointi 202–215

automaattiosoitin385–387

funktio-olio 346kloonaa-jasenfunktio 213matala- 204–205olion 202perusteet 202syva- 205–206viipaloituminen 210–215viite- 203–204

kopiorakentaja 83, 206–210,311

estaminen 209–210ja periytyminen 207–208kaantajan luoma 208–209poikkeusolion 374

kovarianssi 160kuormittaminen 81kuukaudenAlkuun 225kysyJaTestaa 345kayttajarooli 123kayttotapaus 123, 125, 127,

140kaannosaikainen

tyypitys 284kaannosaikainen

metaohjelmointi 350tehokkuus 310tyypitys 303

kaannosyksikko 39kaanteis-iteraattori 336kaantajan luoma

kopiorakentaja 208–209oletusrakentaja 83oletusrakentajan kutsu

153sijoitusoperaattori

219–220

laatu 27, 120, katso [Pirsig,1974]

LainausJarjestelma::LainausJarjestelma134

laskeFibonacci 314laskeKeskiarvo 376laskeTaulukkoVastaus 427Laskija::˜Laskija 254Laskija::Laskija 254Laskija::tulosta_tilasto 254laula 173Lem, Stanislaw 26

Page 456: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

Hakemisto 456

liiku 173, 174, 192, 193, 195limerick 263lineaarinen tehokkuus 310Linne, Carl von 142lisaanny 192lisaaViiteLaskuria 50, 424<list> 317list 315lista (STL) 315lisays-iteraattori 336lisaaja katso

“lisays-iteraattori”logaritminen tehokkuus 310lokaalisuusperiaate 119, 133Lokiviesti::Lokiviesti 154loppuiteraattori 333lower_bound 322lukumaarasuhde 132LukuPuskuri::katso 316LukuPuskuri::lisaa 316LukuPuskuri::lue 316LukuPuskuri::onkoTyhja 316Luo 34, 52, 114luo 43Luokannimi::Luokannimi 81luoKayttoliittyma 233luokka

-funktio 252–253-invariantti 243, 246-muuttuja 250–252-olio 249-vakio 251:: 42ajoaikainen kysyminen

165–167ali- 144attribuutti 122dualismi 58ennakkoesittely 112

esittely 60, 60, 61julkinen rajapinta 101,

102kanta- 144kuvaaminen (UML) 128kaytto 66–67loytaminen 123meta- 249moduulina 58rajapinnan nakyvyys 101rajapinta- 177, 190–198rajapintatyyppi 253rooli 132sisalto 60tarjotut palvelut 122tietotyyppina 59vastuualue 122ystavafunktio 259ystavaluokka 260

luokkahierarkia 144luokkainvariantti 246luokkakaavio 127luokkamalli 287–290

erikoistus 296osittaiserikoistus 297

luoLukko 261luoPaivaysOlio 50lapikutsufunktio 180

maailmankaikkeus 26main 44, 46, 47, 68, 88, 163,

167, 222, 225, 299,379, 415, 417, 418

malli 283–301erikoistus 295–297, 324export 299funktio- 286–287instantiointi 285jasenfunktio- 289

Page 457: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

Hakemisto 457

koodin sijoittelu 298–299luokka- 287–290malliparametri 294–295oletusparametri 292–293syntaksi 285typename 285, 299–301tyyppi vai arvo 299–301tyyppiparametri 285tyyppiparametrien

vaatimukset 290vakioparametri 294

mallikieli 268malliparametri katso

“tyyppiparametri”<map> 321, 322map 321–322matalakopiointi 204–205max 299, 414<memory> 384merge 339metafunktio 352metaluokka 249–250metaohjelma 349metaohjelmointi 349

kaannosaikainen 350metodi 64min 286, 292, 355, 356, 361minimitakuu 390mitatoida (iteraattori) 334mixin 178Mjono::kloonaa 214Mjono::Mjono 207Modula-3 34, 51modulaarisuus 118moduuli 31–34

C 39–41C++ 41–50elinkaari 37jako kaannosyksikoilla 39

kayttaja 32lokaalisuusperiaate 119riippuvuus 118toteuttaja 32vastuualue 122

monijoukko (STL) 319–320moniperiytyminen 149,

175–190, 194erotteleva 186salmiakki- 190toistuva 186toistuva kantaluokka 190virtuaalinen 187yhdistava 187

monirekisterointi 320monirekisterointi2 321moniselitteisyys 181–186, 187muistin loppuminen 87, 370muistivirheet 370muistivuotojen valttaminen

91–92, katso“automaattiosoitin”

mukautuva jarjestelma 351multimap 322multiset 319–320muna-kana -ongelma 112, 119muni 192, 193muodostin katso “rakentaja”muodostuminen 134Murtoluku::Murtoluku 234mutable 108muunnosjasenfunktio 236muutanKuitenkin 109myohassako 165

namespace 41nappulaaPainettu 233NaytaIlmoitus 278, 279naytaViesti 73, 76

Page 458: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

Hakemisto 458

naytaViesti1 78naytaViesti2 78neliollinen tehokkuus 310new 89<new> 87, 89new 86–89new(nothrow) 89new[] 90nimeamiskaytanto 62nimeamaton nimiavaruus 49nimiavaruus 41–50

:: 42alias 46hyodyt 44nimeamaton 49nakyvyystarkenninope-

raattori42

std 45synonyymi 46syntaksi 42using 47

nollaaAlkiot 333nothrow 89nothrow-takuu 392nakyvyysmaare 101–104

ja periytyminen 150–152oletus 101

nakyvyystarkenninoperaattori44

O-notaatio 309odotaOKta 76ohjelmistokriisi 27ohjelmistosuunnittelu 117OkDialogi 73, 76OkDialogi::˜OkDialogi 78OkDialogi::OkDialogi 78OKodotus 73

oletusrakentaja 82–83kaantajan luoma 83

olioalustamaton 218alustaminen katso

“rakentaja”arvo 216arvoparametrina 224–227dynaaminen luominen ja

tuhoaminen 85–86elinkaari 71–72identiteetti 57, 58instantiointi 56kahteen kertaan

tuhoaminen 92–94kerrosrakenne 149kommunikointi 36kopiointi 202–215kuvaaminen (UML) 128kaytto tuhoamisen

jalkeen 92–94luokka 56, 57luomistoimenpiteet

70–71omistusvastuu 91paluuarvona 224poikkeus- 374rajapinta 95sijoitus 215–222simulointi 54tietotyyppi 57tila 37, 55, 202tilan eriyttaminen

405–407tilan vaihtaminen

407–408todellinen vs.

ohjelmallinen 55tuhoamistoimenpiteet 71

Page 459: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

Hakemisto 459

tuhoutuminen katso“purkaja”

vakio- 106–108valiaikais- 225

olio-ohjelmointi 36oliomaarittely 35oliosuunnittelu 35, 116–141oliot

funktio- 343, 340–348Ω-notaatio 309omistussuhde 134omistusvastuu 91onkoAlle5 342OnkoAlle::OnkoAlle 344OnkoAlle::operator 344onkoKarkauspaivaa 68onkoTyhja 316operaatio katso “jasenfunktio”operator 344optimointi

inline 413ja mallit 295ja STL 318

osittava jaottelu 118osoitin

-aritmetiikka 332automaatti- katso

“automaattiosoitin”funktio- 341ja ennakkoesittely 112ja iteraattorit 332jasenmuuttuja 133kantaluokka- 155kelvollisuus 335this 66vaarat 411vakio- 109viite osoittimeen 411alykas 387

osoitinvakio 111ostream_iterator 337otsikkotiedosto 39, 60

suojaus useaan kertaankaytolta 40

otsikkotiedostotISOC++ 45paate 46

package (JAVA) 259paint 52PaivattyLokiviesti::PaivattyLokiviesti

154PaivattyMjono::kloonaa 214PaivattyMjono::PaivattyMjono

208Paivays::˜Paivays 84Paivays::annaPaiva 107, 258Paivays::Paivays 80pakka (STL) 315pakkaus (JAVA) 259paluuarvo ja

automaattiosoitin385

palvelu katso “jasenfunktio”parametri

arvonvalitys 224viite- 412

Parnas’s Principles 96Parnas, David 96, 121Parnasin periaatteet 96partition 339peittaminen 168periytetty luokka katso

“aliluokka”periytyminen 143, 144

dynaaminen sitominen160–164

erikoistaminen 147

Page 460: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

Hakemisto 460

hinta 169–170ja kopiorakentaja 207–208ja laajentaminen 156–159ja luokkainvariantti 247ja nakyvyys 150–152ja purkajat 154–155ja sijoitusoperaattori 219ja tyyppimuunnos

164–165ja vastuu 153, 160luokan kysyminen

165–167moni- katso

“moniperiytyminen”peittaminen 168perusteet 149–150rajapintaluokka 190–198syntaksi (C++) 150toistuva 186uudelleenkaytto 147–148viipaloituminen 156,

210–215, 221–222vs. kooste 198–199yleistaminen 147

periytymishierarkia 143–147perustakuu 390pienin 353PieniPaivays::annaPaiva 65PieniPaivays::asetaPaiva 65PieniPaivays::asetaPaivays 65PieniPaivays::sijoitaPaivays

105pimpl 405–407pino (STL) 325poikkeus 367

-kategoriat 371–373, 375-kasittelija 374-maareet 382–383-neutraalius 392

-olio 374-takuu 389-turvallinen sijoitus

399–408dynaamisesti luodut oliot

380heittaminen 374ja olioiden tuhoaminen

380–381ja purkajat 380, 397ja rakentajat 393kaytto 374–377minimitakuu 390muistin loppuminen 87nothrow-takuu 392perustakuu 390sieppaamaton 377–378sieppaaminen 374sisakkaiset 378–379uudelleenheittaminen

379vahva takuu 391valvontalohko 374vuotaminen 375yleiskasittelija 378

poistaLukko 261poke 415polymorfismi 146pop 325, 326pop_back 313, 313, 315, 317pop_front 313, 315, 317Pratchett, Terry 142prioriteettijono (STL) 326priority_queue 326private 101, 103–104, 151protected 101, 152public 101–103, 151puhdas virtuaalifunktio 172puhelinluettelo 322

Page 461: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

Hakemisto 461

PuhLuettelo::lisaa 324PuhLuettelo::poista 324PuhLuettelo::tulosta 324purkaja 76, 83–85

ja periytyminen 154–155ja poikkeukset 380ja virtuaalifunktiot

170–171kutsuhetki 84kutsumatta jattaminen

86, 246virtuaali- 168–169

push 325, 326push_back 312, 313, 313, 315,

317push_front 313, 315, 317PVM::PVM 114pysyvyys- ja

vaihtelevuusanalyysi264

pysyvaisvaittama katso“invariantti”

<queue> 326queue 326

rajapinta 31, 95–99, 240-funktio 31-tyyppi 253–258hyvan rajapinnan

tunnusmerkkeja97–98

julkinen 32jasenmuuttuja 102kuvaaminen (UML) 128luettelo erilaisista 98luokan ja olion 248luokan julkinen 101luokkavakio 251

lupaus 147sisainen 258sopimus 33, 240suunnittelu 33, 96tiedon katkenta 33tietotyypit 253toteuttaminen (UML) 137

rajapintaluokka 177, 190–198rakentaja 74, 76, 80–83

alustuslista 81ja periytyminen 152–154ja virtuaalifunktiot

170–171kopio- 83, 206–210, 311oletus- 82–83tyyppimuunnoksena

233–236random_shuffle 339rbegin 336reflektio 349reinterpret_cast 231–232rekisterointi 319rekisterointi_optimoitu 320rend 336riippuvuus 131robustness 120role 132rooli

luokka (UML) 132suunnittelumalli 270

roskienkeruu 71, 72, 74RTTI 164Run-Time Type Information

164rutiini katso “jasenfunktio”

saakoHameenKuukaudessa111

Sandman 69

Page 462: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

Hakemisto 462

sarja (STL) 312<set> 318, 320set 318–319Shakespeare, William 54siirry 364siirry_apu 364siivoa 76Siivous 73siivousfunktio1 381siivousfunktio2 382, 385siivousfunktio3 386siivoustoimenpiteet 71, 378sijoita 222sijoituksen estaminen 220sijoitus 215–222

automaattiosoitin385–387

itseen 218–219poikkeusturvallinen

399–408viipaloituminen 221–222

sijoitusoperaattori 216–220estaminen 220ja periytyminen 219kaantajan luoma 219–220

silmukkaviittaus 119silta 274–276Simula-67 54sisainen rajapinta 258sisainen tyyppi katso

“rajapintatyypit”sisakkaiset valvontalohkot

378–379size 313, 325, 326Smalltalk 204–206, 249

metaluokka 249sopimus rajapinnasta 241sopimussuunnittelu 240–245sort 339

sovelluskehys 199–200sovitin

iteraattori- 336–337, 337sailio- 304, 325–326

spagettikoodi 29splice 317staattinen elinkaari 77–79<stack> 325stack 325Standard Template Library

katso “STL”static 40, 49static_cast 229std 45STL 302–348

algoritmit 337–340assosiaatiomonitaulu 322assosiaatiotaulu 321–322assosiatiivinen sailio

317–318avain 317bittivektori 325funktio-olio 343, 340–348geneerisyys 302indeksointi 313, 315, 321iteraattori katso

“iteraattori”ja automaattiosoittimet

387ja perustaulukot 312ja string 312ja viitteet 311jono 326joukko 318–319kaksipainen jono katso

“pakka”kertaluokka 309kirjallisuus 304lista 315

Page 463: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

Hakemisto 463

monijoukko 319–320muistinhallinta 311optimointi 318pakka 315perusteet 303–305pino 325prioriteettijono 326sarja 312sailio 310–311sailiosovitin 325–326totuusvektori 323–324vector<bool> 323–324vektori 312–315viipaloituminen 212vali 329

STLport 335string 238<string> 418string 416–419substantiivihaku 123sulkeuma 343summaa 295, 300summaaLuvut 376suoritusaika 309suunnittelumalli 267–270

iteraattori 273–274kohdistin 273kokoelma 271–273silta 274–276

suurten kokonaisuuksienhallinta 29

switch 428syklinen viittaus 119synonyymi katso “viite”Systema Naturae 142syvakopiointi 205–206syottoiteraattori 329sailio 304, 310–311

-sovitin 304

assosiatiivinen 317–318ja iteraattorit 332–334ja viitteet 311lapikayminen 326, 333sarja 312viipaloituminen 212

sailiosovitin 325–326

taikuri 27tapahtumasekvenssi 138–139taulukko-delete 90taulukko-new 90tehokkuuskategoriat 308–310tehokkuusvaatimus 307template 285template-metaohjelmointi 350terminate 377Θ-notaatio 309this 66throw 375tiedon katkenta 33, 118tietojasen katso

“jasenmuuttuja”tietorakenne 30

jaotteluperusteet 306–308kertaluokka 309luokka 60, 66lapikayminen 326rajapinnat 307tehokkuuskategoriat

308–310Tila 406, 409tilakone 139–140tilan eriyttaminen 405–407tilan vaihtaminen 407–408top 325toteutusmalli 267totuusvektori (STL) 323–324trait 353

Page 464: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

Hakemisto 464

Trurl 26try 375Tuhoa 114tuhoutuminen

olion katso “purkaja”Tulosta 34, 52tulosta 43, 323TulostaAikaisempi 36tulostaAlkiot 333tulostaAlle 346tulostaAlle2 349tulostaAlle5 342tulostaKirjat 163tulostaKTeostiedot 184, 185tulostaPalautusaika 230tulostaTiedot 160, 182, 185tulostus-iteraattori 330tuotaNeliotaulukko 386tuotaTaulukko 386tuplaa 412type_info 166typedef 332typeid 166<typeinfo> 166typename 285, 299–301tyyppimuunnos 227–236

ajoaikainen 231C 228const_cast 230–231dynamic_cast 164–165,

231implisiittinen 256, 287,

291ja rakentaja 233kaannosaikana 229ohjelmoijan maarittelema

232olio-ohjelmointi 228

reinterpret_cast231–232

static_cast 229uusi C++ syntaksi 228

Tyyppinimi 86tyyppiparametri 285

vaatimukset 290

UMLassosiaatio 132interface 128kooste 134koostuminen 135lukumaarasuhde 132luokka 128luokkakaavio 127luokkien valiset yhteydet

130muodostuminen 134–135olio 128periytyminen 137rajapinta 128rakennekuvaus 140riippuvuus 131tapahtumasekvenssi 138tilakone 139toteuttaminen 137

uncaught_exception 398Unified Modelling Language

126upper_bound 322using 47using namespace 48uusiTyyppi 228UusiVirheIkkuna 279

vahva takuu 391vakio 105–111

-iteraattori 332

Page 465: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

Hakemisto 465

-jasenfunktio 107-muuttuja 105-olio 106-osoitin 109-viite 109, 224, 412osoitin- 111perustietotyyppi 105suunnitteluvirhe 231tyyppimuunnos 230

vakioaikainen tehokkuus 310valvontalohko 374vanilla 178vappu 51varain 305varmistusehto 246varmistusrutiini 245vastuualue 38, 122

elinkaari 69ja periytyminen 153, 160luokka 122–123moduuli 32moduulin toteuttaja 32rajapinnan sopimus 240rajapinta 95–99

<vector> 313, 415vector 312–315, 414–416vector<bool> 323–324vektori (STL) 312–315viipaloituminen 156,

210–215, 221–222viite 411, 410–413

-parametri 412ja ennakkoesittely 112ja sailiot 311kantaluokka- 155kelvollisuus 335osoittimeen 411paluuarvona 413syntaksi 411

vakio- 109vakioparametri 412virheellinen 413

viitekopiointi 203–204viitelaskuri 387virheet

hallittu lopetus 369jatkaminen 370peruuttaminen 370, 391toipuminen 370

virheet ohjelmissa 367–368virhehierarkia 371–373VirheIkkuna::˜VirheIkkuna

278VirheIkkuna::NaytaIlmoitus

278virtaiteraattori 336virtuaalifunktio 159–160

hinta 169–170puhdas 172rakentajissa ja purkajissa

170–171virtuaalinen

moniperiytyminen187

virtuaalipurkaja 168–169virtuaalitaulu 170virtuaalitauluosoitin 170virtual 159, 168

unohtaminen 168, 169vuosi 2000 -ongelma 31, 33,

256vali (STL) 329valiaikaisolio 225

yhdistava moniperiytyminen187

yhtasuuruus 359yleiskayttoisyys 264

Page 466: Olioiden ohjelmointi C++:llaoliot/kirja/olioiden-ohjelmointi-uusin.pdf · 2014-01-22 · c 2012 Matti Rintala ja Jyke Jokinen (Subversion-revisio 1938 ) Tämän teoksen käyttöoikeutta

Hakemisto 466

yleispoikkeuskasittelija 378yleistaminen 147yli-indeksointi 313yliluokka katso “kantaluokka”ys::tulosta 42ystavafunktio 259–260ystavaluokka 260–261

zombie-olio 94

alykas osoitin 387