Statische Member

Einführung

Bei der Deklaration von Klassen lassen sich auch statische Membervariablen und  -funktionen deklarieren, d.h. Variablen und Funktionen, auf die nicht mit Hilfe einer Instanz zugegriffen wird, sondern einfach unter Angabe des Klassennamens. So könnte beispielsweise eine Klasse eine statische Membervariable besitzen, die die Anzahl der Instanzen zählt, welche von eben dieser Klasse gebildet wurden. Eine entsprechende Funktion könnte dann zurückliefern, wie viele Instanzen bisher existieren.

Deklaration

Um eine Membervariable oder -funktion als statisch zu deklarieren muss man die Deklaration lediglich um das Schlüsselwort 'static' erweitern:

class Class
{
private:
	static int s_i;		//statische Membervariable
public:
	static int func(void);	//statische Memberfunktion
};

Nun muss die Funktion noch implementiert und die Variable noch initialisiert werden. Bei der Funktion besteht erneut die Möglichkeit sie innerhalb oder außerhalb der Klasse zu implementieren:

class Class
{
private:
	static int s_i;		//statische Membervariable
public:
	static int func(void)
	{
		return s_i;
	}
};

oder

class Class
{
private:
	static int s_i;		//statische Membervariable
public:
	static int func(void);
};

//Implementierung der Funktion
int Class::func(void)
{
	return s_i;
}

Um nun noch die statische Variable zu initialisieren, muss man außerhalb der Klasse noch folgenden Aufruf tätigen:

int Class::s_i = 0;

Wird diese "Initialisierung" nicht durchgeführt, dann meldet der Linker einen Fehler, der besagt, dass ein "externes Symbol nicht aufgelöst wurde". Aus diesem Grunde ist eine derartige Befehlszeile notwendig. Tatsächlich handlet es sich dabei nicht einzig und allein um eine Initialisierung. Da an dieser Stelle erneut der Datentyp angegeben werden muss, gehört auch diese Befehlszeile zur Deklaration. Man kann dies auch daran erkenn, dass die Variable 's_i' privat ist und somit nach außen hin eigentlich gar nicht zugänglich sein dürfte. Statische Variablen werden standardmäßig mit '0' initialisiert, d.h. im letzten Fall wäre eine eigene Initialisierung nicht nötig und die letzte Zeile ließe sich auch wie folgt schreiben:

int Class::s_i;		//s_i mit 0 initialisiert

Aufruf von statischen Membern innerhalb der Klasse

Im folgenden soll nun eine Klasse deklariert werden, die dem Beispiel der Einführung gerecht wird. Dazu sollen zunächst nur die statischen Member deklariert bzw. implementiert werden:

class CInstance
{
private:
	static unsigned int s_cInstances;
public:
	static unsigned int GetInstances(void);
};

//Deklaration von CInstance::s_cInstances
unsigned int CInstance::s_cInstances = 0;

//Implementierung von CInstance::GetInstances
unsigned int CInstance::GetInstances(void)
{
	return s_cInstances;
}

Nun kann bereits die Anzahl der gebildeten Instanzen von der Klasse 'CInstance', die in der statischen Variablen 's_cInstances' gespeichert ist, über die statische Funktion 'GetInstances'  zurückgegeben werden. Da 's_cInstances' statisch ist, kann die Funktion 'GetInstances', (die ja ebenfalls statisch ist und zur selben Klasse gehört,) auf diese Variable auch ohne den Bereichsauflösungsopertor '::' zugreifen.

Nun fehlt es aber noch an der Implementierung der Funktionalität, die nötig ist um den Instanzzähler zu erhöhen, wenn eine Instanz von 'CInstance' gebildet wird und zu erniedrigen, wenn eine Instanz "abgebaut" wird. Die Erhöhung des Instanzzählers sollte im Standardkonstruktor der Klasse, die Erniedrigung im Destruktor der Klasse stattfinden:

class CInstance
{
private:
	static unsigned int s_cInstances;
public:
	//Standardkonstruktor
	CInstance(void);

	//Destruktor
	~CInstance(void);
	
	static unsigned int GetInstances(void);
};

//Implementierung des Standardkonstruktors
CInstance::CInstance(void)
{
	//Instanzzähler erhöhen
	s_cInstances++;
}

//Implementierung des Destruktors
CInstance::~CInstance(void)
{
	//Instanzzähler erniedrigen
	if (s_cInstances-- == 0)
	{cout << "schwerer Fehler: negative Anzahl an Instanzen" << endl;}
}

//Implementierung und Deklarationen aller statischen Member...

Nun sollte alles wie gewünscht laufen: Im Konstruktor wird also der Instanzzähler inkrementiert und im Destruktor dekrementiert. Wenn beim Destruktoraufruf der Instanzzähler den Wert '0' trägt (was normal unmöglich ist), dann würde eine Dekrementierung für einen ungültigen Wert des Instanzzählers führen, da dieser vom Typ 'unsigned int' ist und somit keine negativen Zahlen aufnehmen kann. Aus diesem Grunde wird dann ein Fehler gemeldet. Normal dürfte dieser Fehler aber in keinem Fall auftreten. (Tatsächlich kann dieser Fehler wirklich auftrete, doch dazu später.) 

Aufruf von statischen Membern außerhalb der Klasse

Nun sollte man sich damit beschäftigen, wie die statischen Member aufgerufen werden. Dazu sollten zunächst einige Instanzen gebildet werden:

CInstance c1, c2, c3;

Nach diesen drei Deklarationen sollte in dem Instanzzähler der Wert '3' gespeichert sein. Um dies zu überprüfen, ließe sich die Funktion 'GetInstances' wie eine gewöhnliche Memberfunktion von jeder der drei Instanzen aufrufen:

cout << "c1: " << c1.GetInstances() << endl;	//Ausgabe: "c1: 3"
cout << "c2: " << c2.GetInstances() << endl;	//Ausgabe: "c2: 3"
cout << "c3: " << c3.GetInstances() << endl;	//Ausgabe: "c3: 3"

Der Vorteil an statischen Membern ist jedoch, dass auf sie auch zugegriffen werden kann, wenn überhaupt keine Instanz der Klasse verfügbar ist, bzw. bisher noch gar keine erstellt wurde. Statische Member stellen im Prinzip die Funktionalität der Klasse und nicht die der einzelnen Instanzen dieser Klasse dar, wobei auf die statischen Member jedoch auch von den Instanzen aus zugegriffen werden kann. So ist in unserem Beispiel die statische Membervariable 's_cInstances' eine Eigenschaft der Klasse und nicht die einer Instanz (was ja überhaupt keinen Sinn ergeben würde). So sollte es immer möglich sein zu erfahren, wie viele Instanzen einer Klasse bereits existieren, ohne erst eine Instanz der Klasse bilden zu müssen, die dann den Zugriff auf den Instanzzähler liefert. Soll also zu einem beliebigen Zeitpunkt in Erfahrung gebracht werden, wie viele Instanzen von 'CInstance' bereits existieren, ohne dass derzeit eine gültige Instanz zur Verfügung steht, dann lässt sich die statische Funktion 'GetInstances' auch mit Hilfe des Operators von '::'  aufrufen:

unsigned int cInstances = CInstance::GetInstances();

So ließe sich beispielsweise bei Programmende feststellen, ob alle Instanzen bereits abgebaut wurden. Dies macht vor allem dann Sinn, wenn die Instanzen von 'CInstance' allesamt auf dem Heap erstellt wurden:

void main (void)
{
	CInstance *pc1, *pc2, *pcarr;
	
	//Instanzen ersttellen
	pc1 = new CInstance;
	pc2 = new CInstance;
	
	pcarr = new CInstance[5];
	
	//Ausgabe: "Anzahl der Instanzen: 7"
	cout << "Anzahl der Instanzen: " << CInstance::GetInstances() << endl;	
	
	/* --- Instanzen verwenden ---
	...
	*/
	
	//Instanzen abbauen
	delete[] pcarr; pcarr = 0;
	
	//Ausgabe: "Anzahl der Instanzen: 2"
	cout << "Anzahl der Instanzen: " << CInstance::GetInstances() << endl;
	
	delete pc1; pc1 = 0;
	delete pc2; pc2 = 0;
	
	//Ausgabe: "Anzahl der Instanzen: 0"
	cout << "Anzahl der Instanzen: " << CInstance::GetInstances() << endl;
	
	//Alle Instanzen abgebaut?
	if (CInstance::GetInstances() == 0)
	{
		cout << "Keine Instanzen mehr. Programm wird beendet..." << endl;
		return;
	}
	else
	{
		cout << "Offenbar sind nicht lokalisierbare Speicherlecks aufgetreten. " 
			<< "Programm wird trotzdem beendet..." << endl;
		return;
	}
	
}

Normal sollte bei diesem Beispiel alles korrekt ablaufen, d.h. es werden alle gebildeten Instanzen wieder freigegeben.

Nun noch zu der Möglichkeit, dass der Fehler auftritt, dass beim Aufruf des Destruktors, der Instanzzähler auf '0' ist und somit eine Dekrementierung der Eindruck entsteht wird, dass mehr Instanzen abgebaut als gebildet werden. Dies ist faktisch natürlich nicht möglich, aber die Klassendeklaration hat einen Schwachpunkt, der wenig offensichtlich erscheint: Es werden für jede Klasse zwei Konstruktoren automatisch generiert, sofern dies nicht vom Programmierer selbst übernommen wird, nämlich der Standard- und der Kopierkonstruktor. Der Standardkonstruktor wurde hier implementiert. Er inkrementiert den Instanzzähler. Da der Kopierkonstruktor hier nicht eigenständig implementiert wurde, wird beim Aufruf eben dieses Konstruktors der Instanzzähler eben nicht inkrementiert, was wiederum zur Folge hat, dass eine auf diese Weise gebildete  Instanz bei der Instanzzählung nicht berücksichtigt wird. Folgender Aufruf wird also spätestesn beim Aufruf des Destruktors Probleme aufwerfen:

CInstance c1, c2 = c1;

Aus diesem Grunde sollte die Klasse um einen Kopierkonstruktor erweitert werden, der lediglich den Instanzzähler inkrementiert:

class CInstance
{
private:
	static unsigned int s_cInstances;
public:	
	//Standardkonstruktor
	CInstance(void);

	//Kopierkonstruktor
	CInstance(const CInstance&);

	//Destruktor
	~CInstance(void);
	
	static unsigned int GetInstances(void);
};

//Implementierung des Kopierkonstruktors
CInstance::CInstance(const CInstance&)
{
	//Instanzzähler erhöhen
	s_cInstances++;
}

//Implementierung der anderen Funktionen...

Nun sollte der Fehler tatsächlich nicht mehr auftreten. Ansonsten müsste ein weiterer Konstruktor existieren, der automatisch erstellt wird. Davon ist mir allerdings nichts weiter bekannt...

Für die nächsten Lektionen

Soviel zu statischen Membern. In einigen Fällen sind sie sehr praktisch. In dem hier eingeführten Beispiel, wurden sie beispielsweise benutzt um zu kontrollieren, ob alle Instanzen ordnungsgemäß abgebaut wurden, bevor ein Programm beendet wird. Dies wäre zumindest beim Debuggen ein hilfreicher Ansatz nach Speicherlecks zu suchen. In den letzten Lektionen der OOP werden statische Member noch einmal aufgegriffen werden, da sie auch ganz bestimmte Techniken ermöglichen, die sonst nur schwer (oder eher gar nicht) umsetzbar wären. In der nächsten Lektion wird es aber vorerst um das überladen von Operatoren gehen.

Zurück Nach oben Weiter