Intelligente Zeiger

Einführung

Reserviert man Speicher und ermöglicht einem Zeiger dann den Zugriff auf diesen Speicher, dann muss sichergestellt werden, dass am Ende der Speicher freigegeben wird. In C++ geschieht muss dies der Programmierer selbst erledigen. Dafür hat er aber den Vorteil genau entscheiden zu können, wann dies geschieht. In anderen Sprachen wird die Freigabe automatisch erledigt und zwar dann, wenn der "Garbage Collector" dies für sinnvoll erachtet. Soll in C++ Speicher nur innerhalb eines Gültigkeitsbereiches zur Verfügung stehen, dann bietet es sich an einen "Intelligenten Zeiger" ("smart pointer") zu verwenden, der eben auf diesen Speicher zeigt. Ein solcher Zeiger gibt dann nach Ende des Gültigkeitsbereiches den Speicher wieder frei. Mit dem bisher Erlerntem lässt sich bereits eine Klasse programmieren, welche den Aufgaben eines intelligenten Zeigers gerecht wird. Dabei ist darauf zu achten, dass sich ein "intelligenter Zeiger" genauso verwenden lassen sollte, wie ein Zeiger. Das bedeutet v.a., dass entsprechende Operatoren (wie etwa der Derefernzierungsoperator '*') überladen werden.

Der Rahmen

Zunächst sollten alle Funktionen, Operatoren, Konstruktoren, etc. deklariert werden, die auf jeden Fall implementiert werden sollen. Dazu gehören neben einem Konstruktor, der einen einfachen Zeiger des selben Datentyps übernimmt, auch der Kopierkonstruktor, der Zuweisungsoperator '=', der Dereferenzierungsoperator '*' und natürlich ein Destruktor, der den Speicher freigibt. Außerdem sollte intern ein einfacher Zeiger gespeichert sein, an den dann alle Aufrufe delegiert werden. Die Klassendeklaration könnte nun wie folgt aussehen:

template <typename T>
class CSmartPointer
{
private:
	//interner Zeiger
	T* m_pIntern;
public:
	//Konstruktor
	CSmartPointer(T* = 0);

	//Destruktor
	~CSmartPointer(void);

	//Kopierkonstrukor
	CSmartPointer(const CSmartPointer<T>&);

	//Zuweisungsoperatoren
	CSmartPointer<T>& operator = (const CSmartPointer<T>&);
	T* operator = (T*);

	//Dereferenzierungsoperator (auch für konstante Zeiger -> 'const')
	T& operator * (void) const;

	
};

All diese Funktionen und Operatoren soll die Klasse letztendlich implementieren. Diese Implementierungen lassen sich soweit auch ganz einfach vornehemen. Lediglich beim ersten Zuweisungsoperator und beim Kopierkonstruktor dürften Probleme auftreten auf die ich später noch zu sprechen komme. Zunächst sollten diese beiden "Funktionen" als 'private' deklariert werden und eine Standardimplementierung erhalten:

template <typename T>
class CSmartPointer
{
private:
	//interner Zeiger
	T* m_pIntern;

	//Kopierkonstrukor
	CSmartPointer(const CSmartPointer<T>& sp) : m_pIntern(sp.m_pIntern)
	{
	}

	//Zuweisungsoperator
	CSmartPointer<T>& operator = (const CSmartPointer<T>&)
	{
		return *this;
	}

public:
	//Konstruktor
	CSmartPointer(T* = 0);

	//Destruktor
	~CSmartPointer(void);

	T* operator = (T*);

	//Dereferenzierungsoperator
	T& operator * (void) const;

	
};

Implementierung der Funktionen

Der Konstruktor muss einfach nur den übergebenen Zeiger übernehmen, der Zuweisungsoperator muss zuvor noch evtl. den Speicher freigeben. Dafür soll zunächst eine Methode '_Reset' als 'private' deklariert werden, die den internen Zeiger zurücksetzt und evtl. zuvor den Speicher freigibt. Ihre Implementierung sähe dann wie folgt aus:

