W poprzednich lekcjach w tym rozdziale nauczyłeś się trochę o tym, jak działa dziedziczenie bazowe. We wszystkich naszych dotychczasowych przykładach korzystaliśmy z dziedziczenia publicznego. Oznacza to, że nasza klasa pochodna publicznie dziedziczy klasę bazową.
W tej lekcji przyjrzymy się bliżej dziedziczeniu publicznemu, a także dwóm innym rodzajom dziedziczenia (prywatnemu i chronionemu). Zbadamy także, w jaki sposób różne rodzaje dziedziczenia współdziałają ze specyfikatorami dostępu, aby umożliwić lub ograniczyć dostęp członkom.
Do tego momentu poznaliśmy specyfikatory dostępu prywatnego i publicznego, które określają, kto może uzyskać dostęp do członków klasy. Dla przypomnienia, dostęp do członków publicznych może uzyskać każdy. Dostęp do członków prywatnych mogą uzyskać jedynie funkcje członkowskie tej samej klasy lub znajomi. Oznacza to, że klasy pochodne nie mają bezpośredniego dostępu do prywatnych elementów klasy bazowej!
class Base
{
private:
int m_private {}; // can only be accessed by Base members and friends (not derived classes)
public:
int m_public {}; // can be accessed by anybody
};Jest to całkiem proste i powinieneś już się do tego przyzwyczaić.
Specyfikator dostępu chronionego
W przypadku klas dziedziczonych sytuacja staje się nieco bardziej skomplikowana.
C++ ma trzeci specyfikator dostępu, o którym jeszcze nie rozmawialiśmy, ponieważ jest przydatny tylko w kontekście dziedziczenia. Specyfikator dostępu protected umożliwia klasie, do której należy element członkowski, przyjaciołom i klasom pochodnym na dostęp do elementu członkowskiego. Jednakże elementy chronione nie są dostępne spoza klasy.
class Base
{
public:
int m_public {}; // can be accessed by anybody
protected:
int m_protected {}; // can be accessed by Base members, friends, and derived classes
private:
int m_private {}; // can only be accessed by Base members and friends (but not derived classes)
};
class Derived: public Base
{
public:
Derived()
{
m_public = 1; // allowed: can access public base members from derived class
m_protected = 2; // allowed: can access protected base members from derived class
m_private = 3; // not allowed: can not access private base members from derived class
}
};
int main()
{
Base base;
base.m_public = 1; // allowed: can access public members from outside class
base.m_protected = 2; // not allowed: can not access protected members from outside class
base.m_private = 3; // not allowed: can not access private members from outside class
return 0;
}W powyższym przykładzie widać, że chroniony element bazowy m_protected jest bezpośrednio dostępny dla klasy pochodnej, ale nie publicznie.
Kiedy więc powinienem używać specyfikatora dostępu chronionego?
Dzięki atrybutowi chronionemu w klasie bazowej klasy pochodne mogą uzyskać bezpośredni dostęp do tego elementu. Oznacza to, że jeśli później zmienisz cokolwiek w tym chronionym atrybucie (typ, znaczenie wartości itp.), prawdopodobnie będziesz musiał zmienić zarówno klasę bazową ORAZ wszystkie klasy pochodne.
Dlatego użycie specyfikatora dostępu chronionego jest najbardziej przydatne, gdy ty (lub twój zespół) będziecie wywodzić się z własnych klas, a liczba klas pochodnych jest rozsądna. W ten sposób, jeśli dokonasz zmian w implementacji klasy bazowej i w rezultacie konieczne będą aktualizacje klas pochodnych, możesz dokonać aktualizacji samodzielnie (i nie będzie to trwało wiecznie, ponieważ liczba klas pochodnych jest ograniczona).
Uznaczenie twoich członków jako prywatnych oznacza, że klasy publiczne i pochodne nie mogą bezpośrednio wprowadzać zmian w klasie bazowej. Jest to dobre do izolowania klas publicznych lub pochodnych od zmian w implementacji i do zapewnienia prawidłowego utrzymania niezmienników. Oznacza to jednak również, że Twoja klasa może potrzebować większego publicznego (lub chronionego) interfejsu do obsługi wszystkich funkcji potrzebnych do działania klas publicznych lub pochodnych, co wiąże się z własnymi kosztami budowy, testowania i utrzymania.
Ogólnie rzecz biorąc, jeśli to możliwe, lepiej ustawić członków prywatnych, jeśli to możliwe, i używać chronionych tylko wtedy, gdy planowane są klasy pochodne, a koszt zbudowania i utrzymania interfejsu dla tych członków prywatnych jest zbyt wysoki.
Najlepsza praktyka
Preferuj członków prywatnych zamiast chronionych członków.
Różne rodzaje dziedziczenia i ich wpływ na dostęp
Po pierwsze, istnieją trzy różne sposoby dziedziczenia klas z innych klas: publiczne, chronione i prywatne.
Aby to zrobić, po prostu określ, jaki typ dostępu chcesz wybrać przy wyborze klasy, z której chcesz dziedziczyć:
// Inherit from Base publicly
class Pub: public Base
{
};
// Inherit from Base protectedly
class Pro: protected Base
{
};
// Inherit from Base privately
class Pri: private Base
{
};
class Def: Base // Defaults to private inheritance
{
};Jeśli nie wybierzesz typu dziedziczenia, domyślnie C++ do dziedziczenia prywatnego (tak jak członkowie domyślnie mają dostęp prywatny, jeśli nie określisz inaczej).
To daje nam 9 kombinacji: 3 specyfikatory dostępu członków (publiczne, prywatne i chronione) oraz 3 typy dziedziczenia (publiczne, prywatne i chronione).
Jaka jest więc między nimi różnica? W skrócie, gdy elementy są dziedziczone, specyfikator dostępu dla dziedziczonego elementu może zostać zmieniony (tylko w klasie pochodnej) w zależności od rodzaju zastosowanego dziedziczenia. Innymi słowy, elementy członkowskie, które były publiczne lub chronione w klasie bazowej, mogą zmieniać specyfikatory dostępu w klasie pochodnej.
Może to wydawać się nieco zagmatwane, ale nie jest tak źle. Resztę tej lekcji spędzimy na szczegółowym badaniu tego.
Przechodząc przez przykłady, pamiętaj o następujących zasadach:
- Klasa może zawsze uzyskać dostęp do swoich własnych (niedziedziczonych) elementów.
- Osoby publiczne uzyskują dostęp do elementów klasy w oparciu o specyfikatory dostępu klasy, do której uzyskuje dostęp.
- Klasa pochodna uzyskuje dostęp do odziedziczonych elementów w oparciu o dostęp specyfikator dziedziczony z klasy nadrzędnej. Różni się to w zależności od specyfikatora dostępu i rodzaju zastosowanego dziedziczenia.
Dziedziczenie publiczne
Dziedziczenie publiczne jest zdecydowanie najczęściej używanym rodzajem dziedziczenia. W rzeczywistości bardzo rzadko będziesz widzieć lub używać innych typów dziedziczenia, dlatego powinieneś skupić się przede wszystkim na zrozumieniu tej sekcji. Na szczęście dziedziczenie publiczne jest również najłatwiejsze do zrozumienia. Kiedy dziedziczysz klasę bazową publicznie, odziedziczone publiczne elementy członkowskie pozostają publiczne, a odziedziczone chronione elementy członkowskie pozostają chronione. Dziedziczone prywatne składowe, które były niedostępne, ponieważ były prywatne w klasie bazowej, pozostają niedostępne.
| Specyfikator dostępu w klasie bazowej | Specyfikator dostępu w przypadku dziedziczenia publicznego |
|---|---|
| Publiczny | Publiczny |
| Chroniony | Chroniony |
| Prywatny | Niedostępny |
Oto przykład pokazujący, jak to działa:
class Base
{
public:
int m_public {};
protected:
int m_protected {};
private:
int m_private {};
};
class Pub: public Base // note: public inheritance
{
// Public inheritance means:
// Public inherited members stay public (so m_public is treated as public)
// Protected inherited members stay protected (so m_protected is treated as protected)
// Private inherited members stay inaccessible (so m_private is inaccessible)
public:
Pub()
{
m_public = 1; // okay: m_public was inherited as public
m_protected = 2; // okay: m_protected was inherited as protected
m_private = 3; // not okay: m_private is inaccessible from derived class
}
};
int main()
{
// Outside access uses the access specifiers of the class being accessed.
Base base;
base.m_public = 1; // okay: m_public is public in Base
base.m_protected = 2; // not okay: m_protected is protected in Base
base.m_private = 3; // not okay: m_private is private in Base
Pub pub;
pub.m_public = 1; // okay: m_public is public in Pub
pub.m_protected = 2; // not okay: m_protected is protected in Pub
pub.m_private = 3; // not okay: m_private is inaccessible in Pub
return 0;
}To jest to samo, co w powyższym przykładzie, w którym wprowadziliśmy specyfikator dostępu chronionego, z tą różnicą, że utworzyliśmy także instancję klasy pochodnej, aby pokazać, że w przypadku dziedziczenia publicznego wszystko działa identycznie w bazowej i pochodnej class.
Dziedziczenie publiczne jest tym, czego powinieneś używać, chyba że masz konkretny powód, aby tego nie robić.
Najlepsza praktyka
Używaj dziedziczenia publicznego, jeśli nie masz konkretnego powodu, aby zrobić inaczej.
Dziedziczenie chronione
Dziedziczenie chronione jest najrzadziej spotykaną metodą dziedziczenia. Prawie nigdy nie jest używany, z wyjątkiem bardzo szczególnych przypadków. W przypadku dziedziczenia chronionego chronione są elementy publiczne i chronione, a elementy prywatne pozostają niedostępne.
Ponieważ ta forma dziedziczenia jest tak rzadka, pominiemy przykład i po prostu podsumujemy w tabeli:
| Specyfikator dostępu w klasie bazowej | Specyfikator dostępu w przypadku dziedziczenia chronionego |
|---|---|
| Publiczny | Chroniony |
| Chroniony | Chroniony |
| Prywatny | Niedostępny |
Dziedziczenie prywatne
W przypadku dziedziczenia prywatnego wszyscy członkowie z klasy bazowej są dziedziczeni jako prywatni. Oznacza to, że elementy prywatne są niedostępne, a elementy chronione i publiczne stają się prywatne.
Zauważ, że nie ma to wpływu na sposób, w jaki klasa pochodna uzyskuje dostęp do elementów odziedziczonych od swojego rodzica! Wpływa tylko na kod próbujący uzyskać dostęp do tych elementów poprzez klasę pochodną.
class Base
{
public:
int m_public {};
protected:
int m_protected {};
private:
int m_private {};
};
class Pri: private Base // note: private inheritance
{
// Private inheritance means:
// Public inherited members become private (so m_public is treated as private)
// Protected inherited members become private (so m_protected is treated as private)
// Private inherited members stay inaccessible (so m_private is inaccessible)
public:
Pri()
{
m_public = 1; // okay: m_public is now private in Pri
m_protected = 2; // okay: m_protected is now private in Pri
m_private = 3; // not okay: derived classes can't access private members in the base class
}
};
int main()
{
// Outside access uses the access specifiers of the class being accessed.
// In this case, the access specifiers of base.
Base base;
base.m_public = 1; // okay: m_public is public in Base
base.m_protected = 2; // not okay: m_protected is protected in Base
base.m_private = 3; // not okay: m_private is private in Base
Pri pri;
pri.m_public = 1; // not okay: m_public is now private in Pri
pri.m_protected = 2; // not okay: m_protected is now private in Pri
pri.m_private = 3; // not okay: m_private is inaccessible in Pri
return 0;
}Podsumowując w formie tabeli:
| Specyfikator dostępu w klasie bazowej | Specyfikator dostępu w przypadku dziedziczenia prywatnego |
|---|---|
| Publiczny | Prywatny |
| Chroniony | Prywatny |
| Prywatny | Niedostępny |
Dziedziczenie prywatne może być przydatne, gdy klasa pochodna nie ma oczywistego związku z klasą bazową, ale używa klasy bazowej do wewnętrznej implementacji. W takim przypadku prawdopodobnie nie chcemy, aby publiczny interfejs klasy bazowej był eksponowany przez obiekty klasy pochodnej (tak by było, gdybyśmy dziedziczyli publicznie).
W praktyce dziedziczenie prywatne jest rzadko stosowane.
Ostatni przykład
class Base
{
public:
int m_public {};
protected:
int m_protected {};
private:
int m_private {};
};Base może uzyskać dostęp do swoich własnych składowych bez ograniczeń. Publiczność może uzyskać dostęp tylko do m_public. Klasy pochodne mają dostęp do m_public i m_protected.
class D2 : private Base // note: private inheritance
{
// Private inheritance means:
// Public inherited members become private
// Protected inherited members become private
// Private inherited members stay inaccessible
public:
int m_public2 {};
protected:
int m_protected2 {};
private:
int m_private2 {};
};D2 może uzyskać dostęp do swoich członków bez ograniczeń. D2 może uzyskać dostęp do członków m_public i m_protected Base, ale nie m_private. Ponieważ D2 odziedziczył Base prywatnie, m_public i m_protected są teraz uważane za prywatne, gdy są dostępne przez D2. Oznacza to, że społeczeństwo nie może uzyskać dostępu do tych zmiennych podczas korzystania z obiektu D2, podobnie jak żadne klasy wywodzące się z D2.
class D3 : public D2
{
// Public inheritance means:
// Public inherited members stay public
// Protected inherited members stay protected
// Private inherited members stay inaccessible
public:
int m_public3 {};
protected:
int m_protected3 {};
private:
int m_private3 {};
};D3 może uzyskać dostęp do swoich własnych elementów bez ograniczeń. D3 ma dostęp do członków m_public2 i m_protected2 D2, ale nie m_private2. Ponieważ D3 dziedziczy D2 publicznie, m_public2 i m_protected2 zachowują swoje specyfikatory dostępu, gdy są dostępne przez D3. D3 nie ma dostępu do m_private Base, które było już prywatne w Base. Nie ma też dostępu do m_protected ani m_public Base, które stały się prywatne, gdy D2 je odziedziczył.
Streszczenie
Sposób, w jaki specyfikatory dostępu, typy dziedziczenia i klasy pochodne współdziałają ze sobą, powoduje wiele zamieszania. Aby spróbować jak najbardziej wyjaśnić sprawę:
Po pierwsze, klasa (i przyjaciele) zawsze mogą uzyskać dostęp do swoich własnych, niedziedziczonych członków. Specyfikatory dostępu wpływają tylko na to, czy osoby z zewnątrz i klasy pochodne mogą uzyskać dostęp do tych elementów.
Po drugie, gdy klasy pochodne dziedziczą elementy członkowskie, elementy te mogą zmieniać specyfikatory dostępu w klasie pochodnej. Nie ma to wpływu na własne (niedziedziczone) składowe klas pochodnych (które mają własne specyfikatory dostępu). Wpływa tylko na to, czy osoby z zewnątrz i klasy wywodzące się z klasy pochodnej mogą uzyskać dostęp do tych odziedziczonych elementów.
Oto tabela wszystkich kombinacji specyfikatorów dostępu i typów dziedziczenia:
| Specyfikator dostępu w klasie bazowej | Specyfikator dostępu w przypadku dziedziczenia publicznego | Specyfikator dostępu w przypadku dziedziczenia prywatnego | Specyfikator dostępu w przypadku dziedziczenia chronionego |
|---|---|---|---|
| Publiczny | Publiczny | Prywatny | Chroniony |
| Chroniony | Chroniony | Prywatny | Chroniony |
| Prywatny | Niedostępny | Niedostępny | Niedostępny |
Na koniec, chociaż w powyższych przykładach pokazaliśmy tylko przykłady z użyciem zmiennych składowych, te reguły dostępu obowiązują dla wszystkich członków (np. funkcji składowych i typów zadeklarowanych wewnątrz klasy).

