operator<()

Jahre code ich herum, und dann passieren mir so unglaublich dumme Anfängerfehler, wie ein falsch implementierter operator<(). (C++ Operator overloading)

Und das Schlimme ist, dass man das nicht immer gleich mitbekommt, wenn plötzlich ganz andere Codes anfangen zu bocken.

Es wird also Zeit, das Thema zu “wiederholen” … so lange bis auch ich es richtig mache.


Datentyp als Mapping-Key

Es war einmal eine Datenverwaltung, die einen int als Schlüssel nutzte, um Datensätze in einer std::map leicht auffindbar zu halten.

Dann erkannte man, dass der int nicht eindeutig genug war, es sollte ein zweiter angefügt werden und so wurde aus:

1std::map<int, data_t> mapping;

ein

1struct int_key
2{
3    int a;
4    int b;
5};
6
7std::map<int_key, data_t> mapping;

Doch das funktioniert noch nicht, weil int_key Instanzen vergleichbar sein müssen, um in einer std::map benutzt werden zu dürfen.

Dafür schreibt man also eine operator<() Funktion, die zwei Instanzen vergleicht, denn (ohne zusätzlich Angaben) nutzt std::map ein std::less(x, y) für die Ordnung innerhalb seiner Elemente und std::less führt wiederum
return x < y; aus.

Und die Implementierung kann dann etwa so aussehen:

 1bool operator<(int_key const& k1, int_key const& k2)
 2{
 3  if(k1.a < k2.a)
 4  {
 5    // primary value less -> return true
 6    return true;
 7  } 
 8  if(k2.a < k1.a) /*OR: if(k1.a > k2.a) */
 9  {
10    // primary value greater -> return false
11    return false;
12  }
13  // primary values are equal -> use second
14  return k1.b < k2.b;
15}

Muss man mehrere Member vergleichen, kann man immer davon ausgehen, dass wenn ein Member kleiner ist als der im Vergleichsobjekt, dann ist das gesamte Objekt bereits “kleiner”. Ist ein Member größer, ist auch das gesamte Objekt größer. Sind beide Member aber gleich, geht man zum nächsten und wiederholt das Spiel, bis am Schluss der letzte Member entscheiden darf, wenn alle seine Vorgänger gleich waren.

So ist alles in Ordnung und eine std::map kann ihre Daten korrekt einordnen.

Und was wenn nicht ??

Ich habe neulich folgendes schlampiges Fabrikat abgeliefert:

1bool operator<(int_key const& k1, int_key const& k2)
2{
3  if(k1.a < k2.a)
4  {
5    return true;
6  } 
7  return k1.b < k2.b;
8}

Und dann brach die Hölle los.
Denn mit der Implementierung werden Größer/Kleiner Aussagen widersprüchlich. Hier ein Beispiel:

1x = (20, 8)
2y = (30, 7)
3
4x < y --> true (wegen: 20 < 30)
5y < x --> true (wegen: 30 !< 20 aber 7 < 8)

std::map kann std::less in beiden Richtungen nutzen, nämlich:

  • std::less(x, y): Ist x kleiner als y ?
  • std::less(y, x): Ist x größer als y ?

Und wenn beides true wird, ergeben sich fiese Abfrageschleifen.

So ist das Problem dann auch aufgefallen, nämlich als das Einfügen eines neuen Wertes plötzlich zu einer Endlosschleife wurde.

Dummerweise waren die Zahlen bei Tests zufällig so gestrickt, dass es mir nicht aufgefallen war.

Fazit

Peinlich! Peinlich! Peinlich!

Ja, Tippfehler passieren, aber ich habe schon auch ein Talent solche Kindergartenbeispiele zu versemmeln.

Ein weiterer “meiner” Klassiker ist:

1if(error_detected)
2{
3  continue_operation();
4}
5else
6{
7  cancel_due_to_error();
8}

Es ist einfach Schlampigkeit oder mangelnde Konzentration, die so etwas auslösen und genau das sollte einem beim finalen Durchlesen sofort auffallen.

Denn was man nicht vergessen darf: Software steuert heute alles. Und leider sind es dann solche Fehler, die böse Dinge auslösen.

Hoffentlich wird mir diese Peinlichkeit eine Lehre sein, damit ich in Zukunft genau hinsehe, wenn ich wieder mal so einen Blödsinn schreiben sollte.

Und vielen Dank an meinen Kollegen, der meinen Patzer herausgefunden hat, bevor er in produktiver Software breiten Schaden anrichten konnte.