olio-ohjelmoinnin perusteet luento 5: rajapinnoista, perinnän huomioon ottamisesesta

96
Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta Sami Jantunen LTY/Tietotekniikan osasto

Upload: phuc

Post on 08-Jan-2016

24 views

Category:

Documents


0 download

DESCRIPTION

Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta. Sami Jantunen LTY/Tietotekniikan osasto. Sisältö. Rajapinnoista Esimerkki Abstrakti luokka Puhdas virtuaalinen funktio Rajapinnan käytöstä Komponentteihin jaottelusta - PowerPoint PPT Presentation

TRANSCRIPT

Page 1: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

Olio-ohjelmoinnin perusteetluento 5: Rajapinnoista, perinnän huomioon ottamisesesta

Sami JantunenLTY/Tietotekniikan osasto

Page 2: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

Sisältö Rajapinnoista

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

Perinnän vaikutus olion luontiin ja tuhoamiseen Rakentajat ja perintä Purkajat ja perintä

Perityn luokan eri tyypit Aliluokan ja kantaluokan suhde Tyyppimuunnokset

Olioiden sijoitus ja kopiointi Olioiden kopiointi Olioiden sijoitus

Yhteenveto Puhdasoppinen luokka Kertaus

Page 3: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

Tarina… Kauan aikaa sitten

työskentelin erään palvelimen parissa

Palvelimen oli tarkoitus pystyä kommunikoimaan lukuisten erilaisten asiakasohjelmien kanssa

Page 4: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

Tarina jatkuu…. Palvelimen ja

asiakasohjelmien väliseksi kommunikointitavaksi valittiin 2-suuntainen putki

Page 5: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

Tarina jatkuu…. Pian huomasin, 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: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

Tarina jatkuu…

Niinpä ajattelin soveltaa yhtä olioajattelun perusajatuksista: Tiedon piilottamista

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

Page 7: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

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: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

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: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

Ja sitten tarinan kysymys!

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

Page 10: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

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: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

Taustatietoa Jokaisella oliolla on olemassa

osoitinmuuttuja this, mikä osoittaa itseensä

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

MyClass *this;

Page 12: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

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ä meidän ongelman?

Page 13: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

Vielä ongelmia

Okei, nyt sitten 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: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

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: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

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: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

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: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

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

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

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: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

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: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

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 luokkan 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: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

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ää abstraktista ja periyttää siitä erirotuisia koiria.

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

Page 21: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

Muistatko viel?Moniperintä -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: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

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: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

Rajapinnoista Rajapintojen käyttö ja toteutuksen

kätkentä on yksi ehkä 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: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

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: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

Missä mennään? Rajapinnoista

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

Perinnän vaikutus olion luontiin ja tuhoamiseen Rakentajat ja perintä Purkajat ja perintä

Perityn luokan eri tyypit Aliluokan ja kantaluokan suhde Tyyppimuunnokset

Olioiden sijoitus ja kopiointi Olioiden kopiointi Olioiden sijoitus

Yhteenveto Puhdasoppinen luokka Kertaus

Page 26: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

Sä muistatko viel? Rakentajien käyttö perinnän yhteydessä

Isäluokan rakentajaa kutsutaan aina!*

CPoodle.cpp

CPoodle::CPoodle(int x, char y[ ]) : CDog (x,y)

{

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

}

Normaalia rakentaja tavaraa

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

Page 27: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

Periytyminen ja rakentajat

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

rakentajansa. Mutta miten pitäisi hoitaa

kantaluokkien alustus?

Mammal

Land-Mammal

int weight

int numLegs

Dogboolean rabid

giveBirth( )

SheepDog

Page 28: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

Periytyminen ja rakentajatVastuut

Aliluokan vastuulla on: Aliluokan mukanaan tuomien

uusien jäsenmuuttujien ja muiden tietorakenteiden alustaminen.

Em. vastuita varten aliluokkiin toteutetaan oma(t) rakentaja(t)

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 rakentajat

Page 29: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

Periytyminen ja rakentajatParametrit?

Miten taataan että kaikki rakentajat saavat tarvitsemansa parametrit?

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

Kantaluokan parametrien saannin takaamiseksi C++:n tarjoama ratkaisu on, että aliluokan rakentajan alustuslistassa kutsutaan kantaluokan rakentajaa ja välitetään sille tarvittavat parametritCPoodle.cpp

