Je suis un programmeur C ++ avec une expérience limitée.
En supposant que je souhaite utiliser un STL map
pour stocker et manipuler certaines données, je voudrais savoir s'il existe une différence significative (également en termes de performances) entre ces 2 approches de structure de données:
Choice 1:
map<int, pair<string, bool> >
Choice 2:
struct Ente {
string name;
bool flag;
}
map<int, Ente>
Plus précisément, y a-t-il des frais généraux utilisant un struct
au lieu d'un simple pair
?
c++
data-structures
stl
Marco Stramezzi
la source
la source
std::pair
est une structure.std::pair
est un modèle .std::pair<string, bool>
est une struct.pair
est entièrement dépourvu de sémantique. Personne ne lisant votre code (y compris vous à l'avenir) ne saura quee.first
c'est le nom de quelque chose à moins que vous ne le signaliez explicitement. Je suis fermement convaincu quepair
c'était un ajout très pauvre et paresseuxstd
, et que quand il a été conçu, personne ne pensait "mais un jour, tout le monde va l'utiliser pour tout ce qui est deux choses, et personne ne saura ce que signifie le code de quiconque ".map
itérateurs ne soient pas des exceptions valides. ("first" = clé et "second" = valeur ... vraiment,std
vraiment?)Réponses:
Le choix 1 est correct pour les petites choses "utilisées une seule fois". Essentiellement,
std::pair
c'est toujours une structure. Comme indiqué par ce commentaire, le choix 1 conduira à un code vraiment moche quelque part dans le trou du lapin,thing.second->first.second->second
et personne ne veut vraiment le déchiffrer.Le choix 2 est meilleur pour tout le reste, car il est plus facile de lire la signification des éléments de la carte. Il est également plus flexible si vous souhaitez modifier les données (par exemple, lorsque Ente a soudainement besoin d'un autre indicateur). Les performances ne devraient pas être un problème ici.
la source
Performance :
Ça dépend.
Dans votre cas particulier, il n'y aura pas de différence de performances car les deux seront également disposés en mémoire.
Dans un cas très spécifique (si vous utilisiez une structure vide comme l'un des membres de données), alors le
std::pair<>
pourrait potentiellement utiliser l'optimisation de base vide (EBO) et avoir une taille inférieure à l'équivalent de la structure. Et une taille plus petite signifie généralement des performances plus élevées:Impressions: 32, 32, 40, 40 sur idéone .
Remarque: Je ne connais aucune implémentation qui utilise réellement l'astuce EBO pour les paires régulières, mais elle est généralement utilisée pour les tuples.
Lisibilité :
Hormis les micro-optimisations, cependant, une structure nommée est plus ergonomique.
Je veux dire,
map[k].first
n'est pas si mal alors qu'ilget<0>(map[k])
est à peine intelligible. Contraste avecmap[k].name
lequel indique immédiatement ce que nous lisons.C'est d'autant plus important lorsque les types sont convertibles entre eux, car les échanger par inadvertance devient une réelle préoccupation.
Vous voudrez peut-être également en savoir plus sur le typage structurel vs nominal.
Ente
est un type spécifique qui ne peut être opéré que par des choses qui s’attendentEnte
, tout ce qui peut opérerstd::pair<std::string, bool>
peut les opérer ... même lorsque lestd::string
oubool
ne contient pas ce qu’il attend, car ilstd::pair
n’a pas de sémantique associée.Entretien :
En termes de maintenance,
pair
c'est le pire. Vous ne pouvez pas ajouter de champ.tuple
convient mieux à cet égard, tant que vous ajoutez le nouveau champ, tous les champs existants sont toujours accessibles par le même index. Ce qui est aussi impénétrable qu'auparavant, mais au moins vous n'avez pas besoin d'aller les mettre à jour.struct
est clairement le gagnant. Vous pouvez ajouter des champs où vous en avez envie.En conclusion:
pair
est le pire des deux mondes,tuple
peut avoir un léger avantage dans un cas très spécifique (type vide),struct
.Remarque: si vous utilisez des getters, vous pouvez utiliser vous-même l'astuce de base vide sans que les clients n'aient à le savoir comme dans
struct Thing: Empty { std::string name; }
; c'est pourquoi l' encapsulation est le prochain sujet que vous devriez vous préoccuper.la source
first
etsecond
, il n'y a pas de place pour l' optimisation de la base vide .pair
qui utiliserait EBO, mais un compilateur pourrait fournir une fonction intégrée. Étant donné que ceux-ci sont censés être membres, cependant, il peut être observable (vérification de leurs adresses, par exemple) auquel cas ce ne serait pas conforme.[[no_unique_address]]
, ce qui active l'équivalent d'EBO pour les membres.La paire brille le plus lorsqu'elle est utilisée comme type de retour d'une fonction avec une affectation déstructurée à l'aide de std :: tie et de la liaison structurée de C ++ 17. Utilisation de std :: tie:
Utilisation de la liaison structurée de C ++ 17:
Un mauvais exemple d'utilisation d'une paire std :: (ou tuple) serait quelque chose comme ceci:
car il n'est pas clair lors de l'appel de std :: get <2> (player_data) ce qui est stocké à l'index de position 2. Souvenez-vous de la lisibilité et de rendre évident pour le lecteur ce que fait le code est important . Considérez que ceci est beaucoup plus lisible:
En général, vous devriez penser à std :: pair et std :: tuple comme des moyens de renvoyer plus d'un objet à partir d'une fonction. La règle de base que j'utilise (et que beaucoup d'autres ont également utilisée) est que les objets retournés dans une paire std :: tuple ou std :: ne sont "liés" que dans le contexte d'un appel à une fonction qui les renvoie ou dans le contexte d'une structure de données qui les relie (par exemple, std :: map utilise std :: pair pour son type de stockage). Si la relation existe ailleurs dans votre code, vous devez utiliser une structure.
Sections connexes des Principes directeurs:
la source