Techniken und Ergänzungen

Einführung

Klassen bringen allerhand Möglichkeiten mit Quellcode strukturierter und übersichtlicher zu gestalten. Viele Eigenschaften von Klassen ermöglichen es dem Programmierer genau festzulegen, wie eine Klasse benutzt werden kann. Aus dem in den vergangenen Lektionen Gelernten lassen sich viele Programmiertechniken ableiten, die zunächst komplex erscheinen, im Prinzip sich aber kaum von anderen Techniken unterscheiden, die bereits zum Standard gehören (wie Funktionsüberladung, statische Member, Zugriffsbezeichner, Referenzen und Zeiger, Verebung, etc.) Hier sollen nun einige dieser vermeintlich neuen Techniken aufgezeigt werden. Gerade bei dem letzten Abschnitt handelt es sich aber auch nur um Ergänzungen.

Private Konstruktoren und Destruktoren und private Operatoren

Wird eine Klasse deklariert, die keinen eigenen Konstruktor besitzt, dann werden zwei Konstruktoren automatisch hinzugefügt: Der Standardkonstruktor und der Kopierkonstruktor. Besitzt die Klasse hingegen einen eigenen Konstruktor, dann wird auf jeden Fall eine Kopierkonstruktor hinzugefügt. Letzterer lässt sich, wie bereits zuvor gezeigt wurde, überschreiben, um u.U. genau zu bestimmen, wie die Member kopiert werden. Ist es jedoch hingegen überhaupt nicht erwünscht, dass von einer Klasse Kopien erstellt werden, dann sollte der Kopierkonstruktor einfach als 'private' deklariert werden:

class CCopysafe
{
private:
	CCopysafe(const CCopysafe&) {}
};

Allerdings sollte zumindest ein öffentlicher Konstruktor verfügbar sein, da sich sonst überhaupt keine Instanz dieser Klasse erstellen lässt (Der Standardkonstruktor wird nur automatisch hinzugefügt, wenn kein eigener Konstruktor deklariert wurde). Die Klasse dürfte dann in etwa so aussehen:

class CCopysafe
{
public:
	CCopysafe(void) {}
private:
	CCopysafe(const CCopysafe&) {}
};

Nun sind alle Instanzen der Klasse 'CCopysafe' vor Kopien (zumindest vor Kopien durch den Kopierkonstruktor)  geschützt:

CCopysafe c1;
//CCopysafe c2 = c1;	//C2248: kein Zugriff auf privates Element

Ganz vor Kopien geschützt sind die Instanzen der Klasse 'CCopysafe' allerdings noch nicht, denn sie können noch nachträglich über den Zuweisungsoperator '=' kopiert werden:

CCopysafe c1, c2;
c2 = c1;

Aus diesem Grunde sollte immer, wenn der Kopierkonstruktor überschrieben wird (in welcher Form auch immer), auch der Zuweisungsoperator überschrieben werden. Umgekehrt sollte auch immer der Kopierkonstruktor überschrieben werden, wenn der Zuweisungsoperator überschrieben wird. In diesem Falle sollte also auch der Zuweisungsoperator als 'private' deklariert werden:

class CCopysafe
{
public:
	CCopysafe(void) {}
private:
	CCopysafe(const CCopysafe&) {}
	CCopysafe& operator= (const CCopysafe&) {return *this;}
};

Nun ist es tatsächlich unmöglich Kopien der Klasse 'CCopysafe' zu erstellen. Wenn der Aufruf von Kopierkonstruktor und Zuweisungsoperator durch einen Zugriffsbezeichner verhindert wird und beide auch nicht innerhalb der Klasse aufgerufen werden, dann ist auch keine Implementierung notwendig. Beim Zuweisungsoperator ist lediglich darauf zu achten, dass der Rückgabetyp berücksichtigt und aus diesem Grunde auch eine Instanz zurückgegeben wird. In diesem Beispiel besitzt der Kopierkonstruktor tatsächlich keine Anweisungen und der Zuweisungsoperator gibt nur die eigene Instanz zurück, ohne irgend etwas zu kopieren. 

