C++-Programmierung/ Im Zusammenhang mit Klassen

Aus Wikibooks
Zur Navigation springen Zur Suche springen
Im Zusammenhang mit Klassen
20101020 Sheep shepherd at Vistonida lake Glikoneri Rhodope Prefecture Thrace Greece.jpg

Zielgruppe:

Anfänger

Lernziel:
Weitere Schlüsselwörter in Zusammenhang mit Klassen: union, static, mutable, volatile


Varianten [Bearbeiten]

Definition[Bearbeiten]

Eine Union ist eine Typdefinition zur Vereinigung verschiedener Typen auf einer Adresse. Dies erleichtert Redefinitionen oder die Verwendung von Variantentypen. Sie nimmt nicht mehrere Elemente nebeneinander auf, sondern zu jedem Zeitpunkt immer nur eines. Wird eine der Definitionsvarianten initialisiert, so werden die anderen damit implizit initialisiert, da sie auf der gleichen Adresse beginnen. Sie wird mit dem Schlüsselwort union deklariert:

Nuvola-inspired-terminal.svg
1 union _Union1
2 {
3   int a;
4   float b;
5   char[4] caC; // 32bit float/int angenommen
6 };

Erläuterung: Es wird ein Typ namens _Union1 definiert (von dem später Variablen angelegt werden können). Sein Inhalt kann aufgefasst werden entweder als ein int-Wert, oder als ein float-Wert, oder als ein char[4]-Array. Da alle drei Möglichkeiten jeweils 4 Bytes benötigen, werden später für eine Variable des Typs _Union1 vier Bytes reserviert.

Beispiel zur Verwendung:

Nuvola-inspired-terminal.svg
1 _Union1 u1;   // Instanz erzeugen
2 u1.a = 10;  // jetzt ist in u1 ein int gespeichert
3 u1.b = 2.5; // jetzt ein float
4             // damit hat u1.a möglicherweise keinen sinnvollen Wert mehr
5 ofstream DateiStream("xxx.data");
6 DateiStream << u1.caC; // Damit wird der Dateninhalt als Rohdaten verwendet

Im Beispiel wird eine Variable u1 angelegt: für sie werden 4 Byte Speicher reserviert. Diese 4 Bytes können als int-Wert aufgefasst/verwendet werden, oder als float-Wert verwendet werden, oder als char[4]. Das Beispiel "wandelt" dadurch das Bitmuster des float-Werts 2.5 in vier char-Zeichen und schreibt diese in die Datei xxx.data .

Eine Variable eines union-Typs benötigt so viel Speicher, wie die größte Inhalts-Variante. Die "kleineren Möglichkeiten" überdecken dann nur den "vorderen Teil" der größten Möglichkeit, ab der Startadresse.

Übliche Verwendungszwecke[Bearbeiten]

Handhaben von Rohdaten[Bearbeiten]

Bei Einsatz von Datenstrukturen mit fester Ausrichtung und kann mithilfe einer union die gesamte Datensammlung als Rohdaten aus dem Speicher betrachtet werden, beispielsweise mit einer anonymen union, die eine Datensammlungsstruktur beinhaltet und einen direkt ansprechbaren Zeiger auf deren Rohdaten enthält.

Binäre Typwandlung[Bearbeiten]

Im obigen Beispiel wird das Bitmuster von 2.5f direkt als char[4] aufgefasst. Im Gegensatz dazu würde beispielsweise eine Wandlung

int i = (int) 4.8f ;

nicht das Bitmuster des float-Werts 4.8f in die Variable i übertragen, sondern den Zahlenwert 4.

Varianten[Bearbeiten]

Beispiel: Eine Klasse "KFZ" soll die Fahrgestellnummer für beliebige PKW als Membervariable halten können. Innerhalb der EU ist dies eine 17-stellige Folge aus Großbuchstaben und Ziffern (z.B. char[17]). In einem Nicht-EU-Land könnte es aber eine Ganzzahl sein, die in eine (long) -Variable passt.

