15.5 Klasy zagnieżdżone

Możliwe jest definiowanie klas wewnątrz definicji klasy. Takie klasy, których definicja jest zanurzona w definicji innej klasy, nazywamy klasami zagnieżdżonymi lub klasami wewnętrznymi. Klasę, we wnętrzu której podana jest definicja klasy zagnieżdżonej, nazywamy klasą otaczającą. Zagnieżdżenie definicji oznacza, że nazwa tej klasy leży w zakresie klasy otaczającej, a nie w zakresie globalnym. W zasadzie nie znaczy nic więcej. Klasy otaczająca i zagnieżdżona nie są specjalnie ze sobą związane. W szczególności, w odróżnieniu od Javy, obowiązują dla nich normalne zasady dostępności, czyli na przykład pola prywatne lub chronione jednej z nich nie są widoczne w drugiej. Z tego, że zakresem definicji klasy zagnieżdżonej jest klasa otaczająca, wynika, że widoczne są w niej, bez żadnych kwalifikacji, nazwy aliasów zdefiniowanych za pomocą typedef wewnątrz klasy otaczającej, czy na przykład nazwy zdefiniowanych w niej typów wyliczeniowych.


Rozpatrzmy jako przykład następujący program:


P122: klawew.cpp     Klasy zagnieżdżone

      1.  #include <iostream>
      2.  #include <cstring>
      3.  using namespace std;
      4.  
      5.  class Klient {
      6.      static int ID;
      7.      char*  nazwisko;
      8.      const  int id;
      9.      int    ma,winien;
     10.  
     11.  public:
     12.      class Saldo {
     13.          int id;
     14.          int saldo;
     15.      public:
     16.          Saldo(int id, int saldo)
     17.              : id(id), saldo(saldo)
     18.          {}
     19.  
     20.          void printinfo();
     21.      };
     22.  
     23.      Klient(const char* n)
     24.          : nazwisko(strcpy(new char[strlen(n)+1],n)),
     25.            id(++ID), ma(0), winien(0)
     26.      {}
     27.  
     28.      Klient& wplata(int w)  { ma     += w; return *this; }
     29.      Klient& wyplata(int w) { winien += w; return *this; }
     30.  
     31.      Saldo* dajSaldo();
     32.  
     33.      ~Klient() { delete [] nazwisko; }
     34.  };
     35.  int Klient::ID = 0;
     36.  
     37.  Klient::Saldo* Klient::dajSaldo() {
     38.      return new Saldo(id, ma - winien);
     39.  }
     40.  
     41.  void Klient::Saldo::printinfo() {
     42.      cout << "id: " << id << " Saldo: " << saldo << endl;
     43.  }
     44.  
     45.  int main() {
     46.      Klient  kow("Kowalski");
     47.      Klient* pmal = new Klient("Malinowski");
     48.  
     49.      kow.wplata(100).wplata(50).wyplata(75);
     50.      pmal->wplata(200).wyplata(50).wyplata(25);
     51.  
     52.      Klient::Saldo* pskow =   kow.dajSaldo();
     53.      Klient::Saldo* psmal = pmal->dajSaldo();
     54.  
     55.      pskow->printinfo();
     56.      psmal->printinfo();
     57.  
     58.      Klient::Saldo anonim(9,500);
     59.      anonim.printinfo();
     60.  
     61.      delete pskow;
     62.      delete psmal;
     63.      delete pmal;
     64.  }

Wewnątrz klasy Klient zdefiniowna jest klasa Saldo. Jej metoda printinfo jest w klasie zadeklarowana, ale zdefiniowana poza klasą. Zauważmy, że w lini 41 musieliśmy do jej zdefiniowania użyć podwójnej kwalifikacji: jest to funkcja printinfo z zakresu klasy Saldo, który z kolei zawarty jest w zakresie klasy Klient.

W liniach 52 i 53 tworzymy zmienne wskaźnikowe typu Saldo*. Zauważmy, że i tutaj musieliśmy użyć kwalifikacji nazwą klasy otaczającej, ponieważ w zakresie globalnym nazwa klasy Saldo jest niewidoczna.

Nie jest jednak tak, że nie da się tworzyć obiektów klasy Saldo poza klasą otaczającą (czyli klasą Klient). Jak widzimy w linii 58, i o czym przekonuje nas wydruk programu

    id: 1 Saldo: 75
    id: 2 Saldo: 125
    id: 9 Saldo: 500
można utworzyć obiekt klasy Saldo w funkcji main w ogóle nie odwołując się do żadnego obiektu ani metody klasy otaczającej (inaczej jest w Javie).

Klasy wewnętrzne występują w programach C++ rzadko, bo w zasadzie nie są nigdy absolutnie konieczne, choć mogą być wygodne.

Na marginesie zauważmy w tym miejscu, że możliwe jest też definiowanie klas lokalnych, definiowanych wewnątrz funkcji. Wtedy rzeczywiście nazwa tej klasy nie jest dostępna nigdzie poza tą funkcją, tak jak nie są dostępne nazwy żadnych zmiennych lokalnych funkcji. Klasy lokalne stosuje się jeszcze rzadziej niż klasy zagnieżdżone.

T.R. Werner, 23 lutego 2019; 23:59