jani rönkkönen [email protected]

122
1 Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, periytymisen huomioon ottamisesta, operaattoreiden uudelleenmäärittely Jani Rönkkönen [email protected] Luennot muokattu Sami Jantusen ja Kari Smolanderin aikaisempien vuosien luennoista

Upload: aerona

Post on 08-Jan-2016

73 views

Category:

Documents


3 download

DESCRIPTION

Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, periytymisen huomioon ottamisesta, operaattoreiden uudelleenmäärittely. Jani Rönkkönen [email protected] Luennot muokattu Sami Jantusen ja Kari Smolanderin aikaisempien vuosien luennoista. Sisältö. Rajapinnoista Esimerkki - PowerPoint PPT Presentation

TRANSCRIPT

Page 1: Jani Rönkkönen jani.ronkkonen@lut.fi

1

Olio-ohjelmoinnin perusteetluento 5: Rajapinnoista, periytymisen huomioon ottamisesta, operaattoreiden uudelleenmäärittely

Jani Rönkkö[email protected]

Luennot muokattu Sami Jantusen ja Kari Smolanderin aikaisempien vuosien luennoista

Page 2: Jani Rönkkönen jani.ronkkonen@lut.fi

2

Sisältö Rajapinnoista

Esimerkki Abstrakti luokka Puhdas virtuaalinen funktio Rajapinnan käytöstä Komponentteihin jaottelusta

Periytymisen vaikutus olion luontiin ja tuhoamiseen

Muodostimet ja periytyminen Purkajat ja periytyminen

Perityn luokan eri tyypit Aliluokan ja kantaluokan suhde Tyyppimuunnokset

Olioiden sijoitus ja kopiointi Olioiden kopiointi Olioiden sijoitus Puhdasoppinen luokka

Serialisaatio Operaattoreiden uudelleenmäärittely

Ystäväfunktiot Ystäväluokat

Yhteenveto

Page 3: Jani Rönkkönen jani.ronkkonen@lut.fi

3

Tarina… Olipa kerran insinööri, joka

työskenteli palvelimen parissa

Palvelimen oli tarkoitus pystyä kommunikoimaan lukuisten erilaisten asiakasohjelmien kanssa

Page 4: Jani Rönkkönen jani.ronkkonen@lut.fi

4

Tarina jatkuu…. Palvelimen ja

asiakasohjelmien väliseksi kommunikointitavaksi valittiin 2-suuntainen putki

Page 5: Jani Rönkkönen jani.ronkkonen@lut.fi

5

Tarina jatkuu…. Pian hän huomasi, että 2-suuntaisen

putken käyttö ei ollut ihan helppoa 2-suuntaisen liikenteen hallinta vaati

synkronointitaitoja Putkesta “tipoittain” lukeminen tukkeutti

putken Putkia piti tarjota sitä mukaan kun

asiakasohjelmat ottivat palvelimeen yhteyttä Säikeistyksen hallinta

Kaikki asiakasohjelmat eivät olleet tiedossa ja niitä tehtiin muiden henkilöiden voimin.kommunikointimekanismi ei saa olla sen käyttäjälle vaikeaa!

Page 6: Jani Rönkkönen jani.ronkkonen@lut.fi

6

Tarina jatkuu…

Niinpä hän päätti soveltaa yhtä olioajattelun perusajatuksista: Tiedon piilottamista

Hän loi kirjaston, joka piilotti putken monimutkaisuuden (synkronointi, säikeiden hallinta, viestien puskurointi, ym.)

Page 7: Jani Rönkkönen jani.ronkkonen@lut.fi

7

Tarina jatkuu…. Ja sen putken käyttö oli

niin mukavaa… Viis hankalista

hallinnoitiasioista. Riitti kun avaa ja

lähettää….

PipeServer

create()send()disconnect()getNumberOfClients()

PipeClient

open()send()disconnect()

Page 8: Jani Rönkkönen jani.ronkkonen@lut.fi

8

Tarina jatkuu… Entäpä viestin vastaanottaminen?

Olisipa mukavaa kun putki osaisi itse kutsua asiakkaan messageArrived –funktiota kun viesti on saapunut

Ainoa asia mitä asiakkaan tarvitsisi tehdä on toteuttaa messageArrived funktio, mihin määriteltäisiin viestin saapumisesta aiheutuva toimintalogiikka.

Page 9: Jani Rönkkönen jani.ronkkonen@lut.fi

9

Ja sitten tarinan kysymys!

Mistä putkikirjasto voi tietää ketä kutsua kun viesti saapuu???

Page 10: Jani Rönkkönen jani.ronkkonen@lut.fi

10

Ratkaisu?

Mitä jos kukin putkea käyttävä olio esittelee itsensä ja antaa osoittimen itseensä. Putki voisi sitten jatkossa

vain käyttää osoitinta ja kutsua sen avulla käyttäjäolion messageArrived-funktiota

Page 11: Jani Rönkkönen jani.ronkkonen@lut.fi

11

Taustatietoa Jokaisella oliolla on olemassa

osoitinmuuttuja this, joka osoittaa itseensä

this –osoitin on aina samaa tyyppiä kun siihen liittyvä osoitinkin aivan kun this olisi määritelty luokassa tyyliin:

MyClass *this;

Page 12: Jani Rönkkönen jani.ronkkonen@lut.fi

12

Lähdetään ratkaisemaan ongelmaa

Oletetaan että putkea käyttävä olio identifioi itsensä kun se avaa putken:

PipeClient _myPipe;

_myPipe.open(this);

Nyt putki tietää sitä käyttävän olion osoitteen.

Ratkaisiko tämä ongelman?

Page 13: Jani Rönkkönen jani.ronkkonen@lut.fi

13

Vielä ongelmia

Okei, nyt tiedetään putkea käyttävän olion osoite. Se ei kuitenkaan riitä

Mistä ihmeestä putkikirjasto tietää minkä tyyppinen annettu osoitin on? Eihän se muuten voi kutsua annettua

oliota

Page 14: Jani Rönkkönen jani.ronkkonen@lut.fi

14

Heureka! Mitäs jos vaadittaisiin, että kaikki putken

käyttäjäluokat periytyvät MessageReader-luokasta Silloinhan tiedettäisiin, että asiakkaat ovat aina myös

tyyppiä MessageReader! putkikirjastoon voitaisiin siis kirjoittaa seuraava

koodipätkä:

//Luokan määrittelyssäMessageReader *_addressOfClient;...

//Putkea avattaessaPipeClient::open(MessageReader *client){

_addressOfClient=client;}...

//jossain päin missä luetaan putkea_addressOfClient->messageArrived();

Page 15: Jani Rönkkönen jani.ronkkonen@lut.fi

15

Mitä taas tuli tehtyä?

Loimme luokan (MessageReader), joka ei itse tee yhtään mitään.

Tämähän on ihan selvä rajapintaluokka!

Ne luokat jotka haluavat tarjota rajapintaluokan määrittelemiä palveluita perivät itsensä rajapintaluokasta

PipeUser

PipeClient *myPipe

MessageReader

virtual void messageArrived(CArchive *message) = 0;

Page 16: Jani Rönkkönen jani.ronkkonen@lut.fi

16

Rajapintaluokista Rajapintaluokat ovat yleensä

abstrakteja luokkia Eivät sisällä mitään muuta kuin rajapinnan

määrittelyjä Ei siis jäsenmuuttujia eikä jäsenfunktioiden

toteutuksia Jossain oliokielissä (kuten Java) tällaisille

puhtaille rajapinnoille on oma syntaksinsa eikä niitä silloin varsinaisesti laskeata edes luokiksi

Page 17: Jani Rönkkönen jani.ronkkonen@lut.fi

17

Abstrakti luokka Mikä hyvänsä luokka, jossa on yksi tai

useampi puhdas virtuaalifunktio, on abstrakti luokka eikä sen tyyppisiä olioita voi luoda.

Puhdas virtuaalifunktio kertoo luokan käyttäjälle kaksi asiaa: Luokan tyyppistä oliota ei voida luoda vaan

siitä pitää periyttää aliluokkia Jokainen puhdas virtuaalifunktio pitää

korvata uudella funktiolla abstraktista luokasta periytetyssä luokassa

Page 18: Jani Rönkkönen jani.ronkkonen@lut.fi

18

Puhdas virtuaalifunktio

Abstrakti luokka tehdään käyttämällä puhtaita virtuaalifunktioita (pure virtual function) Virtuaalifunktio on puhdas, jos se

alustetaan nollalla, esimerkiksi:virtual void Piirra () = 0;