Mit einem

Nuvola-inspired-terminal.svg
1 U_IDENT_NR union {
2   char[17] eu;
3   long     non_eu;
4 }

kann die Klasse KFZ beide Arten aufnehmen: Sie erhält eine Member-Variable

Nuvola-inspired-terminal.svg
1 U_IDENT_NR  identifizierungsNummer;

Statische Membervariablen [Bearbeiten]

Gleich zu Beginn: C++ kennt im Unterschied zu anderen Programmiersprachen, wie z.B. C#, keine statische Klassen![1] Dafür kann eine Klasse aber statische Inhalte haben, die im gleichen Ausführungsobjekt

  • in allen Instanzen dieser Klasse gleich sind und
  • auch verfügbar sind, wenn keine Instanz der Klasse existiert.

Dies gilt auch für statische Mitgliedsvariablen.

Eine statische Variable wird mit dem Speicherklassenschlüsselwort static deklariert. Variablen der Speicherklasse static sind nur in dem Objektcode gültig, in dem sie definiert sind. Um dieses Verhalten bei einer Mitgliedsvariable in einer Klasse zu erreichen, muss neben der Deklaration in der Klasse eine Definition dieser Variable im Ausführungsobjekt der Klasse, außerhalb der Deklaration, erfolgen. Bei konstanten, statischen Mitgliedsvariablen genügt die Deklaration, Definition und Initialisierung innerhalb der Klassendeklaration.

Nuvola-inspired-terminal.svg
1 class mitStatischemInhalt{
2 public:
3 	static int x;
4 };
5 
6 int mitStatischemInhalt::x;

Die Klasse mitStatischemInhalt hat nun eine statische Mitgliedsvariable x, die in allen Instanzen der Klasse und auch ohne Instanzierung über mitStatischemInhalt::x erreichbar ist, und sich auf die eine Definition int mitStatischemInhalt::x; bezieht.

Referenzen[Bearbeiten]

  1. benediktibk. “C++: statische Klasse vs. Namesbereich”. Hackerboard.de, 2007.
Symbol opinion vote.svg
Hinweis

Bitte beachten Sie bei diesem Vorgehen, dass die Speicherklasse static festlegt, dass die statischen Inhalte einer Klasse nur in dem Modul gültig sind, in dem sie definiert wurden. Ein externer Zugriff auf diese Variable ist nicht möglich.

Beispiel: Für ein modular aufgebautes Programm werden erst zwei getrennte Bibliotheken A und B erzeugt, die am Ende zum Hauptprogramm hinzugelinkt werden. Beide verwenden die Klasse C mit der statischen Member-Variable _sm. Dann gibt es in Bibliothek A ein C::_sm, jedoch ein anderes C::_sm in Bibliothek B. Beim Datenaustausch eines C-Objekts zwischen den Bibliotheken kann sich somit _sm scheinbar ändern.

Statische Methoden [Bearbeiten]

Statische Inhalte einer Klasse sind im gleichen Ausführungsobjekt

  • in allen Instanzen dieser Klasse gleich.
  • auch verfügbar, wenn keine Instanz der Klasse existiert.

Eine statische Methode wird mit dem Speicherklassenschlüsselwort static deklariert.

Dies gilt auch für statische Mitgliedsmethoden.

Im aktuellen Abschnitt ist mit statische Methode immer eine statische Mitglieds-Methode einer Klasse gemeint.

Statische Methoden können auf statische Mitglieder der Klasse, in der sie deklariert werden, zugreifen. Nicht-statische Inhalte dürfen nicht von der Methode aus verwendet werden, da die Methode auch ohne eine Instanz gültig ist. Auch wenn ein Objekt verwendet wird und über das Objekt die statische Methode aufgerufen wird, wird der statische Programmcode verwendet.

