Cpp perintä
Mureakuha
Tässä luvussa tarkastellaan perintää hieman tarkemmin kuin esittelyjaksossa alussa. Perintä on OOP:n perusperiaatteita ja täten keskeinen myös C++:ssa. Voidaan sanoa, että olio-ohjelmoinnin perushyödyt tulevat juuri perinnästä: Asoita voidaan määritellä ja luokitella hierarkisesti perinnän avulla.
Sisällysluettelo |
Kantaluokan ominaisuuksien käsittelymahdollisuudet (Base class access control)
Kun luokka perii toisen, määrittely tapahtuu seuraavasti:
class johdettuluokka: periytymistapa kantaluokka { ... };
Periytymistapa voi olla public tai private. Lisäksi asiaan liittyy läheisesti avainsana protected, josta puhutaan myöhemmin. Periytymistapa määrää, kuinka kantaluokan jäsenet periytyvät johdettuun luokkaan.
public: kantaluokan kaikista public-jäsenistä tulee johdetun luokan public jäseniä. private: kantaluokan kaikista public-jäsenistä tulee johdetun luokan private jäseniä.
Kantaluokan private-jäseniä ei peritä kummassakaan tapauksessa.
//Esimerkki1: //Esimerkki2: #include <iostream> #include <iostream> class kanta { .... int x; .... public: void setx( int n ) { x = n; } int getx( ) { return x; } }; class johdettu : public kanta { class johdettu : private kanta { int y; ... public: ... void sety( int n ) { y= n; } int gety( ) { return y; } }; main() { johdettu olio; olio.setx( 10 ); ... olio.setx( 10 ) ei mahdollinen olio.sety( 20 ); cout << "x = " << olio.getx() << ... olio.getx() ei mahdollinen " y = " << olio.gety() << "\n"; return 0; }
Protected-jäsenten käyttö
Johdettu luokka ei pääse käsiksi kantaluokan private-jäseniin. Joskus tulee kuitenkin tarvetta pitää kantaluokan jäsen edelleen private-tyyppisenä, mutta antaa johdetun luokan päästä käsittelemään jäsentä private-tyyppisenä. Tätä tarkoitusta varten C++:ssa on protected-avainsana.
Protected vastaa privatea muuten paitsi, että johdetussa luokassa voidaan käsitellä kantaluokassa protected-tyyppiseksi määriteltyä jäsentä.
Protected avainsana sijaitsee luokkamäärittelyssä tavallisesti private-lohkon jälkeen:
class luokka{ // privaatit jäsenet protected: // mahdolliset protected jäsenet public: // public jäsenet };
Jos johdettu luokka perii kantaluokkansa public-tyyppisesti, kantaluokan protected jäsenistä tulee johdetun luokan protected jäseniä. Mikäli kantaluokka peritään private-tyyppisesti, kantaluokan protected jäsenistä tulee johdetun luokan privatejäseniä.
#include <iostream> class kanta { protected: int a, b; public: void setab( int n, int m ) { a = n; b = m; } }; class johdettu : public kanta { int c; public: void setc( int n ) { c = n; } void getabc( int &n, int &m, int &k) { n = a; m = b; k = c; } }; main() { johdettu olio; int x, y, z; olio.setab( 10, 20 ); olio.setc( 30 ); olio.getabc( x, y, z ); cout << x << ", " << y << ", " << z << "\n"; return 0; }
#include <iostream> class kanta { protected: int a, b; public: void setab( int n, int m ) { a = n; b = m; } }; class johdettu : private kanta { int c; public: void setabc( int n, int m, int k ) { setab(n,m); c = k; } void getabc( int &n, int &m, int &k) { n = a; m = b; k = c; } }; main() { johdettu olio; int x, y, z; // olio.setab( 10, 20 ); ei mahdollinen // Error NONAME00.CPP 27: 'kanta::setab(int,int)' is not accessible in function main() olio.setabc( 10, 20, 30 ); olio.getabc( x, y, z ); cout << x << ", " << y << ", " << z << "\n"; return 0; }
Konstruktorit, destruktorit ja perintä
Sekä kantaluokalla että johdetulla luokalla voi olla konstruktori- ja destruktori funktiot. Mikäli molemmilla on konstruktorit, luotaessa oliota johdettuun luokkaan, kutsutaan ensin kantaluokan konstruktoria ja sitten johdetun luokan konstruktoria. Destruktoreita kutsutaan päinvastaisessa järjestyksessä.
Mikäli vain johdetun luokan kontruktorille välitetään argumentteja, ne välitetään normaalisti, mutta jos kantaluokan konstruktorille pitää välittää argumentteja, asia on hieman monimutkaisempi.
Ensiksikin, kaikki argumentit välitetään johdetun luokan konstruktorille. Johdetun luokan konstruktorin syntaksi muuttuu hieman:
johdettu_konstruktori( kaikki argumentit ) : kantaluokka ( välitettävät argumentit ) { johdetun luokan konstruktorin koodi }
Molemmat luokat voivat käyttää samaa argumenttia ja johdettu luokka voi välittää kaikki argumentit suoraan kantaluokalle.
#include <iostream> class kanta { int i; public: kanta( int n ) { cout << "Luodaan kantaluokan olio\n"; i = n; } ~kanta() { cout << "Tuhotaan kantaluokan olio\n"; } void nayta_i ( ) { cout << i << "\n"; } }; class johdettu: public kanta { int j; public: johdettu( int n ) : kanta( n ) { // Huomaa käytetään n:ää ja n välitetään eteenpäin cout << "Luodaan johdetun luokan olio\n"; j = n+1; } ~johdettu() { cout << "Tuhotaan johdetun luokan olio\n"; } void nayta_j ( ) { cout << j << "\n"; } }; main() { johdettu ob(10); ob.nayta_i(); ob.nayta_j(); return 0; }
Molemmille sekä kantaluokalle ja johdetulle luokalle välitetään omat argumentit, mikä onkin tavallisin tapaus.
#include <iostream> class kanta { int i; public: kanta( int n ) { cout << "Luodaan kantaluokan olio\n"; i = n; } ~kanta() { cout << "Tuhotaan kantaluokan olio\n"; } void nayta_i ( ) { cout << i << "\n"; } }; class johdettu: public kanta { int j; public: johdettu( int n, int m ) : kanta( n ) { cout << "Luodaan johdetun luokan olio\n"; j = m; } ~johdettu() { cout << "Tuhotaan johdetun luokan olio\n"; } void nayta_j ( ) { cout << j << "\n"; } }; main() { johdettu ob(10, 20); ob.nayta_i(); ob.nayta_j(); return 0; }
Moniperintä
Moniperintää voi tapahtua kahdella tavalla:
- Kuten aiemmin harjoituksessa, jossa luotiin johdettu2 niminen luokka: johdettu luokka perii kantaluokan ja kyseinen johdettu luokka toimii seuraavan luokan (johdettu2) kantaluokkana. Tässä tapauksessa johdettu2 perii alkuperäisen kantaluokan epäsuorasti (indirect). Oliot luodaan perimysjärjestyksessä: kanta, johdettu, johdettu2 oliot tuhotaan päinvastaisessa järjestyksessä.
- Johdettu luokka perii suoraan (direct) useamman luokan, jolloin johdettu luokka saa kaikilta kantaluokiltaan ominaisuuksia.
Johdetun luokan määrittely:
class johdettu: access1 kanta1, access2 kanta2, ..., accessN kantaN { luokan runko }; Konstruktoreita kutsutaan johdetussa luokassa esitetyssä järjestyksessä: kanta1, kanta2, ... kantaN, johdettu Jos konstruktorit tarvitsevat argumentteja, johdetun luokan konstruktori on seuraavaa muotoa: <ccode>johdettu_konstruktori( kaikki argumentit ) : kanta1 ( kanta1:een välitettävät argumentit ), kanta2 ( kanta2:een välitettävät argumentit ), ... kantaN ( kantaN:een välitettävät argumentit ) { johdetun luokan konstruktorin koodi }
Epäsuora moniperintä:
#include <iostream> class kanta { int a; public: kanta(int k) { a = k; } int geta() { return a; } }; class valissa : public kanta { int b; public: valissa(int k, int l) : kanta( k ) { b = l; } int getb() { return b; } }; class johdettu : public valissa { int c; public: johdettu( int k, int l, int m ) : valissa( k, l ) { c = m; } int getc() { return c; } void show() { // cout << a << ", " << b << ", " << c << "\n"; // cout << geta() << ", " << b << ", " << c << "\n"; cout << geta() << ", " << getb() << ", " << c << "\n"; } }; main() { johdettu x(10, 20, 30); x.show(); cout << x.geta() << ", " << x.getb() << ", " << x.getc() << "\n"; return 0; }
Suora moniperintä
#include <iostream> class kanta1 { int a; public: kanta1(int k) { a = k; } int geta() { return a; } }; class kanta2 { int b; public: kanta2(int l) { b = l; } int getb() { return b; } }; class johdettu : public kanta1, public kanta2 { int c; public: johdettu( int k, int l, int m ) : kanta1( k ), kanta2( l ) { c = m; } int getc() { return c; } void show() { cout << geta() << ", " << getb() << ", " << c << "\n"; } }; main() { johdettu x(10, 20, 30); x.show(); cout << x.geta() << ", " << x.getb() << ", " << x.getc() << "\n"; return 0; }
Virtuaaliset kantaluokat
Kun johdettu luokka perii suoraan useita kantaluokkia, tilanteeseen liittyy potentiaalinen ongelma. Tarkastellaan tilannetta, jossa Johdettu1 ja Johdettu2 perivät molemmat Kanta-luokan ja Johdettu3 perii edelleen Johdettu1:n ja Johdettu2:n.
Kanta Kanta
| |
| |
Johdettu1 Johdettu2
| |
|____ ____|
|
|
Johdettu3
Nythän Johdettu3 perii Kanta-luokan kahteen kertaan, ensin Johdettu1:n kautta ja sitten vielä Johdettu2:n kautta. Jäsenet, jotka kuuluvat Kanta-luokkaan luotaisiin 2 kertaa Johdettu3-luokkaan. Ongelman hoitamiseksi C++:ssa on mekanismi, jonka nimi on virtuaalinen kantaluokka.
Johdettuun luokkaan ja kaikkiin luokkiin, jotka edelleen perivät johdetun luokan, tulee vain yhteen kertaan kantaluokan jäsenet, mikäli kantaluokka määritellään perittäväksi virtuaalisena.
#include <iostream> class Kanta { public: int i; }; class Johdettu1 : virtual public Kanta { public: int j; int summa() { return i + j; } }; class Johdettu2 : virtual public Kanta { public: int k; }; class Johdettu3 : public Johdettu1, public Johdettu2 { public: int tulo() { return i * j * k ; } }; main() { Johdettu3 ob; Johdettu1 ob2; ob.i = 10; ob.j = 3; ob.k = 5; cout << "Tulo on " << ob.tulo() << "\n"; ob2.i = 10; ob2.j = 3; cout << "Summa on " << ob2.summa() << "\n"; return 0; }
Jos Johdettu1 tai Johdettu2 olisivat perineet Kanta-luokan ei-virtuaalisesti, seurauksena olisi käännösvirhe: Member is ambiguous: 'Kanta::i' and 'Kanta::i' in function Johdettu3::tulo() Member is ambiguous: 'Kanta::i' and 'Kanta::i' in function main()
Ainoa ero virtuaalikantaluokan ja tavallisen välillä on siinä tilanteessa, kun kantaluokka perittäisiin useaan kertaan.
Kopio lisenssistä (englanniksi) löytyy täältä.
Alkuperäinen (c) Petteri Hämäläinen
