Upload
aerona
View
77
Download
3
Embed Size (px)
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
Citation preview
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
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
3
Tarina… Olipa kerran insinööri, joka
työskenteli palvelimen parissa
Palvelimen oli tarkoitus pystyä kommunikoimaan lukuisten erilaisten asiakasohjelmien kanssa
4
Tarina jatkuu…. Palvelimen ja
asiakasohjelmien väliseksi kommunikointitavaksi valittiin 2-suuntainen putki
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!
…
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.)
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()
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.
9
Ja sitten tarinan kysymys!
Mistä putkikirjasto voi tietää ketä kutsua kun viesti saapuu???
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
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;
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?
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
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();
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;
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
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
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;
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.
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
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
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.
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.
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.
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
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
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
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
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;
}
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!
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.
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
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
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;
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;
36
Virtuaalipurkaja
Jos luokasta peritään muita luokkia, muista aina määritellä purkaja virtuaaliseksi!
Ei haittaa vaikka purkaja on eri niminen lapsiluokassa.
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
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
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
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);
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.
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
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ä
44
C++ tyyppimuunnosoperaattorit
Vanha C-kielinen tyyppimuunnos:(uusiTyyppi)vanhaArvo sulkujen sijainti hieman epälooginen
C++ kielessä mahdollista myös: uusiTyyppi(vanhaArvo)
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ää
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
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;
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
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
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.
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!
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;
}
}
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.
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.
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;
}
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
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
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_));
}
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
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
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ä
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
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
64
Viitekopiointi (Reference copy)
Kaikkein helpoin kopiointitavoista. Ei luoda ollenkaan uutta oliota
vaan uutta oliota kuvastaa viite vanhaan olioon.
65
ViitekopiontiEsimerkki
AlkuperainenOlio
Viitekopio
MUISTI:
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
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.
68
MatalakopioEsimerkki
1 2 3 4 5
6 7
1 2 3 4 5 6 7
AlkuperainenOlio
Matalakopio
MUISTI:
Alkuperaisenolion ulkoiset tietorakenteet
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.
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
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.
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
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!
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.
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
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ä
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];};
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 !!!!
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
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?
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
82
Viipaloituminen
PaivattyMjono pmj(“paivays”,“1.1.2006”);
//luodaan kopioMjono mj(pmj); PaivattyMjono
Mjono
Jotain pielessä! Mitä?
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
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
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
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
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
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
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
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
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;
}
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!
93
Sijoituksen estäminen
Estetään samalla tavalla kuin kopioiminenkin Määritellään sijoitusoperaattori
privaatiksi Ei anneta sijoitusoperaattorille
toteutusta ollenkaan
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);
}
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
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;
}
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)
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
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
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
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.
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; }
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
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.
105
Uudelleenmääriteltävät operaattorit C++ kielessä
+ - * / % ^ & | ~ ! = < > += -= *= /= %= ^= &= |= << >> >>= <<= == != <= >= && || ++ -- ->* , -> [] () new delete
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 ->
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
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
109
Operaattorin uudelleenmäärittely jäsenfunktiona Esitetty yhteenlasku
voidaan esittää funktiokutsuna muodossa: kokoNimi = etuNimi.+(sukuNimi);
funktion nimi
argumenttikutsuttavaolio
110
Ongelma!
Miksi seuraava ei onnistu?:char cMjono[] = ”Hello”;Mjono mjono(”World!”);Mjono mJonoKaksi = cMjono + mjono; //Virhe
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)
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);
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
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
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
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
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&);...
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
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
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
};
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
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