Soll ohne weiteres keine Instanz einer Klasse erstellt werden können, dann kann man dies erreichen, indem man alle Konstruktoren als 'private' deklariert. Werden dann keine eigenen Konstruktoren benötigt, reicht es aus lediglich den Kopierkonstruktor als 'private' zu deklarieren. Weitere Konstruktoren werden dann auch nicht automatisch generiert. Um Instanzen dennoch erstellen zu können, sollten irgendwelche Funktionen implementiert werden, die Instanzen erstellen und diese zurückgeben. Dazu ist es natürlich notwendig, dass diese Funktionen Zugriff auf alle Member der Klassen haben. Zu diesem Zweck sollten diese Funktionen entweder statische Memberfunktionen der Klasse, Memberfunktionen einer als 'friend' bezeichneten Klasse oder aber globale Funktionen sein, die ebenfalls als 'friend' bezeichnet wurden. Dazu ein Beispiel der ersten Variante:

class CSpecial
{
private:
	static CSpecial* s_pInstance;
	static int s_cRefs;
	CSpecial(void) {}
	CSpecial(const CSpecial&) {}
	CSpecial& operator= (const CSpecial&) {return *this;}
public:
	//Instanz erstellen (sofern noch nicht vorhanden) und zurückgeben
	static CSpecial* CreateInstance(void)
	{
		if (!s_pInstance) s_pInstance = new CSpecial;
		if (s_pInstance) s_cRefs++;
		
		return s_pInstance;
		
	}
	//Instanz löschen
	static void DeleteInstance(CSpecial** ppInst)
	{
		//Ungültiger Parameter
		if (!ppInst) return;
		if (*ppInst && *ppInst == s_pInstance) 
		{
			//Refernzzähler auf 0->Objekt freigeben
			if (--s_cRefs == 0)
			{
				delete s_pInstance;
				s_pInstance = 0;
			}
			*ppInst = 0;
		}
	}
	
};

//Statische Variablen
CSpecial* CSpecial::s_pInstance = 0;
int CSpecial::s_cRefs = 0;

Von der Klasse 'CSpecial' können auf herkömmliche Art und Weise keine Instanzen erstellt werden. Um dennoch eine Instanz zu erhalten muss man sich der Funktion 'CreateInstance' bedienen, die dann einen Zeiger auf eine intern gespeicherte Instanz zurückgibt. Mit Hilfe dieser Funktion wird erreicht, dass maximal eine Instanz dieser Klasse existiert, die wiederum auf dem Heap gespeichert und durch die statische Variable 's_pInstance' zugänglich ist. Wird die Funktion 'CreateInstance' ein mehrmals aufgerufen, dann wird jedes Mal ein Zeiger auf die selbe Instanz zurückgegeben und nicht jedes Mal eine neue Instanz erstellt. Damit mit alles korrekt funktionieren kann, darf ein erhaltener Zeiger keineswegs per 'delete' wieder freigegeben werden. Stattdessen sollte die Methode 'DeleteInstance' aufgerufen werden, um einen internen Verweiszähler 's_cRefs' zu erniedrigen und den über einen Doppelzeiger übergebenen Verweis zurückzusetzen. Das Objekt wird erst dann freigegeben, wenn keine Verweise mehr existieren. Im folgenden werden also zwei Verweise erstellt und anschließend wieder entfernt:

CSpecial *p1 = CSpecial::CreateInstance();
CSpecial *p2 = CSpecial::CreateInstance();

CSpecial::DeleteInstance(&p2);
CSpecial::DeleteInstance(&p1);