CPoodle::CPoodle(int x, char y[ ]) : CDog (x,y)

{

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

}

Page 30: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

Entä jos? Jos aliluokan rakentajan alustuslistassa ei

kutsuta mitään kantaluokan rakentajaa: Kääntäjä kutsuu automaattisesti kantaluokan

oletusrakentajaa (joka ei siis tarvitse parametreja)

Tällainen ratkaisu harvemmin johtaa toivottuun tulokseen

Muista siis kutsua aliluokan rakentajassa kantaluokan rakentajaa itse!

Page 31: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

Rakentajien suoritusjärjestys

Huipusta alaspäin Olio ikäänkuin rakentuu

vähitellen laajemmaksi ja laajemmaksi.

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

Page 32: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

Periytyminen ja purkajat

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

Purkajien vastuut jaettu samalla lailla kuin rakentajienkin

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

Aliluokat puolestaan siivoavat periytymisessä lisätyt laajennusosat tuhoamiskuntoon

Page 33: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

Purkajien suoritusjärjestys Purkajia kutsutaan päinvastaisessa

järjestyksessä kuin rakentajia Ensin kutsutaan aliluokan purkajia ja

siitä siirrytään perintähierakiassa ylöspäin

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

Page 34: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

Esimerkki

Jotain pahasti pielessä!-Mitä?

Mammal

Land-Mammal

int weight

int numLegs

Dogboolean rabid

giveBirth( )

SheepDog

Mammal *myMammal;myMammal = new SheepDog();

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

delete myMammal;

Page 35: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

Esimerkki

Vain kantaluokka tuhoutuu!

Kuinka korjata tilanne?

Mammal

Land-Mammal

int weight

int numLegs

Dogboolean rabid

giveBirth( )

SheepDog

Mammal *myMammal;myMammal = new SheepDog();

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

delete myMammal;

Page 36: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

Virtuaalipurkaja

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

Ei haittaa vaikka purkaja on eri niminen lapsiluokassa.

Page 37: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

Missä mennään? Rajapinnoista

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

Perinnän vaikutus olion luontiin ja tuhoamiseen Rakentajat ja perintä Purkajat ja perintä

Perityn luokan eri tyypit Aliluokan ja kantaluokan suhde Tyyppimuunnokset

Olioiden sijoitus ja kopiointi Olioiden kopiointi Olioiden sijoitus

Yhteenveto Puhdasoppinen luokka Kertaus

Page 38: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

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: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

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: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

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: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

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

Page 42: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

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: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

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 vaan se, että uusi ja vanha olio on kopioinnista poiketen eri tyyppiä

Page 44: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

C++ tyyppimuunnosoperaattorit

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

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

Page 45: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

Ongelmia tyyppimuunnosten kanssa

Tyyppimuunnoksia voidaan käyttää suorittamaan kaikenlaisia muunnoksia. Esim: kokonasiluvuista 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: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

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: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

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: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

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: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

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: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

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 okean tyyppinen olio, palautetaan kyseiseen olioon osoittava aliluokkaosoitin.

Page 51: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

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 hiettää poikkeuksen (std::bad_cast) Miksi näin? Puhumme poikkeuksista lisää seuraavilla

luennoilla!

Page 52: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

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: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

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.

Page 54: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

reinterpret_cast sallitut käyttökohteet:

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

Kokonaisluvun muuntaminen takaisin osoittimeksi

Osoittimen muunto toisentyyppiseksi osoittimeksi

Viitteen muunto toisentyyppiseksi viitteeksi Funktio-osoittimen muunto toisentyyppiseksi

funktio-osoittimeksi.

Page 55: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

reinterpret_cast käyttöesimerkki

void luoKayttoliittyma(KirjastonKirja *kirja1, KirjastonKirja* kirja2)

{

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)

{

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

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

}

Page 56: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

Missä mennään? Rajapinnoista

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

Perinnän vaikutus olion luontiin ja tuhoamiseen Rakentajat ja perintä Purkajat ja perintä

Perityn luokan eri tyypit Aliluokan ja kantaluokan suhde Tyyppimuunnokset

Olioiden sijoitus ja kopiointi Olioiden kopiointi Olioiden sijoitus