Im Beispiel wird nun eine statische Methode deklariert und in einer Beispielfunktion dreimal verwendet. Beachten Sie, dass die Zuweisungen von p1, p2, p3 immer dieselbe statische Funktion aufrufen.

Nuvola-inspired-terminal.svg
 1 class Q{
 2 public:
 3   static char *gibVierKiloByte(void);
 4 };
 5 
 6 void f( Q &rQ, Q *pQ ){
 7   char *p1 = Q::gibVierKiloByte(); // Aufruf ohne Objekt
 8   char *p2 = rQ.gibVierKiloByte(); // Aufruf über referenziertes Objekt
 9   char *p3 = pQ->gibVierKiloByte(); // Aufruf über Zeiger auf Objekt
10 };

Die zweite und dritte Aufruf-Variante sollte vermieden werden, da man sie als Andeutung verstehen könnte, dass hier eine nicht-statische Methode aufgerufen würde.


Ein weiteres Beispiel, welches zeigt wie man statische Membermethoden und Membervariablen dazu verwenden könnte, um die Anzahl der erzeugten Instanzen/Objekte der Klasse zu bestimmen:

Nuvola-inspired-terminal.svg
 1 #include <iostream>
 2 using namespace std;
 3 
 4 
 5 class Tier{
 6 public:
 7     Tier();
 8     static int getAnzahl();
 9     // statische Membermethode kann auf nur
10     // statische Membervariablen zugreifen
11 
12 private:
13     static int anz;
14 };
15 
16 
17 // Initialisierung der statischen Membervariablen
18 int Tier::anz = 0;
19 
20 Tier::Tier(){
21     ++ anz;
22 }
23 
24 int Tier::getAnzahl(){
25     return anz;
26 }
27 
28 int main(){
29     // Selbst wenn noch keine Instanzen auf
30     // Klasse Tier sind kann man schon
31     // die statische Membermethode aufrufen
32     cout << Tier::getAnzahl() << endl;
33 
34     Tier t1;
35     cout << Tier::getAnzahl() << endl;
36 
37     Tier t2, t3;
38     cout << Tier::getAnzahl() << endl;
39 
40     cin.peek();
41 }
Crystal Clear app kscreensaver.svg
Ausgabe:
1 0
2 1
3 3

static in Funktionen [Bearbeiten]

Funktionen haben einen eigenen Gültigkeitsbereich. Das bedeutet, dass lokale Variablen bei dem Aufruf in einer Funktion ein neuer Wert zugewiesen wird und dieser nach dem Verlassen der Funktion wieder verfällt. Dann gibt es noch globale Variablen, die ihren Wert bereits vor dem Funktionsaufruf erhalten und einen globalen Gültigkeitsbereich besitzen; man kann also von überall auf diese Variablen zugreifen, sie behalten ihren Wert auch nach dem Funktionsaufruf. Dann wären da ja noch die static-Variablen. Wenn also eine Funktion aufgerufen wird und eine static-Variable angelegt wird, behält diese ihren Wert auch noch nach dem Verlassen der Funktion. Hier ein Beispiel für globale, lokale und statische Variablen in Funktionen:

Nuvola-inspired-terminal.svg
 1 #include <iostream>
 2 
 3 using namespace std;
 4 
 5 int globalZahl = 1;
 6 
 7 void funktion(){
 8     static int staticZahl = 1;  // Initialisierung; wird nur beim ersten Funktionsaufruf ausgefuehrt!
 9     int lokalZahl = 1;
10     cout << "global: " << globalZahl << endl;
11     cout << "lokal:  " << lokalZahl << endl;
12     cout << "static: " << staticZahl << endl << endl;
13     globalZahl++;
14     lokalZahl++;
15     staticZahl++;
16 }
17 
18 int main(){
19     funktion();
20     funktion();
21     funktion();
22 }
Crystal Clear app kscreensaver.svg
Ausgabe:
 1 global: 1
 2 lokal:  1
 3 static: 1
 4 
 5 global: 2
 6 lokal:  1
 7 static: 2
 8 
 9 global: 3