Page 19: Jani Rönkkönen jani.ronkkonen@lut.fi

19

Puhtaan virtuaalifunktion ohjelmointi Yleensä abstraktissa kantaluokassa olevalle

puhtaalle virtuaalifunktiolle ei kirjoiteta funktion määrittelyä

Koska luokan tyyppisiä olioita ei voida koskaan luoda, niin ei ole mitään syytä ohjelmoida luokkaan mitään toiminnallisuuttakaan.

Abstrakti luokka on siitä periytetyille luokille yhteinen käyttörajapinta

On toki mahdollista tehdä kantaluokkaan puhtaalle virtuaalifunktiolle toteutus

Sitä kutsutaan silloin lapsiluokista käsin. Esim. se toiminnallisuus, joka on yhteistä kaikille

lapsille siirretään kantaluokkaan.

Page 20: Jani Rönkkönen jani.ronkkonen@lut.fi

20

Milloin kannattaa käyttää abstrakteja luokkia? Ei yksiselitteistä vastausta Päätös tehtävä sen perusteella onko luokan

abstraktisuudesta jotain hyötyä Esimerkki: Eläin-luokka kannattaa olla abstrakti,

mutta Koira-luokka ei, jotta ohjelmassa voidaan käyttää koira-olioita

Toisaalta: Jos ohjelmassa simuloidaan kenneliä, koira-luokka kannattaa jättää abstraktiksi ja periyttää siitä erirotuisia koiria.

Käytettävä abstraktiotaso määräytyy sen mukaan, kuinka hienojakoisesti ohjelman luokat pitää erotella toisistaan

Page 21: Jani Rönkkönen jani.ronkkonen@lut.fi

21

Muistatko viel?Moniperiytyminen -käyttökohteita Rajapintojen yhdistäminen.

Halutaan oman luokan toteuttavan useiden eri rajapintojen toiminnallisuus

Luokkien yhdistäminen. Halutaan esimerkiksi käyttää hyväksi muutamaa yleiskäyttöistä

luokkaa oman luokan kehitystyössä. Luokkien koostaminen valmiista ominaisuuskokoelmista.

Esimerkki: Kaikki lainaamiseen liittyvät toiminnot on kirjoitettu Lainattava-

luokkaan. Vastaavasti kaikki tuotteen myymiseen liittyvät aisat ovat

luokassa Myytävät. Voimme luoda KirjastonKirja –luokan perimällä sen Kirja-

kantaluokasta ja maustamalla sen Lainattava-luokasta saaduilla ominaisuuksilla

Voimme yhtä lailla luoda KaupallinenCD-ROM-luokan perimällä sen CD-ROM kantaluokasta ja ottaa käyttöön ominaisuudet Myytävä-luokasta

Page 22: Jani Rönkkönen jani.ronkkonen@lut.fi

22

Rajapintaluokat ja moniperiytyminen Jos abstraktit kantaluokat sisältävät

ainoastaan puhtaita virtuaalifunktioita Moniperiytymisen käytöstä ei aiheudu

yleensä ongelmia. Jos moniperiytymisessä kantaluokat

sen sijaan sisältävät myös rajapintojen toteutuksia ja jäsenmuuttujia Moniperiytyminen aiheuttaa yleensä

enemmän ongelmia kuin ratkaisee.

Page 23: Jani Rönkkönen jani.ronkkonen@lut.fi

23

Rajapinnoista Rajapintojen käyttö ja toteutuksen

kätkentä on yksi tärkeimmistä ohjelmistotuotannon perusperiaatteista Tästä huolimatta sen tärkeyden

perustelu uraansa aloittelevalle ohjelmistoammattilaiselle on vaikeaa

Merkityksen tajuaa yleensä itsestäänselvyytenä sen jälkeen, kun on osallistunut tekemään niin isoa ohjelmistoa, ettei sen sisäistä toteutusta pysty kerralla hallitsemaan ja ymmärtämään yksikään ihminen.

Page 24: Jani Rönkkönen jani.ronkkonen@lut.fi

24

Komponentteihin jaottelusta Isoissa ohjelmissa

komponenttijako helpottaa huomattavasti kehitystyötä. Yksittäinen ohjelmoijan ei enää

tarvitse jatkuvasti hahmottaa kokonaisuutta

Kehittäjä voi enemmän keskittyä oman komponenttiensa vastuiden toteutukseen.

Page 25: Jani Rönkkönen jani.ronkkonen@lut.fi

25

Missä mennään? Rajapinnoista

Esimerkki Abstrakti luokka Puhdas virtuaalinen funktio Rajapinnan käytöstä Komponentteihin jaottelusta

Periytymisen vaikutus olion luontiin ja tuhoamiseen Muodostimet ja periytyminen Purkajat ja periytyminen

Perityn luokan eri tyypit Aliluokan ja kantaluokan suhde Tyyppimuunnokset

Olioiden sijoitus ja kopiointi Olioiden kopiointi Olioiden sijoitus

Serialisaatio Operaattoreiden uudelleenmäärittely

Ystäväfunktiot Ystäväluokat

Yhteenveto Puhdasoppinen luokka Kertaus

Page 26: Jani Rönkkönen jani.ronkkonen@lut.fi

26

Sä muistatko viel? Muodostimen käyttö periytymisen yhteydessä

Isäluokan muodostinta kutsutaan aina!*

CPoodle.cpp

CPoodle::CPoodle(int x, string y) : CDog (x,y)

{

cout << “Tuli muuten tehtyä puudeli" << endl;

}

Normaali muodostin

Luokkia perittäessä on muodostimien ja purkajien käytössä on paljon huomioitavaa

Page 27: Jani Rönkkönen jani.ronkkonen@lut.fi

27

Periytyminen ja muodostimet

Jokainen aliluokan olio koostuu kantaluokkaosasta (tai osista) sekä aliluokan lisäämistä laajennuksista Aliluokalla on oltava oma

muodostimensa. Mutta miten pitäisi hoitaa

kantaluokkien alustus?

Mammal

Land-Mammal

int weight

int numLegs

Dogboolean rabid

giveBirth( )

SheepDog

Page 28: Jani Rönkkönen jani.ronkkonen@lut.fi

28

Periytyminen ja muodostimetVastuut

Aliluokan vastuulla on: Aliluokan mukanaan tuomien

uusien jäsenmuuttujien ja muiden tietorakenteiden alustaminen.

Em. vastuita varten aliluokkiin toteutetaan oma(t) muodostimet/muodostin

Kantaluokan vastuulla on: Pitää huoli siitä, että aliluokan

olion kantaluokkaosa tulee alustetuksi oikein, aivan kun se olisi irrallinen kantaluokan olio

Tämän alustuksen hoitavat aivan normaalit kantaluokan muodostimet

Page 29: Jani Rönkkönen jani.ronkkonen@lut.fi

29

Periytyminen ja muodostimetParametrit?

Miten taataan että kaikki muodostimet saavat tarvitsemansa parametrit?

Päivänselvää aliluokalle. Sitä luodessahan kutsutaan aliluokan itse määrittelemiä muodostimia

Kantaluokan parametrien saannin takaamiseksi C++:n tarjoama ratkaisu on, että aliluokan muodostimen alustuslistassa kutsutaan kantaluokan muodostinta ja välitetään sille tarvittavat parametrit

CPoodle.cpp

CPoodle::CPoodle(int x, string y) : CDog (x,y)

{

cout << “Tuli muuten tehtyä puudeli" << endl;

}

Page 30: Jani Rönkkönen jani.ronkkonen@lut.fi

30

Entä jos? Jos aliluokan muodostimen

alustuslistassa ei kutsuta mitään kantaluokan muodostinta: Kääntäjä kutsuu automaattisesti

kantaluokan oletusmuodostinta (joka ei siis tarvitse parametreja)

Tällainen ratkaisu ei läheskään aina johda toivottuun tulokseen

Muista siis kutsua aliluokan muodostimessa kantaluokan muodostinta itse!

Page 31: Jani Rönkkönen jani.ronkkonen@lut.fi

31

Muodostimien suoritusjärjestys

Huipusta alaspäin Olio ikäänkuin rakentuu

vähitellen laajemmaksi ja laajemmaksi.

Näin taataan se, että aliluokan muodostin voi jo turvallisesti käyttää kantaluokan jäsenfunktioita.

Page 32: Jani Rönkkönen jani.ronkkonen@lut.fi

32

Periytyminen ja purkajat

Alustamisen tapaan myös olion siivoustoimenpiteet vaativat erikoiskohtelua luokan “kerrosrakenteen” vuoksi

Purkajien vastuut jaettu samalla lailla kuin muodostimienkin