Yhteenveto Puhdasoppinen luokka Kertaus

Page 57: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

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 58: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

Olioiden kopioinnista Kopioidun olion määritelmä:

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

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

yksinkertainen muistin kopiointi Merkkijonon kopiointi puolestaan saattaa

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

Page 59: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

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. hiissin moottoria ohjaavan olion

kopiointi. kopiointi vaatisi myös fyysisen moottorin kopiointia).

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

Page 60: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

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 61: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

Viitekopiointi (Reference copy)

Kaikkein helpoin kopiointitavoista. Ei luoda ollenkaan uutta oliota

vaan uutta oliota kuvastaa viite vanhaan olioon.

Page 62: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

ViitekopiontiEsimerkki

AlkuperainenOlio

Viitekopio

MUISTI:

Page 63: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

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.

Page 64: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

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 65: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

MatalakopioEsimerkki

1 2 3 4 5

6 7

1 2 3 4 5 6 7

AlkuperainenOlio

Matalakopio

MUISTI:

Alkuperaisenolion ulkoiset tietorakenteet

Page 66: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

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 67: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

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 68: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

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

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

Olioiden kannalta ehdottomasti paras kopiointitapa Luodaan kopio kaikista olion tilaan kuuluvista

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

Page 69: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

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 70: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

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 kirjaston kopiointia!

Page 71: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

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

kopiorakentajan, joka suorittaa kopioinnin ohjelmoijan sopivaksi katsomalla tavalla.

Page 72: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

Muistatko viel? Kopiorakentaja (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, missä kopion luominen on tarpeen.

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

Page 73: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

Periytyminen ja kopiorakentaja Periytyminen tuo omat lisänsä kopion

luomiseen. Aliluokan olio koostuu useista osista, ja

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

Aliluokan olion kopioiminen onkin jaettu eri luokkien kesken samoin kuin rakentajat yleensä Aliluokan kopiorakentajan vastuulla on kutsua

kantaluokan kopiorakentajaa ja lisäksi alustaa aliluokan osa olioista kopioksi alkuperäisestä

Page 74: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

Kopiorakentaja esimerkkiMjono.h

class Mjono{public:

Mjono(const char* merkit);

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

private: unsigned long koko_;char* merkit_;

};

Mjono.cpp

Mjono::Mjono(const Mjono& vanha) : koko_(vanha.koko_), merkit_(0)

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

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

{ merkit_[i] = vanha.merkit_[i];} //kopioi merkitmerkit_[koko_] = ‘\0’; //loppumerkki

}}

Pmjono.cpp//olettaa että Paivays-luokalla on kopiorakentajaPaivattyMjono::PaivattyMjono(const PaivattyMjono& vanha) : Mjono(vanha),

paivays_(vanha.paivays_){}

Pmjono.hclass PaivattyMjono : public Mjono{public:

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

//kopiorakentaja PaivattyMjono(const PaivattyMjono& vanha); virtual ~PaivattyMjono();...

private: Paivays paivays_;

};

Page 75: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

Muista!

Jos unohdat aliluokan kopiorakentajassa kutsua kantaluokan kopiorakentajaa Kääntäjä kutsuu kantaluokan

oletusrakentajaa automaattisestiOlio ei kopioidu kunnolla

Page 76: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

Kääntäjän luoma oletusarvoinen kopiorakentaja Jos et määrittele luokalle

kopiorakentajaa, kääntäjä luo sen automaattisesti Yksinkertaistaa ohjelmointia Oletusarvoinen kopiorakentaja käyttää

matalakopiointia Useinmiten matalakopiointi ei ole riittävä

jos kopiorakentajan toteutus unohtuu, oliot kopioituvat väärin

Jokaiseen luokkaan tulisi erikseen kirjoittaa kopiorakentaja

Page 77: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

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

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

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

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

kutsumaan kopiorakentajaaOnko asia nyt ratkaistu? Huomaatko ongelman?

Page 78: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

Kopioinnin estäminen

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

kopiorakentaja ilman toteutustaLinkkeri antaa virheilmoituksen, jos joku yrittää käyttää kopiorakentajaa

Page 79: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

Esimerkki

PaivattyMjono pmj(“paivays”, jokupaivays);

//luodaan kopio

