Klassenattribute
Willemers Informatik-Ecke
Die Attribute stat und const sind im Zusammenhang mit Variablen bereits erläutert worden. Im Zusammenhang mit Klassen haben sie eine etwas andere Bedeutung.

Statische Variablen und Funktionen in Klassen

Statische Elementvariablen in Klassen haben wenig mit den statischen Variablen in Funktionen gemeinsam. Während ansonsten Elementvariablen für jedes Objekt einmal angelegt werden, existieren statische Elementvariablen pro Klasse exakt einmal, egal wie viele Objekte dieser Klasse erzeugt werden. Sie werden angelegt, bevor das erste Objekt der Klasse erzeugt wird. Eine Anwendung für eine solche Variable könnte das Zählen der aktiven Instanzen sein. Dazu wird die statische Variable in jedem Konstruktor inkrementiert und in jedem Destruktor dekrementiert. In der statischen Variable kann dann abgelesen werden, wie viele Objekte dieser Klasse derzeit existieren.

Deklaration

Eine statische Elementvariable wird genauso wie jede andere Elementvariable der Klasse deklariert. Es wird ihr allerdings das Schlüsselwort static vorangestellt. Wohlgemerkt wird sie in der Klasse nur deklariert. Sie muss noch separat von der Klasse definiert werden, sonst beklagt sich der Linker.

class StatKlasse
{
    ...
    static int Zaehler;
};

Initialisierung

Eine statische Variable einer Klasse kann nicht im Konstruktor initialisiert werden, da sie sonst bei jeder Erzeugung eines Objekts wieder zurückgesetzt würde. Stattdessen wird sie wie eine globale Variable definiert, der der Klassenname vorangestellt wurde. Bei dieser Gelegenheit wird sie auch gleich initialisiert:

int StatKlasse::Zaehler = 0;

Statische Funktionen

Auf den ersten Blick scheint es unsinnig zu sein, statische Funktionen zu definieren, denn jede Elementfunktion existiert ja nur einmal im Speicher. Allerdings haben statische Funktionen ihre besondere Aufgabe. Da sie nicht zu einem Objekt gehören, können sie auch nicht auf die Datenelemente eines Objekts zugreifen. Stattdessen haben sie meist die Aufgabe, als Schnittstelle für die statischen Variablen zu dienen. So kann sichergestellt werden, dass beispielsweise der interne Zähler nicht von außen manipuliert wird: Die statische Variable wird privat deklariert und der Stand immer nur durch eine statische Funktion ermittelt. Statische Funktionen haben mit statischen Variablen gemein, dass sie zur Klasse gehören, aber unabhängig von der Zahl oder der Existenz von Objekten verwendet werden können.

Aufruf

Statische Funktionen werden aufgerufen, indem der Klassenname, zwei Doppelpunkte und der Funktionsname genannt werden. Für den Aufruf muss also kein Objekt der Klasse existieren. Das folgende Beispiel enthält eine statische Variable namens Zaehler, die mitzählt, wie viele Instanzen der Klasse derzeit existieren. Die Variable ist privat, aber durch eine statische Funktion wieviele() kann der aktuelle Zählerstand ermittelt werden. Zum Testen werden Arrays von StatKlasse angelegt und der Zählerstand immer wieder ausgegeben.

[Statische Variablen und Funktionen in Klassen (statclass.cpp)]

#include <iostream>
using namespace std;

class StatKlasse
{
public:
    StatKlasse() {Zaehler++;}
    ~StatKlasse() {Zaehler--;}
    static int wieviele() {return Zaehler;}
private:
    static int Zaehler;
};

int StatKlasse::Zaehler=0;

int main()
{
    cout << StatKlasse::wieviele() << endl;
    StatKlasse feld[4];
    cout << StatKlasse::wieviele() << endl;
    {
        StatKlasse feld[5];
        cout << StatKlasse::wieviele() << endl;
    }
    cout << StatKlasse::wieviele() << endl;
}
Nach dem Start des Programms ist der Zähler durch die Initialisierung 0. Das Anlegen eines Arrays mit vier Elementen der StatKlasse führt dazu, dass der Konstruktor viermal aufgerufen wird. Das Array feld wird in dem inneren Block nochmals definiert und überdeckt damit zwar das äußere Array feld. Trotzdem das äußere Array derzeit nicht erreichbar ist, ist es dennoch vorhanden. Also gibt es innerhalb des Blocks neun Elemente. Durch das Verlassen des inneren Blocks, wird das innere Array freigegeben. Der Destruktor wird für jedes Element einmal aufgerufen. Entsprechend liefert das Programm die Zahlen 0, 4, 9 und 4.