Kantaluokan tehtävänä on siivota kantaluokkaolio sellaiseen kuntoon, että se voi rauhassa tuhoutua

Aliluokat puolestaan siivoavat periytymisessä lisätyt laajennusosat tuhoamiskuntoon

Page 33: Jani Rönkkönen jani.ronkkonen@lut.fi

33

Purkajien suoritusjärjestys Purkajia kutsutaan päinvastaisessa

järjestyksessä kuin muodostimia Ensin kutsutaan aliluokan purkajia ja

siitä siirrytään periytymishierakiassa ylöspäin

Näin varmistetaan se, että aliluokan purkajassa voidaan vielä kutsua kantaluokkien toiminnallisuutta

Page 34: Jani Rönkkönen jani.ronkkonen@lut.fi

34

Esimerkki

Jotain pahasti pielessä!-Mitä?

Mammal

Land-Mammal

int weight

int numLegs

Dogboolean rabid

~Mammal ( )

SheepDog

Mammal *myMammal;myMammal = new SheepDog();

...//koodia missä käytetään SheepDog-luokkaa...

delete myMammal;

Page 35: Jani Rönkkönen jani.ronkkonen@lut.fi

35

Esimerkki

Vain kantaluokan purkajaa kutsutaan!

Kuinka korjata tilanne?

Mammal

Land-Mammal

int weight

int numLegs

Dogboolean rabid

~Mammal ( )

SheepDog

Mammal *myMammal;myMammal = new SheepDog();

...//koodia missä käytetään SheepDog-luokkaa...

delete myMammal;

Page 36: Jani Rönkkönen jani.ronkkonen@lut.fi

36

Virtuaalipurkaja

Jos luokasta peritään muita luokkia, muista aina määritellä purkaja virtuaaliseksi!

Ei haittaa vaikka purkaja on eri niminen lapsiluokassa.

Page 37: Jani Rönkkönen jani.ronkkonen@lut.fi

37

Missä mennään? Rajapinnoista

Esimerkki Abstrakti luokka Puhdas virtuaalinen funktio Rajapinnan käytöstä Komponentteihin jaottelusta

Perinnän vaikutus olion luontiin ja tuhoamiseen Muodostimet ja periytyminen Purkajat ja periytyminen

Perityn luokan eri tyypit Aliluokan ja kantaluokan suhde Tyyppimuunnokset

Olioiden sijoitus ja kopiointi Olioiden kopiointi Olioiden sijoitus Puhdasoppinen luokka

Serialisaatio Operaattoreiden uudelleenmäärittely

Ystäväfunktiot Ystäväluokat

Yhteenveto

Page 38: Jani Rönkkönen jani.ronkkonen@lut.fi

38

Aliluokan ja kantaluokan suhde

Aliluokka tarjoaa kaikki ne palvelut mitä kantaluokkakin (+ vähän lisää omia ominaisuuksia) Periytymisessähän vaan lisätään

ominaisuuksia Aliluokkaa voi siis käyttää

kantaluokan sijasta missä päin hyvänsä koodia

Page 39: Jani Rönkkönen jani.ronkkonen@lut.fi

39

Aliluokan ja kantaluokan suhde Voidaan siis ajatella, että aliluokan olio on

tyypiltään myös kantaluokan olio! Aliluokan oliot kuuluvat ikään kuin useaan

luokaaan: Aliluokkaan itseensä Kantaluokkaan Kantaluokan kantaluokkaan, jne

Tämä is-a suhde tulisi pitää mielessä aina kun periytymistä käytetään!

Jos aliluokka on muuttunut vastuualueeltaan niin paljon, että se ei enää ole kantaluokan mukainen, periytymistä on ilmeisesti käytetty väärin

Page 40: Jani Rönkkönen jani.ronkkonen@lut.fi

40

Aliluokan ja kantaluokan suhde C++:ssa aliluokan olio kelpaa kaikkialle

minne kantaluokan oliokin. Kantaluokan osoittimen tai viitteen voi

laittaa osoittamaan myös aliluokan olioon:

class Kantaluokka {…};class Aliluokka : public Kantaluokka {…};void funktio (Kantaluokka& kantaolio);

Kantaluokka *k_p =0;Aliluokka aliolio;k_p = &aliolio;funktio(aliolio);

Page 41: Jani Rönkkönen jani.ronkkonen@lut.fi

41

Olion tyypin ajonaikainen tarkastaminen

Kantaluokkaosoittimen päässä olevalle oliolle voi kutsua vain kantaluokan rajapinnassa olevia funktioita Ei auta vaikka osoittimen päässä

todellisuudessa olisikin aliluokan olio. Normaalisti kantaluokan rajapinnan käyttö

onkin aivan riittävää Joskus tulee kuitenkin tarve päästä käsiksi

aliluokan rajapintaan.

Page 42: Jani Rönkkönen jani.ronkkonen@lut.fi

42

Olion tyypin ajonaikainen tarkastaminen

Jos aliluokan olio on kantaluokkaosoittimen päässä ei aliluokan rajapinta ole siis näkyvissä Ainoa vaihtoehto on luoda uusi osoitin

aliluokkaan ja laittaa se osoittamaan kantaluokkaosoittimen päässä olevaan olioon

Page 43: Jani Rönkkönen jani.ronkkonen@lut.fi

43

Tyyppimuunnokset (type cast) Tyyppimuunnos on operaatio, jota

ohjelmoinnissa tarvitaan, kun käsiteltävä tieto ei ole jotain operaatiota varten oikean tyyppistä

Tyyppimuunnos on terminä hieman harhaanjohtava tyyppiä ei oikeastaan muuteta vaan luodaan

pikemminkin uusi arvo haluttua tyyppiä, joka vastaa vanhaa arvoa

Tyyppimuunnos muistuttaa tässä suhteessa suuresti kopiointia. Erona on se, että uusi ja vanha olio on kopioinnista poiketen eri tyyppiä

Page 44: Jani Rönkkönen jani.ronkkonen@lut.fi

44

C++ tyyppimuunnosoperaattorit

Vanha C-kielinen tyyppimuunnos:(uusiTyyppi)vanhaArvo sulkujen sijainti hieman epälooginen

C++ kielessä mahdollista myös: uusiTyyppi(vanhaArvo)

Page 45: Jani Rönkkönen jani.ronkkonen@lut.fi

45

Ongelmia tyyppimuunnosten kanssa

Tyyppimuunnoksia voidaan käyttää suorittamaan kaikenlaisia muunnoksia. Esim: kokonaisluvuista liukuluvuiksi olio-osoittimista kokonaisluvuiksi

Kaikki tyyppimuunnokset eivät ole järkeviä! Kääntäjä ei tarkista tyyppimuunnosten

järkevyyttä Kääntäjä luottaa täysin ohjelmoijan omaan

harkintaan Tyyppimuunnoksiin jää helposti kirjoitusvirheitä Tyyppimuunnosvirheitä on vaikea löytää

Page 46: Jani Rönkkönen jani.ronkkonen@lut.fi

46

Parannellut tyyppimuunnosoperaattorit

Parannellut tyyppimuunnosoperaattorit ovat: static_cast<uusiTyyppi>(vanhaArvo) const_cast<uusiTyyppi>(vanhaArvo) dynamic_cast<uusiTyyppi>(vanhaArvo) reinterpret_cast<uusiTyyppi>(vanhaArvo)

Yhteensopivia mallien käyttämän syntaksin kanssa (malleista puhutaan myöhemmin)

Kukin operaattoreista on tarkoitettu vain tietynlaisen mielekkään muunnoksen tekemiseen

kääntäjä antaa virheilmoituksen jos niitä yritetään käyttää väärin. Vanhat tavat tehdä tyyppimuunnokset ovat yhteensopivuuden takia edelleen

käytettävissä vältä niiden käyttöä ja suosi uusia operaattoreita

Page 47: Jani Rönkkönen jani.ronkkonen@lut.fi

47

static_cast Suorittaa tyyppimuunnoksia, joiden

mielekkyydestä kääntäjä voi varmistua jo käännösaikana.

Esimerkkejä: muunnokset eri kokonaislukutyyppien välillä muunnokset enum-luettelotyypeistä kokonaisluvuiksi ja

takaisin muunnokset kokonaislukutyyppien ja likulukutyyppien

välillä Käyttöesimerkki. Lasketaan kahden

kokonaisluvun keskiarvo liukulukuna:double ka = (static_cast<double>(i1) + static_cast<double>(i2))/ 2.0;

Page 48: Jani Rönkkönen jani.ronkkonen@lut.fi

48

