Twitter und Facebook-Anbindung
X
Tweet Follow @twitterapi
!!! Anbindung an twitter und facebook öffnen !!!

Wenn Ihnen mein Online-Buch gefällt,
dann bedanken Sie sich doch mit einer kleinen Spende...

24.4.3 Das Umwandeln in ein Template

24.4.3 Das Umwandeln in ein Template

Bevor Sie mit der Umwandlung anfangen können, sollten Sie sich etwas bewusst machen. Ich erwähnte ja bereits, dass man mit Templates Schwierigkeiten mit dem Informationhiding hat, weil man ja quasi eine Kopiervorlage baut. Jeder Quelltext muss also ersichtlich sein. Dies gilt natürlich auch für die CPP Datei. Dies hat zur Folge, dass es nicht mehr reicht, die Header-Datei in der "main" zu inkludieren, weil man nicht nur die reinen Funktionsprototypen braucht, sondern auch die Methodenimplementierungen (jene sollen ja durch den Compiler generiert werden).

Jetzt haben Sie also drei Möglichkeiten.

  1. Sie inkludieren in der "main" die H und die CPP Datei oder
  2. Sie inkludieren die CPP Datei am Ende der H Datei oder
  3. Sie definieren die Methoden in der H Datei als Inline-Methode.

Ich entscheide mich für eine Mischung aus Variante 2 und 3, da es zum einen komisch aussieht bzw. sehr unüblich ist, eine CPP Datei in der "main" zu inkludieren und ich zum anderen Zeigen möchte, wie man eine Templatemethode in einer anderen Datei implementiert, da dies alles andere als nahe liegend ist. Ich werde also den Konstruktor, DestruKtor "inline" implementieren und die Methoden "Append" und "Remove" auslagern. Schauen wir uns also zuerst die neue Header-Datei an, aber ich warne Sie jetzt schon, denn es wird ziehmlich merkwürdig / lustig.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
					
#pragma once



// Klassentemplate für eine Stackklasse die beliebige Elemente aufnehmen kann
template<typename TDataType>
class CStack {
	private:
		struct SStackElement {
			TDataType	tValue;
			SStackElement*	pNext;
		};

		// Zeiger auf das Oberste Element
		SStackElement*		m_pStackPointer;

	public:
		// Initialisieren
		inline CStack() : m_pStackPointer(NULL)	{}

		// Freigeben
		inline ~CStack() {
			while (this->m_pStackPointer != NULL) this->Remove();
		}

		// Etwas auf den Stack legen
		void		Append(TDataType tItem);
		// Etwas vom Stack runter nehmen
		TDataType	Remove();
};

#include "Stack.cpp"
					

Gleich in Zeile 6 und 7 sehen Sie die Templatedefinition für die Klasse. Auch sie beginnt wieder mit dem Schlüsselwort "template". Bis auf die Tatsache, dass ich jetzt eine Klasse definiert habe, gibt es keine weiteren Unterschiede zur Definition eines Funktionstemplates. Wie Sie sehen, wurde aus jedem "float" der neu definierte und später zu ersetzende Datentyp "TDataType" gemacht.

Ab Zeile 19 bis 26 wird jetzt der Konstruktor und Destruktor als Inline-Methode deklariert und ihr zugehörige Funktionsweise implementiert. Bis auf das Schlüsselwort "inline", welches noch nicht einmal notwendig ist, passiert hier nichts besonderes. Es wurde lediglich der Methodenprototyp, durch die komplette Methodenimplementierung ersetzt und auch von der Syntax her, sollte alles klar sein.

In Zeile 29 und 31 stehen dann ganz normal die Prototypen der Methoden, welche ich außerhalb des Templates, also in einer anderen Datei, implementieren werde. Wichtig ist aber die Zeile 33. Hier inkludiere ich jetzt die entsprechende CPP Datei, was eigentlich sehr unüblich, aber für meine Herangehensweise notwendig ist. Das "Include" muss auch unbedingt am Ende der Header-Datei stehen. Aber wenn Sie jetzt glauben, dass dies schon merkwürdig war, dann schauen Sie sich folgenden Quelltext an, welchen ich in die CPP Datei geschrieben habe.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
					
#ifndef STACK_CPP
	#define STACK_CPP
	#include <stdlib.h>
	#include "Stack.h"
	
	
	// Etwas auf den Stack legen //////////////////////////////////////////////
	template<typename TDataType>
	void CStack<TDataType>::Append(TDataType tItem) {
		SStackElement* pElement	= new SStackElement();
		pElement->tValue	= tItem;
		pElement->pNext		= this->m_pStackPointer;
		this->m_pStackPointer	= pElement;
	} // Append ///////////////////////////////////////////////////////////////
	
	
	
	// Etwas vom Stack runter nehmen //////////////////////////////////////////
	template<typename TDataType>
	TDataType CStack<TDataType>::Remove() {
		// Nur wenn was auf dem Stack liegt
		if (this->m_pStackPointer != NULL) {
			SStackElement* pElement	= this->m_pStackPointer;
			this->m_pStackPointer	= pElement->pNext;
			TDataType tReturn	= pElement->tValue;
			pElement->pNext		= NULL;
			delete pElement;
	
			return tReturn;
		} else {
			return (TDataType)0;
		} // end of if
	} // Remove ///////////////////////////////////////////////////////////////
