C++ Kurs

pair Datentyp

Die Themen:

Der in der C++ Standard-Bibliothek definierte Datentyp pair dient zum Verknüpfen von zwei Daten beliebigen Datentyps. Mit Hilfe von pair können damit sogar zwei Werte aus einer Funktion/Memberfunktion zurückgegeben werden, indem als Returntyp pair verwendet wird. Dazu folgt später noch ein Beispiel.

Wenn der Datentyp pair verwendet wird, muss die Header-Datei <utility> eingebunden werden. Außerdem liegt der Datentyp pair, wie übrigens alle Standard-Bibliothek Komponenten, im Namensraum std. Beachten Sie dies bitte beim Einsatz der Komponenten aus der Standard-Bibliothek!

Allen in einem pair abgelegten Elementen ist eines gemeinsam: sie sind immer eine Kopie der ursprünglichen Daten. Werden also nach dem Ablegen der Daten in einem pair noch Änderungen an den Ursprungsdaten vorgenommen, so wirken sich diese Änderungen nicht auf das pair aus.

pair Datentypen

pair für einfache Datentypen

Um ein Objekt des Datentyps pair zu definieren, sind nach dem Datentyp pair in spitzen Klammern die beiden Datentypen der Daten anzugeben, die im pair Objekt abgelegt werden sollen. Im Anschluss daran folgt dann, wie üblich, der Objektname. Wird nach dem Objektnamen nichts weiter angegeben, so werden die beiden Elemente bei einfachen Datentypen wie char, int usw. mit 0 initialisiert.

PgmHeader// pair für einen int und einen long
std::pair<int, long> emptyPair;
// pair für char* und float
std::pair<char*, float>  cfPair("any text", 1.2f);
// Kopieren eines pair Objekts
std::pair<char*, float> copyPair(cfPair);

Zusätzlich stellt pair noch Konstruktore bereit, um ein pair Objekt bei seiner Definition mit Werten zu initialisieren (zweite Anweisung) oder eine Kopie eines bestehenden pair Objekts zu erstellen (dritte Anweisung). Dabei ist natürlich stets auf die Datentypen zu achten, mit denen das pair Objekt initialisiert wird.

pair für Objekte

Außer einfachen Datentypen können in einem pair Objekt auch Objekte abgespeichert werden. Diese Objekte müssen aber die folgenden Eigenschaften besitzen:

  1. Sie müssen kopierbar sein. Bei Objekten, die dynamische Eigenschaften oder andere Objekte enthalten, muss die Objektklasse den Kopierkonstruktor definieren. Zusätzlich empfiehlt es sich, auch den Move-Konstruktor zu definieren, um den Programmablauf zu optimieren.
  2. Sie müssen zuweisbar sein, d.h. das Verhalten des Zuweisungsoperators muss definiert sein. Bei Objekten die dynamische Eigenschaften oder andere Objekte enthalten, muss die Objektklasse dazu explizit den Zuweisungsoperator = überladen. Zur Optimierung des Programmablaufs ist empfehlenswert, zusätzlich noch den Move-Operator = zu definieren.
  3. Sie müssen vergleichbar sein. Dazu muss die Objektklasse die Operatoren < und == durch friend-Funktionen überladen. Die restlichen Operatoren, wie zum Beispiel <= oder != werden dann aus diesen beiden überladenen Operatoren gebildet.
PgmHeader// Klasse die die pair Anforderung erfüllt
class Any
{
   ....
 public:
   // Standard-ctor für Initialisierung mit 'leerem' pair
   Any();
   // copy-ctor
   Any(const Any& CObj);
   // Move-Konstruktor
   Any(Any&& CObj);
   // dtor
   ~Any(); 
   // Überladener Zuweisungsoperator
   Any& operator = (const Any& COp2);
   // Move-Operator =
   Any& operator = (Any&& COp2);
   // Überladener < Operator
   friend bool operator < (const Any& COp1, const Any& COp2);
   // Uberladener == Operator
   friend bool operator == (const Any& COp1, const Any& COp2);
   ....
};

Sollen Objekte in einem pair abgelegt werden, sind innerhalb der spitzen Klammer des pair die Klassen der Objekte anzugeben. Die in einem pair abgelegten Daten können beliebig aus Objekten, Objektzeigern, Standard-Datentypen und Zeigern zusammengesetzt sein. Es ist sogar möglich, als Datentyp ein weiteres pair zu verwenden.

PgmHeader// Leeres pair mit Any-Objekt und float
std::pair<Any, float> emptyPair;
// pair mit zwei Window-Objekten
std::pair<Window, Window>  winPair(Window(...), Window(...));
// pair mit string und Window-Zeiger
std::pair<string, Window*>  swPair("Mein Fenster", new Window(...));

Sehen wir uns jetzt aber einmal genauer an was vonstattengeht, wenn in einem pair Objekte abgelegt werden.