Erst beim zweiten Aufruf von 'DeleteInstance' wird die einzige Instanz von 'CSpecial' freigegeben. Dieser Quellcode verlangt Disziplin von dem Anwender. Zunächst muss der Anwender jedes Mal, wenn er eine weitere Instanz wünscht die Methode 'CreateInstance' aufrufen. Er sollte keinesfalls einfach einen Zeiger auf 'CSpecial' deklarieren und diesem den Wert eines bereits existierenden zuweisen, denn dann kann es passieren, dass dieser Zeiger am Ende auf eine Instanz verweist, die gar nicht mehr existiert. Außerdem darf der Benutzer keinesfalls den Operator 'delete' aufrufen, um die Instanz freizugeben, da alle anderen Verweise dann ungültig werden. Zumindest das Aufrufen des Operators 'delete' kann man mit Hilfe einer Programmiertechnik unterbinden. Man definiert den Destruktor der Klasse einfach als 'private':

class CSpecial
{
private:
	static CSpecial* s_pInstance;
	static int s_cRefs;
	CSpecial(void) {}
	CSpecial(const CSpecial&) {}
	CSpecial& operator= (const CSpecial&) {return *this;}
	~CSpecial(void) {}
public:
	/* --- Implementierung von 'CreateInstance' und 'DeleteInstance' ---
	...
	*/
	
};

//Initialisierung der statischen Variablen...

Nun führt folgender Aufruf zu einem Compilerfehler, da der Aufruf des Operators 'delete' den unmittelbaren Aufruf des Destruktors zur Folge hat, der allerdings als 'private' deklariert wurde und somit nicht von außerhalb aufgerufen werden kann:

CSpecial *p1 = CSpecial::CreateInstance();
delete p1;	//C2248: Kein Zugriff auf privates Element

Ein privater Destruktor erzwingt darüber hinaus, dass von einer Klasse nur Instanzen auf dem Heap erstellt werden können. Angenommen es existiert folgende Klasse:

class CNoStack
{
private:
	~CNoStack(void) {}
};

Würde man nun eine Instanz dieser Klasse lokal (also auf dem Stack) deklarieren, dann würde nach Endes des Gültigkeitsbereiches dieser Instanz automatisch der Destruktor aufgerufen werden. Dieser Aufruf ist aber nicht möglich, da der Destruktor erneut als 'private' deklariert wurde. Somit ist es nur möglich eine Instanz dieser Klasse über den Operator 'new'  zu erstellen. Wählt man eine andere Variante, dann kommt es automatisch zum Compilerfehler:

void main(void)
{
	//CNoStack c;			//C2248: Kein Zugriff auf privates Element
	CNoStack *pc = new CNoStack;	//Ok
}

Nun sollte aber jedes Objekt, welches mit Hilfe des Operators 'new' erstellt wird, mit dem Operator 'delete' freigegeben werden. Dies ist bei dem erstellten Objekt jedoch nicht ohne weiteres nicht möglich, da ein Aufruf von 'delete' wiederum den unmittelbaren Aufruf des Destruktors zur Folge hat, der allerdings gegen Aufrufe von außerhalb gesperrt ist. Aus diesem Grunde sollte eine Klasse, die ihren Destruktor als 'private' deklariert, irgendeine Methode besitzen, welche sich selbst freigibt. Innerhalb dieser Methode reicht dann ein Aufruf von 'delete' in Kombination mit dem 'this'-Zeiger:

class CNoStack
{
public:
	void Delete(void)
	{
		delete this;
	}
private:
	~CNoStack(void) {}
};

An dieser Stelle muss allerdings noch ausdrücklich betont werden, dass der Aufruf von 'delete this' bewirkt, dass das Objekt sofort zerstört wird. Aus diesem Grunde enthalten danach alle Membervariablen ungültige Werte, auf die dann nicht mehr zugegriffen werden sollte. Soll also eine Funktion, in der ein Objekt freigegeben wird, den Wert irgend einer Membervariablen zurückgeben, dann sollte dieser Wert zuvor in einer lokalen Variablen gespeichert werden. Übertragen auf unser Beispiel könnte die Funktion 'Delete' etwa eine Membervariable 'm_exitcode' zurückgeben. Die entsprechende Implementierung sähe dann wie folgt aus:

class CNoStack
{
public:
	int m_exitcode;
	CNoStack(void)
	{
		m_exitcode = 0;
	}
	int Delete(void)
	{
		int exitcode = m_exitcode;
		delete this;
		return exitcode;
	}
private:
	~CNoStack(void) {}
};

Nun könnte also die Klasse wie folgt verwendet werden:

CNoStack *pc = new CNoStack;

pc->m_exitcode = -1;

int ec = pc->Delete();
pc = 0;

Mit Hilfe von privaten Destruktoren und privaten Konstruktoren kann festgelegt werden, wer wie genau eine Instanz einer Klasse erstellt und wann diese Instanz freigegeben werden soll. So sieht beispielsweise das COM-Konzept vor, dass jedes Objekt (COM-Objekte werden immer dynamisch erstellt) u.a. zwei Methoden zur Referenzzählung ('AddRef' und 'Release') wie folgt implementieren muss: Wird 'AddRef' aufgerufen, dann wird ein interner Referenzzähler inkrementiert. Bei einem Aufruf von 'Release' wird eben dieser Zähler dekrementiert und im Falle, dass der Zähler den Wert '0' erreicht, wird das Objekt über einen Aufruf von 'delete this' zerstört. Die Methode 'AddRef' muss immer dann aufgerufen werden, wenn ein Zeiger die Adresse eines Objekts zugewiesen bekommt. Wird das Objekt nicht mehr benötigt, dann muss 'Release' aufgerufen und der Zeiger anschließend zurückgesetzt werden. Auf diese Weise regelt jedes Objekt selber, wie lange es am leben bleibt. Möchte man dieses COM-Konzept für eigene Klassen übernehmen, dann wäre es sinnvoll den Destruktor als 'private' zu deklarieren und allein der Funktion 'Release' die Möglichkeit zu geben, das Objekt zu zerstören.

Wie bereits am Beispiel des Zuweisungsoperators gezeigt wurde, lassen sich auch Operatoren als 'private' deklarieren. Dies ist vor allem dann sinnvoll, wenn Operatoren wie der Zuweisungsoperator '=', der Adressoperator '&' oder wie die Operatoren 'new' und 'delete' automatisch generiert werden. Manchmal ist dies jedoch nicht gewünscht. Zu diesem Zweck sollten sie dann als 'private' deklariert werden, um zu verhindern, dass Instanzen im Falle des Zuweisungsoperators kopiert, im Falle von 'new'  und 'delete' auf dem Heap angelegt und im Falle des Adressoperators durch Zeiger referenziert werden können. Zumindest für den Adressoperator möchte ich hier noch schnell ein Beispiel vorführen:

class CNoRefs
{
private:
	CNoRefs* operator&(void)
	{
	}
};

void main(void)
{
	CNoRefs r;		//Ok
	CNoRefs& refr = r;	//Ok
	//CNoRefs* pr = &r;	//C2248: Kein Zugriff auf private Element
}

Es ist also nun nicht mehr möglich einen Zeiger auf eine Instanz der Klasse 'CNoRefs' zeigen zu lassen, die nicht auf dem Heap erstellt wurde. Referenzen können aber weiterhin wie gewohnt erstellt werden. Werden also aus irgendwelchen Gründen Zeiger auf eine 'CNoRefs'-Instanz benötigt, dann muss eine Instanz auf dem Heap erstellt werden und anschließend die Daten von einer bereits existierenden Instanz kopiert werden. 

Elementinitialisierungslisten bei Konstruktoren