//Zurücksetzen des Zeigers
template <typename T>
void CSmartPointer<T>::_Reset(void)
{
	//Speicher freigeben, wenn nötig
	if (m_pIntern) 
	{delete m_pIntern; m_pIntern = 0;}
}

Die Implementierungen von Kopierkonstruktor und Zuweisungsoperator könnten dann wie folgt aussehen:

//Konstruktor
template <typename T>
CSmartPointer<T>::CSmartPointer(T* p) : m_pIntern(p)
{
}

//Zuweisungsoperator '='
template <typename T>
T* CSmartPointer<T>::operator = (T* p)
{
	//Zurücksetzen
	_Reset();
	
	//Neu setzen
	m_pIntern = p;

	//internen Zeiger zurückgeben
	return m_pIntern;
}

Alles nicht sehr spektakulär. Auch die Implementierung des Destruktors ist recht einfach:

//Destruktor
template <typename T>
CSmartPointer<T>::~CSmartPointer(void)
{
	_Reset();
}

Der Dereferenzierungsoperator muss lediglich den internen Zeiger als Referenz zurückliefern: 

//Dereferenzierungsoperator '*'
template <typename T>
T& CSmartPointer<T>::operator * (void) const
{
	return *m_pIntern;
}

Da all diese Implementierungen nur aus wenigen Zeilen bestehen, lassen sie sich auch direkt in die Klasse aufnehmen, ohne die Übersicht zu gefährden. Die gesamte Klasse sähe dann wie folgt aus:

template <typename T>
class CSmartPointer
{
private:
	//interner Zeiger
	T* m_pIntern;

	//Kopierkonstrukor
	CSmartPointer(const CSmartPointer<T>& sp) : m_pIntern(sp.m_pIntern)
	{
	}

	//Zuweisungsoperator
	CSmartPointer<T>& operator = (const CSmartPointer<T>&)
	{
		return *this;
	}

	//Zurücksetzen des Zeigers
	void _Reset(void)
	{
		//Speicher freigeben, wenn nötig
		if (m_pIntern) 
		{delete m_pIntern; m_pIntern = 0;}
	}

public:
	//Konstruktor
	CSmartPointer(T* p = 0) : m_pIntern(p)
	{
	}

	//Destruktor
	~CSmartPointer(void)
	{
		_Reset();
	}

	//Zuweisungsoperator
	T* operator = (T* p)
	{
		//Zurücksetzen
		_Reset();
	
		//Neu setzen
		m_pIntern = p;

		//internen Zeiger zurückgeben
		return m_pIntern;

	}

	//Dereferenzierungsoperator
	T& operator * (void) const
	{
		return *m_pIntern;
	}

	
};

Die Klasse lässt sich nun bereits verwenden. Es lassen sich Instanzen anlegen und diese so verwenden, als seien es gewöhnliche Zeiger:

CSmartPointer<int> sp = new int;
*sp = 3;

sp = new int;
*sp = 5;

sp = 0;

Probleme mit Kopien eines intelligenten Zeigers

Nur sind bisher keine Kopien eines "intelligenten Zeigers" möglich. Unter Umständen ist dies aber erwünscht. Da aber nicht unbedingt von einem intelligenten Zeiger gefordert sein muss, dass er sich kopieren lässt, bleibt die Klasse 'CSmartPointer' unberührt. Stattdessen wird eine weitere Klasse 'CSmartPointerX' implementiert, bei der sich die Zeiger auch kopieren lassen. Zunächst kann jedoch die Implementierung direkt übernommen werden. Der Unterschied besteht zur Zeit nur darin, dass Zuweisungsoperator und Kopierkonstruktor nun wieder öffentlich sind und auch eine Methode '_Copy' eingeführt wird:

template <typename T>
class CSmartPointerX
{
private:
	//interner Zeiger
	T* m_pIntern;

	//Zurücksetzen des Zeigers
	void _Reset(void)
	{
		//Speicher freigeben, wenn nötig
		if (m_pIntern) 
		{delete m_pIntern; m_pIntern = 0;}
	}