PgmHeader// 1. Fall: leeres pair mit anschl. Zuweisung
std::pair<Any, float> aPair;
aPair = std::pair<Any,float>(Any(...), 1.2f);

// 2. Fall: Objekt getrennt erstellen und dann im pair aufnehmen
Any myObj(...);
std::pair<Any, float> bPair(myObj, 2.0f);

// 3. Fall: Objekt bei pair Definition erzeugen
std::pair<Any, float> cPair(Any(...),3.0f);

// 4. Fall: pair Kopie anlegen
std::pair<Any, float> dPair(cPair);

1. Fall

Bei der Definition eines leeren pair-Objekts wird zunächst der Standard-Konstruktor des im pair abgelegten Objekts Any ausgeführt, um dieses Objekt zu initialisieren. Bei der anschließenden Zuweisung an das pair-Objekt wird dann zunächst ein temporäres Any Objekt erzeugt. Dieses temporäre Objekt wird per Move-Konstruktor in ein neues, weiteres pair Objekt übertragen (das pair-Objekt, welches rechts vom Zuweisungsoperator steht). Erst danach wird dieses neue pair per Move-Operator = dem bisherigen leerem pair zugewiesen. Und zum Schluss wird das Any Objekt im pair rechts vom Zuweisungsoperator so wie das temporäre Objekt gelöscht. Sie sehen, dieses Verfahren ist relativ aufwändig und würde ohne den Einsatz der Move-Memberfunktionen noch wesentlich uneffektiver.

2. Fall

Es wird zuerst das im pair abzulegende Any Objekt erstellt und dieses dann einem pair bei seiner Definition übergeben. Bei der pair Definition wird der Kopierkonstruktor des im pair abgelegten Any Objekts ausgeführt, um eine Kopie des ursprünglichen Objekts im pair abzulegen.

3. Fall

Bei der Definition eines pair wird auch gleichzeitig das darin abzulegende Any Objekt definiert. In diesem Fall wird zuerst ein temporäres Any Objekt erzeugt, das dann per Move-Konstruktor in das pair übernommen wird. Nach der Übernahme wird dieses temporäre Objekt wieder gelöscht.

4. Fall

Wird ein pair bei seiner Definition mit einem anderen pair initialisiert, so wird der Kopierkonstruktor des Any Objekts aufgerufen, das im ursprünglichen pair abgelegt ist.

Die in einem pair abgelegten Objekte werden beim Löschen des pair ebenfalls gelöscht, d.h. es wird der Destruktor des im pair abgelegten Objekts automatisch aufgerufen.

Obacht geben werden muss, wenn statt Daten oder Objekte Zeiger in einem pair abgelegt werden. Zum einen wird, wenn Objektzeiger im pair abgelegt sind, beim Löschen des pair nicht mehr automatisch das über den Zeiger referenzierte Objekt gelöscht, sondern nur der Objektzeiger. Das Objekt muss also explizit selbst vorher entfernt werden.

Und zum anderen wird beim Duplizieren eines solchen pair nur der Zeiger dupliziert. Damit verweisen die Zeiger in beiden pair auf die gleiche Speicherstelle bzw. auf das gleiche Objekt! Das trifft auch dann zu, wenn zwei solche pair einander zugewiesen werden. Auch hier werden nur die Zeiger zugewiesen. Seien Sie also vorsichtig mit pair die Zeiger enthalten!

PgmHeader// pair mit Objektzeiger erzeugen
std::pair<Any*, float> pPair(new Any(...), 4.0f);
...
// pair duplizieren, dupliziert Objektzeiger!
std::pair<Any*, float> cPair(pPair);

pair Felder

Von pair Objekten lassen sich auch entsprechende Felder bilden. Mit Hilfe des gleich beschriebenen Funktions-Templates make_pair(...) lässt sich solch ein Feld auch recht einfach initialisieren.

PgmHeaderstd::pair<std::string, float> myPairs[]  {
    std::pair<std::string,float>("Toast",1.60f),
    std::pair<std::string,float>("Butter",1.05f)
};

make_pair Hilfsfunktion

Die Standard-Bibliothek enthält ein Funktions-Template make_pair(...), das es erlaubt, ein pair ohne die explizite Angabe der pair-Datentypen zu erstellen. Dazu erhält make_pair(...) lediglich die beiden Daten/Objekte als Parameter übergeben, die im pair-Objekt abgelegt werden sollen.

PgmHeader// Definition des pair Objekts
std::pair<char*, float> myPair;
// Zuweisung ohne make_pair(...)
myPair = std::pair<char*,float> ("Toast",1.60f);
// Zuweisung mit make_pair
myPair = std::make_pair("Toast",1.60f);

Im Beispiel wird zunächst ein leeres pair Objekt definiert. Diesem pair Objekt wird dann einmal normal ein 'Wert' zugewiesen und einmal mit Hilfe des make_pair(...) Funktions-Templates.

