A giudicare dal testo della tua domanda (hai usato la parola "nascondi"), sai già cosa sta succedendo qui. Il fenomeno si chiama "nascondere il nome". Per qualche motivo, ogni volta che qualcuno fa una domanda sul perché si verifica il nascondimento dei nomi, le persone che rispondono affermano che questo si chiama "nascondimento dei nomi" e spiegano come funziona (cosa che probabilmente già conosci), o spiegano come sovrascriverlo (che tu mai chiesto), ma nessuno sembra preoccuparsi di affrontare l'attuale domanda "perché".
La decisione, la logica alla base del nascondimento del nome, ovvero perché è stata effettivamente progettata in C ++, è quella di evitare alcuni comportamenti controintuitivi, imprevisti e potenzialmente pericolosi che potrebbero verificarsi se l'insieme ereditato di funzioni sovraccariche fosse permesso di mescolarsi con l'insieme corrente di sovraccarichi nella classe data. Probabilmente sai che in C ++ la risoluzione del sovraccarico funziona scegliendo la migliore funzione dal gruppo di candidati. Questo viene fatto abbinando i tipi di argomenti ai tipi di parametri. Le regole di abbinamento potrebbero essere complicate a volte e spesso portano a risultati che potrebbero essere percepiti come illogici da un utente non preparato. L'aggiunta di nuove funzioni a un set di quelle precedentemente esistenti potrebbe comportare uno spostamento piuttosto drastico dei risultati della risoluzione del sovraccarico.
Ad esempio, supponiamo che la classe base B
abbia una funzione membro foo
che accetta un parametro di tipo void *
e che tutte le chiamate a foo(NULL)
vengono risolte B::foo(void *)
. Supponiamo che non ci sia un nome nascosto e questo B::foo(void *)
è visibile in molte classi diverse da cui discendono B
. Tuttavia, diciamo che in alcuni discendenti [indiretti, remoti] D
della classe viene definita B
una funzione foo(int)
. Ora, senza nome nascondersi D
ha sia foo(void *)
e foo(int)
visibile e partecipando nella risoluzione di sovraccarico. A quale funzione si rivolgeranno le chiamate foo(NULL)
, se effettuate attraverso un oggetto di tipo D
? Si risolveranno D::foo(int)
, poiché int
è una corrispondenza migliore per lo zero integrale (ad esNULL
) rispetto a qualsiasi tipo di puntatore. Quindi, in tutta la gerarchia, le chiamate si foo(NULL)
risolvono in una funzione, mentre in D
(e sotto) si risolvono improvvisamente in un'altra.
Un altro esempio è riportato in The Design and Evolution of C ++ , pagina 77:
class Base {
int x;
public:
virtual void copy(Base* p) { x = p-> x; }
};
class Derived{
int xx;
public:
virtual void copy(Derived* p) { xx = p->xx; Base::copy(p); }
};
void f(Base a, Derived b)
{
a.copy(&b); // ok: copy Base part of b
b.copy(&a); // error: copy(Base*) is hidden by copy(Derived*)
}
Senza questa regola, lo stato di b verrebbe parzialmente aggiornato, portando alla suddivisione.
Questo comportamento è stato ritenuto indesiderabile quando è stata progettata la lingua. Come approccio migliore, è stato deciso di seguire la specifica "nascondere il nome", nel senso che ogni classe inizia con un "foglio pulito" rispetto al nome di ogni metodo che dichiara. Per sovrascrivere questo comportamento, è richiesta un'azione esplicita da parte dell'utente: originariamente una rideclarazione dei metodi ereditati (attualmente deprecati), ora un uso esplicito di using-statement.
Come hai correttamente osservato nel tuo post originale (mi riferisco all'osservazione "Non polimorfico"), questo comportamento potrebbe essere visto come una violazione della relazione IS-A tra le classi. Questo è vero, ma apparentemente allora si decise che alla fine il nascondere il nome si sarebbe rivelato un male minore.