Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial
  • Lezione 32 di 93
  • livello avanzato
Indice lezioni

Ereditarietà privata/protetta e polimorfismo

Alcune considerazioni sull'uso dell'erediterietà privata e protetta in C++, e le conseguenze che ciò ha sul polimorfismo delle classi così generate.
Alcune considerazioni sull'uso dell'erediterietà privata e protetta in C++, e le conseguenze che ciò ha sul polimorfismo delle classi così generate.
Link copiato negli appunti

Nelle lezioni precedenti C++ abbiamo introdotto il concetto di ereditarietà e di polimorfismo, ed abbiamo visto come essi siano complementari nella pratica della programmazione orientata agli oggetti.

Tuttavia, questi concetti diventano antitetici nel caso particolare dell'ereditarietà protetta e privata.

Quindi nei rari casi in cui l'uso dell'ereditarietà protetta o privata è utile ai nostri fini, dobbiamo tenere a mente che vengono meno tutte le caratteristiche del comportamento polimorfico degli oggetti in C++.

Da un punto di vista tecnico, ciò è dovuto al fatto che, quando usiamo un qualificatore diverso da public per la definizione di un vincolo gerarchico tra classe base e derivata, ne limitiamo la visibilità al solo contesto della classe derivata (nel caso di private) o lo estendiamo al più alle discendenti di essa (con protected).

Analizzando il caso della definizione di una gerarchia di classi per modellare il concetto astratto di forma geometrica, si era fatto uso della relazione di ereditarietà privata per definire un vincolo di dipendenza implementativa tra la classe Ellipse e la classe Circle.

Nel listato seguente è osservabile come tale scelta implementativa abbia delle ripercussioni sul piano del comportamento polimorfico. In particolare, per le istanze della classe Circle viene meno la compatibilità con il tipo della classe base Ellipse e, cosa più grave, con quella antenata, cioè Shape.

/* Classe base */
class Shape
{
// membri e metodi di Shape...
};
/* Classe derivata publicamente */
class Ellipse : public Shape
{
// membri e metodi di Ellipse...
};
/* Classe derivata privatamente */
class Circle : private Ellipse
{
// membri e metodi di Circle...
};
int main()
{
Shape* s1 = new Ellipse(); // ok
Ellipse* s2 = new Circle(); // Errore: Ellipse non è una base "accessibile" di Circle
Shape* s3 = new Circle(); // Errore: Shape non è una base "accessibile" di Circle
}

In pratica, l'ereditarietà privata preclude, in questo caso, la possibilità di gestire in maniera uniforme le istanze della classe Circle e quelle di altre classi derivate pubblicamente da Shape, come ad esempio Rect, Triangle, etc.

Ad esempio, non è possibile passare per riferimento un'istanza di Circle alla funzione print_area(), che avevamo definito in precedenza e che riportiamo nel listato seguente per comodità, poichè ciò comporterebbe un errore di compilazione.

void print_area(Shape* s_ptr)
{
if (s_ptr != nullptr)
{
cout << s_ptr->getLabel() << " has an area of " << s_ptr->area() << " sq." << endl;
}
}

La ragione di questo comportamento è facilmente riconducibile alla differenza semantica tra ereditarietà pubblica (regola is-a) e privata/protetta (regola is-implemented-in-terms-of).

Nel primo caso, infatti, il legame tra classe base e derivata sancisce il carattere di omogeneità tra le due classi. La presenza di caratteri comuni (dati membro e metodi), di fatto, definisce un'interfaccia fruibile dal mondo esterno, uniformando e semplificando la codifica di istruzioni per la manipolazione di entità affini.

L'ereditarietà privata o protetta, invece, veicola soltanto dettagli implementativi, ma non definisce alcuna interfaccia comune.

Ad esempio, in virtù delle regole di derivazione, la classe Circle eredita il metodo getLabel() in maniera privata, mentre esso è pubblico per tutte le altre forme che ereditano pubblicamente da Shape. Questo è uno dei motivi per cui l'invocazione della funzione print_area() con un riferimento a Circle non è ammissibile.

Tuttavia, l'errore più grave è senza dubbio quello concettuale. La nostra gerarchia di classi infatti, in questa configurazione, comunica al suo utilizzatore questo messaggio: "Esistono tante forme diverse, ma tutte hanno dei caratteri comuni. A parte il cerchio, che non è una forma."

Quello che è all'apparenza un innocente espediente implementativo, può quindi comportare una trasfigurazione del nostro modello di dati, con implicazioni tecniche non indifferenti.

L'ereditarietà protetta o privata è uno strumento raffinato, il cui uso è limitato a contesti altamente specifici raramente occorrenti. Nella pratica, essa confligge con il comportamento polimorfico e pertanto non trova facilmente applicazione in ambito OOP.

Da questa analisi, emerge una regola non scritta, ma generalmente applicata, che è la seguente: evitarla fin tanto che non è strettamente necessaria.

Ti consigliamo anche