10 lokal:  1
11 static: 3

mutable [Bearbeiten]

Bereits in einem vorherigen Kapitel haben Sie konstante Funktionen in Klassen kennen gelernt. Diese können keine Membervariablen der Klasse ändern. Keine? Doch: Membervariablen, die mutable sind, lassen sich selbst durch konstante Funktionen verändern:

Nuvola-inspired-terminal.svg
 1 class Foo {
 2     mutable int i1;
 3     int i2;
 4     void Func() {
 5         i1++; i2++; // Erlaubt
 6     }
 7     void constFunc() const {
 8         i1++; // Erlaubt! i1 ist mutable.
 9         i2++; // Fehler: i2 ist nicht mutable, aber Member von Foo. Da constFunc const ist, darf es i2 nicht verändern.
10     }
11 };

Sinn und Zweck[Bearbeiten]

Sie werden sich fragen, was mutable soll, da es immerhin den Sinn einer konstanten Memberfunktion, d.h. die Garantie, nichts an der Klasse zu verändern, untergräbt. Das stimmt auch. Es gibt aber seltene Fälle, bei denen der Einsatz von mutable sinnvoll ist. Stellen Sie sich beispielsweise vor, Sie möchten für Debuggingzwecke die Anzahl der Aufrufe einer konstanten Memberfunktion zählen. Dann können Sie mutable einsetzen und die Funktion gilt weiterhin als konstant. Generell kann man sagen, dass mutable nicht eingesetzt werden sollte, wenn die Veränderung dieser Variable das Erscheinungsbild/Verhalten des Objekts von Außen verändert.

volatile [Bearbeiten]

Mit dem Schlüsselwort volatile vor einem Variablentyp (bei der Deklaration einer Variablen) wird der Compiler angewiesen, die Variable bei jedem Zugriff erneut aus dem Speicher zu laden bzw. bei schreibendem Zugriff die Variable sofort in den Speicher zu schreiben. Ohne volatile würde die Optimierung des Compilers möglicherweise dazu führen, dass die Variable in einem Prozessorregister zwischengespeichert wird.

volatile wird dann verwendet, wenn zu erwarten ist, dass auf den Wert der Variablen von außerhalb des Programmablaufs zugegriffen wird. Solch ein Zugriff könnte beispielsweise durch die Hardware stattfinden. Ein typischer Anwendungsfall ist ein Interrupt (also eine Unterbrechung des aktuellen Programms zur Behandlung auftretender Ereignisse), bei dem eine Hardware-Komponente einen neuen Wert in die Variable schreibt oder den aktuellen Wert ausliest. Eine Zwischenspeicherung der Variablen anderswo würde dazu führen, dass das Programm (oder die Hardware) nicht mit dem geänderten Wert arbeitet und diesen möglicherweise sogar überschreibt.

Aber: volatile garantiert weder eine atomare Ausführung/Zugriff, noch Thread-sicheren Zugriff, und ist daher nicht für multi-threaded -Anwendungen zur Synchronisierung einsetzbar.

Zusammenfassung [Bearbeiten]

union[Bearbeiten]

Ein union kann mehrere Datentypen auf einer Speicheradresse vereinen. Es kann also immer nur eine Member abgespeichert werden. Man greift auf die Elemente des union über den Punkt-Operator zu. Diese Datenstruktur verbraucht genauso viel Speicher wie das größte Element.