Konstanten

Insbesondere Funktionsparameter werden gern als konstant deklariert, um dem Aufrufer zu signalisieren, dass sie innerhalb der Funktion nicht verändert werden können. Ist dieser Parameter ein Objekt einer Klasse, darf es innerhalb des Gültigkeitsbereichs dieser Deklaration keinen verändernden Zugriff auf die Datenelemente geben, weder direkt noch mittelbar. Das folgende Beispiel stammt aus dem Abschnitt über Ausgabeoperatoren. Dabei wird der Bruch als Referenz übergeben. Um zu signalisieren, dass der Bruch sich durch die Ausgabe auf dem Bildschirm nicht ändert, wird der zweite Parameter als konstant deklariert.

[Ausgabe eines Bruchs (bruch.cpp)]

ostream& operator<<(ostream& Stream, const tBruch& B)
{
    return Stream << B.GetZaehler() << "/" << B.GetNenner();
}

Verdächtige Aufrufe

Innerhalb der Funktion wird aber für den konstanten Parameter eine Elementfunktion aufgerufen. Dem Compiler ist es nicht so einfach möglich festzustellen, ob die Funktion GetZaehler() die Werte von B ändert. Dazu müsste er prüfen, ob diese Elementfunktion direkt oder indirekt über andere Elementfunktionen Datenelemente verändert oder nicht. Hier nimmt der Compiler den schlechtesten Fall an und geht davon aus, dass eine Elementfunktion Änderungen vornehmen kann. Er wird also den Aufruf von GetZaehler() bemängeln.

Konstante Funktionen

Damit der Compiler sicher sein kann, dass nicht eine der aufgerufenen Elementfunktionen ein Datenelement ändert, können Sie die dort aufgerufenen Funktionen als konstant deklarieren. Das erreichen Sie, indem Sie in der Klassendefinition das Schlüsselwort const hinter die Parameterklammer der Funktion setzen.

[Konstante Funktionen (bruch.cpp)]

class tBruch
{
    ...
    long GetNenner() const  {return nenner;}
    long GetZaehler() const  {return zaehler;}
    ...
};
Erst, wenn nur konstante Funktionen aufgerufen und Elementvariablen nicht verändert werden, kann der Compiler sicher sein, dass das Objekt konstant bleibt. Wird nämlich innerhalb einer als konstant deklarierten Elementfunktion auf eine Variable der Klasse verändernd zugegriffen, wird der Compiler diese Funktion nicht übersetzen.

mutable

Für die Frage, ob ein Objekt einer Klasse konstant bleibt, werden alle Elementvariablen herangezogen. In manchen Fällen enthält die Klasse aber Elementvariablen, die für die Frage, ob ein Objekt in seinem Wert verändert wird, irrelevant ist. So beeinflusst beispielsweise das Datenelement wochentag in der Klasse tDatum nicht das Datum als solches. Die Variable wochentag kann für ein und dasselbe Datum entweder den korrekten Wochentag oder eine -1 enthalten, wenn der Wochentag noch nicht berechnet wurde. Das Datum des Objekts kann dennoch gleich sein. Um solche Elementvariablen zu kennzeichnen, können Sie ihnen das Schlüsselwort mutable voranstellen. Ein so gekennzeichnetes Element kann auch von konstanten Funktionen verändert werden.

Konstanten initialisieren

Konstanten

Konstanten können auch innerhalb der Klassen deklariert werden, auch wenn es scheint, als ob der Compiler eine Initialisierung innerhalb der Klasse nicht zulässt. Tatsächlich ist die übliche Initialisierung mit einem Gleichheitszeichen und dem darauf folgenden Wert in der Klassendefinition nicht möglich. Eine Zuweisung im Konstruktor funktioniert auch nicht, weil dort ja eine Zuweisung auf eine Konstante erfolgen würde. Es ist aber möglich, die Konstante über einen Initialisierer am Konstruktor vorzubelegen.

class tKonstant
{
public:
    const int MaxPuffer;
    tKonstant() : MaxPuffer(500) {}
    tKonstant(long size);
};

tKonstant::tKonstant(long size): MaxPuffer(size)
{
Der Initialisierer wird also immer bei der Definition der Konstruktorfunktion angebracht, nicht bei der Deklaration.