static_cast static_cast ei suostu suorittamaan sellaisia

muunnoksia, jotka ei ole mielekkäitä. Esimerkki:Paivays* pvmp = new Paivays();int* ip = static_cast<int*>(pvmp); //KÄÄNNÖSVIRHE!

static_cast:ia voidaan käyttää myös osoittimen tyyppimuutokseen muunnoksen mielekkyyttä ei tällaisessa

tapauksessa testata ajon aikana Pitää olla itse varma, että kantaluokkaosoittimen

päässä on varmasti aliluokan olio dynamic_cast:n käytto olisi turvallisempaa! static_cast on nopeampi kuin dynamic_cast

Page 49: Jani Rönkkönen jani.ronkkonen@lut.fi

49

const_cast joskus const-sanan käyttö tuo ongelmia const_cast tarjoaa mahdollisuuden

poistaa const-sanan vaikutuksen voi tehdä vakio-osoittimesta ja –viitteestä ei-

vakio-osoittimen tai –viitteen const_cast-muunnoksen käyttö rikkoo C+

+ “vakiota ei voi muuttaa” periaatetta vastaan. sen käyttö osoittaa että jokin osa ohjelmasta

on suunniteltu huonosti Pyri pikemminkin korjaamaan varsinainen

ongelma kuin käyttämään const_cast:ia

Page 50: Jani Rönkkönen jani.ronkkonen@lut.fi

50

dynamic_cast Muunnos kantaluokkaosoittimesta

aliluokkaosoittimeksi onnistuuu tyyppimuunnoksella:dynamic_cast<Aliluokka*>(kluokkaosoitin)

Muunnoksen toiminta on kaksivaiheinen: Ensin tarkastetaan, että

kantaluokkaosoittimen päässä oleva olio todella on aliluokan olio.

Jos kantaluokkaosoittimen päässä on väärän tyyppinen olio, palautetaan tyhjä osoitin 0.

Jos kantaluokkaosoittimen päässä on oikean tyyppinen olio, palautetaan kyseiseen olioon osoittava aliluokkaosoitin.

Page 51: Jani Rönkkönen jani.ronkkonen@lut.fi

51

Kantaluokkaosoittimesta aliluokkaosoittimeksi dynamic_cast –muunnosta voi käyttää

myös olioviitteisiin (siis tuottamaan aliluokkaviitteen)

Ainoa ero osoitinmuunnokseen on se, että jos kantaluokkaviitteen päässä on väärän tyyppinen olio, dynamic_cast heittää poikkeuksen (std::bad_cast), koska ei ole olemassa tyhjää viitettä, jota palauttaa Puhumme poikkeuksista lisää seuraavilla

luennoilla!

Page 52: Jani Rönkkönen jani.ronkkonen@lut.fi

52

dynamic_cast esimerkkibool myohassako(Kirja* kp, const Paivays& tanaan)

{

KirjastonKirja* kpp = dynamic_cast<KirjastonKirja*>(kp);

if(kkp != 0)

{ //jos tultiin tänne, kirja on kirjastonkirja

return kkp->onkoMyohassa(tanaan);

}

else

{ //jos tultiin tänne, kirja ei ole kirjastonkirja

return false;

}

}

Page 53: Jani Rönkkönen jani.ronkkonen@lut.fi

53

reinterpret_cast Joskus joudutaan käsittelemään tietoa

tavalla, joka ei ole sen todellisen tyypin mukainen Esim. osoitinta voi joskus joutua käsittelemään

muistiosoitteena (=kokonaislukuna) reinterpret_cast:ia käytetään tiedon

esitystavan muuttamiseen. Muunnoksen lähes ainoa käyttökohde on

muuttaa tieto ensin toisentyyppiseksi ja myöhemmin takaisin esimerkiksi tallennusta varten.

Page 54: Jani Rönkkönen jani.ronkkonen@lut.fi

54

reinterpret_cast sallitut käyttökohteet:

Osoittimen muunto kokonaisluvuksi, jos kokonaislukutyyppi on niin suuri, että osoitin mahtuu siihen

Kokonaisluvun muuntaminen takaisin osoittimeksi

Tavallisen osoittimen muunto toisentyyppiseksi osoittimeksi

Viitteen muunto toisentyyppiseksi viitteeksi Funktio-osoittimen muunto

toisentyyppiseksi funktio-osoittimeksi.

Page 55: Jani Rönkkönen jani.ronkkonen@lut.fi

55

reinterpret_cast käyttöesimerkki

void luoKayttoliittyma(KirjastonKirja *kirja1, KirjastonKirja* kirja2)

{

//parametrina kirjan nimi ja siihen liittyvä kokonaisluku (muunnettu osoitin)

luoNappula(“Kirja1”, reinterpret_cast<unsigned long int>(kirja1));

luoNappula(“Kirja2”, reinterpret_cast<unsigned long int>(kirja2));

}

//tätä funktiota kutsutaan kun nappulaa painetaan

void nappulaaPainettu(unsigned long int luku)

{

//muunnos kokonaisluvusta takaisin osoittimeksi

KirjastonKirja* kp = reinterpret_cast<KirjastonKirja*>(luku);

cout << “Painettu kirjan “ << kp->annaNimi() << “ nappia.” << endl;

}

Page 56: Jani Rönkkönen jani.ronkkonen@lut.fi

56

Omat tyyppimuunnokset Jokainen yksiparametrinen muodostin tulkitaan

C++:ssa muunnoksena parametrin tyypistä luokan tyypiksi

Aina ei kuitenkaan ole järkevää ajatella muodostinta tyyppimuunnoksena:

Esimerkiksi taulukkotyyppi, joka saa muodostimen parametrina tiedon alkioiden määrästä

Avainsanalla explicit voidaan estää muodostimen käyttö implisiittisissä (kääntäjän automaattisesti tekemissä) tyyppimuunnoksissa

On vieläkin mahdollista käyttää muodostinta normaalilla tyyppimuunnossyntaksilla manuaalisesti

Page 57: Jani Rönkkönen jani.ronkkonen@lut.fi

57

Muunnosjäsenfunktiot Tyyppimuunnokset omista tyypeistä muihin tyyppeihin

voidaan toteuttaa muunnosjäsenfunktioina (conversion member function)

Parametrittomia vakiojäsenfuntioita, joiden nimi muodostuu avainsanasta operator ja muunnoksen kohdetyypistä

Esimerkiksi: operator int() const Muunnosjäsenfunktio palauttaa paluuarvonaan

muunnoksen lopputuloksen, mutta sille ei merkitä esittelyssä paluuarvoa, koska se käy ilmi jo funktion nimestä

explicit avainsanaa ei voi käyttää muunnosjäsenfunktioiden kanssa ja ne toimivat aina sekä implisiittisissä, että eksplisiittisissä muunnoksissa

Page 58: Jani Rönkkönen jani.ronkkonen@lut.fi

58

Muunnosjäsenfunktio esimerkki

Class Murtoluku

{

public:

operator double() const; //muunnosjäsenfunktio

private:

int osoittaja_;

int nimittaja_;

};

Murtoluku::operator double() const

{

return static_cast<double>(osoittaja_) / static_cast<double>(nimittaja_));

}

Page 59: Jani Rönkkönen jani.ronkkonen@lut.fi

59

Missä mennään? Rajapinnoista

Esimerkki Abstrakti luokka Puhdas virtuaalinen funktio Rajapinnan käytöstä Komponentteihin jaottelusta

Perinnän vaikutus olion luontiin ja tuhoamiseen Muodostimet ja periytyminen Purkajat ja periytyminen

Perityn luokan eri tyypit Aliluokan ja kantaluokan suhde Tyyppimuunnokset

Olioiden sijoitus ja kopiointi Olioiden kopiointi Olioiden sijoitus Puhdasoppinen luokka

Serialisaatio Operaattoreiden uudelleenmäärittely

Ystäväfunktiot Ystäväluokat

Yhteenveto

Page 60: Jani Rönkkönen jani.ronkkonen@lut.fi

60

Olioiden kopiointi Olio-ohjelmoinnissa sijoituksen ja

kopioinnin merkitys ei ole yhtä selvä kuin perinteisessä ohjelmoinnissa

C++:ssa varsinkin kopioinnin merkitys korostuu entisestään, koska kääntäjä itse tarvitsee olioiden kopiointia: välittäessään olioita tavallisina

arvoparametreina palauttaessaan olioita paluuarvoina

Page 61: Jani Rönkkönen jani.ronkkonen@lut.fi

61

Olioiden kopioinnista Kopioidun olion määritelmä:

Uuden ja vanhan olion arvojen tai tilojen täytyy olla samat.

Eri tyyppisiä olioita kopioidaan hyvin eri tavalla Kompleksilukuolion kopiointiin voi riittää

yksinkertainen muistin kopiointi Merkkijonon kopiointi puolestaan saattaa

vaatia ylimääräistä muistinvarausta ja muita toimenpiteitä

Page 62: Jani Rönkkönen jani.ronkkonen@lut.fi

62

Olioiden kopioinnista Yleensä kääntäjä ei pysty automattisesti

kopioimaan olioita hyväksyttävällä tavalla, vaan luokan tekijän tulisi itse määritellä mitä kaikkea olioita kopioitaessa täytyy tehdä.

Kaikkia olioita ei ole järkevää kopioida (esim. hissin moottoria ohjaavan olion

kopiointi. kopiointi vaatisi myös fyysisen moottorin kopiointia, joka ei ole realistista).

Tulisi olla mahdollista myös estää luokan olioiden kopiointi kokonaan

Page 63: Jani Rönkkönen jani.ronkkonen@lut.fi

63

Erilaiset kopiointitavat Olioiden kopiointitavat jaotellaan

usein seuraavasti: Viitekopiointi Matalakopiointi Syväkopiointi

Voi olla kuitenkin tarve kopioida osa olioista yhdellä tavalla ja toisia osia toisella

Page 64: Jani Rönkkönen jani.ronkkonen@lut.fi

64

Viitekopiointi (Reference copy)

Kaikkein helpoin kopiointitavoista. Ei luoda ollenkaan uutta oliota

vaan uutta oliota kuvastaa viite vanhaan olioon.

Page 65: Jani Rönkkönen jani.ronkkonen@lut.fi

65

ViitekopiontiEsimerkki

AlkuperainenOlio

Viitekopio

MUISTI:

Page 66: Jani Rönkkönen jani.ronkkonen@lut.fi

66

Viitekopiointi Käytetään etenkin oliokielissä, missä itse

muuttujat ovat aina vain viitteitä olioihin, jotka puolestaan luodaan dynaamisesti (esim. Java ja Smalltalk)

C++:ssa viitekopiointia käytetään vain, kun erikseen luodaan viitteitä olioiden sijaan.

Viitekopioinnin etu on sen nopeus. “Kopion” luominen ei käytännössä vaadi ollenkaan aikaa Mitään kopioimista ei tarvitse oikeastaan tehdä

Viitekopiointi toimii hyvin niin kauan kun olion arvoa ei muuteta.

Jos olion arvoa muutetaan, arvo kopiossakin muuttuu Muuttaminen voidaan estää määrittelemällä viite

vakioksi

Page 67: Jani Rönkkönen jani.ronkkonen@lut.fi

67

Matalakopiointi (shallow copy) Matalakopioinnissa itse oliosta ja sen

jäsenmuuttujista tehdään kopiot Jos jäsenmuuttujina on viitteitä tai

osoittimia olion ulkopuolisiin tietorakenteisiin, ei näitä tietorakenteita kopoida. matalakopioinnin lopputuloksena

molemmat oliot jakavat samat olioiden ulkopuoliset tietorakenteet.

Page 68: Jani Rönkkönen jani.ronkkonen@lut.fi

68

MatalakopioEsimerkki

1 2 3 4 5

6 7

1 2 3 4 5 6 7

AlkuperainenOlio

Matalakopio

MUISTI:

Alkuperaisenolion ulkoiset tietorakenteet

Page 69: Jani Rönkkönen jani.ronkkonen@lut.fi

69

Matalakopiointi Ohjelmointikielten toteutuksen kannalta

matalakopiointi on selkeä operaatio siinä kopioidaan aina kaikki olion

jäsenmuuttujat eikä mitään muuta Selkeydestä johtuen C++ käyttää

oletusarvoisesti matalakopiointia, jos luokan kirjoittaja ei muuta määrää.

Kopioinnin tuloksena on ainakin päällisin puolin kaksi oliota.

Page 70: Jani Rönkkönen jani.ronkkonen@lut.fi

70

Matalakopiointi Yleensä viitekopiointia käytävissä

oliokielissä on myös jokin tapa matalakopiointiin Esim. Javan jäsenfunktio clone

Eri olioiden jakamat ulkoiset tietorakenteet ovat potentiaalinen ongelma. Muutokset ulkoisissa tietorakenteissa

heijastuu kaikkiin matalakopioituihin olioihin

Page 71: Jani Rönkkönen jani.ronkkonen@lut.fi

71

Syväkopiointi (deep copy) Olion ja sen jäsenmuuttujien lisäksi

kopioidaan myös ne olion tilaan kuuluvat oliot ja tietorakenteet, jotka sijaitsevat olion ulkopuolella.

Olioiden kannalta ehdottomasti paras kopiointitapa Luodaan kopio kaikista olion tilaan kuuluvista

asioista Uusi ja alkuperäinen olio ovat täysin erilliset.

Page 72: Jani Rönkkönen jani.ronkkonen@lut.fi

72

SyväkopioEsimerkki

1 2 3 4 5

6 7 1 2

3

1 2 3

1 2 3 4 5 6 7

Alkuperainenolio

Syväkopio

MUISTI:

Alkuperaisenolion ulkoiset tietorakenteet

Syväkopionulkoiset tietorakenteet

Page 73: Jani Rönkkönen jani.ronkkonen@lut.fi

73

Syväkopio Ongelmat Ohjelmointikielen kannalta syväkopiointi

on ongelmallista Usein kopioitavat oliot sisältävät osoittimia

myös sellaisiin olioihin ja tietorakenteisiin, jotka eivät varsinaisesti ole osa olion tilaa ja joita ei tulisi kopioida.

Esim. Kirjaston kirja sisältää osoittimen kirjastoon, josta ne on lainattu. Kirjan tietojen kopioiminen ei saisi aiheuttaa koko kirjaston kopiointia!

Page 74: Jani Rönkkönen jani.ronkkonen@lut.fi

74

Syväkopio Ongelmat Syväkopioinnin ongelmien johdosta

useimmat ohjelmointikielet eivät tue automaattisesti syväkopiointia Poikkeuksena Smalltalk, joissa oliolta löytyy myös

palvelu deepCopy Yleensä oliokielissä annetaan ohjelmoijalle

itselleen mahdollisuus kirjoittaa syväkopioinnille toteutus, jota kieli osaa automaatiisesti käyttää C++-kielessä ohjelmoija kirjoittaa luokalle

kopiomuodostimen, joka suorittaa kopioinnin ohjelmoijan sopivaksi katsomalla tavalla.

Page 75: Jani Rönkkönen jani.ronkkonen@lut.fi

75

Muistatko viel? Kopiomuodostin (copy constructor)

Saa parametrina viitteen olemassa olevaan saman luokan olioon.

Tehtävänä luoda identtinen kopio parametrina saadusta oliosta

Kääntäjä kutsuu sitä automaattisesti tilanteissa, joissa kopion luominen on tarpeen.

Jos kopiomuodostin puuttuu, se luodaan kääntäjän toimesta automaattisesti

Page 76: Jani Rönkkönen jani.ronkkonen@lut.fi

76

Periytyminen ja kopiomuodostin Periytyminen tuo omat lisänsä kopion

luomiseen. Aliluokan olio koostuu useista osista, ja

kantaluokan osilla on jo omat kopiomuodostimensa, joilla kopion kantaluokkaosat saadaan alustetuksi.

Aliluokan olion kopioiminen onkin jaettu eri luokkien kesken samoin kuin muodostimet yleensä

Aliluokan kopiomuodostimen vastuulla on kutsua kantaluokan kopiomuodostinta ja lisäksi alustaa aliluokan osa olioista kopioksi alkuperäisestä

Page 77: Jani Rönkkönen jani.ronkkonen@lut.fi

77

Kopiomuodostin esimerkkiMjono.h

class Mjono{public:

Mjono(const char* merkit);

//kopiomuodostin Mjono(const Mjono& vanha); virtual ~Mjono();...

private: unsigned long mKoko;char* mMerkit;

};

Mjono.cpp

Mjono::Mjono(const Mjono& vanha) : mKoko(vanha.mKoko), mMerkit(0)

{if (mKoko != 0){//Varaa tilaa, jos koko ei ole nolla

mMerkit = new char[mKoko + 1];for (unsigned long i = 0; i != mKoko; ++i)

{ mMerkit[i] = vanha.mMerkit[i];} //kopioi merkitmMerkit[mKoko] = ‘\0’; //loppumerkki

}}