In einer der vergangenen Lektionen wurden Elementinitialisierungslisten bereits eingeführt. Dort wurden sie gebraucht, um innerhalb des Konstruktors einer abgeleiteten Klasse den Konstruktor einer Basisklasse aufzurufen. Das ist im Prinzip nichts besonderes. Dazu braucht man nicht unbedingt diese Elementinitialisierungslisten. Allerdings verhindern diese, dass einige Funktionen mehrfach aufgerufen werden. So wird beispielsweise von jedem Konstruktor einer abgeleiteten Klasse automatisch einer der Konstruktoren der Basisklasse aufgerufen (meist der Standardkonstruktor, wenn vorhanden). Wenn man nun einen ganz bestimmten Konstruktor aufrufen möchte, dann kann man dies durchaus innerhalb des Funktionsrumpfes des eigenen Konstruktors tun. Da aber zuvor bereits ein Konstruktor automatisch aufgerufen wird, hat dies zu folge, dass zwei Konstruktoren aufgerufen werden, was in jedem Fall vermieden werden sollte. Dazu ein Beispiel:

class CVehicle
{
private:
	unsigned short m_cWheels;
	bool m_bEngine;
	unsigned long m_ulColor;
public:
	virtual unsigned short GetWheelCount(void) {return m_cWheels;}
	virtual bool IsMotorized(void) {return m_bEngine;}
	virtual unsigned long GetColor(void) {return m_ulColor;}

	CVehicle(unsigned short cWheels, bool bEngine, unsigned long ulColor)
	{
		m_cWheels = cWheels;
		m_bEngine = bEngine;
		m_ulColor = ulColor;
	}
};

class CBicycle : public CVehicle
{
private:
	unsigned short m_cGears;
public:
	virtual unsigned short GetGearsCount(void) {return m_cGears;}

	CBicycle(unsigned short cWheels, unsigned long ulColor, unsigned short cGears)
	{
		m_cGears = cGears;
	}
};

class CCar : public CVehicle
{
private:
	unsigned short m_cDoors;
public:
	virtual unsigned short GetDoorsCount(void) {return m_cDoors;}
	
	CCar(unsigned short cWheels, unsigned long ulColor, unsigned short cDoors)
	{
		m_cDoors = cDoors;
	}
};

Nun sind drei Klassen implementiert worden: Eine Basisklasse ('CVehicle'), die allgemeine Eigenschaften (wie Anzahl der Räder, motorisiert und Farbe) eine Fahrzeugs speichert. Die abgeleiteten Klassen für Fahrrad ('CBicycle') und Auto ('CCar') besitzen darüber hinaus noch eigene Eigenschaften (wie Anzahl der Gänge und Anzahl der Türen). Die Konstruktoren dieser Klassen sind aber noch nicht vollständig implementiert. So werden bisher nur eigene Eigenschaften gespeichert. Kompilieren lässt sich dieses Beispiel aber noch nicht, denn der Compiler weiß nicht welcher Konstruktor der Basisklasse aufgerufen soll, wenn eine Instanz von 'CBicycle' oder von 'CCar' gebildet wird. Normalerweise würde er den Standardkonstruktor von 'CVehicle' aufrufen. Dieser ist aber nicht verfügbar und deswegen entsteht ein entsprechender Fehler. In diesem Fall muss also explizit angegeben werden, welcher Konstruktor jeweils aufgerufen werden soll. Dies funktioniert wiederum nur über Elementinitialisierungslisten. Dies wird wie bereits zuvor beschrieben bei der Implementierung des jeweiligen Konstruktors mit angegeben. Sie folgt direkt auf den Funktionskopf und wird von diesem durch ein ':' getrennt. Bei mehreren Elementen werden diese durch Kommata abgetrennt. In unserem Beispiel könnte der Basisklassenkonstruktor innerhalb der Elementinitialisierungsliste in etwa wie folgt aufgerufen werden:

class CBicycle : public CVehicle
{
private:
	unsigned short m_cGears;
public:
	virtual unsigned short GetGearsCount(void) {return m_cGears;}

	CBicycle(unsigned short cWheels, 
		unsigned long ulColor, 
		unsigned short cGears) : CVehicle(cWheels, false, ulColor)
	{
		m_cGears = cGears;
	}
};

class CCar : public CVehicle
{
private:
	unsigned short m_cDoors;
public:
	virtual unsigned short GetDoorsCount(void) {return m_cDoors;}
	
