J'utilise beaucoup std::set<int>
et souvent, je dois simplement vérifier si un tel ensemble contient un nombre ou non.
Je trouverais naturel d'écrire:
if (myset.contains(number))
...
Mais à cause de l'absence d'un membre contains
, je dois écrire le lourd manuel:
if (myset.find(number) != myset.end())
..
ou le pas aussi évident:
if (myset.count(element) > 0)
..
Y a-t-il une raison pour cette décision de conception?
Je pense que c'était probablement parce qu'ils essayaient de rendre std::set
et std::multiset
aussi semblables que possible. (Et évidemment, count
a une signification parfaitement sensée pour std::multiset
.)
Personnellement, je pense que c'était une erreur.
Cela n'a pas l'air si grave de prétendre que count
est juste une faute de frappe de contains
et d'écrire le test comme suit:
if (myset.count(element))
...
C'est quand même dommage.
Pour pouvoir écrire if (s.contains())
, contains()
doit renvoyer une bool
(ou un type convertible en bool
, qui est une autre histoire), comme le fait binary_search
.
La raison fondamentale à l'origine de la décision de conception not de procéder ainsi est que contains()
, qui renvoie bool
, perdrait des informations précieuses sur l'emplacement de l'élément dans la collection. find()
préserve et renvoie ces informations sous la forme d'un itérateur. Il s'agit donc d'un meilleur choix pour une bibliothèque générique telle que STL. Tel a toujours été le principe directeur pour Alex Stepanov (par exemple, ici ).
En ce qui concerne l’approche count()
en général, bien que ce soit souvent une solution de contournement satisfaisante, le problème est que cela fait plus de travail qu’uncontains()
devrait faire.
Cela ne veut pas dire qu'une bool contains()
n'est pas très agréable à avoir ou même nécessaire. Il y a quelque temps, nous avons eu une { longue discussion } sur ce même problème dans le groupe Norme ISO C++ - Propositions futures.
Cela manque parce que personne ne l'a ajouté. Personne ne l'a ajouté parce que les conteneurs du STL que la bibliothèque std
incorporait étaient conçus pour avoir une interface minimale. (Notez que std::string
ne vient pas de la STL de la même manière).
Si une syntaxe étrange ne vous dérange pas, vous pouvez la simuler:
template<class K>
struct contains_t {
K&& k;
template<class C>
friend bool operator->*( C&& c, contains_t&& ) {
auto range = std::forward<C>(c).equal_range(std::forward<K>(k));
return range.first != range.second;
// faster than:
// return std::forward<C>(c).count( std::forward<K>(k) ) != 0;
// for multi-meows with lots of duplicates
}
};
template<class K>
containts_t<K> contains( K&& k ) {
return {std::forward<K>(k)};
}
utilisation:
if (some_set->*contains(some_element)) {
}
Fondamentalement, vous pouvez écrire des méthodes d'extension pour la plupart des types C++ std
à l'aide de cette technique.
Il est beaucoup plus logique de faire ceci:
if (some_set.count(some_element)) {
}
mais je suis amusé par la méthode de la méthode d'extension.
Ce qui est vraiment triste, c’est qu’écrire un contains
efficace pourrait être plus rapide sur un multimap
ou multiset
, puisqu’ils doivent simplement trouver un élément, tandis que count
doit trouver chacun d’eux et les compter .
Un multiset contenant 1 milliard de copies de 7 (vous savez, au cas où vous en auriez une) peut avoir une .count(7)
très lente, mais peut avoir une contains(7)
très rapide.
Avec la méthode d'extension ci-dessus, nous pourrions accélérer le processus en utilisant lower_bound
, en comparant à end
, puis en comparant à l'élément. Faire cela pour un miaou non ordonné ainsi que pour un miaou ordonné nécessiterait cependant des surcharges sophistiquées de SFINAE ou spécifiques à un conteneur.
Vous examinez un cas particulier sans voir la situation dans son ensemble. Comme indiqué dans documentationstd::set
répond aux exigences de AssociativeContainer concept. Pour ce concept, il n’a aucun sens d’avoir une méthode contains
, car elle est pratiquement inutile pour std::multiset
et std::multimap
, mais count
fonctionne très bien pour chacun d’eux. Bien que la méthode contains
puisse être ajoutée comme alias pour count
pour std::set
, std::map
et leurs versions hachées (comme length
pour size()
dans std::string
), il semble que les créateurs de bibliothèques n'en aient pas vraiment besoin.
Bien que je ne sache pas pourquoi std::set
n'a pas de contains
mais count
qui ne retourne jamais que 0
ou 1
, .__, vous pouvez écrire une fonction d'assistance contains
basée sur un modèle comme ceci:
template<class Container, class T>
auto contains(const Container& v, const T& x)
-> decltype(v.find(x) != v.end())
{
return v.find(x) != v.end();
}
Et utilisez-le comme ceci:
if (contains(myset, element)) ...
La vraie raison de set
est un mystère pour moi, mais une explication possible de cette même conception dans map
pourrait être d'empêcher les gens d'écrire du code inefficace par accident:
if (myMap.contains("Meaning of universe"))
{
myMap["Meaning of universe"] = 42;
}
Ce qui donnerait lieu à deux recherches map
.
Au lieu de cela, vous êtes obligé d'obtenir un itérateur. Cela vous donne une idée mentale que vous devriez réutiliser l'itérateur:
auto position = myMap.find("Meaning of universe");
if (position != myMap.cend())
{
position->second = 42;
}
qui consomme une seule recherche map
.
Lorsque nous réalisons que set
et map
sont fabriqués à partir de la même chair, nous pouvons appliquer ce principe également à set
. C'est-à-dire que si nous voulons agir sur un élément de la set
uniquement s'il est présent dans la set
, cette conception peut nous empêcher d'écrire du code comme ceci:
struct Dog
{
std::string name;
void bark();
}
operator <(Dog left, Dog right)
{
return left.name < right.name;
}
std::set<Dog> dogs;
...
if (dogs.contain("Husky"))
{
dogs.find("Husky")->bark();
}
Bien sûr, tout ceci n’est qu’une pure spéculation.
Qu'en est-il de binary_search?
set <int> set1;
set1.insert(10);
set1.insert(40);
set1.insert(30);
if(std::binary_search(set1.begin(),set1.end(),30))
bool found=true;