Pmjono.cppPaivattyMjono::PaivattyMjono(const PaivattyMjono& vanha) : Mjono(vanha)

//kutsutaan kantaluokan kopiomuodostinta{ strcpy(mPaivays, vanha.mPaivays);}

Pmjono.hclass PaivattyMjono : public Mjono{public:

PaivattyMjono(const char* merkit, const char* paivays);

//kopiomuodostin PaivattyMjono(const PaivattyMjono& vanha); virtual ~PaivattyMjono(){};...

private: char mPaivays[20];};

Page 78: Jani Rönkkönen jani.ronkkonen@lut.fi

78

Muista!

Jos unohdat aliluokan kopiomuodostimessa kutsua kantaluokan kopiomuodostinta Kääntäjä kutsuu kantaluokan

oletusmuodostinta automaattisesti (Huom ei kantaluokan kopiomuodostinta, vaan parametritonta muodostinta!)

Olio ei kopioidu oikein !!!!

Page 79: Jani Rönkkönen jani.ronkkonen@lut.fi

79

Kääntäjän luoma oletusarvoinen kopiomuodostin

Jos et määrittele luokalle kopiomuodostinta, kääntäjä luo sen automaattisesti Yksinkertaistaa ohjelmointia Oletusarvoinen kopiomuodostin käyttää

matalakopiointia Useimmiten matalakopiointi ei ole riittävä

jos kopiomuodostimen toteutus unohtuu, oliot kopioituvat väärin

Jokaiseen luokkaan tulisi erikseen kirjoittaa kopiomuodostin

Page 80: Jani Rönkkönen jani.ronkkonen@lut.fi

80

Kopioinnin estäminen Kun ei ole mitään järkeä kopioida oliota,

kääntäjän automaattisesta kopiomuodostinsta on vain haittaa.

Kopiointi on mahdollista estää määrittelemällä kopiomuodostin privaatiksi. Kun olet itse määrittänyt kopiomuodostimen,

kääntäjä ei yritä tuputtaa omaansa Kukaan luokan ulkopuolella ei pääse

kutsumaan kopiomuodostintaOnko asia nyt ratkaistu? Huomaatko ongelman?

Page 81: Jani Rönkkönen jani.ronkkonen@lut.fi

81

Kopioinnin estäminen

privaattiin kopiomuodostimeen pääsee käsiksi luokan sisältä tai ystävien kautta Ongelma ratkaistaan jättämällä

kopiomuodostin ilman toteutustaLinkkeri antaa virheilmoituksen, jos joku yrittää käyttää kopiomuodostinta

Page 82: Jani Rönkkönen jani.ronkkonen@lut.fi

82

Viipaloituminen

PaivattyMjono pmj(“paivays”,“1.1.2006”);

//luodaan kopioMjono mj(pmj); PaivattyMjono

Mjono

Jotain pielessä! Mitä?

Page 83: Jani Rönkkönen jani.ronkkonen@lut.fi

83

Viipaloituminen (Slicing) Ilmiötä, missä oliota kopioitaessa

kopioidaankin erehdyksessä vain olion kantaluokkaosa kutsutaan viipaloitumiseksiPaivattyMjono pmj(“paivays”, “1.1.2006”);

Mjono mj(pmj); //luodaan kopio

PaivattyMjono

Mjono Copy of Mjono

PaivattyMjono

Page 84: Jani Rönkkönen jani.ronkkonen@lut.fi

84

Viipaloitumisen kiertäminen C++ kielessä Voidaan toteuttaa kloonaa-funktio joka luo uuden olion

ja palauttaa osoittimen tähän. Lisäksi määritellään se virtuaaliseksi (muodistinta ei voi määrittää virtuaaliseksi).

Viipaloitumista ei tapahdu, sillä kloonaa funktion virtuaalisuus takaa sen, että kutsutaan ensin alimmaista lapsiluokkaa

Viipaloituminen on kuitenkin edelleen vaarana parametrin välityksessä ja paluuarvoissa, joissa kopiomuodostinta kutsutaan automaattisesti.

Siksi paras ratkaisu välttää viipaloitumista on huolellinen suunnittelu ja ongelman tiedostaminen.

Yksi tapa estää viipaloitumista on myös se, että kaikki kantaluokat ovat abstrakteja.

Viipaloitumista ei pääse tapahtumaan, sillä pelkkää abstraktia luokkaa ei voi muodostaa

Page 85: Jani Rönkkönen jani.ronkkonen@lut.fi

85

Kloonausfunktio esimerkki

Mjono.hclass Mjono{public: virtual Mjono* Clone() { return new Mjono (*this); }};

Pmjono.h

class PaivattyMjono: public Mjono{public: virtual Mjono* Clone() { return new PaivattyMjono (*this); }};

PaivattyMjono pmj(“paivays”, “1.1.2006”);

Mjono *mj = pmj.Clone(); //luodaan kopio Clone funktiolla

Page 86: Jani Rönkkönen jani.ronkkonen@lut.fi

86

Olioiden sijoittaminen

Olioiden kopioimisen lisäksi on toinenkin tapa saada aikaan kaksi keskenään samanlaista oliota: Sijoittaminen

Sijoittamisen ja kopioinnin ero: kopioinnista luodaan uusi olio, joka

alustetaan vanhan olion perusteella sijoittamisessa muutetaan olemassa

olevan olion arvo vastaamaan toista oliota

Page 87: Jani Rönkkönen jani.ronkkonen@lut.fi

87

Sijoittamiseen liittyviä ongelmia Liittyvät useimmiten vanhan sisällön käsittelyyn

Usein joudutaan vapauttamaan vanhaa muistia ja siivoamaan oliota purkajien tapaan ennen kuin uudet arvot voidaan alustaa olioon.

Mitä jos siivousoperaatio johtaa virhetilanteeseen?

Luultavasti haluttaisiin palauttaa vanhat arvot takaisin oliollePitäisi varmistua siitä, että siivottuja arvoja ei ole vielä heitetty roskiin

On myös olemassa tilanteita, missä ei ole mielekästä sallia sijoitusta.pitää olla mahdollista estää sijoitusoperaatio

Page 88: Jani Rönkkönen jani.ronkkonen@lut.fi

88

C++ sijoitusoperaattori(assignment operator) C++:ssa olioiden sijoittaminen tapahtuu

erityisellä jäsenfunktiolla, jota kutsutaan sijoitusoperaattoriksi

Kun ohjelmassa tehdään kahden olion sijoitus a = b, kyseisellä ohjelmarivillä kutsutaan itse asiassa olion a sijoitusoperaattoria ja annetaan sille viite olioon b parametrina.

Sijoitus aiheuttaa jäsenfunktiokutsun a.operator =(b)

Sijoitusoperaattorin tehtävänä on sitten tuhota olion a vanha arvo ja korvata se olion b arvolla.

Se mitä kaikkia operaatioita tähän liittyy, riippuu täysin kyseessä olevasta luokasta

Page 89: Jani Rönkkönen jani.ronkkonen@lut.fi

89

Sijoitus itseen

Mitä seurauksia seuraavalla koodilla on?

a=a;

Miten ongelman voi ehkäistä?

Ensin lähdetään tyhjentämään sijoitettavan olion vanhaa arvoa Samalla tuhotaan vahingossa sijoitettava arvo Eli muistialueen alustamaton sisältö kopioidaan itsensä päälle

Tarkastetaan ennen sijoitusoperaatioon ryhtymistä, että kyseessä ei ole sijoitus itseen.

Jätetään sijoitusoperaatio tekemättä jos näin on

Page 90: Jani Rönkkönen jani.ronkkonen@lut.fi

90

Sijoitusoperaattorin toteutusEsimerkki

Mjono.hclass Mjono{public:

Mjono& operator =(const Mjono& vanha);...

};Mjono.cpp

Mjono& Mjono::operator =(const Mjono& vanha){

if (this != &vanha)//Ei sijoiteta itseen{

delete[] mMerkit; mMerkit = 0; //Vapauta vanhamKoko = vanha.mKoko; //Sijoita kokoif (mKoko != 0){ //Varaa tila, jos koko ei nolla

mMerkit = new char[mKoko + 1];for (unsigned long i = 0; i != mKoko; ++i)

{ mMerkit[i] = vanha.mMerkit[i];} //kopioi merkitmMerkit[mKoko] = ‘\0’; //loppumerkki

}}return *this;

}

Palauttaa viitteen itseensä mahdollistaa

ketjusijoituksen a=b=c

Page 91: Jani Rönkkönen jani.ronkkonen@lut.fi

91

Periytyminen ja sijoitusoperaattori