	CCar(unsigned short cWheels, 
		unsigned long ulColor, 
		unsigned short cDoors) : CVehicle(cWheels, true, ulColor)
	{
		m_cDoors = cDoors;
	}
};

Nun wird alles kompiliert und die Klassen lassen sich wie gewohnt verwenden. An diesem Beispiel ist die Verwendung einer Elementinitialisierungslisten unbedingt notwendig. Würde man für die Basisklasse 'CVehicle' einen Standardkonstruktor implementieren, dann gäbe es zwar keinen Compilerfehler, wenn man die Elementinitialisierungslisten nicht verwendet, aber die Werte für die Eigenschaften können nicht mehr innerhalb der abgeleiteten Klasse gesetzt werden. Würde dann der Konstruktor von 'CVehicle' innerhalb des Funktionsrumpfes des Konstruktors von 'CBicycle' oder eben 'CCar' aufgerufen werden, dann würde diesem Aufruf zunächst ein Aufruf des Standardkonstruktors von 'CVehicle' vorausgehen. Dies wiederum hätte zur Folge, dass alle Elemente doppelt initialisiert werden. Aus diesem Grunde sollte man dann auch hier Elementinitialisierungslisten verwenden, selbst wenn der Compiler im anderen Falle keinen Fehler meldet. Es lassen sich auch Membervariablen innerhalb von Elementinitialisierungslisten initialisieren. Dies erweist sich zumindest bei selbst definierten Typen als vorteilhaft, da dann bei der Initialisierung gleich die übergebenen Werte zugewiesen werden. Initialisiert man aber alle Member innerhalb des Konstruktors, dann unterliegen sie zunächst einer Standardinitialisierung und bekommen dann innerhalb des Konstruktoraufrufs ihre eigentlichen Werte zugewiesen. Um die drei vorliegenden Klassen auch für eigene Member die Elementinitialisierungslisten verwenden zu lassen, muss der Quelltext wie folgt geändert werden:

class CVehicle
{
private:
	unsigned short m_cWheels;
	bool m_bEngine;
	unsigned long m_ulColor;
public:
	virtual unsigned short GetWheelCount(void) {return m_cWheels;}
	virtual bool IsMotorized(void) {return m_bEngine;}
	virtual unsigned long GetColor(void) {return m_ulColor;}

	CVehicle(unsigned short cWheels, 
		bool bEngine, 
		unsigned long ulColor) : m_cWheels(cWheels), 
					m_bEngine(bEngine),
					m_ulColor(ulColor) 
	{
	}
};

class CBicycle : public CVehicle
{
private:
	unsigned short m_cGears;
public:
	virtual unsigned short GetGearsCount(void) {return m_cGears;}

	CBicycle(unsigned short cWheels, 
		unsigned long ulColor, 
		unsigned short cGears) : CVehicle(cWheels, false, ulColor),
					m_cGears(cGears)
	{
	}
};

class CCar : public CVehicle
{
private:
	unsigned short m_cDoors;
public:
	virtual unsigned short GetDoorsCount(void) {return m_cDoors;}
	
	CCar(unsigned short cWheels, 
		unsigned long ulColor, 
		unsigned short cDoors) : CVehicle(cWheels, true, ulColor),
					m_cDoors(cDoors)
	{
	}
};

Es existieren noch weitere Fälle, in denen Membervariablen innerhalb der Elementinitialisierungslisten und nicht innerhalb des Konstruktors initialisiert werden müssen. Dies betrifft die Membervariablen, die gleich bei der Erstellung einen gültigen Wert erhalten müssen, der nicht mehr verändert werden kann. Zu solchen Membervariablen zählen Konstanten und Referenzen. Dazu folgendes Negativbeispiel:

class CDemo
{
private:
	int& m_refi;
	const int m_iInvalid;
public:
	CDemo(int& refi)
	{
		m_refi = refi;	//C2758
		m_iInvalid = 0;	//C2758 und C2166
	}
};

