it-swarm.com.de

Warum hat std :: set keine Member-Funktion "enthält"?

Ich benutze viel std::set<int> und oft muss ich einfach überprüfen, ob ein solcher Satz eine Zahl enthält oder nicht.

Ich würde es natürlich finden, zu schreiben:

if (myset.contains(number))
   ...

Aber wegen des Fehlens eines contains-Mitglieds muss ich das umständlich schreiben:

if (myset.find(number) != myset.end())
  ..

oder das nicht so offensichtlich:

if (myset.count(element) > 0) 
  ..

Gibt es einen Grund für diese Designentscheidung?

92
Jabberwocky

Ich denke, es war wahrscheinlich, weil sie versuchten, std::set und std::multiset so ähnlich wie möglich zu machen. (Und natürlich hat count eine absolut sinnvolle Bedeutung für std::multiset.)

Ich persönlich denke, das war ein Fehler.

Es sieht nicht ganz so schlimm aus, wenn Sie so tun, als wäre count nur ein Schreibfehler von contains und schreiben Sie den Test als:

if (myset.count(element)) 
   ...

Es ist immer noch eine Schande.

142
Martin Bonner

Um if (s.contains()) schreiben zu können, muss contains() einen bool (oder einen Typ, der in bool konvertierbar ist, was eine andere Geschichte ist) zurückgeben, wie binary_search Funktioniert.

Der grundlegende Grund hinter der Entwurfsentscheidung , dies nicht auf diese Weise zu tun , ist Das contains(), das ein bool zurückgibt, würde wertvolle Informationen darüber verlieren, wo sich das Element in der Auflistung befindet . find() behält diese Informationen in Form eines Iterators bei und gibt sie zurück. Daher ist es eine bessere Wahl für eine generische Bibliothek wie STL. Dies war für Alex Stepanov schon immer der Leitsatz, wie er oft erklärte (zum Beispiel hier ).

Bezüglich des count()-Ansatzes im Allgemeinen ist das Problem, obwohl es oft eine gute Umgehung ist, dass es mehr Arbeit leistet als eincontains() = müsste machen.

Das soll nicht heißen, dass eine bool contains() nicht sehr gut zu haben oder sogar notwendig ist. Vor einiger Zeit hatten wir eine lange Diskussion über genau dieses Problem in der Gruppe ISO C++ Standard - Future Proposals.

38
Leo Heinsaar

Es fehlt ihm, weil niemand es hinzugefügt hat. Niemand fügte es hinzu, weil die Container aus der STL, die in die std-Bibliothek aufgenommen wurden, so konzipiert waren, dass sie nur eine minimale Schnittstelle hatten. (Beachten Sie, dass std::string nicht auf dieselbe Weise aus der STL stammt.).

Wenn Ihnen eine seltsame Syntax nichts ausmacht, können Sie sie fälschen:

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)};
}

benutzen:

if (some_set->*contains(some_element)) {
}

Grundsätzlich können Sie mit dieser Methode Erweiterungsmethoden für die meisten C++ std-Typen schreiben.

Es macht viel mehr Sinn, dies einfach zu tun:

if (some_set.count(some_element)) {
}

aber ich bin amüsiert über die Methode der Erweiterungsmethode.

Das wirklich Traurige ist, dass das Schreiben eines effizienten contains auf einem multimap oder multiset schneller sein könnte, da sie nur ein Element finden müssen, während count jedes von ihnen finden und zählen muss.

Ein Multiset mit einer Milliarde Kopien von 7 (Sie wissen, falls Sie ausgehen) kann .count(7) wirklich langsam sein, kann aber contains(7) sehr schnell haben.

Mit der obigen Erweiterungsmethode könnten wir es für diesen Fall schneller machen, indem Sie lower_bound verwenden, mit end vergleichen und dann mit dem Element vergleichen. Dies für ein ungeordnetes Meow sowie ein geordnetes Meow zu tun, würde jedoch ausgefallene SFINAE- oder Containerspezifische Überladungen erfordern.

Sie betrachten den speziellen Fall und sehen kein größeres Bild. Wie in Dokumentation angegebenstd::set erfüllt die Anforderungen von AssociativeContainer concept. Für dieses Konzept macht es keinen Sinn, eine contains-Methode zu haben, da es für std::multiset und std::multimap ziemlich nutzlos ist, aber count für alle von ihnen gut funktioniert. Die Methode contains könnte zwar als Alias ​​für count für std::set, std::map und deren Hash-Versionen (wie length für size() in std::string) hinzugefügt werden, sieht jedoch so aus, als ob die Bibliotheksersteller dies nicht für nötig gehalten hätten.

12
Slava

Obwohl ich nicht weiß, warum std::set keine contains hat, sondern count, die immer nur 0 oder 1, Zurückgibt, können Sie eine contains-Hilfsfunktion wie folgt schreiben:

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();
}

Und benutze es so:

    if (contains(myset, element)) ...
10
rustyx

Der wahre Grund für set ist für mich ein Rätsel, aber eine mögliche Erklärung für dasselbe Design in map könnte darin bestehen, zu verhindern, dass Personen versehentlich ineffizienten Code schreiben:

if (myMap.contains("Meaning of universe"))
{
    myMap["Meaning of universe"] = 42;
}

Was zu zwei map-Suchvorgängen führen würde.

Stattdessen müssen Sie einen Iterator erhalten. Dies gibt Ihnen einen mentalen Hinweis, dass Sie den Iterator wiederverwenden sollten:

auto position = myMap.find("Meaning of universe");
if (position != myMap.cend())
{
    position->second = 42;
}

das verbraucht nur eine map-Suche.

Wenn wir erkennen, dass set und map aus demselben Fleisch bestehen, können wir dieses Prinzip auch auf set anwenden. Wenn wir also nur dann ein Element in der set bearbeiten möchten, wenn es in der set vorhanden ist, kann dieses Design uns daran hindern, Code wie folgt zu schreiben:

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();
}

Natürlich ist das alles nur eine Spekulation.

7
Martin Drozdik

Was ist mit 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;
0
Massimiliano D