Toimitaan samoin kuin kopiomuodostimenkin kanssa

aliluokka kutsuu kantaluokan sijoitusoperaattoria Pmjono.h

class PaivattyMjono : public Mjono{public:

PaivattyMjono& operator =(const PaivattyMjono& vanha);};

Pmjono.cpp

PaivattyMjono& PaivattyMjono::operator =(const PaivattyMjono& vanha){

if (this != &vanha){//Jos ei sijoiteta itseen

Mjono::operator =(vanha); //Kantaluokan sijoitusoperaattori

strcpy(mPaivays, vanha.mPaivays);}return *this;

}

Page 92: Jani Rönkkönen jani.ronkkonen@lut.fi

92

Oletus-sijoitusoperaattori Jos luokalla ei ole kirjoitettu

sijoitusoperaattoria, kääntäjä luo sen itse. Oletussijoitusoperaattori yksinkertaisesti

sijoittaa kaikki olion jäsenet yksi kerrallaan Jos jäseninä on osoittimia, molemmat oliot

tulevat sijoituksen jälkeen osoittamaan samaan paikkaan EI HALUTTUA!

Jokaiseen luokkaan tulisi erikseen kirjoittaa sijoitusoperaattori!

Page 93: Jani Rönkkönen jani.ronkkonen@lut.fi

93

Sijoituksen estäminen

Estetään samalla tavalla kuin kopioiminenkin Määritellään sijoitusoperaattori

privaatiksi Ei anneta sijoitusoperaattorille

toteutusta ollenkaan

Page 94: Jani Rönkkönen jani.ronkkonen@lut.fi

94

Sijoitus ja viipaloituminen

Viipaloituminen on mahdollista jos sijoittaminen tapahtuu kantaluokkaosoittimien tai -viitteiden kautta void sijoita (Mjono& mihin, const Mjono& mista)

{mihin = mista;

}

int main(){

Mjono mj(“Tavallinen”);PaivattyMjono pmj(“Päivätty”, “1.1.2006”);

//Viipaloituminen funktion sisällä!sijoita(mj, pmj);

}

Page 95: Jani Rönkkönen jani.ronkkonen@lut.fi

95

Viipaloitumisen välttäminen sijoituksessa Sijoitusoperaattoriakaan ei voi

määritellä virtuaaliseksi Helpointa olisi tehdä luokkahierarkia,

jossa kaikki kantaluokat ovat abstrakteja, jolloin kantaluokassa sijoitusoperaattori voidaan määritellä protected:iksi

Voit myös aina tarkastaa sijoituksen yhteydessä että molemmat oliot ovat varmasti oikeaa tyyppiä tämä onnistuu typeid-operaattorin avulla

Page 96: Jani Rönkkönen jani.ronkkonen@lut.fi

96

Sijoitettavien olioiden tyypin tarkastus

#include <typeinfo>Mjono& Mjono::operator =(const Mjono& vanha){

if (typeid(*this) != typeid(vanha)) { /*virhetoiminta*/}

if (this != &vanha)//Ei sijoiteta itseen{

.

.

.}return *this;

}

Page 97: Jani Rönkkönen jani.ronkkonen@lut.fi

97

Puhdasoppinen luokka

Olisi hyvä jos kaikki luokat määrittelisivät seuraavat tärkeät funktiot

Oletusmuodostin (Default constructor) Kopiointimuodostin (Copy constructor) Sijoitusoperaattorin (Assignment operator) Purkajan (Destructor)

Tällainen luokkarakenne tunnetaan puhdasoppisen kanonisen luokan muotona (orthodox canonical class)

Page 98: Jani Rönkkönen jani.ronkkonen@lut.fi

98

Puhdasoppinen luokkaEsimerkki

#ifndef Mjono_H

#define Mjono_Hclass Mjono{public://constructors///////////////////////

Mjono(const Mjono& vanha); //kopiomuodostin Mjono(const char* merkit);

//destructors////////////////////////virtual ~Mjono(); //purkaja

//operators/////////////////////////Mjono& operator =(const Mjono& vanha); //sijoitusoperaattori

//operations////////////////////////unsigned long kerroKoko(){ return koko;}

protected:

private: unsigned long mKoko;char* mMerkit;

};#endif /* Mjono_H */

Mjono.h

Page 99: Jani Rönkkönen jani.ronkkonen@lut.fi

99

Missä mennään? Rajapinnoista

Esimerkki Abstrakti luokka Puhdas virtuaalinen funktio Rajapinnan käytöstä Komponentteihin jaottelusta

Perinnän vaikutus olion luontiin ja tuhoamiseen muodostint ja periytyminen Purkajat ja periytyminen

Perityn luokan eri tyypit Aliluokan ja kantaluokan suhde Tyyppimuunnokset

Olioiden sijoitus ja kopiointi Olioiden kopiointi Olioiden sijoitus Puhdasoppinen luokka

Serialisaatio Operaattoreiden uudelleenmäärittely

Ystäväfunktiot Ystäväluokat

Yhteenveto

Page 100: Jani Rönkkönen jani.ronkkonen@lut.fi

100

Serialisaatio (Serialization)

Joskus tulee tarve tallentaa olio tai vaikkapa siirtää se verkon yli

Tällöin olio pitää muuttaa ”kasaksi bittejä”, eli esimerkiksi tietovirraksi, joka voidaan sitten vaikka tallentaa tiedostoon

Vastaavasti tarvitaan metodi, jolla tietovirta saadaan muutettua takaisin olioksi

Page 101: Jani Rönkkönen jani.ronkkonen@lut.fi

101

Serialisaatio (Serialization) Serialisaatio tulisi hoitaa kunkin luokan sisällä

tarkoitukseen sopivalla jäsenfunktiolla joka esimerkiksi muuttaa olion datan std::ostream tyyppiseksi tietovirraksi

Vastaavasti kussakin luokassa tulee tällöin olla jäsenfunktio, jolla olio voidaan palauttaa (Unserialization)

Toteutetaan esimerkiksi niin, että luodaan uusi olio ja alusteaan se std::istream tyyppisen tiedon perusteella

Serialisaatioon liittyy samoja ongelmia, kuin kopiointiin ja sijoittamiseen, jos luokassa on dynaamisia rakenteita tai se on osa periytyminenhierarkiaa

Serialisaatiossa muunnoksen pitää käsitellä kaikki olion tilaan liittyvät asiat samoin kuin syväkopioinnissa

Lisäksi pitää ottaa huomioon muunnokseen liittyvät asiat, kuten muunnoksen formaatti, perityn olion tyyppitiedon tallennus jne.

Page 102: Jani Rönkkönen jani.ronkkonen@lut.fi

102

Esimerkki serialisaatiosta#include ”Engine.h”class Car { private: Engine myEngine; int yearModel;

public: serialize(ostream& s) {

engine.serialize(s); s << ‘ ‘ << yearModel; } // ‘ ‘ numeroiden erotinunserialize(istream& s) { engine.unserialize(s); s >> yearModel; }

};