Mit Hilfe der Elementinitialisierungslisten lassen sich all diese Fehler beheben:

class CDemo
{
private:
	int& m_refi;
	const int m_iInvalid;
public:
	CDemo(int& refi) : m_refi(refi), m_iInvalid(0)
	{
	}
};

Soviel also zu Elementinitialisierungslisten. In vielen Fällen sind sie optional in einigen Fällen hingegen sind sie obligatorisch. 

'const' bei Memberfunktionen und 'mutable' bei Membervariablen

Das Schlüsselwort ist schon mehrere Male aufgetaucht. Hier an dieser Stelle wird es aber das erste Mal in Verbindung mit Funktionen und nicht wie sonst mit Variablen gebraucht. Handelt es sich nämlich bei einer Memberfunktion um eine Funktion, die keinen der Klassenmember verändert, dann kann man diese als 'const' deklarieren. Als 'const'  deklarierte Funktionen lassen sich nämlich auch von konstanten Objekten aufrufen. Eine Methode wird als 'const' deklariert, in dem man das Schlüsselwort sowohl bei Deklaration als auch bei Implementierung direkt hinter den Funktionskopf anfügt. Dazu folgendes Beispiel:

class CConstDemo
{
private:
	int m_var1;
	double m_var2;
public:
	int m_var3;
	CConstDemo(int var1, double var2) : m_var1(var1), m_var2(var2)
	{
	}
	
	int GetVar1(void) const {return m_var1;}
	double GetVar2(void) {return m_var2;}
};

Wird nun ein konstantes Objekt dieser Klasse erstellt, dann lassen sich nur die konstanten Funktionen (in diesem Fall also nur 'GetVar1') aufrufen:

const CConstDemo c(1, 3.0);

c.GetVar1();	//Ok
//c.GetVar2();	//C2662

Member eines konstanten Objekts lassen sich für gewöhnlich nicht ändern. Allerdings kann man dies Beschränkung für bestimmte Member aufheben. So ist gegenwärtig die Variable 'm_var3' der Instanz 'c' trotz des Zugriffsbezeichners 'public' nicht veränderbar, da 'c' konstant ist. Fügt man jedoch das Schlüsselwort 'mutable' der Variablendeklaration hinzu, dann ist die Variable (trotz der konstanten Instanz) veränderbar:

class CConstDemo
{
private:
	int m_var1;
	double m_var2;
public:
	mutable int m_var3;
	CConstDemo(int var1, double var2) : m_var1(var1), m_var2(var2)
	{
	}
	
	int GetVar1(void) const {return m_var1;}
	double GetVar2(void) const {return m_var2;}
};

Nun sind also auch folgende Aufrufe gestattet:

const CConstDemo c(1, 3.0);

c.GetVar1();	//Ok
c.GetVar2();	//nun Ok
c.m_var3 = 3;	//nun Ok

Normalerweise können innerhalb von Memberfunktionen, welche als 'const' deklariert sind, keine Membervariablen verändert werden. Für Membervariablen, die hingegen als 'mutable' deklariert werden, gilt diese Beschränkung hingegen nicht.

Für die nächsten Lektionen

Dies waren also, die noch zu erwähnenden Techniken und Ergänzungen, die es noch galt zu erwähnen. Es handelte sich dabei lediglich um wenige. Viele Techniken wurden nämlich schon innerhalb der jeweiligen Lektionen angewendet. Die hier vorgestellten passten jedoch aus gewissen Gründen nicht in einige Lektionen und wurden deshalb hier eingeführt. In den nächsten Lektionen sollen Klassen vorgestellt werden, welche einige der in dieser und in den letzten Lektionen aufgeführten Techniken aufgreifen und sich außerdem für eigene Projekte als nützlich erweisen könnten. So wird darunter auch eine Klasse sein, die im Prinzip eine Form eines dynamischen Arrays darstellt und die ich bereits in vielen Projekten verwendet habe, obwohl sie sehr schlicht ist.

Zurück Nach oben Weiter