	//Kopieren eines intelligenten Zeigers
	void _Copy(const CSmartPointerX<T>& sp)
	{
	}

public:
	//Konstruktor
	CSmartPointerX(T* p = 0) : m_pIntern(p)
	{
	}

	//Destruktor
	~CSmartPointerX(void)
	{
		_Reset();
	}

	//Kopierkonstrukor
	CSmartPointerX(const CSmartPointerX<T>& sp) : m_pIntern(0)
	{
	}

	//Zuweisungsoperator
	CSmartPointerX<T>& operator = (const CSmartPointerX<T>& sp)
	{
		return *this;
	}

	//Zuweisungsoperator
	T* operator = (T* p)
	{
		//Zurücksetzen
		_Reset();
	
		//Neu setzen
		m_pIntern = p;

		//internen Zeiger zurückgeben
		return m_pIntern;

	}

	//Dereferenzierungsoperator
	T& operator * (void) const
	{
		return *m_pIntern;
	}

	
};

Im folgenden müssen nur noch die Implementierungen der Funktionen '_Copy' und '_Reset' ergänt bzw. geändert werden. Nun zu dem Problem, welches beim Kopieren von "intelligenten Zeigern" besteht: Wird einem einfachen Zeiger die Adresse eines anderen zugewiesen, dann verweisen beide auf das selbe Ziel. So sollte dies dementsprechend auch bei einem intelligenten Zeiger sein. So müsste einfach der interne Zeiger kopiert werden. Dies erweist sich aber insofern als problematisch, da dann beide Zeiger auf den gleichen Speicher zeigen und beim jeweiligen Destruktoraufruf jedes Mal der Speicher freigegeben wird. Dies wird spätestens beim zweiten Freigeben für Fehler sorgen. Aus diesem Grunde bekommt die Klasse eine interne Referenzzählung, die nach außen hin verborgen bleibt. Zu diesem Zweck nun als weitere private Variable ein 'int*'-Zeiger (namens 'm_pcRefs') eingeführt, der jeweils auf eine Variable auf dem Heap zeigt, die Referenzen zählt. Wird eine neue Instanz eines intelligenten Zeigers gebildet, dann wird diese Variable dynamisch erstellt und mit eins initialisiert. Wird dann anschließend dieser intelligente Zeiger kopiert, dann wird der Zeiger von dem neuen intelligenten Zeiger einfach übernommen und inkrementiert. So wissen alle Kopien eines intelligenten Zeigers immer, wie viele Referenzen tatsächlich existieren. Wird nun die Methode '_Reset' aufgerufen und der Zeiger somit zurückgesetzt, dann wird der Referenzzähler dekrementiert. Ist der Referenzzähler danach null, dann wird sowohl der interne Zeiger 'm_pIntern' als auch der Zeiger zur Referenzzählung 'm_pcRefs' freigegeben. Auf diese Weise werden Kopien nachträglich ermöglicht. Folgende Änderungen sind nötig (die Implementierungen werden hier jeweils außerhalb der Klasse vorgenommen, können aber durchaus auch weiterhin innerhalb der Klasse verweilen):

1.) Die Funktion '_Reset':

//Zurücksetzen des Zeigers
template <typename T>
void CSmartPointerX<T>::_Reset(void)
{
	//interner Zeiger sollte kein Null-Zeiger sein
	if (m_pIntern /*&& m_pcRefs*/) 
	{
		//Speicher freigeben, wenn nötig
		if (--(*m_pcRefs) == 0)
		{
			//Speicher freigeben
			delete m_pIntern; m_pIntern = 0;
			delete m_pcRefs; m_pcRefs = 0;
		}
		//Zeiger nur zurücksetzen
		else
		{
			m_pIntern = 0;
			m_pcRefs = 0;
		}
	}
}

2.) Der Konstruktor:

//Konstruktor
template <typename T>
CSmartPointerX<T>::CSmartPointerX(T* p)	: m_pIntern(p)
{
	//Nur für Nicht-Null-Zeiger Referenzzählung
	if (m_pIntern)
	{
		m_pcRefs = new int;
		*m_pcRefs = 1;
	}
	else m_pcRefs = 0;
}

