Ereditarietà: “A” è una base inaccessibile di “B”

$ cat inheritance.cpp #include  using namespace std; class A { }; class B : private A { }; int main() { A* ab = new B; } $ $ g++ inheritance.cpp inheritance.cpp: In function 'int main()': inheritance.cpp:9: error: 'A' is an inaccessible base of 'B' $ 

Non capisco proprio questo errore.

Come comprendo, e come conferma questo tutorial , private ereditarietà private dovrebbe solo cambiare il modo in cui i membri della class B sono visibili al mondo esterno.

Penso che lo specifier privato stia facendo molto più che semplicemente cambiando la visibilità dei membri della class B qui.

  • Cosa ottengo questo errore e cosa significa?
  • Fondamentalmente cosa c’è di sbagliato nel permettere questo tipo di codice in C ++? Sembra totalmente innocuo.

Rendendo privata l’eredità, stai sostanzialmente dicendo che anche il fatto che B erediti da A (del tutto) è privato – non accessibile / visibile al mondo esterno.

Senza entrare in una lunga discussione su cosa succederebbe se fosse permesso, il semplice fatto è che non è permesso. Se si desidera utilizzare un puntatore alla base per fare riferimento a un object di tipo derivato, si è praticamente bloccati dall’utilizzo dell’ereditarietà pubblica.

Edit: Dal momento che qualcuno si è preso la briga di inviare un’e-mail per chiedere maggiori informazioni su cosa potrebbe accadere se ciò fosse consentito, immagino che approfondirò un po ‘su di esso.

Il problema fondamentale è che l’ereditarietà privata non è necessariamente intesa a seguire il principio di sostituzione di Liskov . L’ereditarietà pubblica asserisce che un object derivato può essere sostituito da un object della class base, e la semantica corretta risulterà comunque. Tuttavia, l’ereditarietà privata non lo afferma. La solita descrizione della relazione implicita nell’eredità privata è “è implementata in termini di”.

Eredità pubblica significa che una class derivata mantiene tutte le capacità della class base e potenzialmente aggiunge altro. L’ereditarietà privata spesso significa più o meno l’opposto: la class derivata utilizza una class base generale per implementare qualcosa con un’interfaccia più ristretta.

Ad esempio, supponiamo per il momento che i contenitori della libreria standard C ++ siano stati implementati utilizzando l’ereditarietà piuttosto che i modelli. Nel sistema corrente, std::deque e std::vector sono contenitori, e std::stack è un adattatore per container che fornisce un’interfaccia più ristretta. Poiché si basa su modelli, puoi usare std::stack come adattatore per std::deque o std::vector .

Se volessimo fornire essenzialmente lo stesso con l’ereditarietà, probabilmente utilizzeremo l’ereditarietà privata, quindi std::stack sarebbe qualcosa del tipo:

 class stack : private vector { // ... }; 

In questo caso, non vogliamo che l’utente sia in grado di manipolare il nostro stack come se fosse un vector . Facendolo potremmo (e probabilmente vorrebbero) violare le aspettative di uno stack (ad esempio, l’utente potrebbe inserire / rimuovere gli elementi nel mezzo, piuttosto che uno stile puramente pila come previsto). In pratica utilizziamo il vector come un modo conveniente per implementare il nostro stack, ma se (ad esempio) abbiamo modificato l’implementazione per lo stack stand alone (senza dipendenza da una class base) o re-implementato in termini di std::deque , non vogliamo che ciò influisca su alcun codice client – al codice client, questo dovrebbe essere solo uno stack, non una varietà specializzata di vettore (o deque).

l’ereditarietà privata dovrebbe solo cambiare il modo in cui i membri della class B sono visibili al mondo esterno

Lo fa. E se

 A* p = new B; 

erano permessi, quindi i membri ereditati di qualsiasi B potevano essere raggiunti dal mondo esterno, semplicemente facendo un A* . Dal momento che sono ereditati privatamente, quell’accesso è illegale, così come l’upcast.

clang++ dà un messaggio di errore leggermente più comprensibile:

 example.cpp:9:13: error: cannot cast 'B' to its private base class 'A' A* ab = new B; ^ example.cpp:6:11: note: declared private here class B : private A { }; ^~~~~~~~~ 1 error generated. 

Non sono un esperto in C ++, ma sembra che semplicemente non sia permesso. Andrò a spulciare le specifiche e vedere cosa mi viene in mente.

Modifica: ecco il riferimento pertinente dalle specifiche – Sezione 4.10 Conversioni puntatore , paragrafo 3:

Un valore di prvalore di tipo “puntatore a cv D “, dove D è un tipo di class, può essere convertito in un valore di tipo “puntatore a cv B “, dove B è una class base di D Se B è una class base inaccessibile o ambigua di D , un programma che richiede questa conversione è mal formato.

È piuttosto semplice: il fatto che A sia ereditato privatamente significa che il fatto che B estende A è un segreto, e solo B “sa”. Questa è la vera definizione dell’eredità privata.

L’ereditarietà privata significa che al di fuori della class derivata, le informazioni sull’ereditarietà sono nascoste. Ciò significa che non puoi trasmettere la class derivata alla class base: la relazione non è nota al chiamante.