Quasi ogni risorsa C ++ che ho visto che discute questo genere di cose mi dice che dovrei preferire approcci polimorfici all'uso di RTTI (identificazione del tipo in fase di esecuzione). In generale, prendo seriamente questo tipo di consiglio e cercherò di capire la logica - dopo tutto, il C ++ è una bestia potente e difficile da capire nella sua profondità. Tuttavia, per questa particolare domanda, disegno uno spazio vuoto e vorrei vedere che tipo di consiglio può offrire Internet. Innanzitutto, vorrei riassumere ciò che ho imparato finora, elencando i motivi comuni che vengono citati per cui RTTI è "considerato dannoso":
Alcuni compilatori non lo usano / RTTI non è sempre abilitato
Davvero non mi piace questo argomento. È come dire che non dovrei usare le funzionalità di C ++ 14, perché ci sono compilatori là fuori che non lo supportano. Eppure, nessuno mi scoraggerebbe dall'usare le funzionalità di C ++ 14. La maggior parte dei progetti avrà influenza sul compilatore che stanno utilizzando e su come è configurato. Anche citando la manpage di gcc:
-fno-rtti
Disabilita la generazione di informazioni su ogni classe con funzioni virtuali per l'uso da parte delle funzionalità di identificazione del tipo in fase di esecuzione C ++ (dynamic_cast e typeid). Se non usi queste parti della lingua, puoi risparmiare spazio usando questo flag. Si noti che la gestione delle eccezioni utilizza le stesse informazioni, ma G ++ le genera secondo necessità. L'operatore dynamic_cast può ancora essere utilizzato per i cast che non richiedono informazioni sul tipo di runtime, ad esempio i cast a "void *" o a classi base non ambigue.
Ciò che questo mi dice è che se non sto usando RTTI, posso disabilitarlo. È come dire, se non stai usando Boost, non devi collegarti ad esso. Non devo pianificare il caso in cui qualcuno sta compilando -fno-rtti
. Inoltre, in questo caso il compilatore fallirà forte e chiaro.
Costa memoria extra / Può essere lento
Ogni volta che sono tentato di usare RTTI, significa che ho bisogno di accedere a qualche tipo di informazione sul tipo o tratto della mia classe. Se implemento una soluzione che non utilizza RTTI, di solito questo significa che dovrò aggiungere alcuni campi alle mie classi per memorizzare queste informazioni, quindi l'argomento della memoria è un po 'nullo (darò un esempio di questo più avanti).
Un dynamic_cast può essere davvero lento. Di solito ci sono modi per evitare di doverlo usare in situazioni critiche per la velocità, però. E non vedo bene l'alternativa. Questa risposta SO suggerisce di utilizzare un enum, definito nella classe base, per memorizzare il tipo. Funziona solo se conosci a priori tutte le tue classi derivate. È un bel "se"!
Da quella risposta, sembra anche che il costo di RTTI non sia chiaro. Persone diverse misurano cose diverse.
Eleganti design polimorfici renderanno superfluo RTTI
Questo è il tipo di consiglio che prendo sul serio. In questo caso, semplicemente non riesco a trovare buone soluzioni non RTTI che coprano il mio caso d'uso RTTI. Faccio un esempio:
Supponiamo che stia scrivendo una libreria per gestire i grafici di un qualche tipo di oggetti. Voglio consentire agli utenti di generare i propri tipi quando usano la mia libreria (quindi il metodo enum non è disponibile). Ho una classe base per il mio nodo:
class node_base
{
public:
node_base();
virtual ~node_base();
std::vector< std::shared_ptr<node_base> > get_adjacent_nodes();
};
Ora, i miei nodi possono essere di diversi tipi. Cosa ne pensi di questi:
class red_node : virtual public node_base
{
public:
red_node();
virtual ~red_node();
void get_redness();
};
class yellow_node : virtual public node_base
{
public:
yellow_node();
virtual ~yellow_node();
void set_yellowness(int);
};
Diavolo, perché nemmeno uno di questi:
class orange_node : public red_node, public yellow_node
{
public:
orange_node();
virtual ~orange_node();
void poke();
void poke_adjacent_oranges();
};
L'ultima funzione è interessante. Ecco un modo per scriverlo:
void orange_node::poke_adjacent_oranges()
{
auto adj_nodes = get_adjacent_nodes();
foreach(auto node, adj_nodes) {
// In this case, typeid() and static_cast might be faster
std::shared_ptr<orange_node> o_node = dynamic_cast<orange_node>(node);
if (o_node) {
o_node->poke();
}
}
}
Sembra tutto chiaro e pulito. Non devo definire attributi o metodi dove non ne ho bisogno, la classe del nodo base può rimanere snella e meschina. Senza RTTI, da dove comincio? Forse posso aggiungere un attributo node_type alla classe base:
class node_base
{
public:
node_base();
virtual ~node_base();
std::vector< std::shared_ptr<node_base> > get_adjacent_nodes();
private:
std::string my_type;
};
Std :: string è una buona idea per un tipo? Forse no, ma cos'altro posso usare? Crea un numero e speri che nessun altro lo stia ancora usando? Inoltre, nel caso del mio orange_node, cosa succede se voglio usare i metodi da red_node e yellow_node? Dovrei memorizzare più tipi per nodo? Sembra complicato.
Conclusione
Questi esempi non sembrano eccessivamente complessi o insoliti (sto lavorando a qualcosa di simile nel mio lavoro quotidiano, in cui i nodi rappresentano l'hardware reale che viene controllato attraverso il software e che fa cose molto diverse a seconda di cosa sono). Eppure non saprei un modo pulito per farlo con modelli o altri metodi. Tieni presente che sto cercando di capire il problema, non di difendere il mio esempio. La mia lettura di pagine come la risposta SO che ho collegato sopra e questa pagina su Wikibooks sembra suggerire che sto abusando di RTTI, ma vorrei sapere perché.
Quindi, tornando alla mia domanda iniziale: perché è preferibile il "polimorfismo puro" rispetto all'uso di RTTI?
node_base
prendono atto dell'idea che il tuo esempio suggerisce fa parte di una libreria e gli utenti creeranno i propri tipi di nodo. Quindi non possono modificare node_base
per consentire un'altra soluzione, quindi forse RTTI diventa la loro migliore opzione allora. D'altra parte, ci sono altri modi per progettare una tale libreria in modo che i nuovi tipi di nodo possano adattarsi in modo molto più elegante senza bisogno di usare RTTI (e anche altri modi per progettare i nuovi tipi di nodo).