3.) Die Methode '_Copy':

//Kopieren
template <typename T>
void CSmartPointerX<T>::_Copy(const CSmartPointerX<T>& sp)
{
	//Zunächst Zeiger zurücksetzen
	_Reset();
	
	//interner Zeiger sollte kein Null-Zeiger sein
	if (sp.m_pIntern /*&& sp.m_pcRefs*/)
	{
		//Zeiger übernehmen
		m_pIntern = sp.m_pIntern;
		m_pcRefs = sp.m_pcRefs;
		(*m_pcRefs)++;
	}
}

4.) Der Kopierkonstruktor:

//Kopierkonstruktor
template <typename T>
CSmartPointerX<T>::CSmartPointerX(const CSmartPointerX<T>& sp)	: m_pIntern(0), m_pcRefs(0)
{
	_Copy(sp);
}

5.) Die Zuweisungsoperatoren '=':

//Zuweisungsoperator
template <typename T>
CSmartPointerX<T>& CSmartPointerX<T>::operator = (const CSmartPointerX<T>& sp)
{
	_Copy(sp);
	return *this;
}

//Zuweisungsoperator
template <typename T>
T* CSmartPointerX<T>::operator = (T* p)
{
	//Zurücksetzen
	_Reset();
	
	//Neu setzen
	m_pIntern = p;

	//Nur für Nicht-Null-Zeiger Referenzzählung
	if (m_pIntern)
	{
		m_pcRefs = new int;
		*m_pcRefs = 1;
	}

	//internen Zeiger zurückgeben
	return m_pIntern;
}

Zusätzlich muss natürlich noch der Zeiger 'm_pcRefs' deklariert werden. Die gesamte Klassendeklaration sieht nun in etwa wie folgt aus:

template <typename T>
class CSmartPointerX
{
private:
	//interner Zeiger
	T* m_pIntern;

	//Referenzzähler
	int *m_pcRefs;

	//Zurücksetzen des Zeigers
	void _Reset(void)
	{
		//interner Zeiger sollte kein Null-Zeiger sein
		if (m_pIntern /*&& m_pcRefs*/) 
		{
			//Speicher freigeben, wenn nötig
			if (--(*m_pcRefs) == 0)
			{
				//Speicher freigeben
				delete m_pIntern; m_pIntern = 0;
				delete m_pcRefs; m_pcRefs = 0;
			}
			//Zeiger nur zurücksetzen
			else
			{
				m_pIntern = 0;
				m_pcRefs = 0;
			}
		}
	}

	//Kopieren eines intelligenten Zeigers
	void _Copy(const CSmartPointerX<T>& sp)
	{
		//Zunächst Zeiger zurücksetzen
		_Reset();
		
		//interner Zeiger sollte kein Null-Zeiger sein
		if (sp.m_pIntern /*&& sp.m_pcRefs*/)
		{
			//Zeiger übernehmen
			m_pIntern = sp.m_pIntern;
			m_pcRefs = sp.m_pcRefs;
			(*m_pcRefs)++;
		}
	}

public:
	//Konstruktor
	CSmartPointerX(T* p = 0) : m_pIntern(p), m_pcRefs(0)
	{
		//Nur für Nicht-Null-Zeiger Referenzzählung
		if (m_pIntern)
		{
			m_pcRefs = new int;
			*m_pcRefs = 1;
		}
		
	}

	//Destruktor
	~CSmartPointerX(void)
	{
		_Reset();
	}

	//Kopierkonstrukor
	CSmartPointerX(const CSmartPointerX<T>& sp) : m_pIntern(0), m_pcRefs(0)
	{
		_Copy(sp);
	}

	//Zuweisungsoperator
	CSmartPointerX<T>& operator = (const CSmartPointerX<T>& sp)
	{
		_Copy(sp);
		return *this;
	}