Mjono mj(pmj); PaivattyMjono

Mjono

Jotain pielessä! Mitä?

Page 80: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

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

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

Mjono mj(pmj); //luodaan kopio

PaivattyMjono

Mjono Copy of Mjono

PaivattyMjono

Page 81: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

Viipaloitumisen kiertäminen C++ kielessä Otetaan mallia muista oliokielistä

toteutetaan kloonaa-funktio ja määritellään se 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.

Paras ratkaisu näihin on huolellinen suunnittelu ja ongelmien 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 82: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

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 83: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

Sijoittamiseen liittyviä ongelmia Liittyvät useinmiten 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 84: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

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 85: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

Sijoitusoperaattorin toteutusEsimerkki

Mjono.hclass Mjono{public:

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

};Mjono.cpp

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

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

delete[] merkit_; merkit_ = 0; //Vapauta vanhakoko_ = vanha.koko_; //Sijoita kokoif (koko_ != 0){ //Varaa tila, jos koko ei nolla

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

{ merkit_[i] = vanha.merkit_[i];} //kopioi merkitmerkit_[koko_] = ‘\0’; //loppumerkki

}return *this;

}

Palauttaa viitteen itseensä mahdollistaa

ketjusijoituksen a=b=c

Page 86: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

Sijoitus itseen

Mitä seurauksia seuraavalla koodilla on?

a=a;

Miten ongelman voi ehkäistä?

Ensin lähdetään tyhjentämään sijoitettavan luokan 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 87: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

Periytyminen ja sijoitusoperaattori

Toimitaan samoin kuin kopiorakentajankin kanssa aliluokka kutsuu kantaluokan

sijoitusoperaattoriaPmjono.h

class Mjono{public:

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

};

Pmjono.cpp

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

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

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

//Oma sijoitus, oletetaan että Paivays-luokalla on sijoitusoperaattoripaivays_ = vanha.paivays_;

}return *this;

}

Page 88: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

Oletus-sijoitusoperaattori Jos luokalla ei ole kirjoitettu

sijoitusoperaattoria, kääntäjä luo sen itse. Oletus sijoitusoperaattori 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 89: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

Sijoituksen estäminen

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

privaatiksi Ei anneta sijoitusoperaattorille

toteutusta ollenkaan

Page 90: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

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”, tanaan);NumMjono nmj(“Numeroitu”,12);

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

}

Page 91: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

Viipaloitumisen välttäminen sijoituksessa

Helpointa olisi tehdä luokkahierarkia, jossa kaikki kantaluokat ovat abstrakteja

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

avulla

Page 92: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

Sijoitettavien olioiden tyypin tarkastus

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

if (typeid(*this) == typeid(m)) { /*virhetoiminta*/}if (this != &m){//Jos ei sijoiteta itseen

.

.

.}return *this;

}

Page 93: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

Missä mennään? Rajapinnoista

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

Perinnän vaikutus olion luontiin ja tuhoamiseen Rakentajat ja perintä Purkajat ja perintä

Perityn luokan eri tyypit Aliluokan ja kantaluokan suhde Tyyppimuunnokset

Olioiden sijoitus ja kopiointi Olioiden kopiointi Olioiden sijoitus

Yhteenveto Puhdasoppinen luokka Kertaus

Page 94: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

YhteenvetoPuhdasoppinen luokka

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

Oletusrakentaja (Default constructor) Kopiointirakentaja (Copy constructor) Sijoitusoperaattorin (Assignment operator) Purkajan (Destructor)

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

Page 95: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

Puhdasoppinen luokkaEsimerkki

Mjono.hclass Mjono{public://constructors///////////////////////

Mjono(); //oletusrakentaja Mjono(const Mjono& vanha); //kopiorakentaja 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 koko_;char* merkit_;

};

Page 96: Olio-ohjelmoinnin perusteet luento 5: Rajapinnoista, perinnän huomioon ottamisesesta

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

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

Perinnän käyttö pitää ottaa huomioon olioita luodessa ja tuhotessa Rakentajat ja perintä Purkajat ja perintä

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ä

Perintä 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 kopiorakentaja ja sijoitusoperaattori mielummin itse Puhdasoppinen luokka

oletusrakentaja kopiorakentaja sijoitusoperaattori virtuaalinen purkaja