Jaka jest różnica między
public
,private
iprotected
dziedziczenia w C ++?
Wszystkie pytania, które znalazłem w SO, dotyczą konkretnych przypadków.
Jaka jest różnica między
public
,private
iprotected
dziedziczenia w C ++?
Wszystkie pytania, które znalazłem w SO, dotyczą konkretnych przypadków.
Odpowiedzi:
Aby odpowiedzieć na to pytanie, chciałbym najpierw opisać osoby przystępujące do członkostwa własnymi słowami. Jeśli już to wiesz, przejdź do nagłówka „dalej:”.
Istnieją trzy Akcesory że jestem świadomy: public
, protected
i private
.
Pozwolić:
class Base {
public:
int publicMember;
protected:
int protectedMember;
private:
int privateMember;
};
Base
jest również świadome, co Base
zawiera publicMember
.Base
zawiera protectedMember
.Base
jest tego świadomy privateMember
.Przez „jest świadomy” mam na myśli „potwierdzić istnienie, a zatem mieć dostęp”.
To samo dzieje się z dziedzictwem publicznym, prywatnym i chronionym. Rozważmy klasę Base
i klasę, Child
która dziedziczy Base
.
public
, wszystko, co jest świadome Base
i Child
świadome, że Child
dziedziczy Base
.protected
tylko Child
i jego dzieci, są świadome, że dziedziczą Base
.private
, nikt inny nie Child
jest świadomy dziedzictwa.SomeBase
jest po prostu sztywnym sposobem na skomponowanie anonimowego członka typu SomeBase
. To, jak każdy inny członek, ma specyfikator dostępu, który ma taką samą kontrolę nad dostępem zewnętrznym.
class A
{
public:
int x;
protected:
int y;
private:
int z;
};
class B : public A
{
// x is public
// y is protected
// z is not accessible from B
};
class C : protected A
{
// x is protected
// y is protected
// z is not accessible from C
};
class D : private A // 'private' is default for classes
{
// x is private
// y is private
// z is not accessible from D
};
WAŻNA UWAGA: wszystkie klasy B, C i D zawierają zmienne x, y i z. To tylko kwestia dostępu.
O korzystaniu z chronionego i prywatnego dziedzictwa można przeczytać tutaj .
Ograniczenie widoczności dziedziczenia spowoduje, że kod nie będzie mógł zobaczyć, że niektóre klasy dziedziczą inną klasę: niejawna konwersja z pochodnej do bazy nie będzie działać, a static_cast
z bazy do pochodnej nie będzie działać.
Tylko członkowie / przyjaciele klasy mogą widzieć prywatne dziedzictwo, a tylko członkowie / przyjaciele i klasy pochodne mogą widzieć chronione dziedzictwo.
dziedzictwo publiczne
Dziedziczenie IS-A. Przycisk jest oknem i wszędzie tam, gdzie potrzebne jest okno, można również przekazać przycisk.
class button : public window { };
chronione dziedzictwo
Chronione zaimplementowane zgodnie z warunkami. Rzadko przydatne. Używany w boost::compressed_pair
celu czerpania z pustych klas i oszczędzania pamięci przy użyciu pustej optymalizacji klas podstawowych (przykład poniżej nie używa szablonu, aby być na bieżąco):
struct empty_pair_impl : protected empty_class_1
{ non_empty_class_2 second; };
struct pair : private empty_pair_impl {
non_empty_class_2 &second() {
return this->second;
}
empty_class_1 &first() {
return *this; // notice we return *this!
}
};
spadek prywatny
Wdrożono w kategoriach. Zastosowanie klasy podstawowej służy wyłącznie do implementacji klasy pochodnej. Przydatne w przypadku cech i jeśli rozmiar ma znaczenie (puste cechy, które zawierają tylko funkcje, wykorzystają optymalizację pustej klasy podstawowej). Często jednak lepszym rozwiązaniem jest powstrzymywanie . Rozmiar ciągów ma krytyczne znaczenie, dlatego jest to często spotykane zastosowanie
template<typename StorageModel>
struct string : private StorageModel {
public:
void realloc() {
// uses inherited function
StorageModel::realloc();
}
};
członek publiczny
Agregat
class pair {
public:
First first;
Second second;
};
Akcesoria
class window {
public:
int getWidth() const;
};
członek chroniony
Zapewnienie rozszerzonego dostępu dla klas pochodnych
class stack {
protected:
vector<element> c;
};
class window {
protected:
void registerClass(window_descriptor w);
};
członek prywatny
Zachowaj szczegóły implementacji
class window {
private:
int width;
};
Zauważ, że rzutowanie w stylu C celowo pozwala rzutować klasę pochodną na chronioną lub prywatną klasę bazową w określony i bezpieczny sposób, a także rzucić w innym kierunku. Należy tego unikać za wszelką cenę, ponieważ może on uzależnić kod od szczegółów implementacji - ale w razie potrzeby można skorzystać z tej techniki.
Te trzy słowa kluczowe są również używane w zupełnie innym kontekście, aby określić model dziedziczenia widoczności .
W tej tabeli zebrano wszystkie możliwe kombinacje modelu deklaracji i modelu dziedziczenia przedstawiające wynikowy dostęp do komponentów, gdy podklasa jest całkowicie zdefiniowana.
Powyższa tabela jest interpretowana w następujący sposób (spójrz na pierwszy wiersz):
jeśli komponent jest zadeklarowany jako publiczny, a jego klasa jest dziedziczona jako publiczna, wynikowy dostęp jest publiczny .
Przykład:
class Super {
public: int p;
private: int q;
protected: int r;
};
class Sub : private Super {};
class Subsub : public Sub {};
Uzyskany dostęp do zmiennych p
, q
, r
w klasie Subsub jest żaden .
Inny przykład:
class Super {
private: int x;
protected: int y;
public: int z;
};
class Sub : protected Super {};
Uzyskany dostęp do zmiennych y
, z
w klasie Sub jest chroniony i dla zmiennej x
jest żaden .
Bardziej szczegółowy przykład:
class Super {
private:
int storage;
public:
void put(int val) { storage = val; }
int get(void) { return storage; }
};
int main(void) {
Super object;
object.put(100);
object.put(object.get());
cout << object.get() << endl;
return 0;
}
Teraz pozwala zdefiniować podklasę:
class Sub : Super { };
int main(void) {
Sub object;
object.put(100);
object.put(object.get());
cout << object.get() << endl;
return 0;
}
Zdefiniowana klasa o nazwie Sub, która jest podklasą klasy o nazwie Super
lub ta Sub
klasa jest pochodną Super
klasy. Do Sub
klasy wprowadza ani nowych zmiennych ani nowe funkcje. Czy to oznacza, że jakikolwiek obiekt Sub
klasy dziedziczy wszystkie cechy po tym, jak Super
klasa jest w rzeczywistości kopią obiektów Super
klasy?
Nie . Tak nie jest.
Jeśli skompilujemy następujący kod, otrzymamy tylko błędy kompilacji, mówiąc, że put
i get
metody są niedostępne. Dlaczego?
Gdy pominiemy specyfikator widoczności, kompilator zakłada, że zastosujemy tak zwane dziedziczenie prywatne . Oznacza to, że wszystkie publiczne składniki nadklasy zamieniają się w prywatny dostęp, prywatne składniki nadklasy w ogóle nie będą dostępne. Oznacza to w związku z tym, że nie wolno używać tego ostatniego w podklasie.
Musimy poinformować kompilator, że chcemy zachować wcześniej używaną politykę dostępu.
class Sub : public Super { };
Nie daj się wprowadzić w błąd : nie oznacza to, że prywatne elementy klasy Super (takie jak zmienna pamięci) zamieniają się w publiczne w nieco magiczny sposób. Prywatne komponenty pozostaną prywatne , publiczne pozostaną publiczne .
Obiekty Sub
klasy mogą robić „prawie” te same rzeczy, co ich starsze rodzeństwo utworzone z Super
klasy. „Prawie”, ponieważ fakt bycia podklasą oznacza również, że klasa utraciła dostęp do prywatnych elementów nadklasy . Nie możemy napisać funkcji Sub
składowej klasy, która byłaby w stanie bezpośrednio manipulować zmienną pamięci.
To bardzo poważne ograniczenie. Czy istnieje jakieś obejście?
Tak .
Trzeci poziom dostępu nazywa się chroniony . Słowo kluczowe chronione oznacza, że oznaczony nim komponent zachowuje się jak publiczny, gdy jest używany przez jedną z podklas i wygląda jak prywatny dla reszty świata . - Dotyczy to tylko klas dziedziczonych publicznie (takich jak klasa Super w naszym przykładzie) -
class Super {
protected:
int storage;
public:
void put(int val) { storage = val; }
int get(void) { return storage; }
};
class Sub : public Super {
public:
void print(void) {cout << "storage = " << storage;}
};
int main(void) {
Sub object;
object.put(100);
object.put(object.get() + 1);
object.print();
return 0;
}
Jak widać w przykładowym kodzie, mamy nową funkcjonalność dla Sub
klasy i robi ona jedną ważną rzecz: uzyskuje dostęp do zmiennej pamięci z klasy Super .
Nie byłoby to możliwe, gdyby zmienna została zadeklarowana jako prywatna. W głównym zakresie funkcji zmienna i tak pozostaje ukryta, więc jeśli napiszesz coś takiego:
object.storage = 0;
Kompilator poinformuje Cię, że jest to plik error: 'int Super::storage' is protected
.
Wreszcie ostatni program wygeneruje następujące dane wyjściowe:
storage = 101
Ma to związek z tym, jak publiczni członkowie klasy podstawowej są narażeni na działanie klasy pochodnej.
Jak wskazuje litb, dziedziczenie publiczne jest tradycyjnym dziedzictwem, które można zobaczyć w większości języków programowania. To znaczy modeluje relację „IS-A”. Dziedziczenie prywatne, coś, co AFAIK jest specyficzne dla C ++, to relacja „WDROŻONE W WARUNKACH”. Oznacza to, że chcesz korzystać z interfejsu publicznego w klasie pochodnej, ale nie chcesz, aby użytkownik klasy pochodnej miał dostęp do tego interfejsu. Wielu argumentuje, że w tym przypadku należy agregować klasę podstawową, czyli zamiast mieć klasę bazową jako bazę prywatną, w elemencie pochodnym w celu ponownego wykorzystania funkcjonalności klasy bazowej.
Member in base class : Private Protected Public
Typ dziedziczenia : Obiekt odziedziczony jako :
Private : Inaccessible Private Private
Protected : Inaccessible Protected Protected
Public : Inaccessible Protected Public
1) Dziedziczenie publiczne :
za. Prywatni członkowie klasy podstawowej nie są dostępni w klasie pochodnej.
b. Chronieni członkowie klasy podstawowej pozostają chronieni w klasie pochodnej.
do. Członkowie publiczni klasy podstawowej pozostają publiczni w klasie pochodnej.
Tak więc inne klasy mogą korzystać z publicznych elementów klasy Base za pośrednictwem obiektu klasy Derived.
2) Dziedziczenie chronione :
za. Prywatni członkowie klasy podstawowej nie są dostępni w klasie pochodnej.
b. Chronieni członkowie klasy podstawowej pozostają chronieni w klasie pochodnej.
do. Publiczni członkowie klasy Base również stają się chronionymi członkami klasy Derived.
Tak więc inne klasy nie mogą używać publicznych członków klasy Base za pośrednictwem obiektu klasy Derived; ale są dostępne dla podklasy pochodnej.
3) Dziedziczenie prywatne :
za. Prywatni członkowie klasy podstawowej nie są dostępni w klasie pochodnej.
b. Chronieni i publiczni członkowie klasy Base stają się prywatnymi członkami klasy Derived.
Zatem do innych klas klasy Base nie można uzyskać dostępu przez inne klasy za pośrednictwem obiektu klasy Derived, ponieważ są one prywatne w klasie Derived. Zatem nawet podklasa klasy pochodnej nie może uzyskać do nich dostępu.
Dziedziczenie publiczne modeluje związek IS-A. Z
class B {};
class D : public B {};
każdy D
jest B
.
Dziedziczenie prywatne modeluje relację IMPLEMENTOWANĄ-UŻYTKOWĄ (lub jakkolwiek to się nazywa). Z
class B {};
class D : private B {};
a nieD
jestB
, ale każdy D
używa jej B
w jego realizacji. Prywatne dziedziczenie można zawsze wyeliminować, używając zamiast tego zabezpieczenia:
class B {};
class D {
private:
B b_;
};
To D
również można zrealizować za pomocą B
, w tym przypadku za pomocą jego b_
. Ograniczenie jest mniej ścisłym sprzężeniem między typami niż dziedziczenie, więc ogólnie powinno być preferowane. Czasami stosowanie ograniczania zamiast dziedziczenia prywatnego nie jest tak wygodne, jak dziedziczenie prywatne. Często jest to kiepska wymówka dla lenistwa.
Nie sądzę, żeby ktokolwiek wiedział, jakie protected
modele dziedziczenia. Przynajmniej nie widziałem jeszcze żadnego przekonującego wyjaśnienia.
D
wywodzi się prywatnie D
, może zastąpić funkcje wirtualne B
. (Jeśli, na przykład, B
jest interfejsem obserwatora, wówczas D
mógłby go zaimplementować i przekazać this
do funkcji wymagających takiego interfejsu, bez możliwości korzystania z niego przez wszystkich D
jako obserwatora.) D
Może również selektywnie udostępnić członków B
swojego interfejsu poprzez działanie using B::member
. Oba są niewygodne pod względem składniowym, gdy B
są członkami.
protected
dziedziczenie, które uznałem za przydatne w przypadku virtual
klasy podstawowej i protected
ctor:struct CommonStuff { CommonStuff(Stuff*) {/* assert !=0 */ } }; struct HandlerMixin1 : protected virtual CommonStuff { protected: HandlerMixin1() : CommonStuff(nullptr) {} /*...*/ }; struct Handler : HandlerMixin1, ... { Handler(Stuff& stuff) : CommonStuff(&stuff) {} };
Jeśli dziedziczysz publicznie po innej klasie, wszyscy wiedzą, że dziedziczysz i każdy może używać polimorficznie przez wskaźnik klasy bazowej.
Jeśli odziedziczysz w sposób chroniony, tylko twoje dzieci będą mogły korzystać z ciebie polimorficznie.
Jeśli odziedziczysz prywatnie, tylko ty będziesz mógł wykonywać metody klas nadrzędnych.
Co w gruncie rzeczy symbolizuje wiedzę reszty klas na temat twojego związku z klasą nadrzędną
Dostęp do chronionych danych mogą uzyskać wszystkie klasy, które dziedziczą po twojej klasie. Prywatni członkowie danych nie mogą jednak. Powiedzmy, że mamy następujące elementy:
class MyClass {
private:
int myPrivateMember; // lol
protected:
int myProtectedMember;
};
Od wewnątrz rozszerzenia do tej klasy odwołania this.myPrivateMember
nie będą działać. Jednak this.myProtectedMember
będzie. Wartość ta jest wciąż enkapsulowana, więc jeśli mamy instancję tej klasy o nazwie myObj
, to myObj.myProtectedMember
nie będzie działać, więc działa podobnie do prywatnego członka danych.
Accessors | Base Class | Derived Class | World
—————————————+————————————+———————————————+———————
public | y | y | y
—————————————+————————————+———————————————+———————
protected | y | y | n
—————————————+————————————+———————————————+———————
private | | |
or | y | n | n
no accessor | | |
y: accessible
n: not accessible
Na podstawie tego przykładu dla java ... Myślę, że stolik wart tysiąca słów :)
Podsumowanie:
Podczas dziedziczenia możesz (w niektórych językach) zmienić typ ochrony członka danych w określonym kierunku, np. Z chronionego na publiczny.
Dostęp do prywatnych członków klasy podstawowej mogą uzyskać tylko członkowie tej klasy podstawowej.
Członkowie publiczni klasy bazowej mogą uzyskać dostęp do członków tej klasy bazowej, członków jej klasy pochodnej, a także członków spoza klasy podstawowej i klasy pochodnej.
Dostęp do chronionych członków klasy podstawowej mogą mieć członkowie klasy podstawowej oraz członkowie jej klasy pochodnej.
prywatny : baza
chroniony : podstawa + pochodny
public : baza + pochodna + dowolny inny członek
Znalazłem łatwą odpowiedź i dlatego pomyślałem o opublikowaniu jej również w przyszłości.
Jest z linków http://www.learncpp.com/cpp-tutorial/115-inheritance-and-access-specifiers/
class Base
{
public:
int m_nPublic; // can be accessed by anybody
private:
int m_nPrivate; // can only be accessed by Base member functions (but not derived classes)
protected:
int m_nProtected; // can be accessed by Base member functions, or derived classes.
};
class Derived: public Base
{
public:
Derived()
{
// Derived's access to Base members is not influenced by the type of inheritance used,
// so the following is always true:
m_nPublic = 1; // allowed: can access public base members from derived class
m_nPrivate = 2; // not allowed: can not access private base members from derived class
m_nProtected = 3; // allowed: can access protected base members from derived class
}
};
int main()
{
Base cBase;
cBase.m_nPublic = 1; // allowed: can access public members from outside class
cBase.m_nPrivate = 2; // not allowed: can not access private members from outside class
cBase.m_nProtected = 3; // not allowed: can not access protected members from outside class
}
Zasadniczo jest to ochrona dostępu publicznego i chronionych członków klasy podstawowej w klasie pochodnej. Dzięki dziedziczeniu publicznemu klasa pochodna może widzieć publicznych i chronionych członków bazy. W przypadku dziedziczenia prywatnego nie może. W trybie chronionym klasa pochodna i wszystkie klasy pochodne od niej mogą je zobaczyć.