static-Attribute in C++ Klassen

An dieser Stelle soll die Aussage

“Statische Klassenattribute müssen außerhalb der Klassendefinition initialisiert werden.”

ein wenig näher untersucht werden. Betrachtet sei dazu die folgende Klassendefinition:

class KlasseMitStaticElement {
public:
static int anzahl;
KlasseMitStaticElement() {
cout << "Konstruktor aufgerufen" << endl;
KlasseMitStaticElement::anzahl++;
}
};

Geht man nun nach der oben genannten Regel vor, so würde die naive Initialisierung (eigentlich eine Zuweisung, siehe Zusatz unten) so aussehen: KlasseMitStaticElement::anzahl = 0;. Leider resultiert diese Zeile in einen Kompilerfehler, es wird über das undefined symbol “KlasseMitStaticElement::anzahl” gemeckert. Faszinierenderweise gelingt die Initialisierung aber mit  int KlasseMitStaticElement::anzahl = 0; warum?

Dazu müssen Deklaration, Definition und Initialisierung Begrifflich sauber voneinander getrennt werden. Ich verweise dazu auf ein C++-Forum, in dem von Benjamin Kaufmann folgendes geschrieben ist:

  • Deklaration: Ein Programmausdruck der einen Namen in einen Scope ein- bzw. wiedereinführt.
  • Definition: Eine Deklaration die die Details einer Entität bekannt macht oder, im Fall von Variablen, die dazu führt, dass Speicher für die Entität reserviert wird. Eine Deklaration einer Klasse (struct, class, enum, union) Funktion oder Methode wird zu einer Definition, wenn auf die Deklaration ein in geschweiften Klammern eingeschlossener Block folgt.
    Variablendeklarationen sind immer auch Definitionen es sei denn, der Deklaration ist ein extern vorangestellt.
  • Initialisierung: Eine Definition mit expliziter Anfangswertzuweisung.

Nach obigen Versuchen stellen wir also fest, dass auch die Voranstellung von static im Klassenkontext dazu führt, dass eine ursprüngliche Definition nur eine Deklaration darstellt. In dem Beispielprogramm matstatic.zip habe ich als statisches Attribut keinen einfachen Datentyp genutzt, sondern eine Klasse mit Standardkonstruktor verwendet. So kann man sehen, dass die Definition der Variable nach der Klassendefinition dazu führt, dass der Standardkonstruktor aufgerufen wird.

Bezogen auf das obige Beispiel stellt man also fest, dass die explizite Initialisierung (und damit implizite Definition) der Variable anzahl nicht notwendig ist. Eine Definition wie  int KlassenMitStaticElement::anzahl; wäre ausreichend, da die integer-Variable damit automatisch vom Compiler mit 0 initialisiert wird.

Ob die automatische Initialisierung einen allgemeinen C++-Standard oder nur eine Laune meines Compilers darstellt, ist mir zwar nicht bekannt — eventuell kann ohne Anfangszuweisung der Schwachsinn, der vorher an der Speicherstelle der Variable stand, von uns weiter genutzt werden — aber die anfangs erwähnte Aussage muss korrigiert werden.

“Statische Klassenattribute müssen außerhalb der Klassendefinition DEFINIERT werden.”

Zusatz:

Über Initialisierungen zu sprechen, stellt sich als nicht ganz einfach heraus, da das Verhalten des Compilers nicht nur von der Anweisung selbst, sondern auch von ihrer Position im Quelltext abhängt. Eine implizite Initialisierung mit 0 wird für die Anweisung int a; als globale Definition vorgenommen, während die gleiche Anweisung im Funktionskontext einen undefinierten Wert für a behält.

Man könnte nun auf der einen Seite eine implizite und eine explizite Initialisierung unterscheiden (nach der Definition aus dem Forum wäre nur die explizite Initialisierung überhaupt eine Initialisierung), oder man geht dem Problem aus dem Weg, indem man jede Variablendefinition auch gleichzeitig mit einer expliziten Initialisierung im Quelltext versieht. Da nur Variablen mit eindeutig zugewiesenen Werten semantischen Sinn ergeben, kann man die ganz oben genannte Aussage als Merkregel sehr gut für die Praxis verwenden. Doch sollten wir ein wenig verallgemeinern:

“MERKREGEL: Alle Variablen müssen initialisiert werden. Bei statischen Klassenattributen geschieht dies außerhalb der Klassendefinition.”

Lustige Operatorensuche in C++ (Koenig Lookup?)

Bei der Korrektur von Übungsblättern zu einer C++ Vorlesung bin ich auf das folgende, auf den ersten Blick sehr simple Phänomen gestoßen: Da definiert mir doch jemand eine friend Methode innerhalb der Klasse. Wohlgemerkt nicht nur eine Deklaration, sondern eine ganz Implementation. Ausschnittsweise sieht das nun wie folgt aus:

class Koordinaten
{
...
friend ostream& operator<<( ostream& os, const Koordinaten& out );
{
os << "X-Koordinate: " << out.x << endl ;
os << "Y-Koordinate: " << out.y << endl ;
os << "Z-Koordinate: " << out.z << endl ;
return os ;
}
};

Naja, hübsche Idee, das wird definitiv nicht kompilieren — dachte ich mir. Schief gewickelt. Klappt sogar sehr gut. Aber warum?

Bisher dachte ich, nach friend darf gar keine Definition folgen. Gut, deklariert man eine Methode an mehreren Stelle (z.B. Aufteilung in hpp und cpp Dateien), dann könnte es doch eigentlich egal sein, an welcher Stelle die Implementierung steh. Aber ganz ohne Deklaration außerhalb der Klasse hätte ich nicht vermutet, dass C++ diesen Operator überhaupt findet.

Naja, im Rahmen einiger Recherchen zu den verschiedensten C++-Phänomenen bin ich auf die Homepage von Benjamin Kaufmann gestoßen. Er schildert in seiner C++ FAQ in kurzer und knapper Weise das Prinzip des Koenig Lookup und wenn ich mich nicht völlig verdreht habe, dann könnte gerade dieses die Lösung zur Operatorensuche (bzw. zum Phänomen des Findens) sein. Betrachtet man nämlich den zweiten Punkt

ist T eine Instanz einer Klasse/Struktur X, kommen alle Namensräume hinzu, in denen X definiert wird.

dann interpretiere ich mal die Klasse als Namensraum, welcher automatisch durchsucht wird, da als Parameter des Operators ein Objekt dieser Klasse vorkommt.

Neben der Tatsache, dass ich natürlich eine solche Definition nicht gutheiße, da sie ziemlich verwirrend ist, wollte ich meine Erkenntnisse einfach einmal mitteilen, denn bisher dachte ich, dass soetwas abgefahrenes überhaupt nicht möglich sei.

Zusatz:

Habe von Benjamin Kaufmann eine kleine Erklärung bekommen, die meine Gedanken zu der Problematik bestätigt. Es sei hier nur noch der Hinweis angebracht, dass das Koenig Lookup auch argument dependent lookup genannt wird und dazu sogar eine kleine Seite in der Wikipedia existiert (zumindest auf Englisch) http://en.wikipedia.org/wiki/Argument_dependent_lookup