	//Zuweisungsoperator
	T* operator = (T* p)
	{
		//Zurücksetzen
		_Reset();
		
		//Neu setzen
		m_pIntern = p;
		
		//Nur für Nicht-Null-Zeiger Referenzzählung
		if (m_pIntern)
		{
			m_pcRefs = new int;
			*m_pcRefs = 1;
		}
		
		//internen Zeiger zurückgeben
		return m_pIntern;
	}

	//Dereferenzierungsoperator
	T& operator * (void) const
	{
		return *m_pIntern;
	}

	
};

Nun lassen sich also auch Kopien von "intelligenten Zeigern" anlegen:

CSmartPointerX<int> sp = new int, sp2 = sp;
*sp = 3;

sp = new int;
*sp = 5;

sp2 = sp;

sp = 0;

Intelligente Zeiger auf Instanzen von Klassen

Natürlich lassen sich die bereits existierenden Klassen auch für "intelligente Zeiger" auf Klasseninstanzen verwenden. So wären folgende Aufrufe durchaus möglich:

//Intelligenter Zeiger für die 'CRect'-Klasse
CSmartPointer<CRect> sp = new CRect(1000, 500);

CRect r1(100, 100), r2;

//Zeiger derereferenzieren und Instanz zuweisen
*sp = r1;

Auch ließe sich mit dem intelligenten Zeiger auch auf die Member der Klasse zugreifen. Allerdings ist gegenwärtig nur folgender Aufruf zulässig:

//Zugriff auf Memberfunktion
unsigned long ulArea = (*sp).GetArea();

Wünschenswert wäre es, wenn nun auch die Verwendung des Operators '->' möglich wäre. Dazu müsste dieser aber erst überladen werden. Wenn man aber die bereits existierenden Klassen 'CSmartPointer' und 'CSmartPointerX' überlädt, dann stehen diese Klassen nicht mehr für Standarddatentypen zur Verfügung. Aus diesem Grunde müssten neue Klassen ('CSmartPointerObj' und 'CSmartPointerObjX') deklariert werden, welche nur für Klassen gedacht sind. Da sie aber alle anderen Operatoren und Funktionen, die bereits in den Klassen 'CSmartPointer' und 'CSmartPointerX' implementiert wurden, ebenfalls implementieren müssten, würde es sich anbieten diese Klassen als Basisklassen zu verwenden. Damit sowohl 'CSmartPointer' als auch 'CSmartPointerX' als Basisklasse dienen können, sind zuvor erst einige Änderungen vorzunehmen. Zunächst sollte die Variable 'm_pIntern' zunächst als 'protected' (und nicht wie zuvor als 'private') deklariert werden. Außerdem sollte der jeweilige Destruktor als 'virtual' deklariert werden. Bei den abgeleiteten Klassen 'CSmartPointerObj' und 'CSmartPointerObjX' müssen dann neben dem Operator '->', die Konstruktoren und der (erste) Zuweisungsoperator überladen bzw. überschrieben werden. Ihre Klassendeklarationen sähen dann wie folgt aus:

//Intelligente Zeiger für Klasseninstanzen ohne Kopierfähigkeit
template <typename T>
class CSmartPointerObj : public CSmartPointer<T>
{
private:
	//Kopierkonstruktor
	CSmartPointerObj(const CSmartPointerObj<T>&)
	{
	}

	//Zuweisungsoperator
	CSmartPointerObj<T>& operator = (const CSmartPointerObj<T>&)
	{
		return *this:
	}
public:
	//Konstruktor
	CSmartPointerObj(T* p = 0) : CSmartPointer<T>(p)
	{
	}
	
	//Operator '->' (auch für konstante Zeiger -> 'const')
	T* operator -> (void) const
	{
		return m_pIntern;
	}
	
}; 

//Intelligente Zeiger für Klasseninstanzen mit Kopierfähigkeit
template <typename T>
class CSmartPointerObjX : public CSmartPointerX<T>
{
public:
	//Kopierkonstruktor
	CSmartPointerObjX(const CSmartPointerObjX<T>& sp) : CSmartPointerX<T>(sp)
	{
	}

	//"Kopierkonstruktor", der auch Instanzen der Basisklasse übernehmen kann
	CSmartPointerObjX(const CSmartPointerX<T>& sp) : CSmartPointerX<T>(sp)
	{
	}