class Engine{ private: int engineType; public: serialize(ostream& s) { s << ‘ ‘ << EngineType; } // ‘ ‘ numeroiden erotinunserialize(istream& s) { s>>engineType; }

Page 103: Jani Rönkkönen jani.ronkkonen@lut.fi

103

Operaattoreiden uudelleenmäärittely Olemme tottuneet käyttämään operaatioita

kuten +, -, ==, *, /, jne… Nämä ovat itse asiassa funktioita! Tällaisia funktioita vain kutsutaan hieman eri

tavalla! Esimerkki x + 7;

Esitetty ihmiselle helpossa muodossa Voidaan kuitenkin ajatella myös muodossa +

(x,7), missä: ‘+’ on funktion nimi x,7 ovat argumentit Funktio paluattaa argumenttiensa summan

Page 104: Jani Rönkkönen jani.ronkkonen@lut.fi

104

Operaattorien uudelleenmäärittely

Olisi kiva, jos voisimme operoida omiakin olioita tutuilla operaattoreilla!

Olioiden tietojen käsittely operaattorien avulla on ihmiselle luontevampaa

Kääntäjä ei voi kuitenkaan tietää miten tuttuja operaatioita voidaan soveltaa oikein eri tyyppisten luokkien kanssa Luokan kirjoittajan tulisi itse määritellä

miten operaatiot tulisi suorittaa.

Page 105: Jani Rönkkönen jani.ronkkonen@lut.fi

105

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

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

Page 106: Jani Rönkkönen jani.ronkkonen@lut.fi

106

Operaattorien uudelleenmäärittely Perusteet

Hyvin samanlainen funktioiden uudelleenmäärittelyn kanssa Operaattori on itse asiassa funktion “nimi”

Kaksi eri toteutustapaa Jäsenfunktiona Tavallisena funktiona

Joidenkin operaattorien uudelleenmääritys on pakko olla toteutettuna jäsenfunktiona: sijoitusoperaattori = taulukon indeksointioperaattori [] funktiokutsu () jäsenen osoitusoperaattori ->

Page 107: Jani Rönkkönen jani.ronkkonen@lut.fi

107

Ohjeita uudelleenmäärittelystä

Määrittele, jos se selventää ohjelmaa

Älä uudelleenmäärittele siten, että operaattorin merkitys ei ole itsestäänselvä

Palauta operaattorifunktiosta aina kyseisen luokan tyyppinen olio

Page 108: Jani Rönkkönen jani.ronkkonen@lut.fi

108

Operaattorin uudelleenmäärittely jäsenfunktiona

Määritellään Mjono-luokalle jäseneksi ‘+’ operaattori: const Mjono operator +(const Mjono& teksti); Huomaa vain yksi argumentti! Operaation toinen osapuoli on olio jota

kutsutaan

Käyttöesimerkki: Mjono sukuNimi(“Rönkkönen”); Mjono etuNimi= ”Jani”; Mjono kokoNimi = etuNimi + sukuNimi;

Automaattinen tyyppimuunnosMerkkijonostaMjono olioksi

Page 109: Jani Rönkkönen jani.ronkkonen@lut.fi

109

Operaattorin uudelleenmäärittely jäsenfunktiona Esitetty yhteenlasku

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

funktion nimi

argumenttikutsuttavaolio

Page 110: Jani Rönkkönen jani.ronkkonen@lut.fi

110

Ongelma!

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

Page 111: Jani Rönkkönen jani.ronkkonen@lut.fi

111

Syy ongelmaan

Mjono mJonoKaksi = cMjono + mjono; //Virhe

funktion nimi

argumenttikutsuttavaolio

cMjono on C-tyyppinen merkkijono sillä ei voi olla jäsenfunktioita se ei voi suoriutua komennosta: cMjono.operator+(mjono)

Page 112: Jani Rönkkönen jani.ronkkonen@lut.fi

112

Ratkaisu

Loogisesti ajateltuna edellä mainittu kahden merkkijonon yhteen liittäminen tulisi onnistua!!!

Mikä olisi ongelmaan ratkaisu? Määritetään funktio:const Mjono operator +( const Mjono& teksti1,

const Mjono& teksti2);

Page 113: Jani Rönkkönen jani.ronkkonen@lut.fi

113

Mitä tuli tehtyä? Uudelleenmäärittelimme ‘+’ –

operaattorin tavallisena funktiona:const Mjono operator +( const Mjono& teksti1,

const Mjono& teksti2);

Yhden argumentin sijasta annamme kaksi argumenttia

C-tyyppinen merkkijono voidaan muuntaa sopivan tyyliseksi oikeanlaisella muodostimella

Page 114: Jani Rönkkönen jani.ronkkonen@lut.fi

114

Vielä pieni ongelma Tavallisina funktioina toteutetut

operaattorien uudelleenmäärittelyt ovat tehottomia Olioiden tietoihin voi päästä käsiksi vain olioiden

julkisten rajapintojen kautta (get-metodit, muunnosmetodit)

Ylimääräistä työtä!

Olisi parempi, jos operaattorin uudelleenmäärittelyn toteuttavassa funktiossa päästäisiin käsiksi käsiteltävien olioiden tietoihin suoraan

Page 115: Jani Rönkkönen jani.ronkkonen@lut.fi

115

Ystäväfunktio Luokalla on mahdollisuus määritellä

joukon funktioita “ystävikseen” Luokan ystäväfunktioiden koodi pääsee

käsiksi myös luokan olioiden private-osiin. ystäväfunktioilla on käytännössä katsoen

samat oikeudet luokan olioihin kuin luokan omilla jäsenfunktioilla

Ystäväfunktiot ei kuitenkaan pääse käsiksi olioiden this-osoittimeen

Page 116: Jani Rönkkönen jani.ronkkonen@lut.fi

116

Ystäväfunktion määrittely

Ystäväfunktio määritellään kirjoittamalla varattu sana friend ja sen jälkeen funktion koko nimi

Kyseinen esittely EI tee ystäväfunktiosta luokan jäsenfunktiota! kyseessä on täysin erillinen normaali

funktio, jolle vain sallitaan pääsy luokan olioiden private-osaan

Page 117: Jani Rönkkönen jani.ronkkonen@lut.fi

117

‘+’-operaattorin uudelleenmäärittely ystäväfunktiona

class Mjono{

public:

Mjono();Mjono(const char *const);Mjono(const Mjono &)~Mjono;

Mjono operator+(const Mjono&);

friend Mjono operator+(const Mjono&, const Mjono&);...

Page 118: Jani Rönkkönen jani.ronkkonen@lut.fi

118

Ystäväfunktiot yhteenveto

Ystäväfunktioilla on lupa päästä käsiksi olioiden kaikkiin tietoihin

Ystäväfunktiot heikentävät tiedon kapselointia, mutta parantavat suorituskykyä

Tyypillisin käyttökohde on operaattoreiden uudelleenmäärittely

Page 119: Jani Rönkkönen jani.ronkkonen@lut.fi

119

C++ Ystäväluokat Toimii samoin kuin ystäväfunktiot Jos joku luokka liittyy kiinteästi

kehitteillä olevaan luokkaan, voidaan liittyvä luokka määritellä ystäväluokaksi Luokkaystävyys saadaan aikaiseksi

määreellä friend class Luokkanimi Luokkaystävyys tulee ilmaista sen luokan

esittelyssä, joka haluaa sallia toisen luokan jäsenfunktioille vapaan pääsyn omien olioidensa sisälle

Page 120: Jani Rönkkönen jani.ronkkonen@lut.fi

120

C++ ystäväluokkaEsimerkki

class KirjastonKirja{

public: ...private:void asetaPalautusPvm(const Paivays& uusiPvm);Paivays palautusPvm_;...friend class LainausJarjestelma;//Lainausjärjestelman oliot kutsuvat funktiota asetaPalautusPvm

};

Page 121: Jani Rönkkönen jani.ronkkonen@lut.fi

121

Missä mennään? Rajapinnoista

Esimerkki Abstrakti luokka Puhdas virtuaalinen funktio Rajapinnan käytöstä Komponentteihin jaottelusta

Perinnän vaikutus olion luontiin ja tuhoamiseen muodostint ja periytyminen Purkajat ja periytyminen

Perityn luokan eri tyypit Aliluokan ja kantaluokan suhde Tyyppimuunnokset

Olioiden sijoitus ja kopiointi Olioiden kopiointi Olioiden sijoitus puhdasoppinen luokka

Serialisaatio Operaattoreiden uudelleenmäärittely

Ystäväfunktiot Ystäväluokat

Yhteenveto

Page 122: Jani Rönkkönen jani.ronkkonen@lut.fi

122

Mitä tänään opimme? Rajapintojen käyttö ja toteutuksen kätkentä on yksi tärkeimmistä

ohjelmistotuotannon perusperiaatteista Rajapinta toteutetaan abstrakteina luokkina Abstrakti luokka luodaan puhtaiden virtuaalifunktioiden avulla Rajapintojen avulla voimme pilkkoa monimutkaiset systeemit pienempiin

osiin Periytymisen käyttö pitää ottaa huomioon olioita luodessa ja tuhotessa

Muodostimet ja periytyminen Purkajat ja periytyminen

Peritty luokka on aina myös kantaluokkansa edustaja. Käytettävissä olevat operaatiot riippuvat siitä minkä tyyppinen osoitin on

kyseessä Opimme muuttamaan osoittimien ja viittausten tyyppiä

Periytyminen pitää ottaa myös huomioon olioita kopioitaessa ja sijoittaessa

Kääntäjä ei voi tietää miten kopioidaan ja sijoitetaan järkevästi Oletuskopiointi ja oletussijoitus menevät helposti pieleen

toteuta kopiomuodostin ja sijoitusoperaattori mieluummin itse Puhdasoppinen luokka

oletusmuodostin, kopiomuodostin, sijoitusoperaattori, virtuaalinen purkaja Voimme uudelleenmääritellä muitakin operaattoreita kuin

sijoitusoperaattorin helpottamaan luokkiemme käyttöä On mahdollista (vaikkei välttämättä suositeltavaa) antaa luokan

ulkopuoliselle funktiolle tai luokalle vapaa pääsy luokan jäseniin