#endif
					

Die erste Merkwürdigkeit sollte hier das "Include" der Header-Datei sein, da ich ja in der Header-Datei, bereits die CPP Datei inkludiert habe. Auf den ersten Blick sollte man meinen, dass man jetzt eine gegenseitige Abhängigkeit geschaffen hat und sich die Dateien jetzt im Kreis inkludieren. Das bringt mich auch gleich zur zweiten Merkwürdigkeit. Normalerweise wäre es auch so, dass sich die Dateien im Kreis inkludieren, aber die CPP Datei besitzt jetzt ihren eigenen Includewächter, wie Sie dies sonst nur aus Header-Dateien kennen. Gemeint ist dieses "#ifndef" und anschließende "#define" in Zeile 1 und 2. Wichtig ist hier, dass man nicht den Präprozessorbefehl "#pragma once" benutzen darf, da jener die komplette Datei inkludiert oder nicht. Das Include der Header-Datei ist aber unbedingt erforderlich und nur die reinen Methoden dürfen nicht doppelt vorkommen. Das "Include" der Header-Datei ist im Zweifelsfall auch nicht kritisch, da sich jene selbst vor doppelten Import schützt.

Aber so richtig Merkwürdig wird es dann in Zeile 8 und 9 und spätestens dort, sollten Ihnen die Nackenhaare zu Berge stehen. Was wird hier gemacht? Zunächst wird wieder rein Formal das Template definiert mit seiner Parameterliste. Dies ist notwendig, um zu definieren, dass kommender Abschnitt, mit zu dem bereits definiertem Template gehört. Dies muss dann auch für jede Methode gemacht werden, wie Sie dies auch in Zeile 19 und 20 sehen. Anschließend fängt man jetzt an, den Methodenheader zu definieren. Es geht los mit dem Rückgabetyp. Anschließend folgt der Klassennamen als Namespace, um die Zugehörigkeit zur Klasse festzulegen. Allerdings handelte es sich um eine Templateklasse, was zur Folge hat, dass man nach dem Klassennamen eine spitze Klammer öffnen muss und den definierten Ersetzungstyp übergibt (ggf. auch mehrere, falls mehrere definiert wurden). Hier benötigt man allerdings nur die Parameter, welche die Methode benötigt. Dann schließt man die Spitze Klammer, setzt den doppelten Doppelpunkt und schreibt den Methodennamen, mit den zugehörigen Parametern der Methode in runden Klammern. Abschließend schreibt man dann noch, wie gewohnt in geschweiften Klammern, den eigentlichen Quelltext der Methode. Dort passiert jetzt nichts mehr aufregendes. Man ersetzt lediglich die vorher festen Datentypen, durch die neu definierten Templatedatentypen.

Sie sehen also, dass es durchaus möglich ist, die Implementierung der Methoden in eine andere Datei auszulagern, allerdings auf Kosten der Verständlichkeit. Ich empfehle Ihnen also, nur Inline-Methoden, wie ich es beim Konstruktor und Destruktor gemacht habe, zu schreiben. Sie sollteen sich allerdings diesen Abschnitt genau anschauen, da es, gerade in den internen Bibliotheken von Visual Studio, gerne mal in dieser Art gemacht wird und Sie nachvollziehen können sollten, was passiert, wenn Sie mal einen solchen Quelltext sehen.

Abschließend möchte ich noch zeigen, wie man dieses Template in der "main" einbindet.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
					
#include <stdio.h>
#include "Stack.h"



// Hauptfunktion der Anwendung //////////////////////////////////////////////// 
int main (int argc, char** argv) {
	CStack<float> oStack;
	oStack.Append(3.7f);
	oStack.Append(4.6f);

	printf("%g\n", oStack.Remove());
	printf("%g\n", oStack.Remove());

	return 0;
} // main /////////////////////////////////////////////////////////////////////
					

Wie Sie gleich in Zeile 2 sehen können, braucht man nur die Header-Datei der Templateklasse einzubinden. In Zeile 8 wird jetzt ein Objekt der Templateklasse instantiiert und ähnlich wie Sie das bei den Funktionstemplates gesehen haben, benötigt man jetzt nach dem Klassennamen spitze Klammern in welchen man jetzt den gewünschten Datentyp angibt. Sie erhalten also als Resultat, wieder einen Stack mit Floats. Der Rest ist dann wie gehabt.

Die Ausgabe ist wieder, und warum sollte es auch anders sein, die Gleiche und deswegen gebe ich sie auch nicht noch einmal an.

Zum Seitenanfang
Zum Inhaltsverzeichnis

© Copyright by Thomas Weiß, 2009 - 2012