Nuvola-inspired-terminal.svg
 1 #include <iostream>
 2 
 3 union Abc {
 4      int a;           // = 4 Bytes  (32-bit, 64-bit)
 5      short b;         // = 2 Bytes  (32-bit, 64-bit)
 6      long double c;   // = 12 Bytes (32-bit), 16 Bytes (64-bit)
 7 };
 8 
 9 int main(void) {
10      Abc myUnion;        // Deklaration des Unions myUnion vom Typ Abc
11 
12      // die Größe des Unions ist immer konstant
13      std::cout << " Größe am Anfang: " << sizeof(myUnion) << "\n" << std::endl;
14 
15      myUnion.a = 32000;   // Zugriff auf die Elemente über den Punkt-Operator
16      std::cout << " a initialisiert. a = " << myUnion.a << std::endl;
17      std::cout << " Größe: " << sizeof(myUnion) << "\n" << std::endl;
18 
19      myUnion.b = -15000;  // jetzt hat a keinen gültigen Wert mehr
20      std::cout << " b initialisiert. a = " << myUnion.a << "; b = " << myUnion.b << std::endl;
21      std::cout << " Größe: " << sizeof(myUnion) << "\n" << std::endl;
22 
23      myUnion.c = 3.512;   // a und b haben keinen sinnvollen Wert mehr
24      std::cout << " Größe am Ende: " << sizeof(myUnion) << std::endl;
25 
26      return 0;
27 }
Crystal Clear app kscreensaver.svg
Ausgabe:
1 Größe am Anfang: 16
2 
3  a initialisiert. a = 32000
4  Größe: 16
5 
6  b initialisiert. a = 50536; b = -15000
7  Größe: 16
8 
9  Größe am Ende: 16

static[Bearbeiten]

In C++ gibt es, im Gegensatz zu C#, zwar keine statischen Klassen, jedoch können einzelne Membermethoden oder -variablen static sein. Das heißt, sie existieren nur ein Mal für alle Objekte der Klasse und sind auch nutzbar, wenn es noch keine Instanz dieser Klasse gibt. Statische Klassenvariablen sind nur in dem Modul gültig, wo sie deklariert wurden. Eine statische Methode kann nicht auf nicht-statische Membervariablen zugreifen; die static-Methode gehört ja nicht zu einem bestimmten Objekt.

Eine Funktion kann static "lokale" Variablen besitzen. Auf sie kann nur innerhalb der Funktion zugegriffen werden. Sie verhalten sich jedoch wie globale Variablen: Sie behalten ihren Wert zwischen Funktionsaufrufen. Ihre Initialisierung wird nur beim ersten Aufruf der Funktion ausgeführt.

mutable[Bearbeiten]

Konstante Funktionen können eigentlich keine Membervariablen verändern. Variablen, die mit mutable gekennzeichnet sind, können jedoch auch von const-Funktionen verändert werden. Beispiel:

Nuvola-inspired-terminal.svg
 1 class Foo {
 2     mutable int i1;
 3     int i2;
 4     void Func() {
 5         i1++; i2++; // Erlaubt
 6     }
 7     void constFunc() const {
 8         i1++; // Erlaubt! i1 ist mutable.
 9         i2++; // Fehler: i2 ist nicht mutable, aber Member von Foo. Da constFunc const ist, darf es i2 nicht verändern.
10     }
11 };

'mutable' ist nur in wenigen Fällen sinnvoll; meistens sollte stattdessen die Methode zu 'nicht-const' geändert werden.

volatile[Bearbeiten]

Der Wert einer Variablen, die mit volatile gekennzeichnet ist, wird vor jedem Zugriff neu eingelesen. Die Variable darf der Compiler also nicht im schnellen Prozessorcache zwischenspeichern. Hauptsächlich wird volatile dann eingesetzt, wenn Hardware-nahe Routinen sicherstellen müssen, dass bei der Kommunikation mit der Hardware (über Ram-Bereiche) auch der aktuelle Wert verwendet wird.

volatile ist alleine ungeeignet, Variablen-Zugriffe (z.B. für den Austausch von Inhalten) zwischen verschiedenen parallel-laufenden Threads innerhalb eines Programms zu synchronisieren,[1] kann jedoch ggf. einen Teil zu den dafür notwendigen Bedingungen beitragen.

Weblinks[Bearbeiten]

  1. stackoverflow.com: volatile and multi-threading, citating Bjarne Stroustrup