Cpp perintä

Mureakuha

Loikkaa: valikkoon, hakuun

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:

  1. 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ä.
  2. 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.

Tämän dokumentin kopiointi, levittäminen sekä muokkaaminen on sallittua GNU Free Documentation Licensen version 1.2 tai uudemman Free Software Foundationin julkaiseman version mukaisesti, ilman muuttumattomuuslauseketta tai kansitekstejä. Tätä koskee vastuuvapaus.
Kopio lisenssistä (englanniksi) löytyy täältä.

Alkuperäinen (c) Petteri Hämäläinen

Henkilökohtaiset työkalut