	//Zuweisungsoperator
	CSmartPointerObjX<T>& operator = (const CSmartPointerX<T>& sp)
	{
		//Operator der Basisklasse aufrufen
		CSmartPointerX<T>::operator = (sp);
		return *this;
	}

	//Konstruktor
	CSmartPointerObjX(T* p = 0) : CSmartPointerX<T>(p)
	{
	}
	
	//Operator '->' (auch für konstante Zeiger -> 'const')
	T* operator -> (void) const
	{
		return m_pIntern;
	}
	
};

Bei der Klasse 'CSmartPointerObj' ist nicht viel neues zu erkennen. Lediglich die Implementierung des Operators '->' dürfte neu sein. Wesentlich interessanter ist hingegen die Klasse 'CSmartPointerObjX'. Zuerst wurde ein typischer Kopierkonstruktor implementiert, damit auch Kopien dieser Klasse gleich beim Konstruktoraufruf erfolgen können. Als interessant erweist sich der zweite implementierte Konstruktor. Er unterscheidet sich vom Kopierkonstruktor im wesentlichen nur dadurch, dass das übergebene Argument vom Typ der Basisklasse (also 'CSmartPointerX') und nicht von der eigenen (also 'CSmartPointerObjX') ist. Auf diese Weise wird folgender Aufruf möglich:

CSmartPointerX<CRect> spb = new CRect(100, 100);
CSmartPointerObjX<CRect> spd = spb;

unsigned long ulArea = spd->GetArea();

Aus dem selben Grunde wird auch der Zuweisungsoperator so deklariert, dass mein ein Argument der Basisklasse übergeben kann. Da jedes Objekt einer abgeleiteten Klasse auch als ein Objekt seiner Basisklasse aufgefasst werden kann, sind zwei Implementierungen des Zuweisungsoperators (einmal für 'CSmartPointerObjX' und einmal für 'CSmartPointerX' als Argument) nicht nötig. Anders ist dies jedoch beim Kopierkonstruktor, da dieser immer automatisch generiert wird, wenn er nicht in der Klasse gefunden wird. Die zweite Implementierung des Konstruktors würde demzufolge nicht ausreichen, da diese nicht dem Syntax eines Kopierkonstruktors entspricht und der eigentliche Kopierkonstruktor somit fehlen würde.

Nachträgliche Anmerkungen

Alle, die hier vorgestellten Klassen ermöglichen es intelligente Zeiger so zu verwenden, dass man Speicher dynamisch reservieren kann, sich aber um seine Freigabe nicht kümmern muss, da diese während des Destruktoraufrufs durchgeführt wird. Möchte man hingegen den Speicher vorher freigeben, dann muss man dem Zeiger einfach nur eine neue Adresse zuweisen. Dann wird der alte gelöscht und kein neuer angelegt. So schön diese Klassen nun auch erscheinen mögen, so sind nicht gegen alle Anwendungen gesichert. Wird beispielsweise Speicher dynamisch reserviert und die Adresse in einem gewöhnlichen Zeiger festgehalten und werden anschließend zwei (oder mehr) intelligente Zeiger erstellt, die alle jeweils diese Adresse zugewiesen bekommen, dann wird der Speicher schließlich zwei (oder mehrmals) freigegeben und das Programm wird unweigerlich abstürzen. Dies wird in folgendem Aufruf anschaulicher:

//Dieser Quelltext lässt das Programm abstürzen
int *p = new int;
CSmartPointer<int> sp1 = p, sp2 = p;

Im Prinzip ließe sich dieses Problem nur lösen, wenn jede Adresse, die einem intelligenten Zeiger zugewiesen wird, in einem Array gespeichert wird und jedes Mal , wenn ein neuer Zeiger erstellt wird, dieser Array durchsucht wird, ob nicht eine Adresse schon existiert. Dies wiederum würde aber ziemlich viel Overhead bedeuten und das Programm wohl wesentlich langsamer machen. Aus diesem Grunde sollte man nur intelligente Zeiger in sich selbst überführen. Dazu wurde hier neben der Klasse 'CSmartPointer' auch die Klasse 'CSmartPointerX' mit interner Referenzzählung implementiert. So sollte obiger Aufruf wie folgt geändert werden:

//Dieser Quelltext lässt das Programm nicht abstürzen
int *p = new int;
CSmartPointerX<int> sp1 = p, sp2 = sp1;

Um diesem Problem gänzlich entgegenzutreten, wäre es sicherlich am besten, wenn man reservierten Speicher sofort und ohne Umwegen einem intelligenten Zeiger überlässt. So wäre folgender AUfruf von allen der sicherste:

//Dieser Quelltext lässt das Programm nicht abstürzen
CSmartPointerX<int> sp1 = new int, sp2 = sp1;

Ein weiteres Problem dieser intelligenten Zeiger ist ganz einfach. Die Klasse geht davon aus und muss davon ausgehen, dass der interne Zeiger auf dynamischen Speicher zeigt, der mit dem Operator 'new' angelegt wurde, damit dieser am Ende korrekt freigegeben werden kann. Wird also Speicher etwa mit 'malloc' oder 'new[]' reserviert und diese Adresse übergeben, dann wird das Programm am Ende u.U. den Speicher nicht korrekt freigeben. Fatal wäre es auch, einem intelligenten Zeiger eine Adresse zu übergeben, die gar nicht auf Speicher auf dem Heap verweist. So erzeugt folgender Quelltext einen Absturz:

//Dieser Quelltext lässt das Programm abstürzen
int i0;
CSmartPointer<int> sp1 = &i;

Zusammenfassend sollte hier also gesagt werden, dass die Verwendung von intelligenten Zeigern durchaus mit Vorsicht zu genießen ist. Werden sie richtig verwendet, dann dürften keine Probleme auftreten. Eine falsche Anwendung kann unerwünschte Ergebnisse liefern, Speicherlecks oder im schlimmsten Fall einen Programmabsturz hervorrufen.

Noch eine paar Anmerkungen am Rande: Soll der Speicher nicht mit 'new' reserviert und anschließend mit 'delete' freigegeben, sondern vielmehr das Paar 'new[]'/'delete[]' verwendet weden, dann wäre es sinnvoll für die "Smart Pointer"- Klassen die Operatoren '+', '-' und all ihre Varianten zu überladen.

Bei der Klasse 'CSmartPointer' wurde ein Zuweisungsoperator als 'private' deklariert, damit dieser nicht aufgerufen werden kann um Kopien zu erstellen. Man kann diese Implementierung jedoch auch ganz weg lassen, weil der Zuweisungsoperator noch einmal überladen wurde. In diesem Falle unterscheidet sich der Zuweisungsoperator vom Kopierkonstruktor. Der Kopierkonstruktor muss selbst implementiert werden, wenn man eine automatische Implementierung verhindern will. Bei dem Zuweisungsoperator reicht es diesen in irgendeiner Form zu überladen. Man braucht sich also nicht wie beim Kopierkonstruktor genau an einen konkreten Syntax halten um eine automatische Implementierung zu unterbinden. Bei der abgeleiteten Klasse 'CSmartPointerObj' wird nur der private Zuweisungsoperator deklariert, d.h. würde man ihn weglassen, würde automatisch einer implementiert. Dies ist hier sicherlich nicht gewünscht. Damit sich Basisklasse und abgeleitete Klasse nicht unterscheiden und mögliche Verwirrungen hervorrufen, wäre es wohl am besten die privaten Zuweisungsoperatoren bestehen zu lassen. Eine andere mögliche Variante wäre folgende: Man entfernt jeweils den privaten Zuweisungsoperator, muss dann aber in der abgeleiteten Klasse den zweiten Zuweisungsoperator überladen und selbst implementieren. Ich persönlich ziehe die bestehende Variante jedoch vor.

Hier zum Abschluss noch einmal der Quelltext aller vier Klassen:

smartpointer_src.exe

Zurück Nach oben