AchtungHaben Sie ein pair definiert, das ein Element vom Typ std::string enthält, so müssen Sie beim Aufruf von make_pair(...) auch ein string-Objekt angegeben und keinen C-String. Die folgende Anweisung erzeugt deshalb einen Fehler:

std::pair<std::string, int> myPair;
myPair = std::make_pair("any c-string",1);

Richtig geht's so:

std::pair<std::string, int> myPair;
myPair = std::make_pair(std::string("any string"),1);

Zugriff auf pair-Elemente

Da pair in der Standard-Bibliothek als struct-Datentyp realisiert ist (alle Elemente sind public!), kann direkt auf die beiden im pair abgelegten Werte zugegriffen werden. Das erste Element ist über den Elementnamen first erreichbar und das zwei Element über den Name second.

PgmHeader// pair Definition
std::pair<std::string, float> myPair("Toast",1.60f);
// Ausgabe der beiden pair-Elemente
std::cout << myPair.first << ',' << myPair.second << std::endl;

pair als Returnwert

Der Datentyp pair lässt sich auch sehr gut als Returntyp von Funktionen/Memberfunktionen einsetzen, die mehr als einen Wert zurückliefern. Sehen wir uns dazu einmal das nachfolgende Beispiel an.

PgmHeader// Funktion zur Berechnung einer Wurzel
// Liefert ein pair zurück

inline std::pair<bool,double> CalcSqrt(double val)
{
   if (val < 0)
      return make_pair(false,0.0f);
   else
      return make_pair(true,sqrt(val));
}
...
// Wurzel berechnen lassen
auto result = CalcSqrt(-1.44);
// Ergebnis abprüfen
if (!result.first)
   std::cout << "CalcSqrt() Fehler!";
else
   std::cout << "Wurzel: " << result.second;

Die Funktion CalcSqrt(...) dient zur Berechnung der Quadratwurzel aus einer Zahl. Als Returnwert liefert sie ein pair, in dessen ersten Element first der Erfolg oder Misserfolg der Wurzelberechnung in Form eines bool-Werts abgelegt ist. Konnte die Wurzel erfolgreich berechnet werden, so enthält das zweite Element die Wurzel aus dem übergebenen Wert.

pair-Operatoren

Der Datentyp pair überlädt unter anderem auch den Zuweisungsoperator. Der Zuweisungsoperator weist die beiden Elemente first und second einander direkt zu. Wenn Objekte in einem pair abgelegt sind, so sollte die Klasse des Objekts den Zuweisungsoperator und den Move-Operator =, wie erwähnt, überladen.

PgmHeaderclass Any
{
   ....
   Any& operator = (const Any& rhs)
   {
      ... // Zuweisungen der Any-Elemente
   }
};
...
std::pair<int,Any> firstPair(1,Any(...));
std::pair<int,Any> secondPair;
...
// Ruft = Operator von Any auf
secondPair = firstPair;

Außer dem Zuweisungsoperator überlädt pair noch die Vergleichsoperatoren < und ==. Die restlichen Vergleichsoperatoren, wie z.B. >= und !=, werden aus diesen beiden überladenen Operatoren gebildet.

PgmHeaderclass Any
{
   ....
   friend bool operator < (const Any& lhs, const Any& rhs);
   friend bool operator == (const Any& lhs, const Any& rhs);
  
};
...
std::pair<int,Any> firstPair(1,Any(...));
std::pair<int,Any> secondPair(2,Any(...));
std::pair<Any,int> myPair(Any(...),3);
std::pair<Any,int> yourPair(Any(...),1);
...
// Vergleicht zuerst int und dann Any
if (firstPair < secondPair)
   ....
// Vergleicht zuerst Any und dann int
if (myPair != yourPair)
   ...
// Direkter Vergleich der Elemente immer möglich
if (myPair.first == yourPair.first)
   ...

Beim Vergleichen von pair Objekten hat das Element first Vorrang. D.h. nur wenn der Vergleich von first true ist, wird das Element second mit in den Vergleich einbezogen. Deshalb ist bei der Definition eines pair Objekts darauf zu achten, dass die pair Elemente in der richtigen Reihenfolge definiert sind. Soll zum Beispiel ein pair Objekt für eine Gehaltsliste, bestehend aus einem string für den Namen und einen float für das Gehalt, nach Namen verglichen werden, so muss der Name als erstes angegeben werden. Wenn dagegen die Gehälter verglichen werden sollen, so muss das Gehalt als erstes angegeben sein. Die Reihenfolge ist aber nur dann relevant, wenn pair Objekte direkt miteinander verglichen werden. Es ist aber immer möglich, die Elemente first bzw. second eines pair direkt miteinander zu vergleichen.

Beispiel und ÜbungUnd hier geht's zum Beispiel und zur Übung.