Livelli di accesso nell’ereditarietà
Fa parte della sezione Programmazione orientata agli oggetti del percorso C++ di Coddy. Lezione 49 di 104.
Il tipo di ereditarietà che scegli influisce su come si accede ai membri della classe base nella classe derivata. C++ offre tre opzioni: ereditarietà public, protected e private.
Ecco come ciascun tipo trasforma l'accesso ai membri:
| Membro di base | ereditarietà public | ereditarietà protected | ereditarietà private |
|---|---|---|---|
| public | public | protected | private |
| protected | protected | protected | private |
| private | inaccessibile | inaccessibile | inaccessibile |
class Base {
public:
int pub;
protected:
int prot;
private:
int priv;
};
class PublicDerived : public Base {
// pub è public, prot è protected, priv è inaccessibile
};
class ProtectedDerived : protected Base {
// pub è protected, prot è protected, priv è inaccessibile
};
class PrivateDerived : private Base {
// pub è private, prot è private, priv è inaccessibile
};L'ereditarietà pubblica modella le relazioni «è un» ed è di gran lunga la più comune. Un Dog è un Animal, quindi il codice esterno può trattarlo come tale.
Le relazioni di «implementato in termini di» sono modellate dall’ereditarietà protetta e privata. Nascondono l’interfaccia della classe base al codice esterno, il che è utile quando vuoi riutilizzare l’implementazione senza esporre la relazione di ereditarietà. Con l’ereditarietà privata, anche le classi derivate ulteriormente perdono l’accesso ai membri della classe base originale.
Sfida
FacileEsploriamo come i diversi tipi di ereditarietà influiscono sull'accessibilità dei membri, costruendo una semplice gerarchia di dispositivi. Creerai una classe base Device e tre classi derivate, ciascuna con un diverso tipo di ereditarietà, per vedere direttamente come l'ereditarietà pubblica, protetta e privata modificano l'accesso ai membri.
Organizzerai il codice in due file:
Device.h: Definisci una classe baseDevicecon tre membri a diversi livelli di accesso:- Un
std::string modelpubblico - Un
int serialNumberprotetto - Un
int internalCodeprivato (impostato a 999 per impostazione predefinita) - Un metodo pubblico
showModel()che stampa:Model: <model>
Poi crea tre classi derivate, ciascuna ereditata da
Devicecon un diverso tipo di ereditarietà:PublicDevicecon ereditarietà pubblica: aggiungi un metododisplaySerial()che stampa:Serial: <serialNumber>ProtectedDevicecon ereditarietà protetta: aggiungi un metododisplaySerial()che stampa:Serial: <serialNumber>PrivateDevicecon ereditarietà privata: aggiungi un metododisplaySerial()che stampa:Serial: <serialNumber>
Ogni classe derivata dovrebbe avere un costruttore che accetta un nome del modello e un numero di serie, passando il modello alla classe base e impostando il numero di serie.
- Un
main.cpp: Leggi due input (ciascuno su una riga separata):- Nome del modello (stringa)
- Numero di serie (intero)
Crea un oggetto di ciascun tipo derivato usando gli stessi valori di input. Poi mostra le differenze di accesso:
Per
PublicDevice:- Chiama
showModel()direttamente sull'oggetto (funziona perché l'ereditarietà pubblica lo mantiene pubblico) - Accedi direttamente a
modele stampa:Direct model access: <model> - Chiama
displaySerial()
Per
ProtectedDeviceePrivateDevice:- Chiama solo
displaySerial()(showModel()emodelereditati non sono più accessibili pubblicamente dall'esterno)
Formato dell'output:
--- PublicDevice --- Model: <model> Direct model access: <model> Serial: <serial> --- ProtectedDevice --- Serial: <serial> --- PrivateDevice --- Serial: <serial>
Ad esempio, con gli input Laptop e 12345:
--- PublicDevice ---
Model: Laptop
Direct model access: Laptop
Serial: 12345
--- ProtectedDevice ---
Serial: 12345
--- PrivateDevice ---
Serial: 12345Nota che solo PublicDevice consente l'accesso esterno ai membri pubblici ereditati. Gli altri tipi di ereditarietà nascondono l'interfaccia della classe base al codice esterno, ma le classi derivate possono comunque accedere internamente ai membri protetti tramite i propri metodi.
Provalo tu
#include <iostream>
#include <string>
#include "Device.h"
using namespace std;
int main() {
// Leggi l'input
string modelName;
int serialNum;
getline(cin, modelName);
cin >> serialNum;
// TODO: Crea oggetti di ogni tipo derivato usando i valori di input
// PublicDevice pubDev(modelName, serialNum);
// ProtectedDevice protDev(modelName, serialNum);
// PrivateDevice privDev(modelName, serialNum);
// TODO: Mostra l'accesso a PublicDevice
cout << "--- PublicDevice ---" << endl;
// - Chiama showModel() direttamente sull'oggetto
// - Accedi direttamente a model e stampa: Direct model access: <model>
// - Chiama displaySerial()
// TODO: Mostra l'accesso a ProtectedDevice
cout << "--- ProtectedDevice ---" << endl;
// - Chiama solo displaySerial() (showModel e model non sono accessibili pubblicamente)
// TODO: Mostra l'accesso a PrivateDevice
cout << "--- PrivateDevice ---" << endl;
// - Chiama solo displaySerial() (showModel e model non sono accessibili pubblicamente)
return 0;
}
Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.
Tutte le lezioni di Programmazione orientata agli oggetti
1Fondamenti della programmazione orientata agli oggetti
File esterniBuild e compilazione in C++File header e file sorgenteNamespace e ambitoIntroduzione alla programmazione orientata agli oggetti in C++Classi e oggetti a confrontoIl puntatore 'this'Metodi (funzioni membro)Attributi (membri dati)Fondamenti di costruttori e distruttoriRiepilogo - Calcolatrice semplice4Proprietà delle classi
Membri di istanza e staticiGetter e setterFunzioni membro constParola chiave mutableMetodi e variabili staticiFunzioni e classi friendRiepilogo - Gestore di conti bancari7Ereditarietà
Ereditarietà di baseLivelli di accesso nell’ereditarietàOrdine di chiamata di costruttori e distruttoriRidefinizione dei metodiFunzioni virtuali e VTableEreditarietà multiplaEreditarietà virtualeRiepilogo - Gerarchia dei dipendenti2Gestione della memoria
Memoria Stack vs HeapPuntatori e riferimentiMemoria dinamica (new/delete)Puntatori intelligenti in C++RAII in C++Riepilogo - Gestore di array dinamico5Incapsulamento
Specificatori di accesso in C++Specificatori di accesso in dettaglioOccultamento delle informazioniStruct vs classClassi annidate e interneRiepilogo - Sistema di registrazione degli studenti8Polimorfismo
Polimorfismo a compile time e a runtimeOverload delle funzioniFunzioni virtuali: ripassoFunzioni virtuali pureClassi astratteProgettazione delle interfacce in C++Dynamic casting e RTTIRipasso: calcolatrice di forme11Concetti avanzati di OOP
Composizione vs ereditarietàMixin tramite CRTPIdiom PimplType ErasureEnum class e tipizzazione forteGestione delle eccezioni in OOPGerarchie personalizzate di eccezioni14Pattern di progettazione - Parte 2
Pattern CommandPattern AdapterPattern DecoratorPattern Template MethodPattern StatePattern CompositeRAII come pattern3Costruttori e distruttori
Costruttore predefinitoCostruttore con parametriCostruttore di copiaCostruttore di spostamentoListe di inizializzazione dei costruttoriCostruttori delegantiApprofondimento sui distruttoriRegola del Tre / Cinque / ZeroRipasso - classe String6Sovraccarico degli operatori
Introduzione al sovraccarico degli operatoriSovraccarico degli operatori aritmeticiSovraccarico degli operatori di confrontoOperatori di flussoSovraccarico dell'operatore di assegnazioneSovraccarico degli operatori [] e ()Operatori di conversione di tipoRipasso - Classe Matrix9Template
Template di funzioneTemplate di classeSpecializzazione dei templateTemplate variadiciBasi di SFINAE e dei trait di tipoRiepilogo - Contenitore genericoEsercitati da solo: Compilatore C++ online