Welche Anforderungen müssen std :: map-Schlüsselklassen erfüllen, um gültige Schlüssel zu sein?

73

Ich möchte Objekte einer bestimmten Klasse Objekten einer anderen zuordnen. Die Klasse, die ich als Schlüssel verwenden möchte, wurde jedoch nicht von mir geschrieben und ist einfach structmit einigen Werten. std :: map ordnet den Inhalt an, und ich habe mich gefragt, wie es funktioniert und ob eine beliebige Klasse als Schlüssel verwendet werden kann oder ob eine Reihe von Anforderungen (Operatoren und was nicht) definiert werden müssen.

In diesem Fall könnte ich einen Wrapper für die Klasse erstellen, die die von der Operator-Map verwendeten verwendet. Ich muss nur wissen, was ich zuerst implementieren muss, und keine der Referenzen für die Klasse, die ich online gefunden habe , gibt sie an.

Kian
quelle

Antworten:

67

Für den Schlüssel ist lediglich erforderlich, dass er kopierbar und zuweisbar ist. Die Reihenfolge innerhalb der Karte wird durch das dritte Argument für die Vorlage (und das Argument für den Konstruktor, falls verwendet) definiert. Diese Standardwerte auf std::less<KeyType>, die standardmäßig auf den <Betreiber, aber es gibt keine Notwendigkeit , die Standardeinstellung zu verwenden. Schreiben Sie einfach einen Vergleichsoperator (vorzugsweise als Funktionsobjekt):

struct CmpMyType
{
    bool operator()( MyType const& lhs, MyType const& rhs ) const
    {
        //  ...
    }
};

Beachten Sie, dass eine strikte Reihenfolge definiert werden muss, dh wenn CmpMyType()( a, b )true zurückgegeben wird, CmpMyType()( b, a )muss false zurückgegeben werden. Wenn beide false zurückgeben, werden die Elemente als gleich betrachtet (Mitglieder derselben Äquivalenzklasse).

James Kanze
quelle
+1 In der Tat ist es kopierbar und zuweisbar, was die eigentlichen Anforderungen sind.
Juanchopanza
Warum "vorzugsweise als Funktionsobjekt". Warum nicht als Operator <in der Kandidatenklasse? Zugegeben, das OP steuert die Klasse nicht, kann also keinen Operator hinzufügen, aber in meinem Fall ist dies möglich. Ist es unerwünscht, einen Vergleichsoperator hinzuzufügen, und wenn ja, warum?
Nurdglaw
1
@nurdglaw Ich habe kürzlich einen Vortrag gesehen, in dem einer der Gurus darüber schimpfte, operator<für jede Klasse eine zu liefern . Er gab das Beispiel von Stühlen. Sie können sie nach Höhe, Anzahl der Beine oder sogar nach Farbe sortieren, aber was auch immer Sie wählen, ist völlig willkürlich und tatsächlich gibt es keine natürliche Reihenfolge für Stühle, sondern es ist vielmehr der Behälter, der entscheiden muss, wie die Stühle sein sollen bestellt. Zu oft operator<scheint eine offensichtliche Wahl nur eine von vielen Möglichkeiten zu sein und gehört als solche nicht in die Klasse ... etwas in dieser Richtung war seine Argumentation.
idclev 463035818
@nurdglaw jedenfalls könnte es sein, dass sich das "vorzugsweise als Funktionsobjekt" auf die anderen Alternativen bezieht: (benannte) Mitgliedsmethode oder freie Funktion
idclev 463035818
Ich las es als << Wenn operator <und std::lesstut es nicht für Sie, dann eine Vergleichsfunktion als zusätzliches Argument für die Template - Deklaration std :: map schreiben. Wenn Sie die Komparatorfunktion ausführen, ist es am besten, sie als Funktorobjekt zu deklarieren. >> Der Grund, warum es besser ist, das Funktorobjekt auszuführen, besteht darin, dass die Objektinstanz zur Kompilierungszeit optimiert wird, während der Funktionszeiger physisch im Kartenobjekt vorhanden ist und ist viel schwieriger zu optimieren.
Gem Taylor
23

Sie müssen den Operator <wie folgt definieren:

struct A
{
  int a;
  std::string b;
};

// Simple but wrong as it does not provide the strict weak ordering.    
// As A(5,"a") and A(5,"b") would be considered equal using this function.
bool operator<(const A& l, const A& r )
{
  return ( l.a < r.a ) && ( l.b < r.b );
}

// Better brute force.
bool operator<(const A& l, const A& r )
{ 
    if ( l.a < r.a )  return true;
    if ( l.a > r.a )  return false;

    // Otherwise a are equal
    if ( l.b < r.b )  return true;
    if ( l.b > r.b )  return false;

    // Otherwise both are equal
    return false;
}

// This can often be seen written as
bool operator<(const A& l, const A& r )
{
   // This is fine for a small number of members.
   // But I prefer the brute force approach when you start to get lots of members.
   return ( l.a < r.a ) ||
          (( l.a == r.a) && ( l.b < r.b ));
}
BЈовић
quelle
1
Das ist ein schrecklicher Vergleichsoperator.
Kerrek SB
1
Es war nicht nur schrecklich, es war falsch. Es lieferte nicht die "strenge schwache Reihenfolge", die für den Kartencontainer erforderlich ist. Habe oben eine Brute-Force-Version bereitgestellt, um dies zu kompensieren. Vergleichen Sie den Unterschied zwischen A (5, "A") und A (5, "B")
Martin York
Richtig, ich wollte ein einfaches Beispiel posten und musste es vermasseln. Vielen Dank an Martin für die Korrektur des Beispiels.
BЈовић
4

Die Antwort befindet sich tatsächlich in der Referenz, die Sie verknüpfen, unter der Beschreibung des Vorlagenarguments "Vergleichen".

Die einzige Anforderung ist, dass Compare(standardmäßig verwendet less<Key>, standardmäßig operator<zum Vergleichen von Schlüsseln verwendet) eine "streng schwache Reihenfolge" sein muss.

Nemo
quelle
2

Gleich wie für set: Die Klasse muss eine strenge Reihenfolge im Sinne von "kleiner als" haben. Überladen Sie entweder ein geeignetes operator<oder geben Sie ein benutzerdefiniertes Prädikat an. Zwei beliebige Objekte aund bfür welche!(a<b) && !(b>a) wird gleich betrachtet werden.

Der Kartencontainer behält tatsächlich alle Elemente in der Reihenfolge bei, die durch diese Reihenfolge bereitgestellt wird. Auf diese Weise können Sie die O- (log n) Such- und Einfügezeit nach Schlüsselwert erreichen.

Kerrek SB
quelle