J'ai un décodeur de format de fichier qui retourne un fichier personnalisé itérateur d'entrée . Le type de valeur de cet itérateur (lorsqu'il est déréférencé avec la fonction *iter
) peut être l'un des nombreux types de jetons.
Voici un exemple d'utilisation simplifié :
File file {"/path/to/file"};
for (const auto& token : file) {
// do something with token
}
Comment cela peut-il token
ont plusieurs types possibles ? En fonction du type du jeton, le type de sa charge utile change également.
La performance est importante ici pendant la traversée. Je ne veux pas d'allocation inutile, par exemple. C'est pourquoi le type de l'itérateur est un itérateur de saisie Dès que l'on fait avancer l'itérateur, le jeton précédent est invalidé, conformément aux exigences de la directive sur la sécurité des données. InputIterator
étiquette.
J'ai deux idées en tête pour l'instant :
-
Utilisez un seul
Token
avec une classe privéeunion
de toutes les charges utiles possibles (avec leurs récupérateurs publics) et un ID de type public (enum
) getter. L'utilisateur doit basculer sur cet ID de type pour savoir quel getter de charge utile appeler :for (const auto& token : file) { switch (token.type()) { case Token::Type::APPLE: const auto& apple = token.apple(); // ... break; case Token::Type::BANANA: const auto& banana = token.banana(); // ... break; // ... } }
Bien que ce soit probablement ce que je choisirais en C, je ne suis pas un fan de cette solution en C++ car l'utilisateur peut toujours appeler le mauvais getter et rien ne peut le faire respecter (sauf les vérifications à l'exécution que je veux éviter pour des raisons de performances).
-
Créer un résumé
Token
qui possède une classe de baseaccept()
pour accepter un visiteur, et plusieurs classes concrètes (une pour chaque type de charge utile) héritant de cette classe de base. Dans l'objet itérateur, instanciez un exemplaire de chaque classe concrète au moment de la création. Ayez également unToken *token
membre. Lors de l'itération, remplissez l'objet de charge utile pré-alloué approprié, et définissezthis->token = this->specificToken
. Faireoperator*()
retournerthis->token
(référence à). Demander à l'utilisateur d'utiliser un visiteur pendant l'itération (ou pire, utiliserdynamic_cast
) :class MyVisitor : public TokenVisitor { public: void visit(const AppleToken& token) override { // ... } void visit(const BananaToken& token) override { // ... } }; TokenVisitor visitor; for (const auto& token : file) { token.accept(visitor); }
Cela introduit des appels de fonction supplémentaires pour chaque jeton, dont au moins un qui est virtuel, mais ce n'est peut-être pas la fin du monde ; je reste ouvert à cette solution.
Y a-t-il une autre solution intéressante ? Je considère que le fait de renvoyer un boost::variant
o std::variant
est identique à l'idée n°2.