Presque toutes les ressources C ++ que j'ai vues qui traitent de ce genre de chose me disent que je devrais préférer les approches polymorphes à l'utilisation de RTTI (identification de type à l'exécution). En général, je prends ce genre de conseil au sérieux et j'essaierai de comprendre la raison d'être - après tout, C ++ est une bête puissante et difficile à comprendre dans toute sa profondeur. Cependant, pour cette question particulière, je dessine un blanc et j'aimerais voir quel genre de conseils Internet peut offrir. Tout d'abord, permettez-moi de résumer ce que j'ai appris jusqu'à présent, en énumérant les raisons courantes citées pour lesquelles le RTTI est "considéré comme dangereux":
Certains compilateurs ne l'utilisent pas / RTTI n'est pas toujours activé
Je n'achète vraiment pas cet argument. C'est comme dire que je ne devrais pas utiliser les fonctionnalités C ++ 14, car il existe des compilateurs qui ne le prennent pas en charge. Et pourtant, personne ne me découragerait d'utiliser les fonctionnalités de C ++ 14. La majorité des projets auront une influence sur le compilateur qu'ils utilisent et sur la façon dont il est configuré. Même en citant la page de manuel gcc:
-fno-rtti
Désactivez la génération d'informations sur chaque classe avec des fonctions virtuelles à utiliser par les fonctionnalités d'identification de type d'exécution C ++ (dynamic_cast et typeid). Si vous n'utilisez pas ces parties de la langue, vous pouvez économiser de l'espace en utilisant cet indicateur. Notez que la gestion des exceptions utilise les mêmes informations, mais G ++ les génère selon les besoins. L'opérateur dynamic_cast peut toujours être utilisé pour les transtypages qui ne nécessitent pas d'informations de type à l'exécution, c'est-à-dire les transtypages en "void *" ou en classes de base non ambiguës.
Ce que cela me dit, c'est que si je n'utilise pas RTTI, je peux le désactiver. C'est comme dire, si vous n'utilisez pas Boost, vous n'êtes pas obligé de créer un lien vers celui-ci. Je n'ai pas à planifier le cas où quelqu'un compile avec -fno-rtti
. De plus, le compilateur échouera haut et fort dans ce cas.
Cela coûte de la mémoire supplémentaire / peut être lent
Chaque fois que je suis tenté d'utiliser RTTI, cela signifie que je dois accéder à une sorte d'information de type ou de trait de ma classe. Si j'implémente une solution qui n'utilise pas RTTI, cela signifie généralement que je devrai ajouter des champs à mes classes pour stocker ces informations, donc l'argument de mémoire est un peu vide (je vais en donner un exemple plus bas).
Un dynamic_cast peut être lent, en effet. Il existe généralement des moyens d'éviter de l'utiliser dans des situations critiques en termes de vitesse. Et je ne vois pas tout à fait l'alternative. Cette réponse SO suggère d'utiliser une énumération, définie dans la classe de base, pour stocker le type. Cela ne fonctionne que si vous connaissez toutes vos classes dérivées a-priori. C'est assez gros "si"!
De cette réponse, il semble également que le coût du RTTI n'est pas clair non plus. Différentes personnes mesurent des choses différentes.
Des conceptions polymorphes élégantes rendront le RTTI inutile
C'est le genre de conseil que je prends au sérieux. Dans ce cas, je ne peux tout simplement pas trouver de bonnes solutions non-RTTI qui couvrent mon cas d'utilisation RTTI. Laissez-moi vous donner un exemple:
Disons que j'écris une bibliothèque pour gérer les graphiques de certains types d'objets. Je souhaite permettre aux utilisateurs de générer leurs propres types lors de l'utilisation de ma bibliothèque (la méthode enum n'est donc pas disponible). J'ai une classe de base pour mon nœud:
class node_base
{
public:
node_base();
virtual ~node_base();
std::vector< std::shared_ptr<node_base> > get_adjacent_nodes();
};
Maintenant, mes nœuds peuvent être de différents types. Que penses-tu de ceux-ci:
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);
};
Bon sang, pourquoi pas même l'un de ceux-ci:
class orange_node : public red_node, public yellow_node
{
public:
orange_node();
virtual ~orange_node();
void poke();
void poke_adjacent_oranges();
};
La dernière fonction est intéressante. Voici une façon de l'écrire:
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();
}
}
}
Tout cela semble clair et net. Je n'ai pas à définir des attributs ou des méthodes là où je n'en ai pas besoin, la classe de nœud de base peut rester légère et moyenne. Sans RTTI, par où commencer? Peut-être que je peux ajouter un attribut node_type à la classe de 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;
};
Est-ce que std :: string est une bonne idée pour un type? Peut-être pas, mais que puis-je utiliser d'autre? Inventez un numéro et espérez que personne d'autre ne l'utilise encore? Aussi, dans le cas de mon orange_node, que faire si je veux utiliser les méthodes de red_node et yellow_node? Dois-je stocker plusieurs types par nœud? Cela semble compliqué.
Conclusion
Ces exemples ne semblent pas trop complexes ou inhabituels (je travaille sur quelque chose de similaire dans mon travail quotidien, où les nœuds représentent du matériel réel qui est contrôlé par le logiciel, et qui font des choses très différentes en fonction de ce qu'ils sont). Pourtant, je ne saurais pas comment faire cela avec des modèles ou d'autres méthodes. Veuillez noter que j'essaie de comprendre le problème, pas de défendre mon exemple. Ma lecture de pages telles que la réponse SO que j'ai liée ci-dessus et cette page sur Wikibooks semblent suggérer que j'utilise mal RTTI, mais j'aimerais savoir pourquoi.
Donc, revenons à ma question initiale: pourquoi le «polymorphisme pur» est-il préférable à l'utilisation de RTTI?
node_base
partie d'une bibliothèque et que les utilisateurs créeront leurs propres types de nœuds. Ensuite, ils ne peuvent pas modifier node_base
pour autoriser une autre solution, alors peut-être que RTTI devient alors leur meilleure option. D'un autre côté, il existe d'autres façons de concevoir une telle bibliothèque afin que les nouveaux types de nœuds puissent s'intégrer beaucoup plus élégamment sans avoir besoin d'utiliser RTTI (et d'autres façons de concevoir les nouveaux types de nœuds, aussi).