[C++] Ne mélangeons pas les id de choux avec les id de carottes !
Récemment, dans un projet C++, j’ai du travailler sur une solution permettant d’indexer une grosse quantités d’objets métiers pour permettre de répondre à des requêtes le plus rapidement possible. Du coup, on a écrit un modèle de données où chaque entité est stocké dans un index primaire et associé à un id sur 32bit (représentant sa place dans l’index), et on a constitué des indexes secondaires permettant de filtrer très rapidement nos données (par exemple un index de contrats de mission par compétence requise). Pendant le développement du moteur de requêtage, on s’est aperçu que l’on avait souvent des erreurs de typos dues au fait que les ID n’étaient pas typées (il s’agissait juste de uint32_t), et le compilateur n’était pas capable de détecter ces erreurs :
std::vector<Contract> findConstractByContractorAndRequiredSkill(std::uint32_t contractorId, std::uint32_t skillId){ // std::map<std::uint32_t, std::set<uint32_t>> _contractsByContractorIndex; // std::map<std::uint32_t, std::set<uint32_t>> _contractsByRequiredSkillIndex; auto& filteredByContractor = _contractsByContractorIndex[contractorId]; auto& filteredByRequiredSkill = _contractsByRequiredSkillIndex[contractorId]; std::vector<std::uint32_t> contractIds; std::set_intersection(filteredByContractor.begin(), filteredByContractor.end(), filteredByRequiredSkill.begin(), filteredByRequiredSkill.end(), std::back_inserter(contractIds)); // ... // }
L’erreur ici est que l’on parcours l’index “_contractsByRequiredSkillIndex” en lui passant un contractorId. Le compilateur trouve cela parfaitement valide, pourtant ce code est incorrect !
Du coup on s’est creusé la tête pour résoudre ce soucis, et on a trouvé une solution élégante et sans aucun impact sur les performances pour typer nos ID. J’ai repris l’idée, l’ai généralisée (pour supporter d’autre types sous-jacents que std::uint32_t), et j’en ai fait un project sur Github (contenant un unique fichier .h de quelques lignes) : https://github.com/simonferquel/CppTypedIds
Cela nous permet de définir des types d’id ayant la même performance et le même poid qu’un std::uint32_t (ou autre type sous-jacent au choix), mais qui nous protège à la compilation de ce genre d’erreur (ainsi on évite de comparer des id de choux avec des id carottes !) :
using namespace simonstuff; struct ContractorId : public typed_id_32<ContractorId>{ }; struct SkillId : public typed_id_32<SkillId>{ }; struct ContractId : public typed_id_32<ContractId>{ }; std::vector<Contract> findConstractByContractorAndRequiredSkill(ContractorId contractorId, SkillId skillId){ // std::map<ContractorId, std::set<ContractId>> _contractsByContractorIndex; // std::map<SkillId, std::set<ContractId>> _contractsByRequiredSkillIndex; auto& filteredByContractor = _contractsByContractorIndex[contractorId]; auto& filteredByRequiredSkill = _contractsByRequiredSkillIndex[contractorId]; // build error, type mismatch std::vector<ContractId> contractIds; std::set_intersection(filteredByContractor.begin(), filteredByContractor.end(), filteredByRequiredSkill.begin(), filteredByRequiredSkill.end(), std::back_inserter(contractIds)); // ... // }
Enjoy !
Commentaires