Templates/Codeschablonen
Programmiersprachen unterscheiden sich in den vorhandenen Ausdrucksmöglichkeiten. C++ ist eine der Sprachen, die sich der strengen Typprüfung verschrieben haben, um möglichst viele Probleme während der Übersetzung entdecken zu können. Strenge Typprüfung ist ein typisches Merkmal der Compilersprachen.
Besonders Interpretersprachen bieten einen anderen Ansatzpunkt. Sie erlauben häufig allgemeingültige Algorithmen zu schreiben, die unabhängig vom verwendeten Datentyp Aufgaben erledigen können. Gerade bei den Grundalgorithmen der Informatik, wie Listen, Feldern oder Bäumen, ist der Algorithmus stets der gleiche. Nur der Datentyp der verwalteten Elemente wird jedesmal neu gewählt.
Auf der Suche nach einer Möglichkeit, die beiden Welten der Typorientierung und der allgemeingültigen Algorithmen zu vereinen, kam man in C++ auf die Konstruktion der templates. Auf deutsch könnte man dies mit Codeschablonen übersetzen.
Ein Template ist eine Vorlage des Programmierers, die einen oder mehrere Platzhalter für Datentypen besitzt. Die Funktion oder die ganze Klasse wird unabhängig von einem speziellen Typ geschrieben. Anstelle eines fest vorgegebenen Datentyps wird bei Bedarf einer der Platzhalter angegeben.
Erst bei der Verwendung der Funktion oder der Klasse erzeugt der Compiler auf Grund seiner Codeschablone den tatsächlichen Code. Dabei verwendet er den bei der Verwendung benutzten oder explizit angegebenen Datentyp.
|
Templates/Codeschablonen Seite 237 |
Überlagerte Funktionen
Bisher haben wir Funktionen kennen gelernt, die überlagert werden konnten. Für die verschiedenen Anwendungszwecke mußten wir immer wieder die gleiche Funktion schreiben, die sich nur durch den verwendeten Parameter unterschied.
In solchen Fällen wurde oft der Präprozessor mit seiner Makrofähigkeit verwendet. Leider können jedoch Präprozessoren nur Texte ersetzen und haben keinerlei Kontrolle über ihr Arbeitsergebnis.
Da somit die Makros des Präprozessors fehleranfällig sind, sollten sie in C++ nicht weiter verwendet werden. Die Aufgabe des Präprozessors ist in C++ hauptsächlich die bedingte Übersetzung.
Rahmen3
Im Beispiel (Bild 17-2) finden wir einen Satz von überlagerten Funktionen, die fast alle den gleichen Algorithmus besitzen und sich nur im verwendeten Datentyp unterscheiden. Die einzige Ausnahme bildet die Vergleichsfunktion für Texte ("char *"). Hier wurden nicht die Zeiger verglichen, sondern die Buchstaben, auf die die Zeiger zeigen.
Trotzdem bilden die Funktionen zusammen keine allgemein gültige Lösung. Jeder neue Datentyp, der verglichen werden soll, benötigt wieder eine neue Funktion.
|
Seite 238 Einsteigerseminar C++ |
Rahmen1
Um die Funktionen benutzen zu können, wurde zusätzlich eine Informationsdatei mit den Funktionsdeklarationen angelegt (Bild 17-1).
Die Informationsdatei muß wie die Implementierung bei jeder neuen Funktion ebenfalls gepflegt werden, da der Anwender sie benötigt.
|
Templates/Codeschablonen Seite 239 |
Rahmen5
Im Hauptprogramm (Bild 17-3) lesen wir mit "include" die Informationsdatei der Funktionen ein und verwenden die einzelnen Funktionen. Der Compiler kann bei der Verwendung wie gewohnt aus der Vielzahl der vorhandenen gleichnamigen Funktionen die passende anhand des Parametertyps auswählen und aufrufen.
Diese Lösung ist der Makrolösung deutlich überlegen. Dem Ziel einer allgemeinen Lösung sind wir damit aber kaum näher gekommen.
|
Seite 240 Einsteigerseminar C++ |
Mit der Entwicklung von C++ wurde die Sprache um einen sehr interessanten Mechanismus erweitert, der Typsicherheit und Allgemeingültigkeit elegant miteinander vereint. Der Standard definiert für die Behandlung allgemeiner Fälle Templates oder Codeschablonen.
Templates sind Vorlagen, die bei ihrer Definition nicht zu Code führen. Erst bei der Verwendung wird der Compiler automatisch benötigten Code erzeugen. Soweit ähneln sie den Makros. Da Codeschablonen vom Compiler bearbeitet werden, bleibt im Gegensatz zu Makros die Typsicherheit erhalten.
Alle Funktionen, die einen identischen Algorithmus benutzen, können in einer Codeschablone zusammengefaßt werden. Im hier verwendeten Beispiel, das wir einmal mit Funktionen und einmal mit einer Codeschablone realisieren wollen, bildet die einzige Ausnahme die Vergleichsfunktion für Texte, die getrennt geschrieben werden muß. Die Templates werden zumeist in eine Informationsdatei geschrieben. Um alle Informationen an einer Stelle zu halten, wurde hier auch die Funktion deklariert, die nicht mit dem normalen Algorithmus arbeitet (Bild 17-4).
Rahmen7
|
Templates/Codeschablonen Seite 241 |
Eine Codeschablone beginnt mit dem Schlüsselwort "template". In spitzen Klammern eingeschlossen folgt die Liste der verwendeten Parameter-Datentypen. In unserem Fall begnügt sich die Codeschablone mit einen Typ. Für die Zwecke einer Codeschablone wird mit class generell ein Datentyp bezeichnet, also nicht nur eigene Klasse sondern auch die vordefinierten Datentypen. Unsere Schablone können wir somit auch mit int verwenden.
An allen Stellen, in denen in den Vergleichsfunktionen ein bestimmter Datentyp vorkam, wird der Datentyp bei der Generierung durch den Parameter TYP ersetzt. Der Name des Parameters ist natürlich frei erfunden.
Rahmen9
|
Seite 242 Einsteigerseminar C++ |
Die Schablone gilt auch für Klassen. In diesem Fall muß jedoch vorausgesetzt werden, daß für die verwendete Klasse der Operator > überlagert wurde.
Im Gegensatz zu Makros generiert der Compiler typgeprüfte Funktionen, die auch linkbar sind.
Das Testprogramm (Bild 17-5) wurde geringfügig geändert. Anstelle der Informationsdatei mit den Deklarationen der überlagerten Funktionen kann nun die Informationsdatei mit der Codeschablone benutzt werden.
Bei jedem Aufruf einer "max()"-Funktion sucht der Compiler, ob es bereits eine exakt passende Funktion gibt. Hat der Anwender eine Funktion geschrieben und ist sie während der Übersetzung bekannt, dann wird sie auch verwendet. Bei der Suche des Compilers, welche Funktion er bei einem Aufruf einsetzen soll, haben selbst geschriebene Funktionen Vorrang vor einer Template-Funktion.
In unserem Fall ist die Vergleichsfunktion mit dem Datentyp "char *" erhalten geblieben, wird getrennt übersetzt und schließlich zum Hauptprogramm hinzu gebunden (Bild 17-6).
Ist keine Funktion bekannt, generiert der Compiler anhand der bekannten Codeschablone eine neue Funktion.
Rahmen11
|
Templates/Codeschablonen Seite 243 |
Typangaben innerhalb von Templates
Zur Erleichterung der Überprüfung durch den Compiler werden Typangaben mit Hilfe des Schlüsselworts typename gekennzeichnet. Will man also z.B. innerhalb eines Templates eine Typdefinition mit "typedef" schreiben, setzt man vor den bezogenen Typnamen "typename".
Linken von Template-Funktionen
Es besteht nun die Möglichkeit, daß Funktionen in unterschiedlichen Modulen getrennt generiert werden. Die mehrfach generierten Funktionen werden beim Linken im Normalfall zu einer einzigen Funktion zusammen gefaßt. Dies steht zwar nicht als Pflicht im Standard, aber die Compilerhersteller bemühen sich, dieses Feature einzubauen.
Mit der Einführung der Codeschablonen haben wir ein sehr flexibles Mittel erhalten, in einfacher und allgemeiner Weise Algorithmen zu definieren.
In vielen Fällen kann man durch die Verwendung von Templates Quellcodezeilen sparen und Algorithmen sehr viel allgemeiner formulieren.
Im Gegensatz zur Lösung mit überlagerten Methoden kann der Compiler bei der Verwendung eines neuen Datentyps selbständig die benötigte Funktion generieren. Der Programmierer muß dabei keine Zeile des bisherigen Codes ändern.
Templates tragen deutlich zu einer Verbesserung der Codequalität bei. Insbesondere Template-Bibliotheken, die eine Vielzahl von getesteten Algorithmen enthalten, sollten an keinem Arbeitsplatz eines Programmierers fehlen.
|
Seite 244 Einsteigerseminar C++ |
Explizite Deklaration von generierten Funktionen
Ähnlich der Deklaration von eigenen Funktionen, die zusätzlich zu einer Codeschablone geschrieben worden sind, können auch mögliche Instanzen für automatisch zu generierenden Funktionen deklariert werden. Die Deklaration hat Konsequenzen bei der Verwendung.
Rahmen13
|
Templates/Codeschablonen Seite 245 |
Im Normalfall wird nur dann eine Template-Funktion erzeugt, wenn die Parameter exakt passen. Versuchen wir, eine Funktion aufzurufen, die bei der Parameterübergabe eine implizite Typkonvertierung benötigen würde, wird keine Funktion angelegt. Der Compiler meldet einen Fehler (Bild 17-7).
Haben wir aber eine zu generierende Funktion nach der Definition der Codeschablone zusätzlich deklariert, dann wird sie auf alle Fälle erzeugt und steht auch für Aufrufe mit impliziter Typkonvertierung zur Verfügung.
Ein Aufruf mit unterschiedlichen Parametern wird nun darauf geprüft, ob die explizit deklarierte Funktion nach entsprechender impliziter Typkonvertierung der Parameter gerufen werden kann.
Sehen wir uns die erzwungene Generierung im folgenden Beispiel einmal an.
Dazu fügen wir zur Demonstration eine Deklaration in das bisherige Beispiel ein (Bild 17-8).
|
Seite 246 Einsteigerseminar C++ |
Rahmen15
Beachten Sie insbesondere die Zeile 21, die nun in der Lage ist, eine implizite Typkonvertierung durchzuführen..
|
Templates/Codeschablonen Seite 247 |
Template-Makros
Codeschablonen können nicht nur Funktionen generieren, sondern auch Makros, die wir als "inline"-Funktionen kennengelernt haben. In der Definition der Codeschablone wird wie gewohnt das Schlüsselwort "inline" vorangestellt, um die Generierung eines Makros zu verlangen. Das Makro wird bei jeder Expansion einkopiert (Bild 17-9).
Die anderen Dateien des Beispiels werden durch die Änderung in der Header-Datei nicht betroffen. Sie müssen nur noch einmal übersetzt und gebunden werden.
Rahmen17
Mit der Generierung einzelner Funktionen konnten Codeschablonen erst einen Teil ihrer Leistungsfähigkeit zeigen. In der erweiterten Form können Templates benutzt werden, um ganze Klassen samt den mit ihr verbundenen Methoden zu generieren.
|
Seite 248 Einsteigerseminar C++ |
Ein besonderes Anwendungsfeld finden Klassen-Templates in den Fällen, in denen Klassen Datenstrukturen zur Verwaltung von Variablen oder Objekten repräsentieren. Solche Verwaltungsstrukturen werden auch als Container bezeichnet.
Containerklassen
Eine Klasse Liste verwaltet Elemente eines bestimmten Datentyps. Alle Listen haben identische Algorithmen. Sie unterscheiden sich nur im Datentyp, der verwaltet wird.
Gleiches gilt für Stacks, die sich ebenfalls nur durch den Datentyp unterscheiden, der am Stack verwaltet wird. Viele weitere solche Fälle existieren: Warteschlangen, Puffer usw. Hier finden Codeschablonen ein weites Betätigungsfeld.
Der große Unterschied zu einem Satz vordefinierter Klassen liegt wie bei den Funktionen im Automatismus. Definieren wir einen neuen Datentyp und wollen Objekte dieses Datentyps in einem mit einer Codeschablone definierten Stack verwalten, dann ist kein Eingriff in den allgemeinen Stackcode notwendig. Alle Anpassungen wird der Compiler für uns vornehmen.
Mit Templates kann man Container allgemein beschreiben.
sind daher ein wichtiges und produktives Merkmal des C++-Standards. Ihre Existenz gehört nicht zwangsläufig in den Bereich der Objektorientierung, aber ihre Existenz ist eine der vielen kleinen Annehmlichkeiten, die C++ dem Programmierer bietet. Auch wird hier deutlich, daß C++ nicht nur eine Sprache ist, sondern eine Zusammenfassung vieler fortschrittlicher Programmiertechniken unter einem Dach.
Es gibt Autoren, die berichten von einer Reduzierung von bis zu 30% des Codes eines Programms durch den massiven Einsatz von Templates.
Als Beispiel soll eine einfache Klasse "feld" dienen, die die grundlegende Verwendung von Codeschablonen für Klassen zeigt (Bild 17-10).
|
Templates/Codeschablonen Seite 249 |
Rahmen19
(Fortsetzung nächste Seite)
|
Seite 250 Einsteigerseminar C++ |
Die Feld-Klasse soll eine beliebige Anzahl gleicher Elemente verwalten. Der zu verwaltende Datentyp steht bei Definition der Klasse noch nicht fest. Bei der Definition wird wieder das Schlüsselwort "template" zusammen mit der Platzhalterliste in spitzen Klammern der eigentlichen Klassendefinition vorangestellt.
Innerhalb der Klassendefinition wird überall wo notwendig der Platzhalter anstelle eines vorhandenen Datentyps benutzt.
Bei der Definition der Methoden muß man berücksichtigen, daß sie ihrerseits auch wieder Codeschablonen sind und deshalb den "template"-Vorspann benötigen.
Alternativ könnte man sie als Makros schreiben, also entweder ohne weitere Zusätze innerhalb der Klassendefinition oder mit dem Schlüsselwort "inline" außerhalb.
Im Anwenderprogramm kann die allgemeine Klasse benutzt werden (Bild 17-11). Allerdings ist es notwendig, den allgemeinen Klassennamen beim Definieren eines Objektes um den aktuell benutzten Datentyp in spitzen Klammern zu ergänzen.
|
Templates/Codeschablonen Seite 251 |
Rahmen21
Erst bei der Übersetzung des Anwenderprogramms werden die Klasse und damit der Datentyp sowie die zugehörigen Methoden generiert. Dabei werden nur die Methoden generiert, die auch verwendet werden. Die Template-Definition allein führt noch nicht zu Code.
Behandlung von eigenen Klassen
Für Klassen gilt die gleiche Spielregel wie für Funktionen. Stellt der Programmierer eine Klasse mit einem vorgegebenen Datentyp zur Verfügung, wird der Compiler keine eigene Klasse anlegen.
Selbstdefinierte Klassen haben wieder Vorrang vor generierten Klassen.
|
Seite 252 Einsteigerseminar C++ |
Der Name einer selbstdefinierten Klasse setzt sich wie bei den generierten Klassen aus dem Namen und dem benutzten Typ in spitzen Klammern zusammen.
Templates mit Wertparametern
In den bisherigen Beispielen wurden als Parameter in der Definition der Codeschablonen ausschließlich Platzhalter für Datentypen verwendet. Neben Datentypenparametern (den Platzhaltern für einen Datentyp) können auch konventionelle Parameter stehen, die Platzhalter für Werte sind und bei der Verwendung mit einem konstanten Wert gefüllt werden.
Rahmen23
(Fortsetzung nächste Seite)
|
Templates/Codeschablonen Seite 253 |
Rahmen23 (2)
Die Größe des Speicherfeldes wurde als Parameter für den Wertkonstruktor angegeben. Nun kann die Größe auch in die Typdefinition selbst mit aufgenommen werden. Dazu wird ein Wertparameter benötigt. Er steht in jeder Methode als Teil des Datentyps zur Verfügung und kann im Code verwendet werden.
Die Wertangaben müssen bei der Definition natürlich konstant sein, sonst kann keine Klasse generiert werden.
Interessant ist im Beispiel (Bild 17-12) die Rückgabe des überlagerten Klammer-Operators, die wir schon einmal kennen gelernt haben. Dank der Verwendung einer Referenzrückgabe kann ein Feldzugriff auf beiden Seiten der Zuweisung stehen.
Die Fehlerbehandlung im Zugriffsoperator ist ein Abbruch des Programms bei einer Verletzung des Index. Eine harte, aber nicht unübliche Maßnahme insbesondere bei Multitasking-Systemen.
|
Seite 254 Einsteigerseminar C++ |
Rahmen25
Im Testrahmen (Bild 17-13) wird ein Objekt zur Verwaltung von 100 "long"-Werten definiert. Genauso gut hätten auch Objekte einer selbstdefinierten Klasse verwaltet werden können. Jeder bekannte Datentyp kann als formaler Typparameter bei der Definition des Feldes verwendet werden.
Wert-Parameter und statischer Speicher
Die als Typinformation übergebenen Werte können auch innerhalb der Klassendefinition verwendet werden. Der Parameter, der die Größe des gewünschten Feldes angibt, kann so für eine statische Speicherallokierung durch den Compiler als Größenangabe des Feldes genutzt werden.
|
Templates/Codeschablonen Seite 255 |
Rahmen27
|
Seite 256 Einsteigerseminar C++ |
Damit wird die möglicherweise fehlerbehaftete Allokierung des Speicherplatzes vom Heap umgangen. Der Preis ist dafür ein wesentlich größeres Objekt und ein Datentyp, der durch seine Größe mitdefiniert wird.
Ändern wir daher das Beispiel noch einmal ab und benutzen die statische Definition des Speicherfeldes (Bild 17-14).
Im Konstruktor muß nun kein Speicher angefordert werden, und der Destruktor ist sozusagen arbeitslos geworden und könnte entfallen.
Im Testrahmen wird eine Zeile eingefügt, die die Größe des neuen Objektes
angibt. Die Größe kann wie üblich mit "sizeof(Objekt)" ermittelt werden.
Die angezeigte Größe ist in C++ die Anzahl der benötigten
Bytes (Bild 17-15).
|
Templates/Codeschablonen Seite 257 |
Gegenüber den bisherigen Objektgrößen, die nur den Verwaltungskopf umfaßten, sehen wir eine deutliche Vergrößerung.
Mit den hat der Programmierer eine umfangreiche Hilfestellung und eine große Bandbreite an Möglichkeiten gewonnen. Ein Preis für diesen Komfort könnte ein deutlich steigender Umfang des Codes sein.
Schließlich genügt im letzten Beispiel bereits eine Änderung der Größe, um einen neuen Datentyp zu erzeugen. Und mit dem Datentyp werden auch alle Methoden erneut erzeugt.
Templates und Bibliotheken
Codeschablonen eignen sich ausgezeichnet, um allgemein gültige Algorithmen in standardisierter Form zur Verfügung zu stellen. Als Teil der Standardisierungs-Bemühungen wurde von der Firma Hewlett & Packard in Zusammenarbeit mit AT&T eine Bibliothek entwickelt, die Teil des kommenden C++-Standards geworden ist.
Mit dieser Entwicklung wirkt die Sprache C++ weit über den einfachen objektorientierten Ansatz hinaus. Neben der Sprache entstehen Standard-Bibliotheken mit umfangreichen Sammlungen von Algorithmen. Je mehr standardisiert ist, desto einfacher können Codegeneratoren oder graphische Software-Baukästen produziert werden.
Mancher Manager träumt schon davon, man Software nicht mehr schreiben muß, sondern nur noch montieren. Ein fragwürdiger Denkansatz. Grundlage jeder erfolgreichen Tätigkeit in der Softwareerstellung bleibt persönliches Engagement und Wissen.
Problem der Fehlerbehandlung
Im Beispiel mit der dynamischen Verwaltung des Speicherplatzes trat ein typisches Problem auf. Was soll man tun, wenn in einer der Methoden ein wichtiger Fehler auftritt?
|
Seite 258 Einsteigerseminar C++ |
Im Falle des Konstruktors wurde bei einem Fehler der maximale Index mit -1 vorbelegt. Hier wurde sozusagen der Betriebsstatus festgehalten. Dieser Status hätte eigentlich in allen weiteren Methoden immer und immer wieder abgefragt werden müssen. Insbesondere beim Zugriffsoperator wurde dies unterlassen, um beim wesentlichen Thema des Kapitels zu bleiben.
Auch in der Zugriffsmethode konnte ein Fehler auftreten, der dann durch Programmabbruch behandelt wurde.
|
Templates/Codeschablonen Seite 259 |
Im nächsten Kapitel
Mit der Verwendung von ganzen Template-Bibliotheken stellt sich auch hier die Frage nach einer korrekten Fehlerbehandlung, die sicher und allgemeingültig sein sollte.
Bisherige Fehlerbehandlungen, die auf dem Begriff einer Statusmeldung basieren, sind weder allgemeingültig noch sicher. Kein Programmierer wird schließlich gezwungen, nach einem Betriebssystemaufruf auch den möglichen Fehlerstatus abzufragen. So manches Programm zeigt erst bei der Benutzung unerwünschte Eigenschaften, die oft auf vergessene Statusabfragen zurückzuführen sind.
Aber auch hier bietet C++ ganz neue und verbesserte Möglichkeiten. Das bisherige Modell der Statusabfrage wird durch das Modell der synchronen Signale oder Fehlerauswürfe ersetzt. Davon handelt das folgende Kapitel.
|
Seite 260 Einsteigerseminar C++ |