Softwareentwicklung in C++ - ASC
Softwareentwicklung in C++ - ASC
Softwareentwicklung in C++ - ASC
Erfolgreiche ePaper selbst erstellen
Machen Sie aus Ihren PDF Publikationen ein blätterbares Flipbook mit unserer einzigartigen Google optimierten e-Paper Software.
Klaus Schmaranz<br />
<strong>Softwareentwicklung</strong> <strong>in</strong> <strong>C++</strong><br />
18. November 2002<br />
Spr<strong>in</strong>ger-Verlag<br />
Berl<strong>in</strong> Heidelberg NewYork<br />
London Paris Tokyo<br />
Hong Kong Barcelona<br />
Budapest
Vorwort des Autors<br />
C ++ ist aus vielen guten Gründen derzeit e<strong>in</strong>e der am weitesten verbreiteten<br />
objektorientierten Programmiersprachen. E<strong>in</strong>er der wichtigsten der angesprochenen<br />
vielen guten Gründe für diese Verbreitung ist sicherlich die Flexibilität<br />
der Sprache, die es erlaubt praktisch von tiefster Masch<strong>in</strong>enebene<br />
bis zu beliebig hohen Abstraktionslevels alle Probleme auf e<strong>in</strong>e saubere und<br />
konsistente Art anpacken zu können.<br />
Durch se<strong>in</strong>e mittlerweile schon recht lange Geschichte (bezogen auf die<br />
schnelllebige Welt der Informatik) hat der Sprachstandard von C ++ im Lauf<br />
der Zeit bereits e<strong>in</strong>ige kle<strong>in</strong>e und größere Änderungen h<strong>in</strong>ter sich gebracht.<br />
Alle diese Anpassungen waren <strong>in</strong> me<strong>in</strong>en Augen immer sehr durchdachte Verbesserungen,<br />
die dazu geführt haben, dass man gewisse D<strong>in</strong>ge noch eleganter<br />
lösen konnte als zuvor. Vor allem hatten die angesprochenen Veränderungen<br />
zum allergrößten Teil den Charakter von Erweiterungen, es wurden also<br />
ke<strong>in</strong>e künstlichen Inkompatibilitäten erzeugt, die e<strong>in</strong> mov<strong>in</strong>g Target <strong>in</strong> der<br />
Entwicklung dargestellt hätten. Ich b<strong>in</strong> selbst <strong>in</strong> die C ++ Entwicklung bereits<br />
zu Zeiten der sehr frühen Versionen e<strong>in</strong>gestiegen und war begeistert von den<br />
Möglichkeiten, elegante Lösungen für Probleme zu konzipieren. Jetzt, viele<br />
Jahre später, b<strong>in</strong> ich von C ++ <strong>in</strong> se<strong>in</strong>er heutigen Form mehr begeistert denn<br />
je.<br />
E<strong>in</strong>es soll allerd<strong>in</strong>gs auch nicht verschwiegen werden: Um C ++ wirklich<br />
zu beherrschen, braucht man viel Erfahrung! Vor allem muss man den S<strong>in</strong>n<br />
h<strong>in</strong>ter den e<strong>in</strong>zelnen Konstrukten der Sprache wirklich verstanden haben, um<br />
sie richtig e<strong>in</strong>setzen zu können. Die Gefahr, gewisse Features aufgrund von<br />
Unverständnis zu missbrauchen und dadurch unsauberen Code zu erzeugen,<br />
ist nicht ger<strong>in</strong>g!<br />
Leider muss ich sagen, dass gar nicht so wenige Entwickler, die C ++ e<strong>in</strong>setzen,<br />
die Sprache nicht <strong>in</strong> vollem Umfang beherrschen. Jedoch, und das ist<br />
genau das Problematische daran, glauben die meisten dieser Entwickler, dass<br />
sie gut mit C ++ umgehen könnten. Aus diesem Grund habe ich versucht, die<br />
Erfahrungen aus me<strong>in</strong>en eigenen Fehlern sowie die Erfahrungen mit Fehlern<br />
und Unverständnis Anderer <strong>in</strong> dieses Buch e<strong>in</strong>zubr<strong>in</strong>gen.<br />
Das Buch ist nach bestem Wissen und Gewissen so gestaltet, dass C ++<br />
und die wichtigen Aspekte, die erst e<strong>in</strong>e saubere Objektorientierung ausmachen,<br />
so aufbauend und gut strukturiert wie möglich vermittelt werden.
VI Vorwort des Autors<br />
Es ist das vorliegende Werk auch zusätzlich noch e<strong>in</strong> Resultat aus se<strong>in</strong>er<br />
Verwendung im Rahmen e<strong>in</strong>es im Studium sehr früh angesiedelten Programmierpraktikums<br />
an der Technischen Universität Graz. Aus den Reihen der<br />
Studierenden, die quasi als “Versuchskan<strong>in</strong>chen” fungierten kamen sehr viele<br />
und sehr fruchtbare Anregungen, was den Aufbau und gewisse Erklärungen<br />
betrifft. Das Buch wurde aber nicht nur von Leuten gelesen, die C ++ erst lernen<br />
mussten, sondern auch von e<strong>in</strong>igen Leuten, die bereits echte C ++ Profis<br />
s<strong>in</strong>d. Von beiden Lesergruppen gab es durchgehend sehr positive Reaktionen,<br />
sowohl was die fachliche Seite als auch was die Strukturierung und die leserische<br />
Seite betrifft (der Begriff “literarische” Seite wäre wohl doch etwas zu<br />
hoch gegriffen :-)). Auch die Profis waren angetan davon, dass sie <strong>in</strong> e<strong>in</strong>igen<br />
Punkten durch die Lektüre dieses Buchs e<strong>in</strong> tieferes Verständnis für gewisse<br />
D<strong>in</strong>ge vermittelt bekamen und entsprechend etwas dazugelernt haben.<br />
Obwohl es <strong>in</strong> diesem Buch naturgegebenermaßen sehr stark um technische<br />
Aspekte geht, habe ich versucht, es so angenehm lesbar wie möglich zu<br />
machen. Ich b<strong>in</strong> e<strong>in</strong>fach der Ansicht, dass Fachbücher nicht nur trocken irgendwelche<br />
Fakten vermitteln sollen, sondern dass man auch Spaß am Lesen<br />
haben muss. Auf diese Art hat man gleich viel mehr Motivation, das Gelernte<br />
auch wirklich <strong>in</strong> die Tat umzusetzen.<br />
E<strong>in</strong>en Aspekt zum Umgang mit diesem Buch, der im Pr<strong>in</strong>zip für alle<br />
Fachbücher Gültigkeit besitzt, möchte ich hier noch kurz ansprechen: Es<br />
steckt e<strong>in</strong>e riesige Menge an Information auf den e<strong>in</strong>zelnen Seiten. Je nach<br />
Wissensstand der Leser ist es im Normalfall praktisch nicht möglich, diese<br />
gesamte Information auch wirklich bei e<strong>in</strong>maligem Durchlesen zu ver<strong>in</strong>nerlichen.<br />
Deshalb ist es sehr geistreich, nach e<strong>in</strong>iger Zeit das Buch erneut zur<br />
Hand zu nehmen und es noch e<strong>in</strong>mal durchzugehen. E<strong>in</strong>er me<strong>in</strong>er Studierenden<br />
hat dies mit folgender Aussage auf den Punkt gebracht:<br />
Ich habe das Buch gelesen und mich mit allen dar<strong>in</strong> beschriebenen D<strong>in</strong>gen<br />
gespielt, bis ich geglaubt habe, sie zu verstehen. Danach habe ich das Buch<br />
als Nachschlagewerk verwendet. Nach e<strong>in</strong>iger Zeit dachte ich mir, ich lese es<br />
e<strong>in</strong>fach zum Spaß noch e<strong>in</strong>mal von vorne bis h<strong>in</strong>ten durch. Und bei diesem<br />
zweiten Mal s<strong>in</strong>d mir erst viele D<strong>in</strong>ge aufgefallen, die bisher spurlos an mir<br />
vorüber gegangen s<strong>in</strong>d. Genau diese D<strong>in</strong>ge aber waren die wirklich Wichtigen<br />
für die Praxis.<br />
In diesem S<strong>in</strong>ne wünsche ich allen Lesern viel Spaß bei der Lektüre und<br />
e<strong>in</strong>en guten E<strong>in</strong>stieg <strong>in</strong> die Welt von C ++.<br />
Klaus Schmaranz Graz im August 2002
Danksagung<br />
Vorwort des Autors VII<br />
E<strong>in</strong> Buch zu schreiben ist ganz grundsätzlich e<strong>in</strong>mal sehr viel Arbeit. Vor<br />
allem ist es Arbeit, die man nicht durchgehend alle<strong>in</strong> im stillen Kämmerle<strong>in</strong><br />
erledigen kann, denn e<strong>in</strong>erseits wird man betriebsbl<strong>in</strong>d und andererseits<br />
würde man zwischendurch mehr als nur e<strong>in</strong>mal die Nerven nach e<strong>in</strong>er durchtippten<br />
Nacht wegwerfen. Aus diesem Grund gilt me<strong>in</strong> Dank <strong>in</strong> der Folge<br />
nicht nur den Personen, die <strong>in</strong> Form von Diskussionen und Kritik den Inhalt<br />
direkt und nachhaltig bee<strong>in</strong>flusst haben und den Personen, die das Buch<br />
lektoriert haben. Me<strong>in</strong> Dank gilt ebenso allen Personen, die für mich immer<br />
ansprechbar waren, wenn me<strong>in</strong> Nervenkostüm wieder e<strong>in</strong>mal nicht im<br />
allerbesten Zustand war. E<strong>in</strong>ige der nachstehend angeführten Leute haben<br />
<strong>in</strong> diesem ganzen Prozess auch mehr als nur e<strong>in</strong>e Rolle übernommen.<br />
Ich kann und möchte ke<strong>in</strong>e Reihung der Wichtigkeit der e<strong>in</strong>zelnen Beteiligten<br />
vornehmen, denn dies ist e<strong>in</strong> D<strong>in</strong>g der Unmöglichkeit. Jede e<strong>in</strong>zelne<br />
Person war e<strong>in</strong> wichtiger Teil <strong>in</strong> e<strong>in</strong>em komplizierten Puzzle, das nach se<strong>in</strong>er<br />
Zusammensetzung das fertige Buch ergeben hat. Wie es bei Puzzles üblich<br />
ist, h<strong>in</strong>terlässt jedes fehlende Stück e<strong>in</strong> Loch und jedes e<strong>in</strong>zelne Loch stört<br />
das Gesamtbild enorm.<br />
Es wird Zeit, zum Wesentlichen zu kommen: Me<strong>in</strong> Dank gilt me<strong>in</strong>em<br />
Institutsleiter Hermann Maurer, der mehr als e<strong>in</strong>fach nur “der Chef” ist.<br />
Auch als guter Freund steht er immer mit aufmunternden Worten, gutem<br />
Rat und Unterstützung zur Seite. Wie bereits bei me<strong>in</strong>em ersten Buch <strong>Softwareentwicklung</strong><br />
<strong>in</strong> C war auch hier wieder Hermann Engesser me<strong>in</strong> direkter<br />
Ansprechpartner beim Verlag und <strong>in</strong> gewohnter Weise unbürokratisch und<br />
sehr kooperativ. Me<strong>in</strong>e Arbeitskollegen, Informatik-Gurus und außerdem<br />
guten Freunde Karl Blüml<strong>in</strong>ger, Christof Dallermassl und Dieter Freismuth<br />
waren freiwillige Leser des Manuskripts und durchkämmten dieses auf fachliche<br />
Ungereimtheiten und sonstige Fehler. Christof Rabel, se<strong>in</strong>es Zeichens<br />
enorm kompetenter C ++ Spezialist, war durch se<strong>in</strong>e mannigfaltigen Kommentare<br />
und se<strong>in</strong>e Diskussionsbereitschaft e<strong>in</strong>e sehr große Hilfe, wenn ich<br />
wieder e<strong>in</strong>mal mit Betriebsbl<strong>in</strong>dheit geschlagen war. Ebenso wichtig waren<br />
viele Diskussionen mit me<strong>in</strong>em guten Freund und Informatik-Guru Dirk<br />
Schwartmann, der mir viele Denkanstöße zur Aufbereitung des Inhalts aus<br />
se<strong>in</strong>em reichen Erfahrungsschatz gab. Da schon sehr viel von guten Freunden<br />
die Rede war, geht es gleich mit solchen weiter: Me<strong>in</strong> herzlichster Dank<br />
gebührt Monika Tragner und Anita Lang, die aufgrund ihrer germanistischen<br />
Kompetenz ehrenamtlich und kurz entschlossen das Lektorat für dieses Buch<br />
übernommen haben. Neben den wichtigen Personen <strong>in</strong> me<strong>in</strong>em Leben, die<br />
direkt an der Arbeit beteiligt waren, gibt es auch solche, die “nur” <strong>in</strong>direkt<br />
ihren Teil zur Entstehung des Buchs beigetragen haben, <strong>in</strong>dem sie e<strong>in</strong>fach für<br />
mich da waren. Dies bedeutet aber nicht, dass ihr Beitrag deshalb ger<strong>in</strong>ger<br />
gewesen wäre. Hierbei wären vor allem Manuela Burger und me<strong>in</strong>e Eltern zu<br />
nennen, die mir <strong>in</strong> vielerlei H<strong>in</strong>sicht immer e<strong>in</strong>e ganz große Hilfe waren und<br />
s<strong>in</strong>d.
VIII Vorwort des Autors<br />
Last, but not least, geht auch me<strong>in</strong> besonderer Dank an die Studierenden<br />
der TU-Graz, die im Sommersemester 2002 das Programmierpraktikum besucht<br />
haben, sowie an Harald Krottmaier und an die Tutoren und Tutor<strong>in</strong>nen,<br />
die bei der Durchführung desselben geholfen haben.<br />
Aus besonders traurigem Anlass möchte ich diese Danksagung zum Zeitpunkt<br />
der Drucklegung an dieser Stelle noch ergänzen und widme dieses Buch me<strong>in</strong>em<br />
Vater, dessen Tod mich heute völlig unerwartet, aber dafür umso tiefer<br />
getroffen hat.<br />
Klaus Schmaranz Graz am 17. November 2002
Inhaltsverzeichnis<br />
1. Ziel und Inhalt dieses Buchs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1<br />
1.1 Zum Inhalt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3<br />
1.2 Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6<br />
1.3 Feedback . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8<br />
1.4 Die beiliegende CD-ROM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9<br />
Teil I: Low-Level Konzepte von <strong>C++</strong> . . . . . . . . . . . . . . . . . . . . . . . . . 11<br />
2. Datentypen und Variablen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13<br />
2.1 Primitive Datentypen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13<br />
2.2 Deklaration, Def<strong>in</strong>ition und Initialisierung . . . . . . . . . . . . . . . . . 20<br />
2.3 Das erste <strong>C++</strong> Programm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25<br />
2.4 Zusammengesetzte Datentypen . . . . . . . . . . . . . . . . . . . . . . . . . . . 31<br />
2.4.1 Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31<br />
2.4.2 Structures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37<br />
2.4.3 Unions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40<br />
2.5 Scope und Lifetime . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46<br />
2.6 Symbolische Konstanten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47<br />
2.7 Eigene Typdef<strong>in</strong>itionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49<br />
3. Operatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53<br />
3.1 Überblick und Reihenfolge der Auswertung . . . . . . . . . . . . . . . . 53<br />
3.2 Arithmetische Operatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57<br />
3.3 Logische- und Vergleichsoperatoren . . . . . . . . . . . . . . . . . . . . . . . 59<br />
3.4 Bitoperatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60<br />
3.5 Zuweisungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61<br />
3.6 Datentypabfragen und explizite Typecasts . . . . . . . . . . . . . . . . . 61<br />
3.6.1 Type Identification und Run-Time-Type-Information. 62<br />
3.6.2 Unchecked Cast . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67<br />
3.6.3 Compiletime-checked Cast . . . . . . . . . . . . . . . . . . . . . . . . 67<br />
3.6.4 Runtime-checked Cast . . . . . . . . . . . . . . . . . . . . . . . . . . . 69<br />
3.6.5 Remove-const Cast . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70<br />
3.6.6 C-Style Casts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
X Inhaltsverzeichnis<br />
4. Kontrollstrukturen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73<br />
4.1 Selection Statements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74<br />
4.2 Schleifen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79<br />
4.3 Das unselige goto Statement . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82<br />
5. Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85<br />
6. Po<strong>in</strong>ter und References . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103<br />
6.1 References . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103<br />
6.2 Po<strong>in</strong>ter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114<br />
6.2.1 Po<strong>in</strong>ter und Adressen . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115<br />
6.2.2 Dynamische Memory Verwaltung . . . . . . . . . . . . . . . . . . 119<br />
6.2.3 Str<strong>in</strong>gs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125<br />
6.2.4 Funktionspo<strong>in</strong>ter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125<br />
6.2.5 Besondere Aspekte von Po<strong>in</strong>tern . . . . . . . . . . . . . . . . . . 126<br />
Call-by-reference auf Po<strong>in</strong>ter . . . . . . . . . . . . . . . . . . . . . . 126<br />
Mehrfachpo<strong>in</strong>ter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128<br />
Po<strong>in</strong>ter und Typecasts . . . . . . . . . . . . . . . . . . . . . . . . . . . 131<br />
7. Der Preprocessor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135<br />
7.1 Include Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136<br />
7.2 Bed<strong>in</strong>gte Übersetzung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137<br />
7.3 Macros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138<br />
Teil II: Objektorientierte Konzepte von <strong>C++</strong> . . . . . . . . . . . . . . . . . 141<br />
8. Objektorientierung Allgeme<strong>in</strong> . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143<br />
8.1 Module und Abläufe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146<br />
8.1.1 Der Weg zum Arbeitsplatz – e<strong>in</strong> kle<strong>in</strong>es Beispiel . . . . . 146<br />
8.2 Klassen und Objekte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155<br />
8.3 Richtige Verwendung der OO Mechanismen . . . . . . . . . . . . . . . . 161<br />
9. Klassen <strong>in</strong> <strong>C++</strong> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167<br />
9.1 Besonderheiten von Structures <strong>in</strong> <strong>C++</strong> . . . . . . . . . . . . . . . . . . . . 168<br />
9.2 E<strong>in</strong>fache Klassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171<br />
9.2.1 Konstruktor und Destruktor genauer beleuchtet . . . . . 178<br />
9.2.2 Der Copy Konstruktor . . . . . . . . . . . . . . . . . . . . . . . . . . . 184<br />
9.2.3 Initialisierung vs. Zuweisung . . . . . . . . . . . . . . . . . . . . . . 188<br />
9.2.4 Deklarieren von Konstruktoren als explicit . . . . . . . . 189<br />
9.2.5 Object- und Class-Members. . . . . . . . . . . . . . . . . . . . . . . 192<br />
9.3 Abgeleitete Klassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 196<br />
9.3.1 Mehrfachvererbung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 205<br />
9.3.2 Konstruktoren und Destruktoren . . . . . . . . . . . . . . . . . . 215<br />
9.4 Weitere wichtige technische Aspekte . . . . . . . . . . . . . . . . . . . . . . 219<br />
9.4.1 Static und Dynamic B<strong>in</strong>d<strong>in</strong>g . . . . . . . . . . . . . . . . . . . . . . 219
Inhaltsverzeichnis XI<br />
9.4.2 Abstrakte Methoden und Klassen. . . . . . . . . . . . . . . . . . 232<br />
9.4.3 Virtuelle Ableitung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 235<br />
9.4.4 Downcasts von Klassen . . . . . . . . . . . . . . . . . . . . . . . . . . . 241<br />
9.4.5 Friends von Klassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 247<br />
9.4.6 Overload<strong>in</strong>g von const und non-const Methoden . . . 250<br />
9.4.7 Besonderheiten bei der Initialisierung . . . . . . . . . . . . . . 251<br />
9.4.8 Temporäre Objekte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 253<br />
10. Memory – e<strong>in</strong> kle<strong>in</strong>es Beispiel . . . . . . . . . . . . . . . . . . . . . . . . . . . . 257<br />
10.1 Das ADD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 257<br />
10.1.1 Identifikation der Grobmodule . . . . . . . . . . . . . . . . . . . . 258<br />
10.1.2 Weitere Zerlegung der e<strong>in</strong>zelnen Grobmodule . . . . . . . 259<br />
Input Handl<strong>in</strong>g . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 259<br />
Spielsteuerung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 259<br />
Output Handl<strong>in</strong>g . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 260<br />
Memory Spielfeld . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 260<br />
Memory Karte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 260<br />
Commandl<strong>in</strong>e Handl<strong>in</strong>g . . . . . . . . . . . . . . . . . . . . . . . . . . 260<br />
10.2 Das DDD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 260<br />
10.2.1 Klassendiagramm. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 261<br />
10.2.2 Klassendeklarationen . . . . . . . . . . . . . . . . . . . . . . . . . . . . 261<br />
10.2.3 Vector . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 263<br />
10.2.4 ObjectDeletor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 266<br />
10.2.5 Konkrete Deletors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 267<br />
10.2.6 ArgumentHandler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 271<br />
10.2.7 MemoryCommandl<strong>in</strong>eArgumentHandler . . . . . . . . . . . . . 272<br />
10.2.8 Commandl<strong>in</strong>eHandl<strong>in</strong>g . . . . . . . . . . . . . . . . . . . . . . . . . . . 273<br />
10.2.9 SimpleOutputHandl<strong>in</strong>g . . . . . . . . . . . . . . . . . . . . . . . . . . 275<br />
10.2.10 Displayable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 278<br />
10.2.11 OutputContext . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 279<br />
10.2.12 TextOutputContext . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 279<br />
10.2.13 GameCard . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 281<br />
10.2.14 MemoryGameCard . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 282<br />
10.2.15 MemoryGameboard . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 284<br />
10.2.16 IntDisplayable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 289<br />
10.2.17 TextDisplayable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 290<br />
10.2.18 Event . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 292<br />
10.2.19 WordEvent . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 293<br />
10.2.20 SimpleInputHandl<strong>in</strong>g . . . . . . . . . . . . . . . . . . . . . . . . . . . 294<br />
10.2.21 SimpleEventHandl<strong>in</strong>g . . . . . . . . . . . . . . . . . . . . . . . . . . . 296<br />
10.2.22 MemoryGameControl . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 296<br />
10.2.23 MemoryCardSymbolGenerator . . . . . . . . . . . . . . . . . . . . . 300<br />
10.2.24 MemoryCardpair . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 302<br />
10.3 Auszüge aus der Implementation . . . . . . . . . . . . . . . . . . . . . . . . . 304
XII Inhaltsverzeichnis<br />
11. Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 311<br />
12. Operator Overload<strong>in</strong>g . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 335<br />
12.1 Grundpr<strong>in</strong>zipien des Operator Overload<strong>in</strong>gs . . . . . . . . . . . . . . . . 335<br />
12.2 Typumwandlungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 354<br />
12.3 Speicherverwaltung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 363<br />
12.3.1 E<strong>in</strong>faches new und delete . . . . . . . . . . . . . . . . . . . . . . . . 363<br />
12.3.2 Array new und delete . . . . . . . . . . . . . . . . . . . . . . . . . . . 371<br />
12.3.3 Placement Operator new . . . . . . . . . . . . . . . . . . . . . . . . . 374<br />
12.3.4 delete mit zwei Parametern . . . . . . . . . . . . . . . . . . . . . . 381<br />
12.3.5 Globale new und delete Operatoren . . . . . . . . . . . . . . . 385<br />
12.3.6 Weitere Aspekte der eigenen Speicherverwaltung . . . . 391<br />
Vererbung von new und delete . . . . . . . . . . . . . . . . . . . 391<br />
Verhalten bei “Ausgehen” des Speichers . . . . . . . . . . . . 397<br />
12.4 Abschließendes zu overloadable Operators . . . . . . . . . . . . . . . . . 400<br />
13. Templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 405<br />
13.1 Function Templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 407<br />
13.2 Overload<strong>in</strong>g Aspekte von Function Templates . . . . . . . . . . . . . . 414<br />
13.3 Class Templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 419<br />
13.4 Ableiten von Class Templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . 428<br />
13.5 Explizite Spezialisierungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 431<br />
13.6 Verschiedenes zu Templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 444<br />
13.7 Source Code Organisation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 447<br />
14. Namespaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 453<br />
15. Verschiedenes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 463<br />
15.1 mutable Member Variablen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 463<br />
15.2 Unions im OO Kontext . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 465<br />
15.3 Funktionspo<strong>in</strong>ter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 470<br />
15.4 Besondere Keywords, Diagraphs und Trigraphs . . . . . . . . . . . . . 475<br />
15.5 volatile Objekte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 477<br />
15.6 RTTI und dynamic cast im OO Kontext . . . . . . . . . . . . . . . . . . 477<br />
15.7 Weiterführendes zu Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . 482<br />
Teil III: Ausgesuchte Teile aus der <strong>C++</strong> Standard Library . . . 491<br />
16. Die <strong>C++</strong> Standard Library . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 493<br />
16.1 Übersicht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 493<br />
16.2 Conta<strong>in</strong>er . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 497<br />
16.2.1 Vektoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 497<br />
16.2.2 Listen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 499<br />
16.2.3 Double-Ended Queues . . . . . . . . . . . . . . . . . . . . . . . . . . . . 501<br />
16.2.4 Standard Queues . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 502
Inhaltsverzeichnis XIII<br />
16.2.5 Priority Queues . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 503<br />
16.2.6 Stacks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 505<br />
16.2.7 Maps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 506<br />
16.2.8 Sets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 507<br />
16.2.9 Zusammenfassung der Conta<strong>in</strong>er-Operationen . . . . . . . 510<br />
16.3 Iterators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 513<br />
16.4 Allocators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 517<br />
16.5 Str<strong>in</strong>gs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 518<br />
16.6 Streams . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 521<br />
16.7 Numerik . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 531<br />
16.8 Algorithmen und Funktionsobjekte . . . . . . . . . . . . . . . . . . . . . . . 534<br />
A. Cod<strong>in</strong>g-Standard . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 537<br />
A.1 Generelle Regeln . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 537<br />
A.2 Cod<strong>in</strong>g-Rules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 538<br />
A.3 Design Guidel<strong>in</strong>es . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 541<br />
B. Vollständige Implementation des Memory Spiels . . . . . . . . . . 543<br />
B.1 Implementationen der e<strong>in</strong>zelnen Klassen . . . . . . . . . . . . . . . . . . . 543<br />
B.1.1 Das Hauptprogramm. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 543<br />
B.1.2 Implementation von Vector . . . . . . . . . . . . . . . . . . . . . . 545<br />
B.1.3 Implementation von Commandl<strong>in</strong>eHandl<strong>in</strong>g . . . . . . . . . 546<br />
B.1.4 Implementation von SimpleOutputHandl<strong>in</strong>g . . . . . . . . 547<br />
B.1.5 Implementation von GameCard . . . . . . . . . . . . . . . . . . . . 547<br />
B.1.6 Implementation von MemoryGameCard . . . . . . . . . . . . . . 547<br />
B.1.7 Implementation von MemoryGameboard . . . . . . . . . . . . . 548<br />
B.1.8 Implementation von SimpleInputHandl<strong>in</strong>g . . . . . . . . . 552<br />
B.1.9 Implementation von MemoryGameControl . . . . . . . . . . . 552<br />
B.1.10 Implementation von MemoryCardSymbolGenerator. . . 554<br />
B.1.11 Implementation von MemoryCardpair . . . . . . . . . . . . . . 556<br />
B.1.12 Variablen für die konkreten Deletors . . . . . . . . . . . . . . . 557<br />
B.1.13 Das MemoryMakefile . . . . . . . . . . . . . . . . . . . . . . . . . . . . 557<br />
Literaturverzeichnis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 561<br />
Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 563
1. Ziel und Inhalt dieses Buchs<br />
Dieses Buch richtet sich an alle, die gerne die Programmiersprache C ++ von<br />
Grund auf erlernen wollen oder ihre Kenntnisse vertiefen wollen. Es wird<br />
hierbei e<strong>in</strong> gewisses Grundverständnis für die <strong>Softwareentwicklung</strong> im Allgeme<strong>in</strong>en<br />
vorausgesetzt. Kenntnisse der Programmiersprache C s<strong>in</strong>d natürlich<br />
auch nicht von Nachteil. C ++ hat sich, wie der Name schon sagt, aus C entwickelt<br />
und ist dessen objektorientiertes Pendant. Im Pr<strong>in</strong>zip ist mit sehr<br />
wenigen Ausnahmen alles, was für C gilt, auch für C ++ gültig, allerd<strong>in</strong>gs ist<br />
der Sprachumfang von C ++ durch die Objektorientierung bedeutend größer.<br />
Lesern, die noch ke<strong>in</strong>e Erfahrung mit der Programmiersprache C haben, oder<br />
vielleicht noch überhaupt ke<strong>in</strong>e Erfahrung mit der <strong>Softwareentwicklung</strong> im<br />
Allgeme<strong>in</strong>en, sei als Grundlage, vor dem Durcharbeiten dieses Buchs, das<br />
Buch <strong>Softwareentwicklung</strong> <strong>in</strong> C ans Herz gelegt.<br />
Um nicht missverstanden zu werden: Ich möchte mit diesen e<strong>in</strong>leitenden<br />
Sätzen ke<strong>in</strong>eswegs den Verkauf des oben genannten Buchs ankurbeln (e<strong>in</strong>e<br />
elektronische Version desselben liegt ohneh<strong>in</strong> gratis der mitgelieferten CD-<br />
ROM bei). Es ist auch im vorliegenden Buch alles an Information enthalten,<br />
was man zum Erlernen von C ++ braucht, auch wenn man noch ke<strong>in</strong> C kann.<br />
Nur ist das grundlegende C-Wissen im zuvor genannten Buch ungleich detaillierter<br />
enthalten und daher für E<strong>in</strong>steiger bei weitem leichter genießbar.<br />
E<strong>in</strong>e derartig detaillierte Beschreibung der grundlegenden D<strong>in</strong>ge im hier vorliegenden<br />
Buch über C ++ würde dessen Rahmen bei weitem sprengen.<br />
Die Intention h<strong>in</strong>ter der Empfehlung, zuerst C zu lernen, hat gute Gründe,<br />
die ich noch kurz näher erläutern möchte: Um C ++ wirklich gut zu beherrschen,<br />
muss man unbed<strong>in</strong>gt tieferes Verständnis <strong>in</strong> zwei verschiedenen Wissensgebieten<br />
erlangen:<br />
1. Man muss das Konzept der objektorientierten (kurz: OO) Programmierung<br />
ver<strong>in</strong>nerlicht haben, um e<strong>in</strong> sauber durchdachtes und schlüssiges<br />
Softwaredesign erstellen zu können.<br />
2. Man muss e<strong>in</strong> gewisses Verständnis für die <strong>in</strong>terne Arbeitsweise e<strong>in</strong>es<br />
Computers entwickelt haben, um das sauber durchdachte Design auch<br />
vernünftig umsetzen zu können.<br />
Die Erfahrung zeigt, dass man viel Zeit und Nerven spart, wenn man nicht<br />
beide D<strong>in</strong>ge zugleich lernt. In vielen Büchern und <strong>in</strong> noch mehr Kursen wird
2 1. Ziel und Inhalt dieses Buchs<br />
versucht, <strong>Softwareentwicklung</strong> gleich anhand e<strong>in</strong>er objektorientierten Sprache<br />
zu lehren. Das Argument für diese Vorgehensweise kl<strong>in</strong>gt auf den ersten<br />
Blick sehr schlüssig: OO-Sprachen wurden entwickelt, weil den Menschen die<br />
OO-Denkweise besser liegt als die imperative. Also stellen OO-Sprachen, im<br />
Vergleich zu imperativen Sprachen, e<strong>in</strong>e deutliche Verbesserung dar und s<strong>in</strong>d<br />
deswegen auch leichter zu erlernen.<br />
Leider wurden bei diesem Argument e<strong>in</strong> paar Kle<strong>in</strong>igkeiten übersehen:<br />
Erstens haben Problemlösungen, egal ob mit OO-Sprachen oder mit imperativen<br />
Sprachen, immer e<strong>in</strong>en imperativen Anteil. Man muss ja e<strong>in</strong>em Computer<br />
“genau sagen, was er tun soll”, zum<strong>in</strong>dest im Kle<strong>in</strong>en. Das geht ohne<br />
Verständnis für die <strong>in</strong>terne Arbeitsweise nicht wirklich. Zweitens existiert<br />
sehr wohl e<strong>in</strong> Unterschied zwischen der saloppen Denkweise von Menschen<br />
und den absolut exakten Formulierungen, die e<strong>in</strong>er Programmiersprache zugrunde<br />
liegen. Drittens wurden OO-Sprachen über lange Zeit h<strong>in</strong>weg immer<br />
weiterentwickelt, um noch eleganter und besser e<strong>in</strong>setzbar zu se<strong>in</strong>. Dementsprechend<br />
fanden viele Konstrukte <strong>in</strong> OO-Sprachen E<strong>in</strong>gang, die erst dann<br />
zu verstehen s<strong>in</strong>d, wenn man die notwendige Erfahrung hat, um deren S<strong>in</strong>n<br />
nachvollziehen zu können.<br />
Wählt man also für die ersten Schritte <strong>in</strong> der <strong>Softwareentwicklung</strong> gleich<br />
e<strong>in</strong>e OO-Sprache, so ist man gezwungen, e<strong>in</strong>ige Interna zu begreifen, obwohl<br />
das notwendige Handwerkszeug und die notwendige Erfahrung dazu fehlen.<br />
Me<strong>in</strong>e Beobachtungen von C ++ Neul<strong>in</strong>gen zeigen folgendes Bild:<br />
• Die meisten Entwickler, die zuvor entweder C oder e<strong>in</strong>e andere imperative<br />
Sprache beherrschen, f<strong>in</strong>den sehr schnell Gefallen an C ++, da sie endlich<br />
e<strong>in</strong> Problem elegant lösen können. Es ist mit der nötigen Erfahrung bei<br />
richtiger Anwendung des OO-Ansatzes (und nur dann!) möglich, Probleme<br />
elegant <strong>in</strong> kle<strong>in</strong>e, <strong>in</strong> sich abgeschlossene Teilprobleme zu zerlegen.<br />
Damit kann man sich immer um das Wesentliche des entsprechenden Teilproblems<br />
kümmern, ohne durchgehend das große Ganze akribisch im Auge<br />
behalten zu müssen. Natürlich geht dies nur, nachdem das große Ganze<br />
e<strong>in</strong>mal vernünftig auf höherer Abstraktionsebene entworfen wurde.<br />
• Fast alle Entwickler, die C ++ (oder auch Java) als E<strong>in</strong>stiegssprache lernen,<br />
ohne zuvor mit e<strong>in</strong>er imperativen Sprache gearbeitet zu haben, f<strong>in</strong>den C ++<br />
unglaublich kompliziert und sehen den Wald vor lauter Bäumen nicht mehr.<br />
Das liegt daran, dass sie vor vielen Problemen, die durch das OO-Konzept<br />
gelöst werden, überhaupt noch nie gestanden s<strong>in</strong>d! Wie soll man auch e<strong>in</strong>e<br />
Problemlösung verstehen und den damit verbundenen Overhead gerne <strong>in</strong><br />
Kauf nehmen, wenn man das Problem nicht kennt bzw. nicht verstanden<br />
hat?<br />
Lesern, die trotz dieser Argumente ke<strong>in</strong>e Lust haben, zuerst C zu lernen,<br />
sondern sich gleich C ++ aneignen wollen, möchte ich zum<strong>in</strong>dest die beiliegende<br />
elektronische Version von <strong>Softwareentwicklung</strong> <strong>in</strong> C als Nachschlagewerk<br />
für Zwischendurch empfehlen. Es schadet <strong>in</strong> jedem Fall nicht, wenn man
1.1 Zum Inhalt 3<br />
z.B. bei Abhandlungen über Po<strong>in</strong>ter e<strong>in</strong>mal e<strong>in</strong>en kurzen Blick <strong>in</strong> das entsprechende<br />
Kapitel des anderen Buchs wirft oder dieses vielleicht sogar im<br />
Schnellverfahren e<strong>in</strong>mal von vorne bis h<strong>in</strong>ten querliest.<br />
Auf e<strong>in</strong>en Punkt sei noch ganz besonders h<strong>in</strong>gewiesen: C ++ als OO-<br />
Sprache unterstützt sehr elegant alle Eigenschaften, die immer mit OO-<br />
<strong>Softwareentwicklung</strong> <strong>in</strong> Verb<strong>in</strong>dung gebracht werden. Die wichtigsten Schlagworte<br />
hierbei s<strong>in</strong>d sicherlich die Modularität und die Wiederverwendbarkeit<br />
von Code. Ganz bewusst habe ich das Wort unterstützt besonders hervorgehoben,<br />
denn es herrscht der allgeme<strong>in</strong>e Irrglaube, dass gewisse Eigenschaften,<br />
wie z.B. Wiederverwendbarkeit, alle<strong>in</strong> durch das Verwenden e<strong>in</strong>er OO-<br />
Sprache automatisch existieren bzw. erzwungen werden. Dies ist def<strong>in</strong>itiv<br />
falsch.<br />
Kritisch betrachtet ist sogar das Gegenteil der Fall! Bei Verwendung von<br />
OO-Sprachen kann man Fehler im Design viel länger h<strong>in</strong>ter abenteuerlichen<br />
Objektkonstruktionen verstecken, als es bei Verwendung von imperativen<br />
Sprachen jemals möglich wäre. Wenn man dann allerd<strong>in</strong>gs den Punkt erreicht,<br />
an dem “nichts mehr geht”, dann ist die Katastrophe bei weitem<br />
größer, als sie sonst jemals werden könnte. Bei imperativen Sprachen muss<br />
man viel früher das Handtuch werfen, weil man bei weitem schneller den<br />
Punkt erreicht, an dem man nichts mehr an e<strong>in</strong>em Teil e<strong>in</strong>es Programms<br />
ändern kann, ohne gleich an e<strong>in</strong>er ganz anderen Ecke im Programm e<strong>in</strong>e Katastrophe<br />
hervorzurufen. Bei OO-Sprachen kann man sich bewusst noch e<strong>in</strong>e<br />
Zeit lang an diesen Problemen vorbei schw<strong>in</strong>deln, <strong>in</strong>dem man Objektkapselungsmechanismen<br />
missbraucht.<br />
Das Ziel bei jeder <strong>Softwareentwicklung</strong> ist immer, dass das Endprodukt<br />
sauber und robust ist. Daher wird <strong>in</strong> diesem Buch besonderer Wert darauf<br />
gelegt, OO-<strong>Softwareentwicklung</strong> richtig zu betreiben, denn nur dann gelangt<br />
man zum gewünschten Ziel. Dazu ist sehr viel Erfahrung vonnöten, vor allem,<br />
wenn es um saubere, elegante und allgeme<strong>in</strong> e<strong>in</strong>setzbare Problemlösungsansätze<br />
geht. Wo auch immer auf dem Weg zum erklärten Ziel typische<br />
Stolperste<strong>in</strong>e liegen, an denen sich C ++ Neul<strong>in</strong>ge erfahrungsgemäß stoßen<br />
können, wird besonders auf sie h<strong>in</strong>gewiesen.<br />
1.1 Zum Inhalt<br />
Das Buch führt <strong>in</strong> kle<strong>in</strong>en Schritten von den Pr<strong>in</strong>zipien der OO-<strong>Softwareentwicklung</strong><br />
über die Grundlagen der Sprache (z.B. primitive Datentypen, wie<br />
sie auch <strong>in</strong> C existieren) und über die ersten objektorientierten Gehversuche<br />
zu zum Teil sehr speziellen Konstrukten von C ++. Es wird immer besonderer<br />
Wert darauf gelegt, das Warum zu verstehen, anstatt nur e<strong>in</strong> Kochbuch mit<br />
e<strong>in</strong>fachen Rezepten anzubieten. Nur zu oft sieht man C ++ Code, der aus<br />
purem Unverständnis der H<strong>in</strong>tergründe unglaublich kompliziert ist, obwohl<br />
die Sprache an und für sich e<strong>in</strong>e elegante Lösungen ermöglichen würde.
4 1. Ziel und Inhalt dieses Buchs<br />
Zu allen Sprachkonstrukten werden entsprechende Beispiele angeführt<br />
und ausführlich erklärt. Diese Beispiele s<strong>in</strong>d durchgehend mit kle<strong>in</strong>en Zeilennummern<br />
am l<strong>in</strong>ken Rand versehen, um e<strong>in</strong> Referenzieren im beschreibenden<br />
Text e<strong>in</strong>facher zu machen. Natürlich s<strong>in</strong>d diese Zeilennummern nicht Teil<br />
des C ++ Codes und dürfen dementsprechend nicht <strong>in</strong> Programmen vorkommen!<br />
Alle Beispiele, die im Buch angeführt s<strong>in</strong>d, wurden so nahe wie möglich an<br />
s<strong>in</strong>nvolle Konstrukte angelehnt, die auch <strong>in</strong> der täglichen Praxis vorkommen.<br />
Auch die Herangehensweise an Problemlösungen, die <strong>in</strong> diesem Buch vermittelt<br />
wird, hat sich über lange Zeit bewährt. Natürlich bedeutet dies nicht,<br />
dass die angeführten Beispiele die e<strong>in</strong>zig mögliche Lösung zu e<strong>in</strong>em Problem<br />
darstellen. Ich möchte unbed<strong>in</strong>gt dazu anregen, über alternative Lösungen<br />
nachzudenken und diese mit den vorgeschlagenen zu vergleichen. Dies ist<br />
die beste Methode, den Umgang mit verschiedensten Aspekten der <strong>Softwareentwicklung</strong><br />
zu üben und die Kenntnisse über e<strong>in</strong>e Programmiersprache zu<br />
vertiefen.<br />
Um das Buch so gut wie möglich zu strukturieren, wurde es <strong>in</strong> drei Hauptteile<br />
untergliedert:<br />
Teil I – Low-Level Konzepte von C ++: Im ersten Teil des Buchs werden alle<br />
Konzepte von C ++ besprochen, die noch nichts mit Objektorientierung<br />
zu tun haben. Im Pr<strong>in</strong>zip s<strong>in</strong>d dies e<strong>in</strong>erseits Konzepte, die C ++ von<br />
C übernommen hat, andererseits e<strong>in</strong>ige Features, die entweder im Vergleich<br />
zu C s<strong>in</strong>nvoll erweitert wurden bzw. völlig neu s<strong>in</strong>d. Wo auch<br />
immer Unterschiede zwischen C und C ++ zu f<strong>in</strong>den s<strong>in</strong>d, s<strong>in</strong>d diese explizit<br />
angemerkt.<br />
Teil II – Objektorientierte Konzepte von C ++: Im zweiten Teil des Buchs werden<br />
die Features von C ++ besprochen, die direkt mit OO-<strong>Softwareentwicklung</strong><br />
zu tun haben und die es <strong>in</strong> C vom Konzept her überhaupt<br />
nicht gibt.<br />
Teil III – Ausgesuchte Teile aus der C ++ Standard Library: Zu C ++ gibt es<br />
e<strong>in</strong>e sehr große Standard-Klassenbibliothek, die vieles enthält, was man<br />
im Programmieralltag gut gebrauchen kann. Der Vorteil der Standard-<br />
Library ist, dass man nicht für die alltäglichen Bedürfnisse das Rad neu<br />
erf<strong>in</strong>den muss und auch nicht auf Code von Drittherstellern angewiesen<br />
ist. Die Standard-Library hat, wie der Name schon sagt, den Vorteil, dass<br />
ihr Inhalt erstens standardisiert ist und dass sie zweitens standardmäßig<br />
bei jedem (ok, fast jedem :-)) C ++ Compiler mitgeliefert wird.<br />
Um nun diese Standard-Library umfassend mit erklärenden Codebeispielen<br />
zu behandeln, müsste dieses Buch m<strong>in</strong>destens den doppelten Umfang<br />
haben. Aus diesem Grund wird im dritten Teil des Buchs vor allem der<br />
pr<strong>in</strong>zipielle Umfang dieser Library vorgestellt, um e<strong>in</strong>en Überblick zu<br />
vermitteln. Umfassendere Beschreibungen gibt es nur zu den Teilen, die<br />
man im “üblichen” Alltag sehr häufig braucht. Durch diese Art der Beschreibung<br />
werden die Konzepte klar genug beleuchtet, dass man die E<strong>in</strong>-
1.1 Zum Inhalt 5<br />
stiegspunkte hat, die man braucht, um mit der Onl<strong>in</strong>e-Dokumentation<br />
problemlos zu Rande zu kommen.<br />
Um besondere H<strong>in</strong>weise im Buch auf e<strong>in</strong>en Blick erkennen zu können, s<strong>in</strong>d<br />
diese extra hervorgehoben und besitzen e<strong>in</strong>e besondere Kennzeichnung am<br />
Rand:<br />
• Das Zeichen signalisiert e<strong>in</strong>en H<strong>in</strong>weis auf e<strong>in</strong>en Stolperste<strong>in</strong>.<br />
• Das Zeichen signalisiert e<strong>in</strong>en H<strong>in</strong>weis auf e<strong>in</strong>en wichtigen Unterschied<br />
zwischen C und C ++.<br />
• Das Zeichen signalisiert, dass es als Zusatz<strong>in</strong>formation zu e<strong>in</strong>em bestimmten<br />
Thema e<strong>in</strong>e ausführliche Abhandlung im Buch <strong>Softwareentwicklung</strong><br />
<strong>in</strong> C gibt. E<strong>in</strong>e elektronische Version dieses Buchs ist auch auf der<br />
beiliegenden CD-ROM vorhanden.<br />
Diese speziellen H<strong>in</strong>weise auf C-Konstrukte wurden für Leser e<strong>in</strong>geführt,<br />
die im Umgang mit der Programmiersprache C noch e<strong>in</strong> wenig unsicher<br />
s<strong>in</strong>d oder überhaupt noch ke<strong>in</strong>e C-Erfahrung haben.<br />
E<strong>in</strong> paar Worte möchte ich noch über das Thema Entwicklungsumgebung<br />
verlieren: C ++ Entwicklungsumgebungen gibt es wie Sand am Meer. Je<br />
nachdem, um welche Entwicklungsumgebung es sich handelt und für welche<br />
Zielplattform diese gedacht ist, ist sowohl der Funktionsumfang als auch der<br />
Umfang zusätzlicher plattformabhängiger Libraries sehr verschieden. Nun ist<br />
es nicht im S<strong>in</strong>ne des Erf<strong>in</strong>ders, C ++ als allgeme<strong>in</strong> e<strong>in</strong>setzbare Programmiersprache<br />
an e<strong>in</strong>er bestimmten Umgebung festzumachen. Aus diesem Grund<br />
wurde folgende Wahl getroffen:<br />
Die empfohlene Entwicklungsumgebung, auf der auch pr<strong>in</strong>zipiell das gesamte<br />
Buch beruht, ist e<strong>in</strong>e Umgebung unter L<strong>in</strong>ux mit dem GNU C ++ Compiler<br />
und GNU make. Diese Entscheidung ist dar<strong>in</strong> begründet, dass dies e<strong>in</strong>e<br />
frei verfügbare Open-Source Umgebung ist. Sie ist auch e<strong>in</strong>e der stabilsten<br />
Entwicklungsumgebungen, die derzeit zur Verfügung stehen.<br />
Es soll sich nun allerd<strong>in</strong>gs niemand abschrecken lassen, der lieber unter<br />
MS-W<strong>in</strong>dows mit e<strong>in</strong>er anderen Entwicklungsumgebung arbeiten will. Das<br />
Wissen, das <strong>in</strong> diesem Buch vermittelt wird, ist natürlich auch dafür gültig.<br />
Es steht also jedem frei, e<strong>in</strong>e beliebige Umgebung zu verwenden, ohne dass<br />
dadurch E<strong>in</strong>schränkungen zu erwarten s<strong>in</strong>d. Jedoch möchte ich jedem Softwareentwickler<br />
gerne ans Herz legen, es zum<strong>in</strong>dest <strong>in</strong>teressehalber e<strong>in</strong>mal mit<br />
L<strong>in</strong>ux zu probieren. Nach e<strong>in</strong>er kurzen E<strong>in</strong>gewöhnungsphase lernt man doch<br />
gewisse Vorteile schätzen.<br />
E<strong>in</strong>e kurze Warnung möchte ich jedoch an dieser Stelle noch aussprechen:<br />
E<strong>in</strong>ige Compiler entsprechen def<strong>in</strong>itiv <strong>in</strong> e<strong>in</strong>igen “Fe<strong>in</strong>heiten” nicht dem C ++<br />
Standard und e<strong>in</strong>ige Compiler haben auch nicht alle Features implementiert,<br />
die im Standard enthalten s<strong>in</strong>d. Dies betrifft auch die sehr weit verbreiteten<br />
Compiler gewisser Firmen, die den PC Markt beherrschen. Um zu erfahren,<br />
wo diese Compiler vom Standard abweichen, wirft man am besten e<strong>in</strong>en Blick
6 1. Ziel und Inhalt dieses Buchs<br />
auf die entsprechenden Web-Pages der Compilerhersteller. Dort existiert<br />
üblicherweise e<strong>in</strong>e Liste, die Auskunft über die Abweichungen vom Standard<br />
gibt. Sollten Leser also auf Beispiele im Buch stoßen, die mit ihrem Compiler<br />
nicht übersetzbar s<strong>in</strong>d, so möchte ich empfehlen, e<strong>in</strong>en Blick auf die Web-<br />
Page des Herstellers zu werfen. Sollten dann immer noch Ungereimtheiten<br />
existieren, so ist das Feedback-Forum zum Buch (siehe Abschnitt 1.3) der<br />
beste Ort, e<strong>in</strong>e Frage anzubr<strong>in</strong>gen. Ich werde dann so schnell wie möglich<br />
versuchen, Klarheit zu schaffen.<br />
Leser, die sich gar nicht sicher s<strong>in</strong>d, ob sie nun L<strong>in</strong>ux ausprobieren sollen<br />
oder nicht, können auch e<strong>in</strong>en Zwischenweg wählen: Alle nützlichen Tools<br />
(GNU C ++ Compiler, Emacs als Editor und viele kle<strong>in</strong>e Helferle<strong>in</strong>, wie z.B.<br />
make oder tar) s<strong>in</strong>d auch als MS-W<strong>in</strong>dows Portierungen frei im Internet<br />
verfügbar. Kurze Beschreibungen, die den E<strong>in</strong>stieg <strong>in</strong> die Arbeit mit diesen<br />
Tools erleichtern, s<strong>in</strong>d auch im Buch <strong>Softwareentwicklung</strong> <strong>in</strong> C, das bereits<br />
zu Beg<strong>in</strong>n dieses Kapitels genannt wurde, enthalten.<br />
1.2 Motivation<br />
Softwareentwickler stehen <strong>in</strong> der Industrie im Regelfall unter starkem Druck.<br />
Es wird von ihnen nur zu oft verlangt, dass sie unrealistische Term<strong>in</strong>e e<strong>in</strong>halten,<br />
dass sie jederzeit auf Wunsch mitten <strong>in</strong> der Entwicklung neue Features<br />
<strong>in</strong> die Software e<strong>in</strong>bauen oder auch, dass sie e<strong>in</strong>fach während der Entwicklung<br />
Konzepte ändern. Das Allerschlimmste, das allerd<strong>in</strong>gs passieren kann,<br />
ist, dass e<strong>in</strong> Kunde oder Vorgesetzter frühzeitig “schnell e<strong>in</strong>mal etwas sehen<br />
will”. Leider bedeutet hier der Begriff etwas sehen, dass man e<strong>in</strong>en Teil e<strong>in</strong>es<br />
lauffähigen Programms demonstrieren muss. Kann man dies nicht, bzw.<br />
versucht man, e<strong>in</strong> erstelltes Design zu zeigen, dann sieht man sich sehr oft<br />
Zweifeln ausgesetzt, “ob man denn überhaupt etwas weiterbr<strong>in</strong>gt”.<br />
Nun funktioniert aber saubere <strong>Softwareentwicklung</strong> nicht so, dass man<br />
sich e<strong>in</strong>fach zum Computer setzt und schnell e<strong>in</strong> Programm schreibt. Abgesehen<br />
davon, dass das sowieso die schlechtest mögliche Herangehensweise<br />
ist, ist die Komplexität heutiger Softwarepakete so hoch, dass dieser Ansatz<br />
niemals funktionieren kann. Leider herrscht immer noch die Me<strong>in</strong>ung vor,<br />
dass die tatsächliche Arbeit bei der <strong>Softwareentwicklung</strong> die Implementation<br />
der Software ist. Dies ist aber vollkommen falsch! Der größte Teil der Arbeit<br />
im Rahmen e<strong>in</strong>er sauberen (!!) Entwicklung f<strong>in</strong>det <strong>in</strong> der Designphase<br />
statt! Das Codieren selbst nimmt im Rahmen e<strong>in</strong>es Entwicklungszyklus nur<br />
e<strong>in</strong>en sehr ger<strong>in</strong>gen Teil der Zeit <strong>in</strong> Anspruch. E<strong>in</strong>en weiteren großen Teil<br />
im Zyklus nehmen die laufenden Tests e<strong>in</strong>, die die Software von den ersten<br />
Designschritten bis zum Endprodukt begleiten müssen.<br />
Sieht man sich den Projektfortschritt bei e<strong>in</strong>er sauberen Entwicklung an,<br />
so stellt man Folgendes fest: Lange Zeit sche<strong>in</strong>t die Arbeit nur schleppend<br />
voranzugehen. Es gibt viele Diskussionsphasen im Team und das Ergebnis<br />
dieser Diskussionsphasen s<strong>in</strong>d Entwürfe auf Papier. Im Regelfall wird bis
1.2 Motivation 7<br />
über die Hälfte der Gesamtentwicklungszeit h<strong>in</strong>aus ke<strong>in</strong>e Zeile Code produziert.<br />
Danach allerd<strong>in</strong>gs sche<strong>in</strong>t sich die Arbeit von außen gesehen enorm<br />
zu beschleunigen. In sehr kurzer Zeit wird sehr viel implementiert und die<br />
Implementation ist üblicherweise auch relativ fehlerfrei.<br />
Wenn man im Gegensatz dazu den Projektfortschritt bei e<strong>in</strong>er Hacklösung<br />
ansieht, bei der die Arbeit gleich von Beg<strong>in</strong>n an am Computer stattf<strong>in</strong>det, ohne<br />
zuerst e<strong>in</strong> vollständiges (!!) und schlüssiges (!!) Design zu erstellen, dann<br />
beobachtet man als Außenstehender im Pr<strong>in</strong>zip das Gegenteil: Zu Beg<strong>in</strong>n<br />
passiert <strong>in</strong> kurzer Zeit sehr viel und es gibt sehr schnell e<strong>in</strong>en Prototypen zu<br />
sehen, der e<strong>in</strong>en Teil der geforderten Funktionalität mehr oder weniger gut<br />
implementiert. Gewisse Fehler werden natürlich toleriert, denn man steckt<br />
ja noch mitten <strong>in</strong> der Entwicklung. Mit jedem zusätzlichen Feature, das die<br />
Software dem Endprodukt näher br<strong>in</strong>gt, geht die Arbeit schleppender voran.<br />
Nach e<strong>in</strong>er gewissen Zeit bedeuten auch die kle<strong>in</strong>sten Erweiterungen des<br />
halbfertigen Pakets bereits immensen Implementationsaufwand. Außerdem<br />
steigt die Fehlerhäufigkeit auf e<strong>in</strong> unerträglich hohes Maß. Das Beheben von<br />
Fehlern wird immer mehr zum Spießrutenlauf, denn mit jeder Änderung <strong>in</strong><br />
e<strong>in</strong>em Teil der Software passieren ungeahnte D<strong>in</strong>ge <strong>in</strong> anderen Teilen der<br />
Software. Mit Glück ist das geforderte Produkt vom Funktionsumfang her<br />
kle<strong>in</strong> genug, dass man dessen Fertigstellung noch irgendwie mehr schlecht als<br />
recht über die Runden br<strong>in</strong>gt. Sehr oft allerd<strong>in</strong>gs muss man den Kunden erklären,<br />
dass gewisse Features “aus technischen Gründen gar nicht realisierbar<br />
s<strong>in</strong>d” und hoffen, dass diese das akzeptieren.<br />
Noch viel dramatischer wird das Bild, wenn Erweiterungen oder Änderungen<br />
e<strong>in</strong>es existenten Softwarepakets gefordert s<strong>in</strong>d. Bei der sauberen Entwicklung<br />
ist die Erweiterbarkeit e<strong>in</strong> Teil des Konzepts und im Regelfall s<strong>in</strong>d<br />
neue Features relativ e<strong>in</strong>fach realisierbar. Sollte durch neue Features das<br />
Ursprungskonzept verändert werden müssen, so ist auch das im Normalfall<br />
möglich, wenn auch mit e<strong>in</strong>igem Aufwand verbunden. Das Endprodukt bleibt<br />
trotzdem sauber.<br />
Ganz im Gegensatz dazu steht die Erweiterbarkeit der Hacklösung. Schon<br />
die kle<strong>in</strong>sten Änderungen dauern enorm lang und destabilisieren die gesamte<br />
Software. Sehr oft beobachtet man den Fall, dass <strong>in</strong> sogenannten Bugfix-<br />
Releases zwar e<strong>in</strong>ige bekannte schwere Fehler behoben wurden, dafür aber<br />
m<strong>in</strong>destens genau so viele neue Fehler e<strong>in</strong>gebaut wurden, da die Auswirkungen<br />
von Programmänderungen nicht mehr überschaubar s<strong>in</strong>d.<br />
Vollkommen katastrophal wird es, wenn man die Menge an Source-Code<br />
e<strong>in</strong>es sauberen Projekts mit der e<strong>in</strong>er Hacklösung vergleicht. Im Normalfall<br />
ist die Hacklösung bei gleicher Funktionalität gleich x-Mal so groß wie die<br />
saubere Lösung. Nicht nur, dass die Codemenge ungleich größer ist, auch die<br />
Komplexität des Codes ist durch Seiteneffekte enorm.<br />
Es gibt e<strong>in</strong>en ganz bestimmten Grund, warum ich mich über saubere<br />
und Hacklösungen so ausführlich auslasse: Zur Zeit, zu der OO-Sprachen,<br />
<strong>in</strong>sbesondere C ++, ihren E<strong>in</strong>gang <strong>in</strong> die Industrie fanden, war die Euphorie
8 1. Ziel und Inhalt dieses Buchs<br />
über die diesen Sprachen angedichteten Eigenschaften riesig. Plötzlich wurde<br />
überall von Wiederverwendung gesprochen und davon, dass sich dadurch die<br />
Entwicklungszyklen drastisch verkürzen. Entsprechend wurden sofort die<br />
Entwicklungszeiten kürzer angesetzt als zuvor, allerd<strong>in</strong>gs bei e<strong>in</strong>er höheren<br />
Anzahl von Features. Man arbeitet ja objektorientiert, und damit geht das<br />
alles viel besser und schneller.<br />
Leider wurde bei dieser Euphorie etwas übersehen: OO-Entwicklung will<br />
erst richtig gelernt se<strong>in</strong> und es braucht e<strong>in</strong>ige Erfahrung, um e<strong>in</strong> tragfähiges<br />
Konzept zu erstellen! Aber genau die Zeit, die Entwickler brauchen, um<br />
die Denkweise vollständig zu ver<strong>in</strong>nerlichen und die Konzepte h<strong>in</strong>ter dieser<br />
Denkweise <strong>in</strong> vernünftige Softwarekonstrukte umzusetzen, wurde und wird<br />
ihnen nicht ausreichend gegeben. Es ist für erfahrene C-Entwickler ohne<br />
große Probleme möglich, C ++ als Sprache <strong>in</strong> e<strong>in</strong>igen Tagen, bis zu wenigen<br />
Wochen, zu lernen. Es ist allerd<strong>in</strong>gs vollkommen unmöglich, die Denkmuster<br />
<strong>in</strong>nerhalb dieser kurzen Zeit umzustellen. Imperativ zu programmieren<br />
ist eben e<strong>in</strong>mal e<strong>in</strong> ganz anderer Ansatz als objektorientiert zu entwickeln.<br />
Vor allem verändert sich das Design der Software drastisch! Die Syntax von<br />
C ++ zu beherrschen bedeutet noch lange nicht, objektorientiert C ++ zu programmieren.<br />
Es ist leicht, C ++ zu vergewaltigen, also Klassen und andere<br />
Konstrukte zu verwenden und trotzdem im Pr<strong>in</strong>zip re<strong>in</strong> imperativ zu programmieren.<br />
Genau bei e<strong>in</strong>er solchen Arbeitsweise kauft man den gesamten<br />
Overhead von C ++ gegenüber C e<strong>in</strong>, ohne die damit verbundenen zusätzlichen<br />
Möglichkeiten zum Vorteil zu nützen.<br />
Polemisch festgestellt: Wenn man mit e<strong>in</strong>em Kle<strong>in</strong>wagen gut umgehen<br />
kann, dann bedeutet dies noch lange nicht, dass man sich e<strong>in</strong>fach <strong>in</strong> e<strong>in</strong><br />
Rennauto setzen kann und damit schneller ans Ziel kommt. Die Gefahr,<br />
durch das Rennauto überfordert zu werden, se<strong>in</strong>e Grenzen nicht zu kennen<br />
und deshalb gleich e<strong>in</strong>en Unfall zu haben, ist sehr groß.<br />
Deshalb kann ich abschließend nur sagen, dass alle Leser dieses Buchs sich<br />
so viel Zeit wie möglich zum Spielen und Probieren nehmen sollten, um C ++<br />
und die dah<strong>in</strong>ter liegenden Konzepte zu verstehen, bevor sie sich an C ++<br />
im Rahmen e<strong>in</strong>er kommerziellen Entwicklung versuchen.<br />
1.3 Feedback<br />
Software wird niemals fertig. Kaum wird e<strong>in</strong>e Version freigegeben, kommen<br />
auch schon die nächsten Wünsche. Genau dasselbe passiert bei Büchern, die<br />
Wissen vermitteln sollen. Es gibt ke<strong>in</strong> Buch, das man nicht noch verbessern<br />
könnte und das gilt natürlich auch (hoffentlich nicht ganz besonders)<br />
für dieses Buch. Aus diesem Grund gibt es e<strong>in</strong> Feedback-Forum, über das<br />
Wünsche, Anregungen, Beschwerden, Lob und Tadel an den Autor übermittelt<br />
werden können. Dieses Feedback Forum ist onl<strong>in</strong>e erreichbar unter der<br />
Web-Page zum Buch:<br />
http://courses.iicm.edu/SWEntwicklungInCplusplus
1.4 Die beiliegende CD-ROM 9<br />
Ich würde mich freuen, wenn die e<strong>in</strong>e oder andere Leser<strong>in</strong> bzw. der e<strong>in</strong>e<br />
oder andere Leser dazu beiträgt, dieses Buch zu verbessern, <strong>in</strong>dem sie/er<br />
entsprechende Vorschläge macht.<br />
Das Feedback-Forum ist aber nicht nur dazu gedacht, Lesern die Möglichkeit<br />
zu geben, ihre Me<strong>in</strong>ung zum Buch mitzuteilen. Ich sehe das Forum vielmehr<br />
als Leserforum, <strong>in</strong> dem auch eigene Beispiele, Alternativen zu vorgeschlagenen<br />
Lösungen, etc. veröffentlicht werden können und diverse Aspekte<br />
der <strong>Softwareentwicklung</strong> diskutiert werden können. Es wäre schön, wenn<br />
das Forum e<strong>in</strong>e Eigendynamik <strong>in</strong> diese Richtung entwickeln würde, denn das<br />
kommt sicher vielen Leuten zugute.<br />
1.4 Die beiliegende CD-ROM<br />
Dem Buch liegt e<strong>in</strong>e CD-ROM bei. Weil der Inhalt e<strong>in</strong>er CD-ROM im Gegensatz<br />
zu e<strong>in</strong>em Buch sehr kurzlebig ist, möchte ich an dieser Stelle nur so<br />
viel erwähnen: Es s<strong>in</strong>d auf der CD-ROM alle im Buch abgedruckten Programme<br />
vorhanden. Was sonst noch auf der CD-ROM zu f<strong>in</strong>den ist und wie<br />
man damit arbeitet, kann man erfahren, <strong>in</strong>dem man die Datei<br />
<strong>in</strong>dex.html<br />
mit e<strong>in</strong>em der gängigen Internet-Browser ansieht. Eventuelle Zusätze, die<br />
sich im Lauf der Zeit nach Auslieferung der CD-ROM als nützliche Add-ons<br />
herausstellen, s<strong>in</strong>d immer aktuell über die zuvor erwähnte Web-Page zum<br />
Buch abrufbar.
Teil I<br />
Low-Level Konzepte von <strong>C++</strong>
2. Datentypen und Variablen<br />
Die Verwendung von Variablen ist e<strong>in</strong>es der Grundkonzepte von OO-Sprachen,<br />
gleich wie bei imperativen und im Gegensatz zu funktionalen Programmiersprachen.<br />
Pr<strong>in</strong>zipiell ist e<strong>in</strong>e Variable e<strong>in</strong> Datenobjekt, das über e<strong>in</strong>en symbolischen<br />
Namen (=Identifier) angesprochen werden kann und dessen Inhalt<br />
vom Programm manipuliert werden kann. Zusätzlich zum Identifier besitzen<br />
Variablen <strong>in</strong> C ++ auch noch e<strong>in</strong>en Datentyp, der über ihre Natur Auskunft<br />
gibt. Damit wird dem Compiler mitgeteilt, welchen Speicherbedarf e<strong>in</strong>e Variable<br />
hat und welche Operationen auf ihr ausführbar s<strong>in</strong>d, bzw. wie gewisse<br />
Operatoren <strong>in</strong> diesem Kontext <strong>in</strong>terpretiert werden müssen.<br />
2.1 Primitive Datentypen<br />
Als sogenannte primitive Datentypen werden die Typen bezeichnet, mit denen<br />
e<strong>in</strong> Computer im Pr<strong>in</strong>zip “von sich aus” umgehen kann. Dies s<strong>in</strong>d verschiedene<br />
Arten von Ganzzahlen (=<strong>in</strong>tegrale Typen), verschiedene Arten von<br />
Gleitkommazahlen (=float<strong>in</strong>g-po<strong>in</strong>t Typen) und darstellbare Zeichen (=Characters).<br />
In C ++ stehen alle primitiven Datentypen zur Verfügung, die bereits<br />
<strong>in</strong> C existieren. Wie man <strong>in</strong> der folgenden Tabelle sehen kann, gibt es <strong>in</strong> C ++<br />
auch e<strong>in</strong>en boolschen Datentyp, den man <strong>in</strong> C vermisst. Ebenso ist wchar_t<br />
e<strong>in</strong> e<strong>in</strong>gebauter Datentyp und nicht, wie <strong>in</strong> C, e<strong>in</strong> simples typedef.<br />
Typ Bedeutung<br />
char E<strong>in</strong> Character, nimmt e<strong>in</strong> (üblicherweise) 8 Bit Zeichen auf.<br />
wchar_t E<strong>in</strong> wide Character, nimmt e<strong>in</strong> m<strong>in</strong>destens (!)<br />
16 Bit Zeichen auf.<br />
bool E<strong>in</strong> boolscher Wert, kann e<strong>in</strong>en der Werte true oder<br />
false annehmen<br />
<strong>in</strong>t E<strong>in</strong> ganzzahliger Wert <strong>in</strong> der für die jeweilige Masch<strong>in</strong>e<br />
“natürlichen” Größe.<br />
float E<strong>in</strong>e Gleitkommazahl mit e<strong>in</strong>facher Genauigkeit.<br />
double E<strong>in</strong>e Gleitkommazahl mit doppelter Genauigkeit.<br />
Zum Thema 8 Bit Zeichen <strong>in</strong> e<strong>in</strong>em char möchte ich noch e<strong>in</strong>e kurze<br />
Ergänzung liefern: Per Def<strong>in</strong>ition ist e<strong>in</strong> char m<strong>in</strong>destens 8 Bit lang. Aller-
14 2. Datentypen und Variablen<br />
d<strong>in</strong>gs ist auf allen gebräuchlichen Plattformen e<strong>in</strong> char wirklich genau 8 Bit<br />
lang und deshalb werde ich diese Spitzf<strong>in</strong>digkeit <strong>in</strong> der Folge nicht mehr weiter<br />
beachten.<br />
Wie <strong>in</strong> C gibt es auch noch die folgenden Qualifiers, mit denen man die<br />
Eigenschaften bestimmter Grunddatentypen steuern kann:<br />
Qualifier Bedeutung anwendbar auf<br />
signed vorzeichenbehaftet (normalerweise char, <strong>in</strong>t<br />
nicht explizit angegeben)<br />
unsigned nicht vorzeichenbehaftet char, <strong>in</strong>t<br />
short “kurz” <strong>in</strong>t<br />
long “lang” <strong>in</strong>t, double<br />
Der Qualifier signed wurde hier nur aus Gründen der Vollständigkeit angegeben.<br />
Im Normalfall wird dieser nicht explizit <strong>in</strong> Programmen verwendet,<br />
da als Default immer angenommen wird, dass es sich um e<strong>in</strong>e vorzeichenbehaftete<br />
Zahl handelt.<br />
Wenn man die erste Tabelle mit den Grunddatentypen näher betrachtet,<br />
dann fällt auf, dass C ++ leider e<strong>in</strong> ganz großes Manko von C geerbt<br />
hat: Den Datentypen liegt die “natürliche” Größe auf verschiedenen Zielplattformen<br />
zugrunde! Man kann nun viel über Vor- und Nachteile dieser<br />
Spezifikation diskutieren. Tatsache ist, dass oft sogar von erfahrenen Entwicklern<br />
immer wieder falsche Annahmen über die Größe von Datentypen<br />
getroffen werden. Dies führt dann <strong>in</strong> manchen Fällen zu völlig unerklärbaren<br />
Fehlern mit manchmal katastrophalen Auswirkungen.<br />
Die möglichen Komb<strong>in</strong>ationen der Grunddatentypen mit entsprechenden<br />
Qualifiers ergibt folgendes Gesamtbild:<br />
char:<br />
E<strong>in</strong>e Variable vom Typ char kann genau e<strong>in</strong> Zeichen aus dem gültigen<br />
(üblicherweise) 8 Bit-Zeichensatz der jeweiligen Zielplattform halten.<br />
Vorsicht ist geboten, denn es wird ke<strong>in</strong>e b<strong>in</strong>dende Aussage getroffen, welcher<br />
Zeichensatz auf der Zielplattform gültig ist. Hier gibt es massive Unterschiede,<br />
denn außer Plattformabhängigkeiten gibt es auch Abhängigkeiten<br />
von der natürliche Sprache (z.B. Deutsch, Englisch), auf die e<strong>in</strong><br />
Zeichensatz ausgelegt ist. Im Pr<strong>in</strong>zip ist das E<strong>in</strong>zige, was man e<strong>in</strong>igermaßen<br />
sicher sagen kann, dass im Normalfall e<strong>in</strong> Zeichensatz die Buchstaben<br />
a–z, A–Z, die Zahlen 0–9 und die wichtigsten Satzzeichen, wie Punkt,<br />
Komma, etc. enthält. Man kann jedoch nicht davon ausgehen, dass diese<br />
Zeichen <strong>in</strong> verschiedenen Zeichensätzen denselben Character-Code besitzen.<br />
Man kann nicht e<strong>in</strong>mal davon ausgehen, dass alle möglichen 256<br />
Zeichen tatsächlich <strong>in</strong> e<strong>in</strong>em Zeichensatz belegt s<strong>in</strong>d, dass also e<strong>in</strong> echter<br />
8 Bit Zeichensatz vorliegt. Oft f<strong>in</strong>det man e<strong>in</strong>en 7 Bit Zeichensatz auf<br />
e<strong>in</strong>er Masch<strong>in</strong>e vor, bei dem Characters mit e<strong>in</strong>em Code größer als 127<br />
e<strong>in</strong>fach “leer” s<strong>in</strong>d.
2.1 Primitive Datentypen 15<br />
Wenn man sich kurz überlegt, dass <strong>in</strong> e<strong>in</strong>em char e<strong>in</strong>fach nur e<strong>in</strong> Code<br />
<strong>in</strong> Form e<strong>in</strong>er Zahl gespeichert wird, der dann erst bei der Darstellung<br />
des entsprechenden Zeichens <strong>in</strong> e<strong>in</strong>er Zeichensatztabelle “nachgeschlagen”<br />
wird, dann erkennt man leicht, was es mit e<strong>in</strong>em char eigentlich<br />
im Grunde auf sich hat: Für den Computer ist er e<strong>in</strong>fach e<strong>in</strong>e (zumeist)<br />
8 Bit lange Ganzzahl. Daher kann man auch mit e<strong>in</strong>em char rechnen<br />
wie mit allen anderen <strong>in</strong>tegralen Datentypen.<br />
Hier ist allerd<strong>in</strong>gs allergrößte Vorsicht geboten: Es gibt ke<strong>in</strong>e b<strong>in</strong>dende<br />
Def<strong>in</strong>ition, ob e<strong>in</strong> Compiler e<strong>in</strong>en char nun pr<strong>in</strong>zipiell als signed oder<br />
unsigned behandelt! Will man also e<strong>in</strong>en char als kle<strong>in</strong>e vorzeichenbehaftete<br />
Ganzzahl verwenden, so ist der e<strong>in</strong>zig sichere Weg, den voll<br />
ausgeschriebenen Datentyp signed char zu verwenden. In diesem Fall<br />
ist dann das Fassungsvermögen mit e<strong>in</strong>em Wertebereich von -128–127<br />
nicht gerade berauschend, aber je nach Anwendung ist dies ausreichend<br />
und wird aus Gründen der Speicher-Ersparnis verwendet.<br />
signed char:<br />
Wenn schon bei char nicht gesagt ist, ob dieser auf e<strong>in</strong>er Plattform nun<br />
vorzeichenbehaftet ist oder nicht, dann muss man e<strong>in</strong>en vorzeichenbehafteten<br />
char eben erzw<strong>in</strong>gen, wenn man unbed<strong>in</strong>gt e<strong>in</strong>en solchen benötigt.<br />
Genau dies ist dann der hier vorgestellte signed char mit e<strong>in</strong>em Wertebereich<br />
von -128–127.<br />
unsigned char:<br />
Wo man e<strong>in</strong>en signed char erzw<strong>in</strong>gen kann, gibt es natürlich auch e<strong>in</strong><br />
(sicher-)nicht-vorzeichenbehaftetes Pendant dazu: den unsigned char<br />
mit dem resultierenden Wertebereich von 0–255.<br />
Im Zusammenhang mit der Darstellung von Zeichen aus dem Zeichensatz<br />
ist es egal, ob man mit char, signed char oder unsigned char arbeitet.<br />
Zum “Nachschlagen” <strong>in</strong> der Zeichensatztabelle wird er sowieso als nichtvorzeichenbehaftet<br />
betrachtet (oder hat schon jemand negative <strong>ASC</strong>II-<br />
Codes gesehen? :-)).<br />
wchar_t:<br />
Für e<strong>in</strong>en wchar_t gilt dasselbe, was schon über char gesagt wurde. Der<br />
e<strong>in</strong>zige Unterschied ist, dass es sich hier um e<strong>in</strong>en “Wide” Character<br />
handelt, der für Unicode und ähnliche Zeichensätze e<strong>in</strong>gesetzt wird und<br />
als solcher m<strong>in</strong>destens 16 Bit lang ist. Sich den Kopf über signed und<br />
unsigned Varianten desselben zu zerbrechen hat auch nicht übertrieben<br />
viel S<strong>in</strong>n, denn zum Rechnen stehen <strong>in</strong> diesem Größenbereich bereits die<br />
“echten” Ganzzahlen zur Verfügung (z.B. short).<br />
bool:<br />
Der Datentyp bool ist e<strong>in</strong> primitiver Typ, der <strong>in</strong> C ++ erst vor wenigen<br />
Jahren e<strong>in</strong>geführt und zum Standard erklärt wurde. In C und <strong>in</strong><br />
frühen C ++ Implementationen gab es diesen Datentyp noch nicht, allerd<strong>in</strong>gs<br />
gab es auch damals schon starke Bestrebungen, e<strong>in</strong>en solchen<br />
e<strong>in</strong>zuführen. Aus diesem Grund f<strong>in</strong>det man <strong>in</strong> vielen älteren Standard
16 2. Datentypen und Variablen<br />
Libraries noch verschiedenste Typdef<strong>in</strong>itionen für e<strong>in</strong>en boolschen Wert,<br />
die von boolean über Boolean und BOOL bis zu bool reichen. Natürlich<br />
gab es auch die entsprechenden Def<strong>in</strong>itionen der Wahrheitswerte dazu,<br />
die ebenfalls nicht standardisiert waren. Beliebige Schreibweisen wie z.B.<br />
TRUE/FALSE für die Wahrheitswerte existierten.<br />
Um diesem Wildwuchs E<strong>in</strong>halt zu gebieten, wurde der Datentyp bool mit<br />
den dazugehörigen Wahrheitswerten true und false <strong>in</strong> den C ++ Standard<br />
aufgenommen. Wo auch immer man also explizit e<strong>in</strong>en boolschen<br />
Wert braucht, wird unbed<strong>in</strong>gt die Verwendung dieses Typs empfohlen.<br />
Rekapituliert man kurz die Def<strong>in</strong>ition von C, die auch C ++ voll<strong>in</strong>haltlich<br />
zugrunde liegt, wann etwas als wahr bzw. falsch <strong>in</strong>terpretiert wird, dann<br />
kommt man schnell auf des Pudels Kern, wie e<strong>in</strong> bool <strong>in</strong>tern funktioniert:<br />
• E<strong>in</strong> Ganzzahlenwert von 0 wird als false <strong>in</strong>terpretiert.<br />
• Jeder Ganzzahlenwert ungleich 0 wird als true <strong>in</strong>terpretiert.<br />
Der Datentyp bool ist <strong>in</strong> Wirklichkeit h<strong>in</strong>ter den Kulissen e<strong>in</strong> Ganzzahlendatentyp.<br />
Es ist für ihn ke<strong>in</strong>e bestimmte Größe vorgeschrieben.<br />
Üblicherweise wird e<strong>in</strong> bool <strong>in</strong>tern durch e<strong>in</strong>en char oder auch e<strong>in</strong>en<br />
short (siehe unten) repräsentiert.<br />
Per Def<strong>in</strong>ition ist false die Ganzzahl 0. Weil nun für true das ganze<br />
Spektrum aller möglichen Zahlen ungleich 0 zur Verfügung steht, e<strong>in</strong>igte<br />
man sich darauf, true durch die Ganzzahl 1 zu repräsentieren.<br />
Wo auch immer Variablen vom Typ bool <strong>in</strong> Rechenoperationen verwendet<br />
werden (ja, das funktioniert, bool ist ja e<strong>in</strong>e Ganzzahl!), werden sie<br />
implizit zu <strong>in</strong>t (siehe unten) umgewandelt. E<strong>in</strong>e Zuweisung von e<strong>in</strong>er<br />
Ganzzahl auf e<strong>in</strong>en bool führt dazu, dass e<strong>in</strong> Wert von 0 zu false wird<br />
(was sonst, false ist ja auch 0 :-)) und jeder Wert ungleich 0 wird konvertiert<br />
zu true (also e<strong>in</strong>fach zu 1).<br />
Verwirrend? Ke<strong>in</strong>e Sorge, wir werden <strong>in</strong> der Folge noch e<strong>in</strong> Beispiel<br />
betrachten, <strong>in</strong> dem diese Zusammenhänge genau demonstriert werden.<br />
Das E<strong>in</strong>zige, was es mit bool, true und false auf sich hat, ist, dass<br />
hier e<strong>in</strong> eigener Datentyp e<strong>in</strong>geführt wurde und die Wahrheitswerte im<br />
Endeffekt genau def<strong>in</strong>iert auf 0 und 1 abgebildet werden, anstatt auf 0<br />
und “irgendwas ungleich 0”, um dem existierenden Wildwuchs e<strong>in</strong> Ende<br />
zu setzen.<br />
<strong>in</strong>t:<br />
Der Datentyp <strong>in</strong>t (oder ganz genau signed <strong>in</strong>t, diese Schreibweise<br />
ist aber nicht üblich) repräsentiert den vorzeichenbehafteten Standard-<br />
Ganzzahlentyp auf der jeweiligen Zielplattform. Es gibt ke<strong>in</strong>e Regel, wie<br />
groß denn nun e<strong>in</strong> <strong>in</strong>t tatsächlich ist. Heute üblich ist e<strong>in</strong>e Größe von<br />
32 Bit, verlassen darf man sich darauf allerd<strong>in</strong>gs niemals! Bis vor nicht<br />
allzu langer Zeit waren 16 Bit für e<strong>in</strong>en <strong>in</strong>t auf PC-Systemen durchaus<br />
üblich. Was passiert, wenn man sich darauf verlässt, dass 32 Bit<br />
zur Verfügung stehen (Wertebereich ca. -2 Mrd.–2 Mrd.) und plötzlich
2.1 Primitive Datentypen 17<br />
hat man aber nur noch 16 Bits zum Speichern e<strong>in</strong>er Zahl (Wertebereich<br />
-32768–32767), das kann man sich sicherlich ausmalen.<br />
unsigned <strong>in</strong>t bzw. unsigned:<br />
Der Datentyp unsigned (=übliche Kurzform für unsigned <strong>in</strong>t) unterliegt<br />
genau denselben Gesetzen wie se<strong>in</strong> vorzeichenbehaftetes Pendant:<br />
Se<strong>in</strong> Fassungsvermögen ist masch<strong>in</strong>enabhängig. Allerd<strong>in</strong>gs ist e<strong>in</strong><br />
unsigned auf e<strong>in</strong>er Zielplattform garantiert immer gleich lang wie e<strong>in</strong><br />
<strong>in</strong>t, nur die Interpretation ist verschieden: E<strong>in</strong> unsigned wird immer<br />
als positive Ganzzahl betrachtet, also ohne Vorzeichenbit. Damit kann<br />
er auf Kosten der wegfallenden negativen Zahlen “doppelt so große” positive<br />
Zahlen speichern (0 gilt auch als positive Zahl!) wie se<strong>in</strong> vorzeichenbehafteter<br />
Bruder.<br />
short <strong>in</strong>t bzw. short:<br />
Der Datentyp short (=übliche Kurzform für short <strong>in</strong>t) bezeichnet<br />
e<strong>in</strong>en “kurzen” <strong>in</strong>t. Tolle und unglaublich genaue Aussage, oder :-)?<br />
Leider kann wirklich nichts allzu Genaues darüber gesagt werden. Das<br />
E<strong>in</strong>zige, was per Def<strong>in</strong>ition garantiert ist, ist Folgendes:<br />
E<strong>in</strong> short ist e<strong>in</strong>e vorzeichenbehaftete Ganzzahl, deren Größe kle<strong>in</strong>er<br />
oder gleich der Größe e<strong>in</strong>es <strong>in</strong>t ist. Zum Glück kann man die Aussage<br />
zum<strong>in</strong>dest e<strong>in</strong> kle<strong>in</strong>es Bisschen präzisieren: Es wird auch garantiert, dass<br />
e<strong>in</strong> short m<strong>in</strong>destens 16 Bit lang ist.<br />
unsigned short <strong>in</strong>t bzw. unsigned short:<br />
Genauso toll, wie die Aussage über short ausgefallen ist, fällt sie auch<br />
für unsigned short (=übliche Kurzform für unsigned short <strong>in</strong>t) aus:<br />
E<strong>in</strong> unsigned short bezeichnet e<strong>in</strong>en “kurzen” unsigned. Garantieren<br />
kann man wieder nur:<br />
E<strong>in</strong> unsigned short ist e<strong>in</strong>e positive Ganzzahl, deren Größe kle<strong>in</strong>er oder<br />
gleich der Größe e<strong>in</strong>es unsigned ist. Auch hier gilt natürlich wieder, dass<br />
die Länge m<strong>in</strong>destens 16 Bit beträgt.<br />
long <strong>in</strong>t bzw. long:<br />
Weil wir gerade bei den epochalen Erkenntnissen waren, machen wir<br />
damit gleich weiter: E<strong>in</strong> long (=übliche Kurzform für long <strong>in</strong>t) bezeichnet<br />
e<strong>in</strong>en “langen” <strong>in</strong>t, für den Folgendes garantiert ist:<br />
E<strong>in</strong> long ist e<strong>in</strong>e vorzeichenbehaftete Ganzzahl, deren Fassungsvermögen<br />
größer oder gleich dem e<strong>in</strong>es <strong>in</strong>t ist. Zum Glück kann man auch diese<br />
Aussage zum<strong>in</strong>dest e<strong>in</strong> kle<strong>in</strong>es Bisschen präzisieren: Es wird weiters<br />
garantiert, dass e<strong>in</strong> long m<strong>in</strong>destens 32 Bit lang ist.<br />
unsigned long <strong>in</strong>t bzw. unsigned long:<br />
Dass nun für e<strong>in</strong>en unsigned long (=übliche Kurzform für<br />
unsigned long <strong>in</strong>t) Folgendes gilt, läßt sich leicht erraten:<br />
E<strong>in</strong> unsigned long ist e<strong>in</strong>e positive Ganzzahl, deren Fassungsvermögen<br />
größer oder gleich dem e<strong>in</strong>es unsigned ist. Die Garantie für die M<strong>in</strong>destlänge<br />
von 32 Bit gilt natürlich hier ebenfalls.<br />
long long <strong>in</strong>t bzw. long long:
18 2. Datentypen und Variablen<br />
Je nach Plattform gibt es auch e<strong>in</strong>en “besonders langen” long Wert.<br />
Se<strong>in</strong>e Existenz ist allerd<strong>in</strong>gs vom Compiler abhängig. Hier hilft leider nur<br />
ausprobieren. Wenn er def<strong>in</strong>iert ist, so kann man Folgendes garantiert<br />
sagen:<br />
E<strong>in</strong> long long ist e<strong>in</strong>e vorzeichenbehaftete Ganzzahl, deren Fassungsvermögen<br />
größer oder gleich dem e<strong>in</strong>es long ist.<br />
unsigned long long <strong>in</strong>t bzw. unsigned long long:<br />
Bezüglich der Existenz e<strong>in</strong>es unsigned long long gilt dasselbe, wie für<br />
den long long Datentyp zuvor. Wenn er def<strong>in</strong>iert ist, so ist Folgendes<br />
garantiert:<br />
E<strong>in</strong> unsigned long long ist e<strong>in</strong>e positive Ganzzahl, deren Fassungsvermögen<br />
größer oder gleich dem e<strong>in</strong>es unsigned long ist.<br />
float:<br />
Wie die Ganzzahl-Datentypen, so s<strong>in</strong>d auch die Gleitkomma-Datentypen<br />
von C ++ masch<strong>in</strong>enabhängig: float repräsentiert e<strong>in</strong>e vorzeichenbehaftete<br />
Gleitkommazahl mit e<strong>in</strong>facher Genauigkeit (=s<strong>in</strong>gle Precision), was<br />
auch immer das heißt. Ich möchte es e<strong>in</strong>fach so formulieren: Arbeitet<br />
man <strong>in</strong> e<strong>in</strong>em Programm mit Gleitkommazahlen, deren Größe sich <strong>in</strong> e<strong>in</strong>em<br />
Bereich von <strong>in</strong> etwa ±10 15 –±10 35 bewegt und bei denen Rundungsfehler<br />
ke<strong>in</strong>e besondere Rolle spielen, dann ist die Verwendung von float<br />
normalerweise ausreichend. Will man ganz genau über das Fassungsvermögen,<br />
die Genauigkeit und andere Eigenschaften e<strong>in</strong>es float und<br />
auch anderer Datentypen Bescheid wissen, so kann man dies über die sogenannten<br />
numeric_limits tun. Da es sich hierbei um e<strong>in</strong>e objektorientierte<br />
Implementation handelt, wird die Besprechung auf Abschnitt 16.7<br />
verschoben.<br />
Nicht-vorzeichenbehaftete Gleitkommazahlen (also unsigned float) gibt<br />
es nicht. Aufgrund der Natur dieser Zahlen wären diese auch nicht besonders<br />
s<strong>in</strong>nvoll.<br />
double:<br />
E<strong>in</strong> double repräsentiert e<strong>in</strong>e vorzeichenbehaftete Gleitkommazahl mit<br />
doppelter Genauigkeit (=double Precision), was auch immer man darunter<br />
nun versteht. Als kle<strong>in</strong>en Anhaltspunkt möchte ich eigentlich nur<br />
erwähnen, dass double üblicherweise vom Wertebereich (ca. ±10 300 )<br />
als auch von der Anfälligkeit gegenüber Rundungsfehlern her für gängige<br />
Gleitkomma-Anwendungen brauchbar ist. Auch hier s<strong>in</strong>d, wie bei float,<br />
alle Eigenschaften über die numeric_limits herausf<strong>in</strong>dbar.<br />
long double:<br />
Wo auch immer double nicht ausreichend ist, kann man auf long double<br />
zurückgreifen, der e<strong>in</strong>e Gleitkommazahl mit erweiterter Genauigkeit<br />
(=extended Precision) bezeichnet. Hier wird es allerd<strong>in</strong>gs gleich noch<br />
etwas schwammiger als bei float und double zuvor, denn Genaues über<br />
se<strong>in</strong>e Natur und se<strong>in</strong> Fassungsvermögen erfährt man wirklich nur noch<br />
durch die entsprechenden Abfragen von numeric_limits.
2.1 Primitive Datentypen 19<br />
Leser, die ihr Grundwissen über primitive Datentypen (auch Gleitkommazahlen),<br />
Besonderheiten derselben und die <strong>in</strong>terne Repräsentation derselben<br />
kurz durch e<strong>in</strong>e viel ausführlichere Erklärung auffrischen möchten, f<strong>in</strong>den e<strong>in</strong>e<br />
genaue Behandlung dieses Themas <strong>in</strong> Kapitel 4 und auch <strong>in</strong> Anhang A<br />
des Buchs <strong>Softwareentwicklung</strong> <strong>in</strong> C.<br />
Dort wird auch anhand von Beispielen die besondere Gefahr beim Mischen<br />
von Datentypen sowie von signed und unsigned-Varianten desselben Typs<br />
aufgezeigt. Alle diese Betrachtungen s<strong>in</strong>d nicht nur für C sondern auch für<br />
C ++ zu 100% gültig!<br />
Leider f<strong>in</strong>det man immer wieder dieselben Fehler beim Umgang mit Variablen<br />
verschiedener Datentypen, egal, ob sie nun von Neul<strong>in</strong>gen begangen<br />
werden oder ob es sich um erfahrenere Entwickler handelt. Die Ursache vieler<br />
Fehler ist sicherlich dar<strong>in</strong> zu f<strong>in</strong>den, dass Datentypen e<strong>in</strong>fach e<strong>in</strong>mal nach<br />
dem Motto “es gibt sie und sie funktionieren” betrachtet werden. Die genauen<br />
Interna werden als “das weiß sowieso der Computer” abgetan. Ich möchte<br />
wirklich allen Lesern ans Herz legen, sich zum<strong>in</strong>dest e<strong>in</strong>mal mit den Interna<br />
genau ause<strong>in</strong>anderzusetzen, um das notwendige Verständnis für potentielle<br />
Fehlerquellen zu entwickeln. Aus diesem Grund möchte ich dieses Kapitel<br />
auch mit e<strong>in</strong> paar H<strong>in</strong>weisen auf mögliche Fallen schließen:<br />
Vorsicht Falle: Es dürfen niemals Annahmen über die Größe und das damit<br />
verbundene Fassungsvermögen von Datentypen getroffen werden. Insbesondere<br />
ist das E<strong>in</strong>zige, was man <strong>in</strong> Bezug auf die Größe von Ganzzahlendatentypen<br />
garantieren kann, Folgendes:<br />
sizeof(short)
20 2. Datentypen und Variablen<br />
Vorsicht Falle: Annahmen über den zugrunde liegenden Zeichensatz auf<br />
e<strong>in</strong>er Plattform s<strong>in</strong>d nicht zulässig. Aus diesem Grund sollen niemals Characters<br />
im Programm hardcodiert durch ihren Character-Code def<strong>in</strong>iert werden.<br />
Weiters ist ke<strong>in</strong>esfalls garantiert, dass es im Zeichensatz des Zielsystems<br />
bestimmte Sonderzeichen (z.B. deutsche Umlaute) überhaupt gibt.<br />
Das Thema der Zeichensätze ist leider ohneh<strong>in</strong> e<strong>in</strong> sehr Leidiges, da gerade<br />
hier praktisch seit der Erf<strong>in</strong>dung der Computer viele parallele “Standards”<br />
existieren.<br />
2.2 Deklaration, Def<strong>in</strong>ition und Initialisierung<br />
Nachdem jetzt zum<strong>in</strong>dest bekannt ist, welche primitiven Datentypen für Variablen<br />
zur Verfügung stehen, wird es Zeit, diese auch wirklich zu verwenden.<br />
Dazu möchte ich gleich zu Beg<strong>in</strong>n e<strong>in</strong>e Def<strong>in</strong>ition der Begriffe Deklaration,<br />
Def<strong>in</strong>ition und Initialisierung anführen, denn diese Begriffe werden fast<br />
durchgehend <strong>in</strong> der Literatur völlig durchmischt und nur allzu oft gänzlich<br />
falsch verwendet.<br />
Deklaration: Etwas zu deklarieren bedeutet, e<strong>in</strong>en H<strong>in</strong>weis zu geben, dass<br />
es “irgendwo” so etwas gibt. Im Falle e<strong>in</strong>er Variable bedeutet damit e<strong>in</strong>e<br />
Deklaration e<strong>in</strong>fach nur e<strong>in</strong>en H<strong>in</strong>weis an den Compiler:<br />
Es gibt e<strong>in</strong>e Variable unter e<strong>in</strong>em bestimmten Namen mit e<strong>in</strong>em bestimmten<br />
Typ. Sollte genau dieser Variablenname irgendwo verwendet werden,<br />
dann weißt du, wie sie zu behandeln ist.<br />
Im Falle e<strong>in</strong>er Variable bedeutet deklarieren def<strong>in</strong>itiv nicht, sie anzulegen<br />
und für sie Speicher zu reservieren, obwohl sich diese Fehlverwendung<br />
des Begriffs durch die Literatur durchzieht. Speicher reservieren, etc. ist<br />
e<strong>in</strong>e Def<strong>in</strong>ition!<br />
Def<strong>in</strong>ition: Etwas zu def<strong>in</strong>ieren bedeutet, es tatsächlich mit allen Konsequenzen<br />
anzulegen. Im Falle e<strong>in</strong>er Variable wird z.B. <strong>in</strong>tern der Code e<strong>in</strong>gesetzt,<br />
der entsprechenden Speicherplatz reserviert, etc.<br />
E<strong>in</strong>er der Hauptgründe, warum die Begriffe Deklaration und Def<strong>in</strong>ition<br />
so oft vermischt werden, ist, weil <strong>in</strong> den meisten Programmiersprachen,<br />
so auch <strong>in</strong> C ++, die Deklaration nicht explizit stattf<strong>in</strong>den muss (aber<br />
kann!), sondern die Deklaration auch implizit bei der Def<strong>in</strong>ition passieren<br />
kann.<br />
Initialisierung: Etwas zu <strong>in</strong>itialisieren bedeutet, ihm beim Anlegen e<strong>in</strong>en<br />
Startwert zuzuweisen. Dies kann <strong>in</strong> den meisten Programmiersprachen,<br />
auch <strong>in</strong> C ++, im Rahmen e<strong>in</strong>er Def<strong>in</strong>ition geschehen, aber niemals bei<br />
e<strong>in</strong>er expliziten Deklaration. Im Pr<strong>in</strong>zip sonnenklar, denn wo nichts<br />
angelegt wird, dort kann auch nichts zugewiesen werden!
2.2 Deklaration, Def<strong>in</strong>ition und Initialisierung 21<br />
In C ++ wurde auch der Begriff der extern-Deklaration für die Deklaration<br />
von Variablen von C übernommen. Zum Thema der expliziten Deklaration<br />
von Variablen <strong>in</strong> C f<strong>in</strong>det sich <strong>in</strong> Kapitel 17 im Buch <strong>Softwareentwicklung</strong> <strong>in</strong><br />
C e<strong>in</strong>e ausführlichere Diskussion, die auch für C ++ volle Gültigkeit hat.<br />
Explizite Deklaration von Variablen ist nur notwendig, wenn e<strong>in</strong>e Variable<br />
<strong>in</strong> e<strong>in</strong>em anderen File verwendet werden soll als <strong>in</strong> dem, <strong>in</strong> dem sie<br />
def<strong>in</strong>iert wurde. Der Grund ist e<strong>in</strong>fach: Beim übersetzen “sieht” der Compiler<br />
immer nur e<strong>in</strong> File nach dem anderen. Erst der L<strong>in</strong>ker, als letztes Glied<br />
bei der Programmübersetzung, sieht alle Files und verknüpft sie zu e<strong>in</strong>em<br />
Gesamtprogramm. Nun muss <strong>in</strong> e<strong>in</strong>em solchen Fall also dem Compiler mitgeteilt<br />
werden, dass es e<strong>in</strong>e Variable unter e<strong>in</strong>em bestimmten Namen und<br />
mit e<strong>in</strong>em bestimmten Typ “irgendwo anders” gibt. Selbstredend muss e<strong>in</strong>e<br />
Deklaration für e<strong>in</strong>e Variable denselben Typ angeben, wie die Def<strong>in</strong>ition, ansonsten<br />
beschwert sich der Compiler. Wie sich weiters leicht erraten lässt,<br />
darf es <strong>in</strong> e<strong>in</strong>em Programm, egal aus wie vielen Files es besteht, immer nur<br />
genau e<strong>in</strong>e Def<strong>in</strong>ition und beliebig viele Deklarationen geben.<br />
Der soeben beschriebene Fall, der e<strong>in</strong>e explizite Deklaration notwendig<br />
macht, ist allerd<strong>in</strong>gs nicht der Regelfall. In sauber strukturiertem Code passiert<br />
es sehr selten bis überhaupt nicht, dass globale Variablen existieren,<br />
die aus mehreren Files heraus sichtbar se<strong>in</strong> müssen. Der Normalfall <strong>in</strong> C ++<br />
ist der, dass Variablen <strong>in</strong> e<strong>in</strong>em e<strong>in</strong>zigen Schritt deklariert und gleichzeitig<br />
def<strong>in</strong>iert werden. Dies geschieht, gleich wie <strong>in</strong> C, <strong>in</strong> der folgenden Form:<br />
;<br />
Es wird also zuerst der Typ (mit optionalem Qualifier), gefolgt vom<br />
gewünschten (hoffentlich sprechenden!) Variablennamen angegeben. Beispiele<br />
für gültige Variablendef<strong>in</strong>itionen wären:<br />
<strong>in</strong>t count;<br />
unsigned <strong>in</strong>dex;<br />
long x_coord<strong>in</strong>ate;<br />
Es gibt auch die Möglichkeit, mehrere Variablen desselben Typs gleich geme<strong>in</strong>sam<br />
<strong>in</strong> e<strong>in</strong>em Statement zu def<strong>in</strong>ieren, <strong>in</strong>dem man die e<strong>in</strong>zelnen Variablennamen<br />
durch e<strong>in</strong>en Beistrich trennt. E<strong>in</strong> Beispiel hierfür wäre:<br />
bool connected, send<strong>in</strong>g_data, receiv<strong>in</strong>g_data;<br />
Allerd<strong>in</strong>gs möchte ich von dieser Art der Def<strong>in</strong>ition aus Gründen der Lesbarkeit<br />
des endgültigen Programms dr<strong>in</strong>gend abraten! Es tut nicht weh, wenn<br />
e<strong>in</strong> Programm um wenige Zeilen länger wird, aber es hilft, auf e<strong>in</strong>en Blick<br />
alles zu f<strong>in</strong>den.<br />
Wie später noch näher beleuchtet werden wird, gibt es e<strong>in</strong>en sehr wichtigen<br />
Unterschied zwischen C und C ++, was den erlaubten Ort von Variablendef<strong>in</strong>itionen<br />
angeht. In C durften Variablen ausschließlich zu Beg<strong>in</strong>n e<strong>in</strong>es<br />
Blocks def<strong>in</strong>iert werden. Im Gegensatz dazu dürfen Variablen <strong>in</strong> C ++ pr<strong>in</strong>zipiell<br />
beliebig im laufenden Code def<strong>in</strong>iert werden, was den resultierenden<br />
Code deutlich lesbarer machen kann. Neben der erhöhten Lesbarkeit gibt es,
22 2. Datentypen und Variablen<br />
je nach Situation, auch e<strong>in</strong>e Performancesteigerung zu erwähnen. Denn wo<br />
ke<strong>in</strong>e Variable angelegt (und <strong>in</strong>itialisiert) wird, dort wird auch ke<strong>in</strong> Speicher<br />
und ke<strong>in</strong>e Rechenleistung dafür verbraucht.<br />
Ist man tatsächlich gezwungen, Variablen aus bereits genannten Gründen<br />
explizit zu deklarieren, so geschieht dies durch Voranstellen des Keywords<br />
extern. Das Statement<br />
extern bool master_runn<strong>in</strong>g;<br />
stellt e<strong>in</strong>e solche explizite Deklaration dar. Durch diese wird dem Compiler<br />
nur mitgeteilt, dass es “irgendwo” die Variable namens master_runn<strong>in</strong>g gibt,<br />
und dass sie den Typ bool hat.<br />
E<strong>in</strong>e explizite Initialisierung von Variablen kann zusammen mit deren<br />
Def<strong>in</strong>ition vorgenommen werden, <strong>in</strong>dem man ihnen gleich direkt e<strong>in</strong>en Wert<br />
zuweist (ich setze hier mutigerweise das = Zeichen als Zuweisungsoperator<br />
als bekannt voraus :-)). Beispiele für gültige Def<strong>in</strong>itionen von Variablen, die<br />
gleich e<strong>in</strong>e Initialisierung be<strong>in</strong>halten, wären:<br />
<strong>in</strong>t count = 0;<br />
unsigned long x_coord = 0, y_coord = 0;<br />
Für den Fall, dass ke<strong>in</strong>e explizite Initialisierung wie eben beschrieben<br />
vorgenommen wird, verhält sich C ++ anders (und besser!) als C. Es wird<br />
nämlich unter bestimmten Umständen e<strong>in</strong>e implizite Initialisierung durchgeführt,<br />
was <strong>in</strong> C nicht der Fall ist.<br />
Diese bestimmten Umstände, die zu e<strong>in</strong>er impliziten Initialisierung führen,<br />
s<strong>in</strong>d Folgende:<br />
• Globale Variablen werden auf ihre entsprechenden Nullwerte <strong>in</strong>itialisiert.<br />
• Als static def<strong>in</strong>ierte lokale Variablen werden auf ihre entsprechenden Nullwerte<br />
<strong>in</strong>itialisiert.<br />
Leser, denen nicht aus C geläufig ist, was static pr<strong>in</strong>zipiell bewirkt,<br />
möchte ich auf Abschnitt 17.3 von <strong>Softwareentwicklung</strong> <strong>in</strong> C h<strong>in</strong>weisen.<br />
• Namespace Variablen werden auf ihre entsprechenden Nullwerte <strong>in</strong>itialisiert<br />
(was Namespace Variablen genau s<strong>in</strong>d, wird <strong>in</strong> Kapitel 14 noch erklärt).<br />
Entsprechende Nullwerte im S<strong>in</strong>ne der Initialisierung bedeutet, dass Zahlenvariablen<br />
auf 0 (bzw. 0.0 bei Gleitkommazahlen), Po<strong>in</strong>ter auf 0 und bool-<br />
Variablen auf false <strong>in</strong>itialisiert werden.<br />
Vorsicht Falle: Lokale Variablen (=auto-Variablen) werden nicht implizit<br />
<strong>in</strong>itialisiert. E<strong>in</strong>er der häufigsten und am schwierigsten zu lokalisierenden<br />
Fehler ist es, Variablen nicht zu <strong>in</strong>itialisieren, ihnen auch danach ke<strong>in</strong>en Wert<br />
zuzuweisen, dann aber ihren Inhalt auszulesen! Dieser Inhalt ist dann “irgende<strong>in</strong>”<br />
Wert. Das Problem, das e<strong>in</strong>en solchen Fehler so schwer lokalisierbar<br />
macht, ist der Umstand, dass ziemlich oft tatsächlich zufällig (durch Interna<br />
des Systems) e<strong>in</strong> Nullwert <strong>in</strong> der Variable steht. Damit kann es passieren,
2.2 Deklaration, Def<strong>in</strong>ition und Initialisierung 23<br />
dass Programme lange Zeit sche<strong>in</strong>bar fehlerlos arbeiten, bis plötzlich nach<br />
e<strong>in</strong>er kle<strong>in</strong>en Änderung an irgende<strong>in</strong>er Stelle im Programm die unerklärlichsten<br />
D<strong>in</strong>ge passieren, obwohl der geänderte Teil und der verrückt spielende<br />
Teil im Pr<strong>in</strong>zip gar nichts mite<strong>in</strong>ander zu tun haben. Wenn so etwas unter<br />
dem üblichen Zeitdruck bei der <strong>Softwareentwicklung</strong> passiert, freuen sich<br />
die Hersteller von Kaffeeautomaten und die Pizzalieferanten, denn die spätnächtliche<br />
Debugg<strong>in</strong>g-Session ist damit garantiert (mit etwas Glück ist es nur<br />
e<strong>in</strong>e Nacht :-)).<br />
Da das Thema der <strong>in</strong>ternen Abwicklung der Initialisierung mit dem jetzigen<br />
Wissensstand noch nicht erschöpfend abgehandelt werden kann, vor<br />
allem da es im Kontext mit den OO-Features von C ++ noch e<strong>in</strong>iges dazu zu<br />
sagen gibt, möchte ich es im Augenblick bei diesen grundsätzlichen Aussagen<br />
belassen. Wo auch immer es wichtige weitere Fakten dazu gibt, werden diese<br />
ergänzend angeführt.<br />
E<strong>in</strong>e Kle<strong>in</strong>igkeit, die zwar auch bei der Initialisierung gebraucht wird,<br />
aber im Pr<strong>in</strong>zip e<strong>in</strong> eigenes Thema ist, s<strong>in</strong>d die sogenannten Literals. Als<br />
Literals werden explizite konstante Zahlen- (z.B. 17, 12.5, etc.) und andere<br />
Werte (z.B. ’a’, true) bezeichnet.<br />
Ich möchte das Thema dieser besonderen Konstanten bzw. Literals hier nur<br />
kurz und beispielhaft behandeln, da es im Pr<strong>in</strong>zip völlig <strong>in</strong>tuitiv ist. Leser,<br />
die sich nach dieser kurzen Beschreibung vielleicht doch nicht so sicher fühlen,<br />
möchte ich auf Kapitel 4 aus <strong>Softwareentwicklung</strong> <strong>in</strong> C verweisen.<br />
Leser, die mit dem oktalen oder dem hexadezimalen Zahlensystem noch<br />
nicht vertraut s<strong>in</strong>d, f<strong>in</strong>den zusätzliche Information dazu <strong>in</strong> Abschnitt A.2.2<br />
im Buch <strong>Softwareentwicklung</strong> <strong>in</strong> C.<br />
Ganzzahlen-Literals: Diese s<strong>in</strong>d im Kontext mit allen verschiedenen Variationen<br />
von Ganzzahl-Datentypen verwendbar, also für <strong>in</strong>t, short,<br />
unsigned, etc., natürlich auch für char bzw. unsigned char und, last,<br />
but not least auch bei bool. Bei Verwendung im Kontext mit bool gibt<br />
es noch e<strong>in</strong> paar Kle<strong>in</strong>igkeiten zu wissen, e<strong>in</strong> Beispiel dazu f<strong>in</strong>det sich<br />
weiter unten.<br />
Die üblichste Schreibweise, die für die meisten Belange die beste ist,<br />
ist die völlig <strong>in</strong>tuitive Dezimalschreibweise: Man schreibt e<strong>in</strong>fach die<br />
gewünschte Zahl, also z.B. 12, 348. Neben dieser Schreibweise gibt es<br />
aber noch zwei besondere Schreibweisen, nämlich die hexadezimale (=zur<br />
Basis 16) und die oktale (=zur Basis 8). Oktalzahlen werden durch e<strong>in</strong>e<br />
vorangestellte 0 gekennzeichnet, Hexadezimalzahlen (kurz: hex-Zahlen)<br />
durch das Präfix 0x. Die folgenden Literals s<strong>in</strong>d also im Pr<strong>in</strong>zip gleichwertig:<br />
18, 0x12 und 022, denn die letzteren beiden s<strong>in</strong>d nur andere<br />
Darstellungen von 18 im hexadezimalen bzw. oktalen Zahlensystem.<br />
Will man e<strong>in</strong> Ganzzahlen-Literal explizit als unsigned kennzeichnen, so<br />
gibt man ihm das Suffix U bzw. u. Will man explizit e<strong>in</strong> long-Literal, so
24 2. Datentypen und Variablen<br />
ist das Suffix L bzw. l angebracht. Also bezeichnet z.B. 18U oder auch<br />
18u explizit e<strong>in</strong>en unsigned Wert, wogegen 18 vom Compiler als <strong>in</strong>t<br />
betrachtet wird. Durch 18L wird dann entsprechend e<strong>in</strong>e Interpretation<br />
als long erzwungen. Natürlich funktioniert die Komb<strong>in</strong>ation 18UL zum<br />
Erzw<strong>in</strong>gen e<strong>in</strong>er Interpretation als unsigned long genauso.<br />
Wenn man, wie es <strong>in</strong> der Praxis der Regelfall ist, explizit ke<strong>in</strong>e Interpretation<br />
als bestimmten Datentyp erzw<strong>in</strong>gt, so “schätzt” der Compiler,<br />
was die s<strong>in</strong>nvollste Interpretation wäre und handelt entsprechend.<br />
Boolean-Literals: Wie zu erwarten s<strong>in</strong>d die Boolean-Literals e<strong>in</strong>fach als true<br />
bzw. false h<strong>in</strong>schreibbar, ohne dass irgendwelche besonderen Konventionen,<br />
wie z.B. Anführungszeichen vonnöten wären.<br />
Character-Literals: E<strong>in</strong> Character-Literal wird e<strong>in</strong>fach als entsprechender<br />
Character, e<strong>in</strong>geschlossen <strong>in</strong> e<strong>in</strong>fache Anführungszeichen dargestellt<br />
(z.B. ’x’ steht für den Character x). Das Umschließen mit e<strong>in</strong>fachen<br />
Anführungszeichen ist notwendig, denn woher sollte sonst der Compiler<br />
zwischen e<strong>in</strong>em Buchstaben und z.B. e<strong>in</strong>er Variable mit e<strong>in</strong>em e<strong>in</strong>buchstabigen<br />
Namen unterscheiden können (abgesehen davon, dass e<strong>in</strong>buchstabige<br />
Namen sowieso nicht erwünscht s<strong>in</strong>d :-))?<br />
Nun gibt es auch Characters, die nicht so e<strong>in</strong>fach als druckbare (!) E<strong>in</strong>zelzeichen<br />
zur Verfügung stehen, wie z.B. e<strong>in</strong> Zeilenumbruch oder e<strong>in</strong> Tabulator.<br />
Für diesen Fall gibt es die sogenannten Escape-Sequenzen, die aus<br />
e<strong>in</strong>em \ (=Backslash), gefolgt von zum<strong>in</strong>dest e<strong>in</strong>em Zeichen bestehen.<br />
E<strong>in</strong> typischer Vertreter davon ist z.B. ’\n’, was für e<strong>in</strong>en Zeilenumbruch<br />
steht, oder ’\\’, was das Backslash-Zeichen selbst darstellt.<br />
Steht man vor dem Problem, dass man e<strong>in</strong> Zeichen als char-Literal schreiben<br />
will, das weder direkt darstellbar ist, noch über e<strong>in</strong>e vordef<strong>in</strong>ierte<br />
Escape-Sequenz zur Verfügung steht, so kann man auch direkt dessen<br />
numerischen Character-Code <strong>in</strong> e<strong>in</strong>er Escape-Sequenz verwenden. Diese<br />
Sequenz hat dann die Form ’\ddd’, wobei ddd für e<strong>in</strong>e dreistellige<br />
Oktalzahl steht.<br />
Wide-Character-Literals: Das e<strong>in</strong>zige Problem bei diesen Literals ist, dem<br />
Compiler klar zu machen, dass er es nicht mit e<strong>in</strong>em normalen char zu<br />
tun hat, sondern mit e<strong>in</strong>em wchar_t. Dies geschieht durch Voranstellen<br />
von L vor das Literal. Z.B. würde also L’x’ den Unicode Buchstaben x<br />
bezeichnen. Im Vorgriff möchte ich hier auch noch gleich erwähnen, dass<br />
diese Regel auch für Str<strong>in</strong>gs gilt: Man stellt ihnen e<strong>in</strong> L voran. Wenn<br />
also e<strong>in</strong> normaler Str<strong>in</strong>g z.B. als "otto" geschrieben wird, so wird se<strong>in</strong><br />
Unicode-Pendant als L"otto" im Code verewigt.<br />
Gleitkomma-Literals: Auch diese Literals s<strong>in</strong>d völlig <strong>in</strong>tuitiv, z.B. stellen<br />
17.3 oder 2.0 gültige Gleitkommazahlen dar. Die “wissenschaftliche”<br />
Schreibweise, z.B. 0.173e2 (steht für 0.173 ∗ 10 2 ) oder 14.93e-15 (steht<br />
für 14.93 ∗ 10 −15 ) ist zulässig. Hierbei ist darauf zu achten, dass ke<strong>in</strong>e<br />
Leerzeichen im Literal vorkommen. Z.B. wäre 14.93 e -15 ungültig!
2.3 Das erste <strong>C++</strong> Programm 25<br />
Pr<strong>in</strong>zipiell werden Gleitkomma-Literals vom Compiler als double <strong>in</strong>terpretiert,<br />
jedoch kann man wie bei Ganzzahl-Literals auch e<strong>in</strong>e andere<br />
Interpretation (als float) erzw<strong>in</strong>gen, sollte dies gewünscht se<strong>in</strong>. Man<br />
muss dazu nur das Suffix F bzw. f verwenden, also z.B. 2.5f.<br />
Vorsicht Falle: Vor allem im deutschsprachigen Raum gibt es manchmal<br />
das e<strong>in</strong>e oder andere Problem bei Neul<strong>in</strong>gen: Das Dezimaltrennzeichen<br />
bei Gleitkommazahlen ist <strong>in</strong> C ++ immer e<strong>in</strong> Punkt! E<strong>in</strong> Komma<br />
wird vom Compiler nicht akzeptiert.<br />
2.3 Das erste <strong>C++</strong> Programm<br />
Genug der grauen Theorie, es wird nun wirklich Zeit für e<strong>in</strong> kle<strong>in</strong>es Beispiel,<br />
das die bisher diskutierten D<strong>in</strong>ge zusammenfasst. Dazu schreiben wir e<strong>in</strong><br />
C ++ Programm mit dem Namen var_demo.cpp.<br />
E<strong>in</strong> kle<strong>in</strong>er Exkurs: Historisch gesehen haben sich für C ++ Programme<br />
die Extensions cpp, cc und C (im Gegensatz zu c für C-Programmen)<br />
e<strong>in</strong>gebürgert. Welche Extension man nun verwendet, ist im Pr<strong>in</strong>zip Geschmackssache.<br />
Ich war selbst aus mehreren Gründen immer e<strong>in</strong> Anhänger<br />
von C, aber mittlerweile setzt sich cpp auf allen Plattformen als Standard<br />
durch. Leider gibt es im Augenblick auch weit verbreitete Compiler gewisser<br />
Hersteller, die sich absolut weigern, Programme mit e<strong>in</strong>em C als Extension<br />
als C ++ Programme anzuerkennen. Das allerd<strong>in</strong>gs ist def<strong>in</strong>itiv <strong>in</strong> me<strong>in</strong>en<br />
Augen nicht akzeptabel. Wie dem auch sei, im Rahmen dieses Buchs wird<br />
durchgehend cpp als Extension verwendet.<br />
Lesern, die bisher noch nie e<strong>in</strong> C-Programm gesehen haben, möchte ich die<br />
Lektüre von Kapitel 3 aus <strong>Softwareentwicklung</strong> <strong>in</strong> C sehr ans Herz legen,<br />
bevor wir zum ersten C ++ Programm kommen.<br />
Das File var_demo.cpp hat folgenden Inhalt (man bemerke: Das erste<br />
Programm heißt nicht hello_world.cpp :-)):<br />
1 // var demo . cpp − demo program to show the behaviour o f <strong>C++</strong> Variables<br />
2<br />
3 #<strong>in</strong>clude <br />
4<br />
5 us<strong>in</strong>g std : : cout ;<br />
6 us<strong>in</strong>g std : : endl ;<br />
7<br />
8 void aFunction ( ) ; // function d e c l a r a t i o n<br />
9<br />
10 <strong>in</strong>t a g l o b a l v a r = 1 7 ; // e x p l i c i t l y i n i t i a l i z e d<br />
11 <strong>in</strong>t a n o t h e r g l o b a l v a r ; // i m p l i c i t l y i n i t i a l i z e d to 0<br />
12<br />
13 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )
26 2. Datentypen und Variablen<br />
14 {<br />
15 aFunction ( ) ;<br />
16<br />
17 cout
a g l o b a l v a r : 1 7<br />
a n o t h e r g l o b a l v a r : 0<br />
bool s e t to true : 1<br />
bool s e t to f a l s e : 0<br />
bool from i n t : 1<br />
bool to i n t : 1<br />
signed (−1) or unsigned ( 2 5 5 ) char ? −1<br />
c a l l c o u n t e r : 2<br />
u n i n i t i a l i z e d var : 134513980<br />
2.3 Das erste <strong>C++</strong> Programm 27<br />
Der hier <strong>in</strong>kludierte Output stammt von e<strong>in</strong>em 32 Bit L<strong>in</strong>ux System. Die Zeilen,<br />
die die Werte der un<strong>in</strong>itialisierten Variablen anzeigen, können natürlich<br />
bei jedem neuen Programmlauf und je nach System verschiedensten Output<br />
liefern. Die Zeile, <strong>in</strong> der ausgegeben wird, ob wir es auf unserem System implizit<br />
mit signed oder unsigned Characters zu tun haben, ist natürlich von<br />
System zu System verschieden. Auf den meisten Systemen wird allerd<strong>in</strong>gs<br />
-1 ausgegeben werden.<br />
Bösartigerweise habe ich e<strong>in</strong> wenig vorgegriffen und im Programm bereits<br />
e<strong>in</strong> C ++ Konstrukt verwendet, das erst im OO-Teil des Buchs genau<br />
besprochen werden wird: Den Standard-Output-Stream (namens cout) zur<br />
Bildschirmausgabe. Aber ke<strong>in</strong>e Panik, dieser Stream erleichtert unser Leben<br />
als Entwickler deutlich und es ist auch ganz e<strong>in</strong>fach, damit umzugehen.<br />
Führen wir also das Programm e<strong>in</strong>mal häppchenweise e<strong>in</strong>er Analyse zu:<br />
• Bevor wir zu den technischen Details kommen, muss ich noch kurz etwas<br />
anmerken: Leser, denen z.B. die Namen der globalen Variablen komisch<br />
vorkommen, weil sie mit e<strong>in</strong>em Underl<strong>in</strong>e enden, möchte ich kurz auf den<br />
Cod<strong>in</strong>g Standard <strong>in</strong> Anhang A verweisen, der den Beispielen <strong>in</strong> diesem<br />
Buch zugrunde liegt.<br />
• In Zeile 1 sehen wir die typische Schreibweise von Kommentaren <strong>in</strong> C ++:<br />
E<strong>in</strong> Kommentar beg<strong>in</strong>nt mit // irgendwo <strong>in</strong> der Zeile und endet automatisch<br />
mit dem Ende der Zeile.<br />
In C ++ kann auch die C-Schreibweise der Block-Kommentare verwendet<br />
werden, die mit /* begonnen und mit */ beendet wird. Dies wird allerd<strong>in</strong>gs<br />
nicht empfohlen, da Schachtelungen dieser Blöcke nicht erlaubt s<strong>in</strong>d und<br />
dies zu Problemen führen kann. E<strong>in</strong>e genaue Diskussion zu diesem Thema<br />
f<strong>in</strong>det sich <strong>in</strong> <strong>Softwareentwicklung</strong> <strong>in</strong> C <strong>in</strong> Kapitel 4.<br />
• Das #<strong>in</strong>clude-Statement <strong>in</strong> Zeile 3 bewirkt, dass Entwickler, die Standard-<br />
C gewohnt s<strong>in</strong>d, lächelnd den Kopf schütteln und sich sicher s<strong>in</strong>d, dass mir<br />
hier e<strong>in</strong> Fehler unterlaufen ist, denn hier sollte doch wohl<br />
#<strong>in</strong>clude <br />
stehen. Leser, die noch ke<strong>in</strong>e Erfahrung mit C haben, beg<strong>in</strong>nen erst jetzt<br />
lächelnd den Kopf zu schütteln und s<strong>in</strong>d sich sicher, dass ich im Fieber<br />
liege, weil ich gerade wirres Zeug schreibe. Beides ist mitnichten der Fall!<br />
Des Rätsels Lösung sieht folgendermaßen aus:<br />
– Ich habe bereits erwähnt, dass es explizite Deklarationen von Variablen<br />
gibt. Ebenso gibt es auch explizite Deklarationen von allen möglichen
28 2. Datentypen und Variablen<br />
anderen D<strong>in</strong>gen, wie z.B. Funktionen (<strong>in</strong> Zeile 8 unseres Programms<br />
zu sehen) oder Klassen und anderen Datentypen, die wir noch kennen<br />
lernen werden. In sogenannten Libraries, also Programmbibliotheken,<br />
werden hilfreiche Tools zur Verfügung gestellt, die Entwicklern das Leben<br />
erleichtern. Diese Libraries werden vom L<strong>in</strong>ker zu unserem ausführbaren<br />
Programm (=Executable) dazugehängt, damit der dar<strong>in</strong> enthaltene Code<br />
auch vom Programm aus aufrufbar ist. Die Deklarationen, was nun<br />
genau für uns im Programm aus den Libraries verwendbar ist, stehen<br />
<strong>in</strong> sogenannten Header-Files. Header-Files haben üblicherweise die File-<br />
Extension .h.<br />
– Die Header-Files der Libraries, die wir im Programm verwenden wollen,<br />
müssen wir explizit e<strong>in</strong>b<strong>in</strong>den, damit die Deklarationen wirksam<br />
werden. Diese Aufgabe übernimmt der sogenannte Preprocessor, auf<br />
den wir später noch genauer e<strong>in</strong>gehen werden. Dieser arbeitet das Programm<br />
noch vor dem C ++ Compiler durch und nimmt textuelle Übersetzungen<br />
vor. Das E<strong>in</strong>b<strong>in</strong>den der Headers erfolgt mittels der #<strong>in</strong>clude<br />
Preprocessor-Anweisung.<br />
– Bei #<strong>in</strong>clude wurde traditionsgemäß immer der Name der Headers <strong>in</strong>klusive<br />
der Extension .h angegeben, dies hat sich allerd<strong>in</strong>gs <strong>in</strong> den neueren<br />
Versionen der C ++ Compiler geändert. E<strong>in</strong>erseits aus Gründen der<br />
Kompatibilität zu älteren Versionen und andererseits um gewisse Optimierungen<br />
z.B. durch Vor-Übersetzung von Headers zu ermöglichen,<br />
wird es dem Preprocessor bei Standard-Headers selbst überlassen, die<br />
entsprechende Extension implizit zu verwenden oder auch e<strong>in</strong>fach mit<br />
Headers ohne Extension zu arbeiten. Damit wird bei solchen Headers<br />
ke<strong>in</strong>e File-Extension mehr angegeben. Genaueres zur Arbeitsweise des<br />
Preprocessors wird <strong>in</strong> Kapitel 7 besprochen.<br />
• Die beiden Anweisungen <strong>in</strong> den Zeilen 5–6 des Programms möchte ich hier<br />
kurz überspr<strong>in</strong>gen und erst weiter unten bei der Erklärung von cout darauf<br />
zurückkommen.<br />
• In Zeile 8 steht die Deklaration e<strong>in</strong>er Funktion namens aFunction, deren<br />
Def<strong>in</strong>ition ab Zeile 40 zu f<strong>in</strong>den ist. Für Leser, die noch ke<strong>in</strong>e Erfahrung<br />
mit C haben und noch nicht so genau wissen, was e<strong>in</strong>e Funktion ist, möchte<br />
ich ganz kurz vorgreifen:<br />
E<strong>in</strong>e Funktion ist e<strong>in</strong>e Zusammenfassung mehrerer Anweisungen zu e<strong>in</strong>em<br />
aufrufbaren Ganzen. Sie nimmt beliebig viele Parameter entgegen und liefert<br />
e<strong>in</strong>en return-Wert als Ergebnis.<br />
E<strong>in</strong>e genaue Erklärung, was es mit Funktionen <strong>in</strong> C pr<strong>in</strong>zipiell auf sich hat,<br />
f<strong>in</strong>det sich <strong>in</strong> Kapitel 8 des Buchs <strong>Softwareentwicklung</strong> <strong>in</strong> C. Ausgenommen<br />
von e<strong>in</strong>igen sehr angenehmen Erweiterungen <strong>in</strong> C ++, auf die später noch<br />
näher e<strong>in</strong>gegangen wird, besitzt alles, was für Funktionen <strong>in</strong> C gilt, auch<br />
<strong>in</strong> C ++ se<strong>in</strong>e volle Gültigkeit.
2.3 Das erste <strong>C++</strong> Programm 29<br />
• Zeile 10 zeigt die Def<strong>in</strong>ition e<strong>in</strong>er globalen Variable mit expliziter Initialisierung,<br />
wie sie auch <strong>in</strong> C üblich ist.<br />
• In Zeile 11 f<strong>in</strong>den wir dann e<strong>in</strong>e implizite Initialisierung, wie sie nur <strong>in</strong> C ++,<br />
nicht aber <strong>in</strong> C funktioniert. Durch das Fehlen e<strong>in</strong>er expliziten Initialisierung<br />
setzt der Compiler beim Übersetzen den richtigen Code e<strong>in</strong>, der die<br />
Variable automatisch auf 0 <strong>in</strong>itialisiert.<br />
• Wie <strong>in</strong> C, so ist auch <strong>in</strong> C ++ Programmen die spezielle Funktion ma<strong>in</strong> der<br />
Startpunkt e<strong>in</strong>es jeden Programms. Und auch hier nimmt ma<strong>in</strong> als ersten<br />
Parameter die Anzahl der übergebenen Command-L<strong>in</strong>e Arguments und<br />
als zweiten Parameter e<strong>in</strong> Array von Str<strong>in</strong>gs, das ebendiese hält. Auch der<br />
return-Wert von ma<strong>in</strong> ist wie gewohnt e<strong>in</strong> <strong>in</strong>t.<br />
E<strong>in</strong>e genaue Beschreibung, was es mit den Command-L<strong>in</strong>e Arguments auf<br />
sich hat, f<strong>in</strong>det sich <strong>in</strong> Abschnitt 20.1 <strong>in</strong> <strong>Softwareentwicklung</strong> <strong>in</strong> C.<br />
• Den Aufruf von aFunction <strong>in</strong> Zeile 15 möchte ich an dieser Stelle überspr<strong>in</strong>gen,<br />
viel <strong>in</strong>teressanter ist das Konstrukt, das <strong>in</strong> Zeile 17 zu f<strong>in</strong>den<br />
ist:<br />
– cout ist der Name des Output-Streams, der den Standard-Output von<br />
Programmen repräsentiert. Dieses cout trägt auch Schuld daran, dass<br />
im Programm der Header iostream <strong>in</strong>kludiert wurde, denn dort f<strong>in</strong>det<br />
sich se<strong>in</strong>e Deklaration.<br />
– Was rechts neben cout so aussieht wie der left-Shift Bit-Operator, hat<br />
<strong>in</strong> Verb<strong>in</strong>dung mit Output-Streams <strong>in</strong> C ++ e<strong>in</strong>e besondere Bedeutung,<br />
nämlich: Schreibe das Folgende auf diesen Stream. Diese Def<strong>in</strong>ition des<br />
30 2. Datentypen und Variablen<br />
teilen wir dem Compiler mit, dass wir cout aus dem Namespace std<br />
verwenden wollen. Würden wir diese Anweisung nicht im Programm<br />
stehen haben, dann würde sich der Compiler bei jeder Verwendung von<br />
cout beschweren, dass er diesen Stream nicht kennt.<br />
Ganz gleich verhält es sich mit endl. E<strong>in</strong>e genaue Erklärung, was es<br />
mit Namespaces auf sich hat, f<strong>in</strong>det sich <strong>in</strong> Kapitel 14. E<strong>in</strong>stweilen ist<br />
es genug, zu wissen, dass man mittels us<strong>in</strong>g die Verwendung gewisser<br />
Teile aus e<strong>in</strong>em Namespace bekannt gibt.<br />
Fassen wir also noch e<strong>in</strong>mal die E<strong>in</strong>zelteile <strong>in</strong> Zeile 17 zusammen, dann<br />
liest sie sich so: Schreibe h<strong>in</strong>tere<strong>in</strong>ander den Str<strong>in</strong>g "a_global_var_: "<br />
und den Inhalt der <strong>in</strong>t-Variablen a_global_var_, gefolgt von e<strong>in</strong>em Zeilenumbruch<br />
auf den Standard-Output des Programms.<br />
• In Zeile 20 f<strong>in</strong>det sich e<strong>in</strong> Beispiel dafür, dass Variablen <strong>in</strong> C ++ beliebig im<br />
laufenden Code def<strong>in</strong>iert werden können, was <strong>in</strong> C nicht möglich ist.<br />
• In den Zeilen 24–28 wird e<strong>in</strong>e besondere Eigenschaft von Variablen vom<br />
Typ bool demonstriert: Sie s<strong>in</strong>d kompatibel zu <strong>in</strong>t-Variablen <strong>in</strong> der H<strong>in</strong>sicht,<br />
dass man beliebige Zahlen zuweisen kann. Allerd<strong>in</strong>gs werden die<br />
Zahlen bei der Zuweisung immer genau auf die vordef<strong>in</strong>ierten Wahrheitswerte<br />
true und false, also auf 0 und 1 reduziert.<br />
• In den Zeilen 30–32 wird kurz demonstriert, wie man untersuchen kann, ob<br />
char auf e<strong>in</strong>er Zielplattform nun per Default als signed oder als unsigned<br />
<strong>in</strong>terpretiert wird: Liefert Zeile 32 als Output die Zahl 255, so wird char<br />
als unsigned <strong>in</strong>terpretiert. Liefert sie allerd<strong>in</strong>gs als Output -1, so liegt per<br />
Default e<strong>in</strong>e signed-Interpretation zugrunde.<br />
Wem jetzt nicht ganz klar ist, wie man plötzlich von 255 auf -1 kommt: 255<br />
ist die längste Zahl, die man mit 8 Bit ohne Vorzeichen darstellen kann.<br />
Dabei s<strong>in</strong>d <strong>in</strong>tern alle Bits gesetzt (=auf 1). Wird diese Zahl nun als signed<br />
<strong>in</strong>terpretiert, dann wird das höchstwertige Bit davon zum Vorzeichenbit.<br />
Weil dieses gesetzt ist, wird die ganze Zahl als negative Zahl angesehen. Die<br />
8 Bit-Darstellung von -1 entspricht genau dem, was <strong>in</strong> der Zahl enthalten<br />
ist: Es s<strong>in</strong>d alle Bits gesetzt.<br />
Lesern, die noch e<strong>in</strong> wenig unsicher mit der <strong>in</strong>ternen Darstellung von<br />
b<strong>in</strong>ären Ganzzahlen s<strong>in</strong>d, möchte ich an dieser Stelle die Lektüre von Anhang<br />
A.2 aus <strong>Softwareentwicklung</strong> <strong>in</strong> C empfehlen.<br />
• Zeile 42 <strong>in</strong> aFunction demonstriert noch, dass auch als static def<strong>in</strong>ierte<br />
Variablen implizit <strong>in</strong>itialisiert werden.<br />
• Last, but not least demonstriert Zeile 46, dass un<strong>in</strong>itialisierte Variablen nun<br />
wirklich ke<strong>in</strong>e sehr gute Idee s<strong>in</strong>d. Dass e<strong>in</strong>e solche Variable “irgende<strong>in</strong>en”<br />
nicht vorherbestimmbaren Wert hat, sieht man e<strong>in</strong>deutig am Output des<br />
Programms.
2.4 Zusammengesetzte Datentypen<br />
2.4 Zusammengesetzte Datentypen 31<br />
Die reale Welt besteht nicht e<strong>in</strong>fach nur aus e<strong>in</strong>zelnen, alle<strong>in</strong> stehenden Zahlen<br />
oder Buchstaben. Es liegt <strong>in</strong> der Natur der Sache, dass verschiedenste<br />
Zusammengehörigkeiten von kle<strong>in</strong>eren E<strong>in</strong>heiten e<strong>in</strong> großes Ganzes ergeben.<br />
Wenn man e<strong>in</strong>fach nur geschriebenen Text betrachtet, dann sieht man schon,<br />
dass mehrere Buchstaben ane<strong>in</strong>ander gereiht e<strong>in</strong> Wort ergeben, mehrere Worte<br />
ergeben e<strong>in</strong>en Satz, mehrere Sätze e<strong>in</strong>en Absatz, etc. Parallel dazu liegt<br />
e<strong>in</strong>em gedruckten Text noch e<strong>in</strong>e Seitenstruktur zugrunde, usw.<br />
Um den Entwicklern beim Modellieren wie auch immer gearteter Zusammenhänge<br />
entgegenzukommen, gibt es <strong>in</strong> C ++ neben den OO-Konstrukten<br />
zur Modellierung von Zusammenhängen auch die von C übernommenen zusammengesetzten<br />
Datentypen. Dieses Kapitel befasst sich genau mit diesen,<br />
denn auch für OO-Entwicklung ist das Wissen darüber unabd<strong>in</strong>gbar.<br />
2.4.1 Arrays<br />
Der erste zusammengesetzte Datentyp, der näher unter die Lupe genommen<br />
werden soll, ist das Array, genauer gesagt, das statische Array (Anm.: Der<br />
Unterschied zum dynamischen Array, das später besprochen wird, liegt nur<br />
<strong>in</strong> der Art und Weise, wie Speicher dafür reserviert wird).<br />
E<strong>in</strong> Array ist e<strong>in</strong>fach e<strong>in</strong> <strong>in</strong>dizierbares Feld von Daten desselben Typs, egal<br />
um welchen Typ es sich dabei handelt. Bei der Def<strong>in</strong>ition von Array Variablen<br />
<strong>in</strong> C ++ wird angegeben, welchen Typ die e<strong>in</strong>zelnen Elemente haben und wie<br />
viele Elemente maximal dar<strong>in</strong> gespeichert werden können. Syntaktisch und<br />
semantisch entspricht dies der Def<strong>in</strong>ition e<strong>in</strong>er “normalen” Variable, gefolgt<br />
von der Anzahl der Elemente <strong>in</strong> eckigen Klammern. Z.B. würde die Def<strong>in</strong>ition<br />
e<strong>in</strong>es Arrays, das 50 Characters halten kann, folgendermaßen aussehen:<br />
char my_char_array[50];<br />
Zuvor war die Rede davon, dass e<strong>in</strong> Array <strong>in</strong>dizierbar ist, dass man also<br />
über e<strong>in</strong>en Index auf die e<strong>in</strong>zelnen Elemente dieses Arrays zugreifen kann.<br />
Dies wird e<strong>in</strong>fach dadurch bewerkstelligt, dass man beim Zugriff den Index<br />
des gewünschten Elements <strong>in</strong> eckigen Klammern der entsprechenden Variable<br />
nachstellt. Will man z.B. aus dem oben def<strong>in</strong>ierten Array das Element mit<br />
dem Index 20 ansprechen, dann liest sich das so:<br />
my_char_array[20]<br />
Man kann natürlich sowohl das angesprochene Element auslesen als ihm<br />
auch e<strong>in</strong>en Wert zuweisen. Jetzt ist nur noch genau zu klären, was es mit<br />
dem Index auf sich hat: Wie <strong>in</strong> den meisten Programmiersprachen üblich,<br />
ist der Index des ersten Elements 0! Das bedeutet also logischerweise, dass<br />
für e<strong>in</strong> Array mit size Elementen die Indizes von 0...(size − 1) im erlaubten<br />
Bereich liegen.<br />
Vorsicht Falle: In C ++ Programmen wird zur Laufzeit ke<strong>in</strong>e Überprüfung<br />
vorgenommen, ob der <strong>in</strong>dizierte Zugriff auf e<strong>in</strong> Array auch <strong>in</strong>nerhalb der
32 2. Datentypen und Variablen<br />
erlaubten Grenzen liegt! Zumeist wird auf e<strong>in</strong>zelne Elemente e<strong>in</strong>es Arrays<br />
auch nicht über konstante Werte zugegriffen, sondern es wird im Programm<br />
e<strong>in</strong> Index ausgerechnet, über den dann zugegriffen wird. Dabei passiert es<br />
z.B. durch typische off-by-one Fehler (=durch Unachtsamkeit alles um 1 verschoben)<br />
nur allzu leicht, dass man e<strong>in</strong>mal danebengreift. Das Programm<br />
quittiert im Glücksfall solche Fehler sofort mit e<strong>in</strong>er entsprechenden Segmentation<br />
Violation oder mit e<strong>in</strong>em Blue-Screen. Hat man Pech, dann stürzt das<br />
Programm nicht ab, weil der Speicher, auf den man irrtümlich zugreift, dem<br />
Programm selbst gehört. Damit überschreibt man unabsichtlich irgendetwas,<br />
was gerade zufällig dort zu liegen kam (z.B. e<strong>in</strong> Element e<strong>in</strong>es anderen<br />
Arrays). Als Folge verfärben ungeahnte seltsame Ergebnisse bei diversen<br />
Testläufen das Gesicht des Entwicklers zuerst <strong>in</strong> e<strong>in</strong> zartes Rosa, das im Lauf<br />
der Zeit <strong>in</strong> e<strong>in</strong> kräftiges Rot umschlägt. Dann setzt die Phase des kalten<br />
Schweißes e<strong>in</strong>, wobei gleichzeitig das Gesicht zu e<strong>in</strong>em leicht grünlichen Weiß<br />
verblasst.<br />
Für die Initialisierung der e<strong>in</strong>zelnen Felder von Array-Variablen gilt dasselbe,<br />
was bereits für Variablen primitiver Datentypen gesagt wurde: globale,<br />
static-auto und Namespace Variablen werden auf die jeweiligen 0-Werte <strong>in</strong>itialisiert.<br />
Legt man also z.B. e<strong>in</strong>e globale Variable an, die e<strong>in</strong> statisches<br />
<strong>in</strong>t-Array repräsentiert, so werden bei Fehlen von expliziter Initialisierung<br />
die e<strong>in</strong>zelnen Elemente auf 0 gesetzt.<br />
Natürlich kann man Arrays, gleich wie <strong>in</strong> C, explizit <strong>in</strong>itialisieren. Dazu<br />
gibt man die Werte zu dessen Initialisierung, getrennt durch Beistriche, <strong>in</strong> e<strong>in</strong>em<br />
Block an (d.h. von geschwungenen Klammern e<strong>in</strong>gefasst). Dies lässt sich<br />
am besten gleich an e<strong>in</strong>em Beispiel zeigen, das das Initialisierungsverhalten<br />
von Arrays demonstriert (array_<strong>in</strong>itializ<strong>in</strong>g_demo.cpp):<br />
1 // a r r a y i n i t i a l i z i n g d e m o . cpp − small program to demonstrate , how<br />
2 // array i n i t i a l i z i n g works <strong>in</strong> <strong>C++</strong><br />
3<br />
4 #<strong>in</strong>clude <br />
5<br />
6 us<strong>in</strong>g std : : cout ;<br />
7 us<strong>in</strong>g std : : endl ;<br />
8<br />
9 // i m p l i c i t l y i n i t i a l i z e d<br />
10 <strong>in</strong>t f i r s t g l o b a l a r r a y [ 5 ] ;<br />
11<br />
12 // e x p l i c i t l y i n i t i a l i z e d<br />
13 <strong>in</strong>t s e c o n d g l o b a l a r r a y [ ] = { 1 , 2 , 3 , 4 , 5 } ;<br />
14<br />
15 // parts of the array e x p l i c i t l y i n i t i a l i z e d<br />
16 // r e s t of i t i m p l i c i t l y i n i t i a l i z e d<br />
17 <strong>in</strong>t t h i r d g l o b a l a r r a y [ 5 ] = { 1 , 2 , 3 } ;<br />
18<br />
19 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
20 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
21 {<br />
22 unsigned count ;<br />
23<br />
24 cout
26 cout
34 2. Datentypen und Variablen<br />
Sobald alle Werte aus der expliziten Initialisierung “aufgebraucht” s<strong>in</strong>d, wird<br />
der verbleibende Rest des Arrays auf 0 gesetzt.<br />
In den Zeilen 13 und 17 sieht man, dass man bei Verwendung expliziter<br />
Initialisierung die Wahl hat, entweder den Compiler die Größe e<strong>in</strong>es Arrays<br />
ermitteln zu lassen oder die Größe selbst vorzugeben. Wenn man allerd<strong>in</strong>gs<br />
die Größe selbst vorgibt, so muss diese unbed<strong>in</strong>gt größer oder gleich der Anzahl<br />
der Elemente <strong>in</strong> der Initialisierungsliste se<strong>in</strong>. Ansonsten reagiert der<br />
Compiler gar nicht sehr freundlich. Würde man z.B. <strong>in</strong> Zeile 17 als Größe<br />
den Wert 2 anstatt 5 angeben, dann würde der Compiler zu Recht behaupten,<br />
dass man sich gefälligst entscheiden soll, was man denn nun überhaupt<br />
will. E<strong>in</strong>erseits soll e<strong>in</strong> Array mit Größe 2 angelegt werden, andererseits will<br />
man gleich danach aber 3 Elemente h<strong>in</strong>e<strong>in</strong> schreiben. Allerd<strong>in</strong>gs lässt sich<br />
durch richtig Stellen der gewünschten Anzahl der Elemente der Compiler<br />
ganz e<strong>in</strong>fach wieder beruhigen :-).<br />
Arrays gibt es, gleich wie <strong>in</strong> C, nicht nur e<strong>in</strong>dimensional, sondern im<br />
Pr<strong>in</strong>zip mit beliebig vielen Dimensionen (solange der Speicher nicht ausgeht...).<br />
Auch das ist am besten schnell an e<strong>in</strong>em Beispiel demonstriert<br />
(multi_dimensional_array_demo.cpp):<br />
1 // multi dimensional array demo . cpp − a short demo program f o r<br />
2 // multidimensional arrays<br />
3<br />
4 #<strong>in</strong>clude <br />
5<br />
6 us<strong>in</strong>g std : : cout ;<br />
7 us<strong>in</strong>g std : : endl ;<br />
8<br />
9 // i m p l i c i t l y i n i t i a l i z e d matrix<br />
10 <strong>in</strong>t f i r s t d e m o m a t r i x [ 3 ] [ 3 ] ;<br />
11<br />
12 // e x p l i c i t l y i n i t i a l i z e d matrix<br />
13 <strong>in</strong>t second demo matrix [ ] [ 3 ] = { { 1 , 2 , 3 } ,<br />
14 { 4 , 5 , 6 } ,<br />
15 { 7 , 8 , 9 } } ;<br />
16<br />
17 // parts of the matrix e x p l i c i t l y i n i t i a l i z e d<br />
18 // r e s t of i t i m p l i c i t l y i n i t i a l i z e d<br />
19 <strong>in</strong>t third demo matrix [ 3 ] [ 3 ] = { { 1 , 2 , 3 } ,<br />
20 { 4 , 5 } } ;<br />
21<br />
22<br />
23 // a l s o t h i s i s p o s s i b l e<br />
24 <strong>in</strong>t fourth demo matrix [ 3 ] [ 3 ] = { { 1 , 2 , 3 } ,<br />
25 { 4 , 5 } ,<br />
26 { 6 , 7 , 8 } } ;<br />
27<br />
28 // t h i s r e s u l t s <strong>in</strong> a warn<strong>in</strong>g , but i s p o s s i b l e<br />
29 <strong>in</strong>t fifth demo matrix [ 3 ] [ 3 ] = { 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 } ;<br />
30<br />
31 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
32 {<br />
33 <strong>in</strong>t outer count = 0;<br />
34 <strong>in</strong>t <strong>in</strong>ner count = 0;<br />
35<br />
36 cout
2.4 Zusammengesetzte Datentypen 35<br />
39 for ( <strong>in</strong>ner count = 0 ; <strong>in</strong>ner count < 3 ; <strong>in</strong>ner count++)<br />
40 cout
36 2. Datentypen und Variablen<br />
Dimensionen mit ihren jeweiligen Größen angegeben. Dass hier wieder e<strong>in</strong>e<br />
implizite Initialisierung stattf<strong>in</strong>det, versteht sich von selbst. Diese Matrix<br />
wird <strong>in</strong> den Zeilen 36–42 Element für Element ausgegeben.<br />
Die Zeilen 13–15 zeigen, wie man mehrdimensionale Arrays explizit <strong>in</strong>itialisiert,<br />
nämlich Zeile für Zeile! Jede Zeile für sich wird <strong>in</strong> e<strong>in</strong>en Block<br />
verpackt (<strong>in</strong>dem man sie <strong>in</strong> geschwungene Klammern e<strong>in</strong>schließt). Die e<strong>in</strong>zelnen<br />
Blöcke, je e<strong>in</strong>er für e<strong>in</strong>e Zeile, werden <strong>in</strong> gewohnter Manier durch<br />
Beistriche getrennt und <strong>in</strong> e<strong>in</strong>em umschließenden Block zusammengefasst.<br />
So weit kl<strong>in</strong>gt das alles ganz logisch und man vermutet, dass man auf diese<br />
Weise auch e<strong>in</strong>fach wieder das Erraten des Fassungsvermögens für die e<strong>in</strong>zelnen<br />
Dimensionen dem Compiler überlassen kann. In Zeile 13 erkennt man<br />
allerd<strong>in</strong>gs dabei e<strong>in</strong>e Besonderheit, die auf den ersten Blick eigentlich nicht<br />
ganz verständlich ist: Hier ist die erste Dimension dem Compiler überlassen,<br />
die zweite Dimension aber ist explizit angegeben! Das ist nicht passiert,<br />
weil ich gerade beim Schreiben des Programms <strong>in</strong> e<strong>in</strong>em Zustand schwerer<br />
geistiger Umnachtung war, dies ist tatsächlich verpflichtend! Man darf ausschließlich<br />
die erste Dimension leer lassen, alle weiteren Dimensionen<br />
müssen explizit angegeben werden.<br />
Der Grund für diese, zugegeben etwas komische, E<strong>in</strong>schränkung ist e<strong>in</strong><br />
historischer, wie <strong>in</strong> Zeile 29 demonstriert wird: Es ist nicht verpflichtend,<br />
die e<strong>in</strong>zelnen Zeilen <strong>in</strong> Blöcken zusammenzufassen. Im Pr<strong>in</strong>zip ist es auch<br />
möglich, e<strong>in</strong>fach alles <strong>in</strong> e<strong>in</strong>em Block ane<strong>in</strong>ander zu reihen. Je nach Compiler<br />
und e<strong>in</strong>gestelltem Warn<strong>in</strong>g-Level wird dies zwar heutzutage als gefährlich<br />
beanstandet, dies bedeutet aber nicht, dass es nicht möglich wäre. Der Output<br />
dieses Arrays, der von den Zeilen 68–74 erzeugt wird, zeigt auch, wie<br />
die e<strong>in</strong>zelnen Initialisierungswerte vom Compiler verwendet werden: Es werden<br />
die Elemente e<strong>in</strong>fach der Reihe nach auf die e<strong>in</strong>zelnen Zeilen der Matrix<br />
aufgeteilt. Wenn e<strong>in</strong>e Zeile voll ist, wird mit der nächsten weitergemacht.<br />
Jetzt kann man sich auch ausmalen, woher die E<strong>in</strong>schränkung kommt,<br />
dass nur die erste Dimension leer gelassen werden darf: Woher soll denn der<br />
Compiler erraten können, wie die Aufteilung der Elemente auf die e<strong>in</strong>zelnen<br />
Dimensionen des Arrays erfolgen soll? S<strong>in</strong>d z.B. 12 Elemente nun 2 Zeilen<br />
zu je 6 Spalten oder doch 4 Zeilen zu je 3 Spalten oder...?<br />
Obwohl durch die <strong>in</strong> C ++ erlaubte (eigentlich sogar stark empfohlene)<br />
vollständige blockweise Aufteilung alle Dimensionen für den Compiler ersichtlich<br />
s<strong>in</strong>d, gibt es eben leider noch die von C geerbte Altlast des nicht-<br />
Spezifizierens der e<strong>in</strong>zelnen Sub-Blöcke. Deshalb wurde konsequenterweise<br />
def<strong>in</strong>iert, dass die historische Regel weiterh<strong>in</strong> Gültigkeit hat, dass nur e<strong>in</strong>e<br />
Dimension dem Compiler überlassen werden darf.<br />
Bleiben wir aber bei der sauberen Schreibweise zur Initialisierung: Die<br />
Def<strong>in</strong>ition der Matrix <strong>in</strong> den Zeilen 19–20 zeigt, dass man sich auch bei<br />
beliebig vielen Dimensionen auf die implizite Initialisierung des verbleibenden<br />
Rests durch den Compiler verlassen kann. Auch der Fall, dass “mitten dr<strong>in</strong>”
2.4 Zusammengesetzte Datentypen 37<br />
e<strong>in</strong>zelne Zeilen nicht vollständig explizit <strong>in</strong>itialisiert werden, ist möglich, wie<br />
die Zeilen 24–26 zeigen.<br />
Vorsicht Falle: Ich möchte dr<strong>in</strong>gend raten, die “Spaghetti-Schreibweise”,<br />
die <strong>in</strong> Zeile 29 des soeben behandelten Programms demonstriert ist, nicht<br />
<strong>in</strong> Programmen zu verwenden. Man kann sich leicht vorstellen, dass diese<br />
Schreibweise sehr schnell unübersichtlich und damit fehleranfällig wird. Es<br />
genügt schon, e<strong>in</strong> Element zwischendurch zu vergessen und alle nachfolgenden<br />
Elemente s<strong>in</strong>d um eben dieses fehlende Element nach vorne verschoben.<br />
Wie unterhaltsam die Suche nach solchen Fehlern wird, überlasse ich der<br />
Phantasie der Leser.<br />
2.4.2 Structures<br />
Arrays, die zuvor behandelt wurden, s<strong>in</strong>d Aggregate vieler Elemente desselben<br />
Typs. Oft braucht man aber Aggregate mehrerer Elemente verschiedenen<br />
Typs, die man aufgrund ihrer logischen Zusammengehörigkeit mite<strong>in</strong>ander<br />
koppeln möchte. Typische Beispiele für solche Bedürfnisse s<strong>in</strong>d z.B. bei<br />
Adressen zu f<strong>in</strong>den: Hier werden Name, Ortsbezeichnung, Straße, Hausnummer,<br />
etc. zu e<strong>in</strong>em geme<strong>in</strong>samen Ganzen, eben der Adresse, verbunden.<br />
Bevor ich allerd<strong>in</strong>gs zur Beschreibung der sogenannten Structures komme,<br />
die diesen gewünschten Aggregat-Typen darstellen, möchte ich noch etwas<br />
vorausschicken: Im Pr<strong>in</strong>zip s<strong>in</strong>d Structures <strong>in</strong> C ++ nicht mehr das, was sie <strong>in</strong><br />
C e<strong>in</strong>mal waren. In C ++ s<strong>in</strong>d sie eigentlich Classes, nur dass alle ihre Members<br />
public s<strong>in</strong>d (Genaueres dazu f<strong>in</strong>det sich <strong>in</strong> Kapitel 9). Alles, was man <strong>in</strong><br />
C mit Structures machen konnte, kann man auch <strong>in</strong> C ++ und noch E<strong>in</strong>iges<br />
mehr. Um also Structures im C ++ S<strong>in</strong>n ernsthaft beschreiben zu können,<br />
brauchen wir den entsprechenden OO-Kontext. Deshalb ist die nachfolgende<br />
Abhandlung über Structures entsprechend kurz gehalten. Wir werden auf<br />
das Thema später noch e<strong>in</strong>mal <strong>in</strong> Abschnitt 9.1 zurückkommen.<br />
E<strong>in</strong>e ausführliche Beschreibung von Structures, wie sie <strong>in</strong> C existieren, kann<br />
man <strong>in</strong> Kapitel 11 des Buchs <strong>Softwareentwicklung</strong> <strong>in</strong> C nachlesen. Dort<br />
wird auch auf e<strong>in</strong>ige wichtige Aspekte der Kapselung e<strong>in</strong>gegangen, die im<br />
Endeffekt zum Konzept von Classes <strong>in</strong> C ++ geführt haben.<br />
Am e<strong>in</strong>fachsten s<strong>in</strong>d Structures gleich am Beispiel erklärt. Um nicht,<br />
wie praktisch überall <strong>in</strong> der Literatur üblich, e<strong>in</strong>e Adresse zur Erklärung<br />
heranzuziehen, b<strong>in</strong> ich fast schon unglaublich kreativ und verwende e<strong>in</strong><br />
Pixel, das x und y-Koord<strong>in</strong>aten, sowie e<strong>in</strong>e Farbe als Status besitzt :-)<br />
(struct_demo.cpp):<br />
1 // struct demo . cpp − a t<strong>in</strong>y demo program f o r the use o f s t r u c t u r e s<br />
2<br />
3 #<strong>in</strong>clude <br />
4
38 2. Datentypen und Variablen<br />
5 us<strong>in</strong>g std : : cout ;<br />
6 us<strong>in</strong>g std : : endl ;<br />
7<br />
8 struct Pixel<br />
9 {<br />
10 unsigned x coord ;<br />
11 unsigned y coord ;<br />
12 // the s i n g l e c o l o r channels red , green and blue<br />
13 // are accessed through the i n d i c e s 0 , 1 and 2<br />
14 unsigned short rgb value [ 3 ] ;<br />
15 } ;<br />
16<br />
17 struct Pixel g l o b a l p i x e l ; // i m p l i c i t l y i n i t i a l i z e d<br />
18<br />
19 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
20 {<br />
21 struct Pixel o n e p i x e l ;<br />
22<br />
23 o n e p i x e l . x coord = 10;<br />
24 o n e p i x e l . y coord = 30;<br />
25 o n e p i x e l . rgb value [ 0 ] = 0xFF ; // maximum red<br />
26 o n e p i x e l . rgb value [ 1 ] = 0 x00 ; // no green<br />
27 o n e p i x e l . rgb value [ 2 ] = 0 x00 ; // no blue<br />
28<br />
29 cout
2.4 Zusammengesetzte Datentypen 39<br />
Vorsicht Falle: Nur allzu leicht wird der Strichpunkt nach der schließenden<br />
Klammer des Deklarationsblocks der Members e<strong>in</strong>er Structure vergessen.<br />
Dies führt dann zu äußerst kryptischen Fehlermeldungen des Compilers, da<br />
dieser versucht, das nachfolgende Statement noch mit der laufenden Deklaration<br />
<strong>in</strong> Verb<strong>in</strong>dung zu br<strong>in</strong>gen. Neul<strong>in</strong>ge suchen bei solchen Fehlermeldungen<br />
zumeist verzweifelt am falschen Ort nach dem Fehler, da der Ort der Fehlermeldung<br />
des Compilers im Regelfall e<strong>in</strong>e der Zeilen ist, die der struct-<br />
Deklaration nachfolgen, obwohl der tatsächliche Fehler, also der vergessene<br />
Strichpunkt, bereits weiter oben liegt.<br />
Deshalb ist es bei unerklärlichen Fehlermeldungen <strong>in</strong> verme<strong>in</strong>tlich richtigen<br />
Programmzeilen immer sehr ratsam, auch e<strong>in</strong> wenig im Code nach oben<br />
zu sehen, ob sich nicht vielleicht irgendwo e<strong>in</strong>e offiziell nicht (durch Strichpunkt)<br />
beendete Deklaration e<strong>in</strong>er Structure f<strong>in</strong>det.<br />
Bevor wir uns wieder dem Programm zuwenden, möchte ich noch schnell<br />
erklären, warum ich bei Structures auf dem Begriff der Deklaration herumreite.<br />
Normalerweise wird immer von der Def<strong>in</strong>ition e<strong>in</strong>er struct gesprochen<br />
(sogar von mir selbst im Buch <strong>Softwareentwicklung</strong> <strong>in</strong> C ).<br />
Ich b<strong>in</strong> aus folgendem Grund selbst dazu übergegangen, sehr p<strong>in</strong>gelig<br />
zu se<strong>in</strong> und auch hier konsequent den Begriff der Deklaration e<strong>in</strong>er struct<br />
zu verwenden: Man kann sich e<strong>in</strong>fach überlegen, was e<strong>in</strong> Compiler aus e<strong>in</strong>er<br />
struct-Deklaration macht. Er nimmt sie quasi als benutzerdef<strong>in</strong>ierten<br />
Datentyp und entnimmt der Deklaration, welche Members <strong>in</strong> ihr enthalten<br />
s<strong>in</strong>d und unter welchen Namen sie ansprechbar s<strong>in</strong>d. Es wird ke<strong>in</strong> Speicher<br />
angelegt oder irgendetwas Anderes unternommen, was sich an der Stelle<br />
der Deklaration im übersetzten Programm wiederf<strong>in</strong>den würde. Und dieser<br />
H<strong>in</strong>weis “es gibt e<strong>in</strong>e struct, die folgendermaßen aussieht...” ist genau e<strong>in</strong>e<br />
Deklaration. Die Def<strong>in</strong>ition e<strong>in</strong>er Variable dieses Typs erfolgt dann erst<br />
später, z.B. <strong>in</strong> unserem Programm <strong>in</strong> Zeile 17.<br />
Nach diesem kurzen Exkurs wieder zurück zu unserem Programm: E<strong>in</strong>e<br />
struct ist, wie schon erwähnt, e<strong>in</strong> Aggregat aus verschiedenen Members,<br />
die beliebigen Typs se<strong>in</strong> können. Members können auch z.B. selbst wieder<br />
irgendwelche Structures se<strong>in</strong>. Wie man nun e<strong>in</strong>e Variable def<strong>in</strong>iert, der e<strong>in</strong>e<br />
struct zugrunde liegt, sieht man <strong>in</strong> den Zeilen 17 und 21.<br />
Nach der Def<strong>in</strong>ition e<strong>in</strong>er struct-Variable kann man auf die e<strong>in</strong>zelnen<br />
Members zugreifen, die für diese struct deklariert wurden. Dies geschieht,<br />
<strong>in</strong>dem man dem Variablennamen e<strong>in</strong>en Punkt nachstellt, gefolgt vom Namen<br />
des gewünschten Members. Die e<strong>in</strong>zelnen Members der Variable one_pixel<br />
werden z.B. angesprochen, wie <strong>in</strong> den Zeilen 23–27 gezeigt.<br />
Auch bei struct-Variablen gelten wieder dieselben Regeln für die implizite<br />
Initialisierung, wie sie bereits besprochen wurden. Bei der impliziten<br />
Initialisierung werden alle Members e<strong>in</strong>zeln auf ihre entsprechenden Nullwerte<br />
gesetzt. Die Def<strong>in</strong>ition von global_pixel_ und das Ausgeben ihres<br />
Inhalts <strong>in</strong> den Zeilen 29–34 demonstrieren dies.
40 2. Datentypen und Variablen<br />
Ganz wichtige Aussagen über die Members e<strong>in</strong>er struct b<strong>in</strong> ich noch<br />
schuldig geblieben, die <strong>in</strong> Kürze <strong>in</strong> Verb<strong>in</strong>dung mit Unions und später ganz<br />
besonders <strong>in</strong> Verb<strong>in</strong>dung mit Po<strong>in</strong>tern an Bedeutung gew<strong>in</strong>nen:<br />
• Die e<strong>in</strong>zelnen Members e<strong>in</strong>er struct s<strong>in</strong>d garantiert genau <strong>in</strong> der Reihenfolge<br />
im Speicher angeordnet, <strong>in</strong> der sie <strong>in</strong> der Deklaration der struct<br />
angeführt werden.<br />
• Es wird allerd<strong>in</strong>gs nicht garantiert, dass die e<strong>in</strong>zelnen Members unmittelbar<br />
h<strong>in</strong>tere<strong>in</strong>ander im Speicher zu liegen kommen! Je nach Compiler<br />
und Zielplattform kann es passieren, dass zwischen den e<strong>in</strong>zelnen Members<br />
“Löcher” im Speicher frei bleiben.<br />
• Es wird garantiert, dass die Startadresse des ersten Members im Speicher<br />
mit der Startadresse der gesamten struct übere<strong>in</strong>stimmt.<br />
2.4.3 Unions<br />
Es gibt Fälle, <strong>in</strong> denen man damit umgehen können muss, dass Variablen<br />
e<strong>in</strong>en kontextabhängigen Typ besitzen. Als Motivation für die Existenz e<strong>in</strong>es<br />
solchen Konstrukts möchte ich hier das Event-Handl<strong>in</strong>g bei graphischen<br />
Benutzeroberflächen anführen:<br />
• Die Benutzeroberflächen der verschiedenen gängigen Betriebssysteme (MS-<br />
W<strong>in</strong>dows, Unix, MacOS, etc.) folgen alle demselben Pr<strong>in</strong>zip: Egal, was<br />
passiert, ob nun e<strong>in</strong>e Taste am Keyboard gedrückt wird, die Maus bewegt<br />
wird oder sonstige Ereignisse auftreten, die Programme bekommen e<strong>in</strong>e<br />
Mitteilung über das Auftreten e<strong>in</strong>es solchen Ereignisses als sogenannten<br />
Event bzw. als Message mitgeteilt.<br />
• Alle Events werden ungeachtet des Typs <strong>in</strong> dieselbe Queue für das jeweilige<br />
Programm gestellt, das von ihnen betroffen ist. Das Programm hat die<br />
Aufgabe, diese Events Stück für Stück aus der Queue zu lesen und darauf<br />
zu reagieren.<br />
Nun kann man sich leicht vorstellen, dass z.B. e<strong>in</strong> Keyboard-Event ganz andere<br />
Informationen enthält als e<strong>in</strong> Mouse-Event. Bei e<strong>in</strong>em Keyboard-Event ist<br />
es <strong>in</strong>teressant, welche Taste gedrückt wurde, welche Modifiers (=Shift, Ctrl,<br />
etc.) gerade gedrückt waren, als die Taste gedrückt wurde und vielleicht<br />
auch noch, wann diese Taste gedrückt wurde. Bei e<strong>in</strong>em Mouse-Event h<strong>in</strong>gegen<br />
ist es <strong>in</strong>teressant, wo der Mauszeiger gerade steht, ob e<strong>in</strong>e oder mehrere<br />
Maustasten gedrückt wurden, etc.<br />
Es gibt nun, je nach System, e<strong>in</strong>e Unmenge verschiedener Event-Typen<br />
mit der daraus folgenden Unmenge an verschiedener Information, die für die<br />
e<strong>in</strong>zelnen Typen relevant ist. E<strong>in</strong>e besonders brutale Methode, wie man nun<br />
alle möglichen Informationsteile aller möglichen verschiedenen Events unter<br />
e<strong>in</strong>en Hut bekommt, ist die Def<strong>in</strong>ition e<strong>in</strong>er struct, die für alle verschiedenen<br />
Fälle alle verschiedenen Members enthält. Je nach Typ des Events<br />
ist dann nur e<strong>in</strong> ger<strong>in</strong>ger Bruchteil davon <strong>in</strong>teressant und der Rest enthält
2.4 Zusammengesetzte Datentypen 41<br />
undef<strong>in</strong>ierte Werte. Woher sollte auch bei e<strong>in</strong>em Keyboard Event e<strong>in</strong>e Koord<strong>in</strong>ate<br />
kommen? Der Computer weiß ja doch nicht, wo am Schreibtisch<br />
nun das Keyboard steht und ob das l<strong>in</strong>ke obere oder das l<strong>in</strong>ke untere Eck des<br />
Schreibtischs die Nullposition markiert :-).<br />
Was also benötigt wird, ist die Möglichkeit, sogenannte Varianten zu<br />
implementieren. In unserem Fall der Events würde e<strong>in</strong>e solche Variante bei<br />
e<strong>in</strong>em Keyboard-Event nur die Daten besitzen, die für das Keyboard-Handl<strong>in</strong>g<br />
<strong>in</strong>teressant s<strong>in</strong>d, bei e<strong>in</strong>em Mouse-Event nur die, die für das Mouse-Handl<strong>in</strong>g<br />
<strong>in</strong>teressant s<strong>in</strong>d, etc.<br />
Wie auch C stellt uns C ++ e<strong>in</strong> solches Konstrukt zur Verfügung: die<br />
union. Auch hier muss ich, wie schon bei den Structures, vorausschicken,<br />
dass Unions <strong>in</strong> C ++ durch die Objektorientierung der Sprache umfassender<br />
def<strong>in</strong>iert s<strong>in</strong>d, als <strong>in</strong> C. Auf die Besonderheiten, die sich daraus ergeben,<br />
komme ich Abschnitt 15.2 zu sprechen.<br />
Bleiben wir hier aber im Augenblick beim Wesentlichen. Per Def<strong>in</strong>ition<br />
lässt sich e<strong>in</strong>e union folgendermaßen charakterisieren:<br />
E<strong>in</strong>e union entspricht e<strong>in</strong>er struct mit dem essentiellen Unterschied,<br />
dass sich die Members denselben Speicherplatz teilen! Dadurch wird<br />
Folgendes impliziert:<br />
• Der Speicherbedarf e<strong>in</strong>er union ist immer genau so hoch, wie der Speicherbedarf<br />
für den größten dar<strong>in</strong> enthaltenen Member. Dies kommt genau daher,<br />
dass ja Members <strong>in</strong> e<strong>in</strong>er union nur alternativ Gültigkeit besitzen<br />
(anstatt gleichzeitig, wie bei e<strong>in</strong>er struct).<br />
• Gerade erwähnt, aber weil es so wichtig ist, noch e<strong>in</strong>mal: Es besitzt zu<br />
e<strong>in</strong>em gewissen Zeitpunkt immer nur e<strong>in</strong> e<strong>in</strong>ziger Member Gültigkeit!<br />
Die restlichen Members enthalten ke<strong>in</strong>e s<strong>in</strong>nvollen Werte.<br />
• Die Deklaration e<strong>in</strong>er union erfolgt äquivalent zur Deklaration e<strong>in</strong>er struct,<br />
nur wird statt des Keywords struct das Keyword union verwendet.<br />
• Der Zugriff auf Members e<strong>in</strong>er union erfolgt analog zum Zugriff auf Members<br />
e<strong>in</strong>er struct. Es wird ebenfalls e<strong>in</strong>em Variablennamen e<strong>in</strong> Punkt<br />
gefolgt vom Namen des gewünschten Members nachgestellt.<br />
• Es gibt ke<strong>in</strong>e von der Sprache direkt unterstützte Möglichkeit, zur Laufzeit<br />
herauszuf<strong>in</strong>den, welcher der Members e<strong>in</strong>er union gerade Gültigkeit<br />
besitzt!<br />
Vorsicht Falle: Wie schon erwähnt, stellt C ++ ke<strong>in</strong>e direkte Möglichkeit<br />
zur Verfügung, mittels der man den zu e<strong>in</strong>em bestimmten Zeitpunkt gültigen<br />
Member e<strong>in</strong>er union herausf<strong>in</strong>den kann. Genau dieser Umstand macht e<strong>in</strong>e<br />
union zu e<strong>in</strong>em potentiellen Kandidaten für Softwarefehler. In Programmen<br />
f<strong>in</strong>den sich leider immer wieder die abstrusesten Konstrukte, die entweder<br />
davon ausgehen, dass “jetzt sowieso nur dieser Member gültig se<strong>in</strong> kann”<br />
oder <strong>in</strong> denen sonstige wilde Annahmen und Schätzungen stattf<strong>in</strong>den.<br />
Vor solchen Vorgehensweisen möchte ich unbed<strong>in</strong>gt abraten, denn diese<br />
führen unweigerlich früher oder später durch Fehlzugriffe zu den unglaub-
42 2. Datentypen und Variablen<br />
lichsten Effekten. In der Folge werden Methodiken vorgestellt, wie man auch<br />
e<strong>in</strong> so gefährliches Konstrukt wie e<strong>in</strong>e union sauber und sicher verwenden<br />
kann.<br />
Die erste Methode, wie man erreichen kann, dass zur Laufzeit bekannt<br />
ist, welcher Member e<strong>in</strong>er union gerade Gültigkeit hat, wird im folgenden<br />
Demoprogrämmchen gezeigt (first_union_demo.cpp):<br />
1 // first union demo . cpp − demo program f o r s a f e r use o f unions<br />
2<br />
3 #<strong>in</strong>clude <br />
4<br />
5 #def<strong>in</strong>e KEY EVENT 1<br />
6 #def<strong>in</strong>e MOUSE EVENT 2<br />
7<br />
8 struct KeyEvent<br />
9 {<br />
10 unsigned event type ;<br />
11 unsigned char key ;<br />
12 unsigned char m o d i f i e r s ;<br />
13 } ;<br />
14<br />
15 struct MouseEvent<br />
16 {<br />
17 unsigned event type ;<br />
18 unsigned x coord ;<br />
19 unsigned y coord ;<br />
20 unsigned char buttons pressed ;<br />
21 } ;<br />
22<br />
23 union Event<br />
24 {<br />
25 unsigned event type ;<br />
26 struct KeyEvent key event ;<br />
27 struct MouseEvent mouse event ;<br />
28 } ;<br />
29<br />
30 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
31 {<br />
32 union Event event ;<br />
33<br />
34 // used f o r a key event<br />
35 event . event type = KEY EVENT;<br />
36 event . key event . key = ’ x ’ ;<br />
37 event . key event . m o d i f i e r s = 0;<br />
38<br />
39 // the same v a r i a b l e , but used f o r a mouse event<br />
40 event . event type = MOUSE EVENT;<br />
41 event . mouse event . x coord = 10;<br />
42 event . mouse event . y coord = 40;<br />
43 event . mouse event . buttons pressed = 0;<br />
44<br />
45 return ( 0 ) ;<br />
46 }<br />
Absichtlich ist hier ke<strong>in</strong> Output <strong>in</strong> den laufenden Text e<strong>in</strong>gebunden, denn<br />
das tolle Programm liefert ganz e<strong>in</strong>fach ke<strong>in</strong>en :-).<br />
Leser, die im Umgang mit C noch nicht geübt s<strong>in</strong>d, f<strong>in</strong>den <strong>in</strong> den Zeilen 5–<br />
6 e<strong>in</strong> bisher unbekanntes Konstrukt vor. Ke<strong>in</strong>e Panik – der Preprocessor, für<br />
den diese Zeilen gedacht s<strong>in</strong>d, wird <strong>in</strong> Kapitel 7 noch näher erklärt. E<strong>in</strong>st-
2.4 Zusammengesetzte Datentypen 43<br />
weilen möchte ich es dabei belassen, dass <strong>in</strong> den Zeilen 5 und 6 e<strong>in</strong>fach nur<br />
symbolische Konstanten def<strong>in</strong>iert s<strong>in</strong>d, die den Code lesbarer machen. Es<br />
s<strong>in</strong>d dies die Konstanten KEY_EVENT und MOUSE_EVENT, die dann <strong>in</strong> den Zeilen<br />
35 und 40 für die Angabe des Event-Typs verwendet werden.<br />
Das Kernstück unseres Programms f<strong>in</strong>det sich <strong>in</strong> den Zeilen 23–28: die<br />
Deklaration der union Event. Diese ist so def<strong>in</strong>iert, dass sich drei Members<br />
denselben Speicherplatz teilen, nämlich e<strong>in</strong> e<strong>in</strong>facher unsigned-Member und<br />
zwei weitere Members, die alternativ entweder e<strong>in</strong>e struct KeyEvent oder<br />
e<strong>in</strong>e struct MouseEvent speichern können. Warum nun garantiert werden<br />
kann, dass der Member event_type aus der union Event immer Gültigkeit<br />
hat und nicht den Alternativen zum Opfer fällt, sieht man am besten an<br />
Abbildung 2.1, die die Anordnung der Elemente im Speicher skizziert.<br />
memory addresses<br />
alternate union members<br />
unsigned struct KeyEvent struct MouseEvent<br />
event_type<br />
event_type<br />
key<br />
modifiers<br />
event_type<br />
event_type<br />
event_type<br />
buttons_pressed<br />
union Event<br />
Abbildung 2.1: Anordnung der Union-Members im Speicher<br />
In dieser Skizze ist der Adressraum im Speicher des Computers von oben<br />
nach unten aufsteigend aufgetragen. Von l<strong>in</strong>ks nach rechts s<strong>in</strong>d die e<strong>in</strong>zelnen<br />
überlappenden Members der union Event mit ihrer jeweiligen Ausdehnung<br />
im Adressraum e<strong>in</strong>gezeichnet. Hierbei symbolisieren die jeweiligen mit vollem<br />
Strich gezeichneten Blöcke den tatsächlichen Speicherbedarf e<strong>in</strong>zelner Teile<br />
der Alternativen. Strichliert e<strong>in</strong>gefasst s<strong>in</strong>d die Gesamtelemente.<br />
Die Skizze ist also wie folgt zu lesen:<br />
• Ganz l<strong>in</strong>ks ist der erste Member der union Event, also der unsigned, der<br />
den Typ repräsentiert, aufgezeichnet.<br />
• Das zweite Element von l<strong>in</strong>ks ist e<strong>in</strong>e Skizze der struct KeyEvent mit ihren<br />
e<strong>in</strong>zelnen Members, nämlich event_type, key und modifiers. An diesem<br />
Element sieht man beispielhaft, was bei der Beschreibung von Structures<br />
damit geme<strong>in</strong>t war, dass nicht garantiert ist, dass die Elemente im<br />
Speicher unmittelbar aufe<strong>in</strong>ander folgen: Zwischen key und modifiers ist<br />
e<strong>in</strong> solches mögliches “Loch” dargestellt.<br />
• Das dritte Element ganz rechts ist e<strong>in</strong>e Skizze der struct MouseEvent mit<br />
ihren e<strong>in</strong>zelnen Members.
44 2. Datentypen und Variablen<br />
• Die union Event braucht genau so viel Speicher, wie der größte ihrer Members,<br />
<strong>in</strong> diesem Fall die struct MouseEvent.<br />
Bei der Besprechung von Structures war neben dem eventuellen Vorhandense<strong>in</strong><br />
von Löchern im Speicher auch die Rede davon, dass die Startadresse<br />
e<strong>in</strong>er struct garantiert gleich der Startadresse ihres ersten Members ist. Die<br />
wichtige Aussage, die es bei Unions zu Adressen im Speicher zu treffen gibt,<br />
ist Folgende:<br />
Die Startadresse der alternativen Members, die <strong>in</strong> e<strong>in</strong>er union deklariert<br />
s<strong>in</strong>d, ist garantiert für alle dieselbe und diese ist auch garantiert die Startadresse<br />
der gesamten union.<br />
Genau aus diesen Aussagen zu den Startadressen von Members <strong>in</strong> e<strong>in</strong>er<br />
struct und <strong>in</strong> e<strong>in</strong>er union ergibt sich, dass die event_type members alle<br />
garantiert genau übere<strong>in</strong>ander (=an e<strong>in</strong> und derselben Stelle) zu liegen<br />
kommen. Es ist also egal, welcher Member der union gerade gültig ist, das<br />
Ergebnis der Abfrage nach dem event_type ist garantiert immer dasselbe.<br />
Das E<strong>in</strong>zige, worauf jetzt noch unbed<strong>in</strong>gt Acht gegeben werden muss ist,<br />
dass man niemals vergisst, den zu e<strong>in</strong>er gewissen Zeit gültigen Typ bei e<strong>in</strong>er<br />
Zuweisung auch wirklich korrekt entsprechend ihrer Verwendung zu setzen.<br />
Dass man vor jeder Auslese-Operation aus e<strong>in</strong>er union dann auch den Typ<br />
abfragen und entsprechend handeln muss, versteht sich selbstredend.<br />
Die soeben vorgestellte Vorgehensweise hat den Vorteil, dass man bei<br />
Verwendung von Po<strong>in</strong>tern und mittels e<strong>in</strong>es expliziten Typecasts die Union<br />
auf den derzeit aktiven Member casten kann (siehe Abschnitt 3.6). Dadurch<br />
würde man sich das hier sehr störende doppelte Ansprechen von Members<br />
ersparen, wie es z.B. <strong>in</strong> Zeile 36 vorkommt. Es gibt auch e<strong>in</strong>e andere Möglichkeit,<br />
wie man e<strong>in</strong>e union e<strong>in</strong>setzen kann, nämlich als anonymous union.<br />
Betrachten wir dies gleich an e<strong>in</strong>em Beispiel (anonymous_union_demo.cpp):<br />
1 // anonymous union demo . cpp − demo program f o r anonymous unions<br />
2<br />
3 #<strong>in</strong>clude <br />
4<br />
5 #def<strong>in</strong>e INT TYPE 1<br />
6 #def<strong>in</strong>e FLOAT TYPE 2<br />
7 #def<strong>in</strong>e DOUBLE TYPE 3<br />
8<br />
9 struct NumberAlternative<br />
10 {<br />
11 short type ;<br />
12 union<br />
13 {<br />
14 <strong>in</strong>t i n t v a l u e ;<br />
15 float f l o a t v a l u e ;<br />
16 double double value ;<br />
17 } ;<br />
18 } ;<br />
19<br />
20 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
21 {<br />
22 struct NumberAlternative num alternative ;<br />
23<br />
24 // used as a f l o a t j u s t a c c e s s f l o a t v a l u e
25 // of the anonymous union<br />
26 num alternative . type = FLOAT TYPE;<br />
27 num alternative . f l o a t v a l u e = 1 7 . 0 ;<br />
28<br />
29 // used as an i n t j u s t a c c e s s i n t v a l u e<br />
30 // of the anonymous union<br />
31 num alternative . type = INT TYPE;<br />
32 num alternative . i n t v a l u e = 10;<br />
33<br />
34 return ( 0 ) ;<br />
35 }<br />
2.4 Zusammengesetzte Datentypen 45<br />
In diesem Beispiel f<strong>in</strong>det sich e<strong>in</strong>e anonymous union direkt als Member e<strong>in</strong>er<br />
struct <strong>in</strong> den Zeilen 12–17 wieder. Da ke<strong>in</strong> expliziter Member <strong>in</strong> der struct<br />
existiert, der vom Typ union wäre, sondern e<strong>in</strong>fach nur 3 Members zu e<strong>in</strong>er<br />
union zusammengefasst werden, s<strong>in</strong>d diese 3 Members der struct e<strong>in</strong>fach alternativ,<br />
ohne weitere Indirektion ansprechbar. In den Zeilen 27 und 32 sieht<br />
man, dass alternativ die Members float_value oder <strong>in</strong>t_value verwendet<br />
werden, je nachdem, welcher Datentyp nun gehalten wird.<br />
Vorsicht Falle: Trotz aller Vorteile, die e<strong>in</strong>e union <strong>in</strong> bestimmten Fällen<br />
bietet, möchte ich doch besonders darauf h<strong>in</strong>weisen, dass sie <strong>in</strong> e<strong>in</strong>em Programm<br />
e<strong>in</strong>en besonderen Gefahrenpunkt darstellt! Im OO-Teil dieses Buchs<br />
werden wir Möglichkeiten kennen lernen, wie man durch saubere Datenkapselung<br />
diese Gefahren so weit wie möglich elim<strong>in</strong>iert. Es gibt nämlich nur<br />
sehr wenige Probleme, die noch schlimmer s<strong>in</strong>d, als sich mit irrtümlich fehl<strong>in</strong>terpretierten<br />
Daten herumzuschlagen!<br />
Manche Leser mögen sich nun wundern, warum ich das Thema der impliziten<br />
Initialisierung bei Unions bisher unter den Tisch gekehrt habe. Der<br />
Grund ist e<strong>in</strong>fach: E<strong>in</strong>e union wird zwar gleich wie z.B. e<strong>in</strong>e struct auf<br />
e<strong>in</strong>en Nullwert <strong>in</strong>itialisiert, allerd<strong>in</strong>gs kann man sich leicht vorstellen, dass<br />
es hierbei e<strong>in</strong>e kle<strong>in</strong>e Diskrepanz gibt. Die Members e<strong>in</strong>er union teilen sich<br />
denselben Speicherplatz, welchen Member also setzt man auf 0? Im Falle,<br />
dass alle Members primitive Datentypen bzw. Structures bestehend nur aus<br />
primitiven Datentypen s<strong>in</strong>d, ist die Antwort e<strong>in</strong>fach, wenn man um die <strong>in</strong>ternen<br />
Repräsentationen der primitiven Datentypen Bescheid weiß: Es wird<br />
e<strong>in</strong>fach der gesamte Speicher, der von der union beansprucht wird, Byte für<br />
Byte auf 0 gesetzt und damit ergeben sich automatisch für alle Members die<br />
entsprechenden Nullwerte.<br />
Gar nicht so e<strong>in</strong>fach zu beantworten wird die Frage später im OO-Teil<br />
werden, wenn wir es mit Classes zu tun bekommen. Deshalb möchte ich<br />
bereits hier im Vorgriff e<strong>in</strong>e Warnung aussprechen:<br />
Vorsicht Falle: Dadurch, dass sich die Members e<strong>in</strong>er union denselben<br />
Speicherplatz teilen, besteht das Problem, dass beim Anlegen e<strong>in</strong>er union<br />
bekannt se<strong>in</strong> müsste, welcher Member gerade Gültigkeit besitzt, um sie entsprechend<br />
implizit <strong>in</strong>itialisieren zu können. Aber genau das kann man gar
46 2. Datentypen und Variablen<br />
nicht wissen! Aus diesem Grund darf man sich niemals auf die implizite<br />
Initialisierung von Unions verlassen.<br />
Der Vollständigkeit halber möchte ich hier zum Thema union noch den Querverweis<br />
auf Kapitel 19 aus <strong>Softwareentwicklung</strong> <strong>in</strong> C anführen, <strong>in</strong> dem dieses<br />
Thema ausführlich behandelt wird.<br />
2.5 Scope und Lifetime<br />
Zwei wichtige Kenngrößen begegnen uns im Life-Cycle von Variablen: Scope<br />
und Lifetime. Dabei bezeichnet der Begriff Scope (auch: Sichtbarkeit), wo<br />
im Programm e<strong>in</strong>e Variable unter dem Namen ansprechbar ist, unter dem<br />
sie def<strong>in</strong>iert wurde. Der Begriff Lifetime (auch: Lebenszeit) bezeichnet, wie<br />
lange der Speicher, der beim Def<strong>in</strong>ieren e<strong>in</strong>er Variable angelegt wurde, erhalten<br />
bleibt. Diese zwei Größen s<strong>in</strong>d ke<strong>in</strong>eswegs äquivalent, wie von manchen<br />
Entwicklern fälschlicherweise angenommen wird!<br />
Abgesehen von e<strong>in</strong>igen Besonderheiten, die uns im Lauf der Zeit <strong>in</strong> diesem<br />
Buch noch begegnen werden, gelten für C ++ dieselben Regeln für Scope und<br />
Lifetime, wie sie auch <strong>in</strong> C Gültigkeit besitzen:<br />
• E<strong>in</strong>e Variable ist immer <strong>in</strong>nerhalb des Blocks sichtbar, <strong>in</strong> dem sie def<strong>in</strong>iert<br />
wurde und zwar genau ab dem Punkt, an dem sie def<strong>in</strong>iert wurde<br />
(man kann ja an jeder beliebigen Stelle im laufenden Code e<strong>in</strong>e Variable<br />
def<strong>in</strong>ieren).<br />
• Die Lifetime e<strong>in</strong>er Variable erstreckt sich von deren Def<strong>in</strong>ition bis zum<br />
Verlassen des umschließenden Blocks. Allerd<strong>in</strong>gs kann man als Entwickler<br />
explizit durch static-Def<strong>in</strong>itionen darauf E<strong>in</strong>fluss nehmen.<br />
• Bei Schachtelung von Blöcken können Def<strong>in</strong>itionen auftreten, bei denen Variablen<br />
äußerer Blöcke durch Variablen <strong>in</strong>nerer Blöcke “versteckt” werden.<br />
Dies passiert, wenn <strong>in</strong> e<strong>in</strong>em <strong>in</strong>neren Block e<strong>in</strong>e Variable mit demselben<br />
Namen def<strong>in</strong>iert wird, wie sie schon <strong>in</strong> e<strong>in</strong>em äußeren Block def<strong>in</strong>iert wurde.<br />
Durch e<strong>in</strong>e solche Def<strong>in</strong>ition kommt e<strong>in</strong>e äußere Variable out of Scope.<br />
• Globale Variablen werden e<strong>in</strong>em fiktiven “alles umschließenden Block” zugerechnet<br />
und dementsprechend beg<strong>in</strong>nt ihre Lifetime zu dem Zeitpunkt,<br />
zu dem ihr Initialisierungscode ausgeführt wird (noch vor Aufruf von ma<strong>in</strong>!)<br />
und dauert den gesamten restlichen Programmlauf an. Ihr Scope ist ohne<br />
explizite Deklaration aus re<strong>in</strong> compilertechnischen Gründen auf e<strong>in</strong> e<strong>in</strong>ziges<br />
File beschränkt, allerd<strong>in</strong>gs kann man den Scope durch entsprechende<br />
extern Deklaration auf andere Files erweitern.
2.6 Symbolische Konstanten 47<br />
Ich möchte Lesern, die ungeübt im Umgang mit Scope und Lifetime von<br />
Variablen <strong>in</strong> C s<strong>in</strong>d, wärmstens die Lektüre der Abschnitte 8.2, 17.1 und<br />
17.3 aus <strong>Softwareentwicklung</strong> <strong>in</strong> C ans Herz legen!<br />
Zu Scope und Lifetime gibt es noch zusätzliche Aspekte, die sich aus der<br />
Objektorientierung und aus dem Konzept von Namespaces ergeben. Diese<br />
besonderen Aspekte werden an den entsprechenden Stellen ergänzend angeführt.<br />
2.6 Symbolische Konstanten<br />
Der Begriff der symbolischen Konstanten bedeutet e<strong>in</strong>e Zuordnung e<strong>in</strong>es symbolischen<br />
Namens zu e<strong>in</strong>em konstanten Wert, wobei im Programm der symbolische<br />
(sprechende!) Name verwendet wird, anstelle des dah<strong>in</strong>ter versteckten<br />
konstanten Wertes. Entwickelt man z.B. Software, <strong>in</strong> der die maximale<br />
Anzahl gleichzeitiger Benutzer e<strong>in</strong>e Rolle spielt, so ist es natürlich wünschenswert,<br />
im Code e<strong>in</strong>fach e<strong>in</strong>en symbolischen Namen wie MAX_USERS zu verwenden,<br />
anstatt z.B. überall 120 h<strong>in</strong>zuschreiben.<br />
Für den Gebrauch von symbolischen Konstanten gibt es vor allem zwei<br />
Hauptmotivationen:<br />
• Konstanten mit sprechenden Namen machen den Code bei weitem leichter<br />
lesbar als kryptische hardcodierte Werte.<br />
• Konstanten, die an e<strong>in</strong>er Stelle def<strong>in</strong>iert und dann vielfach im Code verwendet<br />
werden, erhöhen die Wartbarkeit und Erweiterbarkeit der Software<br />
erheblich. Nimmt man das Beispiel mit der maximalen Anzahl gleichzeitiger<br />
Benutzer von zuvor zur Hand, dann sieht man sofort, dass es e<strong>in</strong>facher<br />
ist, an e<strong>in</strong>er Stelle die Def<strong>in</strong>ition von MAX_USERS von z.B. 120 auf 200<br />
zu ändern, anstatt im gesamten Code nach jedem Auftreten der Zahl 120<br />
zu suchen und diese auszubessern. Lesern, die nun me<strong>in</strong>en, dass so etwas<br />
ja sowieso bei modernen Editoren nach der Search-and-Replace Methode<br />
funktioniert, möchte ich nur entgegenhalten, dass ja ke<strong>in</strong>esfalls garantiert<br />
ist, dass die Zahl 120 nicht auch anderwärtig für etwas völlig Verschiedenes<br />
gebraucht wird. Diese Vorkommen will man natürlich nicht ändern und<br />
damit bleibt nur noch mühsamste Handarbeit. Ab e<strong>in</strong>er gewissen Menge<br />
Code ist jede Änderung de facto unmöglich.<br />
Im Unterschied zu C, wo ausschließlich Preprocessor-Macros für symbolische<br />
Konstanten verwendet werden, ist <strong>in</strong> C ++ e<strong>in</strong>e Möglichkeit der Def<strong>in</strong>ition<br />
von symbolischen Konstanten im Sprachumfang enthalten. Diese werden im<br />
Pr<strong>in</strong>zip def<strong>in</strong>iert wie Variablen, nur dass vor die Bezeichnung des Datentyps<br />
noch das Keyword const gestellt wird.<br />
Der Vorteil der Art der Def<strong>in</strong>ition von Konstanten <strong>in</strong> C ++ gegenüber der<br />
<strong>in</strong> C üblichen Preprocessor-Variante liegt auf der Hand: Konstanten <strong>in</strong> C ++
48 2. Datentypen und Variablen<br />
besitzen e<strong>in</strong>en genau def<strong>in</strong>ierten Datentyp! Mit Preprocessor-Macros erreicht<br />
man nur e<strong>in</strong>e re<strong>in</strong> textuelle Ersetzung im Code, die ke<strong>in</strong>e Typensicherheit<br />
bietet.<br />
Wie man am folgenden Beispiel sehen kann, ist die Def<strong>in</strong>ition von Konstanten<br />
völlig <strong>in</strong>tuitiv (const_demo.cpp):<br />
1 // const demo . cpp − a t<strong>in</strong>y demo program f o r the use o f<br />
2 // typesafe constant d e f i n i t i o n s <strong>in</strong> <strong>C++</strong><br />
3<br />
4 #<strong>in</strong>clude <br />
5<br />
6 const unsigned short RED PART = 0 ;<br />
7 const unsigned short GREEN PART = 1;<br />
8 const unsigned short BLUE PART = 2 ;<br />
9<br />
10 const unsigned short MAX COLOR INTENSITY = 0xFF ;<br />
11 const unsigned short MIN COLOR INTENSITY = 0 x00 ;<br />
12<br />
13 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
14 {<br />
15 // constants do not need to be g l o b a l , the same<br />
16 // r u l e s apply f o r the scope o f constants that<br />
17 // are v a l i d f o r v a r i a b l e s <strong>in</strong> <strong>C++</strong><br />
18 const unsigned short NUM COLOR CHANNELS = 3;<br />
19<br />
20 unsigned short rgb value [NUM COLOR CHANNELS] ;<br />
21<br />
22 rgb value [RED PART] = MAX COLOR INTENSITY;<br />
23 rgb value [GREEN PART] = MIN COLOR INTENSITY;<br />
24 rgb value [BLUE PART] = MIN COLOR INTENSITY;<br />
25<br />
26 return ( 0 ) ;<br />
27 }<br />
Ich denke, es erübrigt sich, dieses Programm genau zu erklären. Allerd<strong>in</strong>gs<br />
schaden e<strong>in</strong> paar kle<strong>in</strong>e Anmerkungen vielleicht nicht:<br />
• Wie der Name schon sagt, s<strong>in</strong>d Konstanten unveränderbar. Also wird logischerweise<br />
jeder Versuch, e<strong>in</strong>er Konstante im Programm etwas zuzuweisen,<br />
mit e<strong>in</strong>em Compilerfehler enden.<br />
• Konstanten müssen explizit auf e<strong>in</strong>en Wert <strong>in</strong>itialisiert werden. Vergisst<br />
man die Initialisierung und schreibt z.B.<br />
const unsigned short RED_PART;<br />
so wird dies vom Compiler bemängelt. Dieses Verhalten ist auch logisch,<br />
denn wozu sollte man e<strong>in</strong>e Konstante brauchen, wenn sie dann vom Compiler<br />
implizit auf 0 gesetzt werden soll?<br />
• Im Gegensatz zu Preprocessor-Macros folgen Konstantendef<strong>in</strong>itionen <strong>in</strong><br />
C ++ den Scope-Regeln, die auch für Variablen gelten.<br />
• Weil e<strong>in</strong>e Konstante nicht änderbar ist, muss auch nicht unbed<strong>in</strong>gt Speicherplatz<br />
für sie reserviert werden. Der Compiler kann nach vorheriger<br />
Überprüfung der Typenkompatibilität ihren Wert e<strong>in</strong>fach e<strong>in</strong>setzen. Dies<br />
hat zwei Konsequenzen:
2.7 Eigene Typdef<strong>in</strong>itionen 49<br />
1. Konstanten haben im Pr<strong>in</strong>zip ke<strong>in</strong>e Lifetime, denn im laufenden Programm<br />
gibt es für sie nicht unbed<strong>in</strong>gt e<strong>in</strong>en reservierten Speicher. E<strong>in</strong>zig<br />
der Scope folgt garantiert denselben Regeln wie für Variablen.<br />
2. Die Zugriffszeiten von Konstanten nach C ++ Konvention s<strong>in</strong>d, wenn<br />
der Compiler sauber arbeitet, äquivalent zu denen, die bei Verwendung<br />
von Preprocessor-Macros entstehen. Manche Entwickler verwenden C ++<br />
Konstanten nicht, da sie glauben, dass sie dadurch E<strong>in</strong>bußen bei der<br />
Laufzeit des Programms <strong>in</strong> Kauf nehmen müssen, doch das ist falsch.<br />
Nur der Vollständigkeit halber möchte ich an dieser Stelle erwähnen, dass wir<br />
neben der Def<strong>in</strong>ition von symbolischen Konstanten noch andere Situationen<br />
kennen lernen werden, <strong>in</strong> denen das Keyword const e<strong>in</strong>e wichtige Rolle spielt.<br />
Entsprechende Ergänzungen folgen zu gegebener Zeit.<br />
2.7 Eigene Typdef<strong>in</strong>itionen<br />
Es gibt verschiedene Fälle, bei denen Entwickler sich wünschen, für e<strong>in</strong>en<br />
bestimmten Datentyp e<strong>in</strong>en symbolischen Namen zur Verfügung zu haben.<br />
Z.B. wissen wir, dass das Fassungsvermögen e<strong>in</strong>es <strong>in</strong>t masch<strong>in</strong>enabhängig<br />
ist. Es gibt aber Fälle, <strong>in</strong> denen man unbed<strong>in</strong>gt z.B. e<strong>in</strong>en 32 Bit langen<br />
Ganzzahltyp braucht und <strong>in</strong> denen es daher nicht ausreichend ist, zu wissen,<br />
dass e<strong>in</strong> <strong>in</strong>t auch eventuell nur 16 Bit lang se<strong>in</strong> kann, weil man mit 16 Bit<br />
nicht arbeiten kann. Speziell bei plattformübergreifender Entwicklung ist es<br />
def<strong>in</strong>itiv e<strong>in</strong> D<strong>in</strong>g der Unmöglichkeit, beim Portieren e<strong>in</strong>es Programms z.B.<br />
alle <strong>in</strong>t-Variablen händisch auf long zu ändern, bloß weil e<strong>in</strong> <strong>in</strong>t auf der<br />
Zielplattform e<strong>in</strong>fach zu kurz ist.<br />
Aus diesem Grund gibt es <strong>in</strong> C das typedef-Statement, das auch <strong>in</strong> C ++<br />
zur Verfügung steht. Mit Hilfe dieses Statements kann man e<strong>in</strong> benutzerdef<strong>in</strong>iertes<br />
Synonym für e<strong>in</strong>en bestimmten Datentyp e<strong>in</strong>führen, wie man an<br />
folgendem Beispiel sieht (typedef_demo.cpp):<br />
1 // typedef demo . cpp − demo program f o r the use o f typedef<br />
2<br />
3 #<strong>in</strong>clude <br />
4<br />
5 us<strong>in</strong>g std : : cout ;<br />
6 us<strong>in</strong>g std : : endl ;<br />
7<br />
8 typedef signed char i nt8 ;<br />
9 typedef unsigned char u<strong>in</strong>t8 ;<br />
10 typedef short <strong>in</strong>t16 ;<br />
11 typedef unsigned short u<strong>in</strong>t16 ;<br />
12 typedef <strong>in</strong>t <strong>in</strong>t32 ;<br />
13 typedef unsigned <strong>in</strong>t u<strong>in</strong>t32 ;<br />
14 typedef long long <strong>in</strong>t64 ;<br />
15 typedef unsigned long long u<strong>in</strong>t64 ;<br />
16<br />
17 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
18 {<br />
19 i n t 8 e x a m p l e 8 b i t i n t e g e r = 0;
50 2. Datentypen und Variablen<br />
20 u<strong>in</strong>t8 e x a m p l e u n s i g n e d 8 b i t i n t e g e r = 0;<br />
21<br />
22 <strong>in</strong>t64 e x a m p l e 6 4 b i t i n t e g e r = 0;<br />
23 u<strong>in</strong>t64 e x a m p l e u n s i g n e d 6 4 b i t i n t e g e r = 0;<br />
24<br />
25 cout
2.7 Eigene Typdef<strong>in</strong>itionen 51<br />
Um überprüfen zu können, ob auf e<strong>in</strong>er bestimmten Zielplattform die durch<br />
typedef spezifizierten Synonyme auch korrekt s<strong>in</strong>d, kann man folgendes Testprogramm<br />
heranziehen:<br />
1 // u s e r t y p e s t e s t . cpp − t e s t program to determ<strong>in</strong>e whether<br />
2 // u s e r t y p e s . h has to be adopted f o r a new development platform .<br />
3<br />
4 #<strong>in</strong>clude <br />
5 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
6<br />
7 us<strong>in</strong>g std : : cout ;<br />
8 us<strong>in</strong>g std : : endl ;<br />
9<br />
10 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
11 {<br />
12 cout
3. Operatoren<br />
Nachdem nun e<strong>in</strong>mal die wichtigsten Grunddatentypen besprochen wurden,<br />
die <strong>in</strong> C ++ zur Verfügung stehen (Po<strong>in</strong>ter, References und OO-Konstrukte<br />
kommen noch), wollen wir mit diesen natürlich auch vernünftig arbeiten<br />
können. Dazu werden <strong>in</strong> der Folge alle Operatoren besprochen, die wir im<br />
täglichen Umgang mit C ++ brauchen.<br />
Der Vollständigkeit halber möchte ich an dieser Stelle zwei D<strong>in</strong>ge erwähnen:<br />
1. In diesem Kapitel s<strong>in</strong>d alle Operatoren angeführt, die es <strong>in</strong> C ++ gibt.<br />
Zu manchen davon wurden allerd<strong>in</strong>gs die Grundlagen bisher noch nicht<br />
besprochen, die notwendig s<strong>in</strong>d, um sie genau zu verstehen. Daher wird<br />
e<strong>in</strong>e vollständige Abhandlung zu diesen speziellen Operatoren auf die<br />
jeweiligen Kapitel später im Buch verschoben.<br />
Um späteres Nachschlagen der Operatoren nicht allzu sehr zu erschweren,<br />
wollte ich <strong>in</strong> diesem Kapitel zum<strong>in</strong>dest e<strong>in</strong>en geme<strong>in</strong>samen E<strong>in</strong>stiegspunkt<br />
bieten.<br />
2. In C ++ gibt es den Mechanismus des Operator-Overload<strong>in</strong>gs. Dies bedeutet,<br />
dass man als Entwickler Zusammenhänge zwischen Operatoren und<br />
Datentypen sowie die Funktionsweise von Operatoren <strong>in</strong> Verb<strong>in</strong>dung mit<br />
bestimmten Typen bee<strong>in</strong>flussen bzw. selbst def<strong>in</strong>ieren kann.<br />
In der Folge wird nur auf die Standard-Bedeutung von Operatoren e<strong>in</strong>gegangen,<br />
wie sie im Core von C ++ implementiert ist. Welche Operatoren<br />
nun welche Funktionsweise <strong>in</strong> Verb<strong>in</strong>dung mit allen möglichen Libraries<br />
haben, muss immer der jeweiligen Dokumentation zu den Libraries entnommen<br />
werden.<br />
Lesern, die noch sehr wenig Erfahrung mit der <strong>Softwareentwicklung</strong> im Allgeme<strong>in</strong>en<br />
haben, empfehle ich an dieser Stelle als wichtigen E<strong>in</strong>stieg die Lektüre<br />
von Kapitel 5 von <strong>Softwareentwicklung</strong> <strong>in</strong> C. Dort f<strong>in</strong>det sich e<strong>in</strong>e sehr genaue<br />
Beschreibung der wichtigsten Operatoren mit sehr vielen Beispielen.<br />
3.1 Überblick und Reihenfolge der Auswertung<br />
Die Reihenfolge, <strong>in</strong> der Operatoren <strong>in</strong> längeren Ausdrücken ausgewertet werden,<br />
ist im Sprachstandard von C ++ festgelegt und <strong>in</strong> Ste<strong>in</strong> gemeißelt. Daran
54 3. Operatoren<br />
kann auch durch Operator-Overload<strong>in</strong>g, wie wir es noch kennen lernen werden,<br />
nichts geändert werden. Die Regeln, die hierbei gelten, s<strong>in</strong>d e<strong>in</strong>fach:<br />
• Alle Operatoren besitzen e<strong>in</strong>en bestimmten Rang (=Precedence). E<strong>in</strong>e<br />
Auflistung der Operatoren nach ihren Rängen ergibt die sogenannte Rangreihenfolge<br />
(=Order of Precedence) der Operatoren untere<strong>in</strong>ander.<br />
E<strong>in</strong> typisches Beispiel für die Rangreihenfolge kennt man ja aus der Schulmathematik,<br />
wo z.B. die Regel Punkt- vor Strichrechnung gelehrt wird.<br />
Diese besagt, dass Multiplikation und Division (=Punktrechnung) ranggleich<br />
s<strong>in</strong>d, ebenso wie Addition und Subtraktion (=Strichrechnung), dass<br />
jedoch die Punktrechnung <strong>in</strong>sgesamt im Rang höher ist als die Strichrechnung.<br />
In wie auch immer gearteten Ausdrücken werden die Operatoren immer<br />
ihrer Rangreihenfolge gemäß, also vom höchsten bis zum tiefsten Rang<br />
absteigend, ausgewertet.<br />
• F<strong>in</strong>det sich <strong>in</strong> e<strong>in</strong>em Ausdruck e<strong>in</strong>e Kette von Operatoren mit demselben<br />
Rang (also gleichwertige Operatoren), so werden diese der Reihe nach von<br />
l<strong>in</strong>ks nach rechts ausgewertet.<br />
• Wenn die Reihenfolge der Auswertung bee<strong>in</strong>flusst werden soll, so gibt es<br />
die Möglichkeit zur Klammerung von Ausdrücken. Dazu können Paare von<br />
runden Klammern <strong>in</strong> beliebiger Schachtelungstiefe verwendet werden. Bei<br />
Klammerungen wird, wie zu erwarten, garantiert, dass die Ausdrücke von<br />
<strong>in</strong>nen nach außen <strong>in</strong> der Schachtelungshierarchie abgearbeitet werden.<br />
Aus Gründen der Lesbarkeit von Ausdrücken möchte ich noch e<strong>in</strong>e Kle<strong>in</strong>igkeit<br />
anregen: Klammerungen dürfen natürlich nicht ausschließlich dazu verwendet<br />
werden, die vorgegebene Auswertungsreihenfolge zu verändern, sondern<br />
sie s<strong>in</strong>d immer erlaubt. Für die Reihenfolge der Auswertung im Pr<strong>in</strong>zip<br />
unnötige Klammerungen schlagen sich def<strong>in</strong>itiv nicht im Laufzeitverhalten<br />
des Programms nieder. Allerd<strong>in</strong>gs erhöhen sie die Lesbarkeit von längeren<br />
Ausdrücken erheblich, wenn man sie zum Signalisieren von logischen Zusammengehörigkeiten<br />
verwendet. Salopp formuliert wäre me<strong>in</strong> Rat also Folgender:<br />
E<strong>in</strong> paar Klammern zu viel schaden nicht, e<strong>in</strong> paar zu wenig machen<br />
das Leben schwer.<br />
In der Folge ist aus Gründen des Überblicks e<strong>in</strong>mal e<strong>in</strong>e Tabelle mit der<br />
Gesamtaufstellung aller Operatoren zusammen mit ihrem zugehörigen Rang<br />
zu f<strong>in</strong>den. Hierbei bedeutet e<strong>in</strong>e niedrige Zahl für den Rang e<strong>in</strong>e höhere<br />
Priorität <strong>in</strong> der Reihenfolge. Diese Konvention, dass e<strong>in</strong>e niedrige Rangzahl<br />
e<strong>in</strong>e hohe Priorität bedeutet, ist <strong>in</strong> sehr vielen Prioritätsschemata üblich,<br />
deshalb will ich mit dieser Systematik hier nicht brechen.<br />
Für die Bedeutungen der e<strong>in</strong>zelnen Operatoren wurden bewusst die englischen<br />
Bezeichnungen verwendet, da diese im täglichen Leben des Softwareentwicklers<br />
gebräuchlicher s<strong>in</strong>d als die oftmals sehr holprigen deutschen<br />
Übersetzungen.<br />
Allen Lesern, die nun erschrecken, weil die Tabelle vielleicht doch auf den<br />
ersten Blick nicht wirklich unglaublich <strong>in</strong>formativ ersche<strong>in</strong>t, kann ich nur
3.1 Überblick und Reihenfolge der Auswertung 55<br />
sagen: Bitte ke<strong>in</strong>e Panik! Es ist durchaus ke<strong>in</strong> Problem, die folgende Tabelle<br />
e<strong>in</strong>mal e<strong>in</strong>fach zu überspr<strong>in</strong>gen und sie nur bei Bedarf zum Nachschlagen<br />
zu verwenden. E<strong>in</strong>e Legende zu den verwendeten Konventionen f<strong>in</strong>det sich<br />
gleich unterhalb der Tabelle.<br />
Rang Bedeutung Operator im Kontext<br />
1 scope resolution classname::member<br />
namespace-name::member<br />
1 global ::name<br />
::qualified-name<br />
2 member access object.member<br />
2 member access po<strong>in</strong>ter->member<br />
2 <strong>in</strong>dex po<strong>in</strong>ter[expr]<br />
2 function call expr(expr-list)<br />
2 value construction type(expr-list)<br />
2 post <strong>in</strong>crement lvalue++<br />
2 post decrement lvalue--<br />
2 type identification typeid(type)<br />
2 runtime type <strong>in</strong>fo typeid(expr)<br />
2 runtime-checked cast dynamic_cast(expr)<br />
2 compiletime-checked cast static_cast(expr)<br />
2 unchecked cast re<strong>in</strong>terpret_cast(expr)<br />
2 remove-const cast const_cast(expr)<br />
3 size of sizeof expr<br />
sizeof(type)<br />
3 pre <strong>in</strong>crement ++lvalue<br />
3 pre decrement --lvalue<br />
3 bit complement ~expr<br />
3 logical NOT !expr<br />
3 unary m<strong>in</strong>us -expr<br />
3 unary plus +expr<br />
3 address of &lvalue<br />
3 dereference *expr<br />
3 create (allocate) new type<br />
3 create (alloc and <strong>in</strong>it) new type(expr-list)<br />
3 create (place) new(expr-list)type<br />
3 create (place and <strong>in</strong>it) new(expr-list)type(expr-list)<br />
3 delete (deallocate) delete po<strong>in</strong>ter<br />
3 delete array delete[] po<strong>in</strong>ter<br />
3 cast (type)expr<br />
4 member access object.*po<strong>in</strong>ter-to-member<br />
4 member access po<strong>in</strong>ter->*po<strong>in</strong>ter-to-member<br />
5 multiply expr * expr<br />
5 divide expr / expr<br />
5 modulo expr % expr
56 3. Operatoren<br />
Rang Bedeutung Operator im Kontext<br />
6 add expr + expr<br />
6 subtract expr - expr<br />
7 bit shift left expr > expr<br />
8 less than expr < expr<br />
8 less than or equal expr expr<br />
8 greater than or equal expr >= expr<br />
9 equal expr == expr<br />
9 not equal expr != expr<br />
10 bit AND expr & expr<br />
11 bit XOR expr ^ expr<br />
12 bit OR expr | expr<br />
13 logical AND expr && expr<br />
14 logical OR expr || expr<br />
15 conditional expression expr ? expr : expr<br />
16 assign lvalue = expr<br />
16 multiply and assign lvalue *= expr<br />
16 divide and assign lvalue /= expr<br />
16 modulo and assign lvalue %= expr<br />
16 add and assign lvalue += expr<br />
16 subtract and assign lvalue -= expr<br />
16 bit shift left and assign lvalue = expr<br />
16 bit AND and assign lvalue &= expr<br />
16 bit OR and assign lvalue |= expr<br />
16 bit XOR and assign lvalue ^= expr<br />
17 throw exception throw expr<br />
18 sequenc<strong>in</strong>g expr , expr<br />
Legende:<br />
In der Spalte Operator im Kontext wird die Verwendung des jeweiligen Operators<br />
skizziert. Dabei stehen kursiv gesetzte Worte für e<strong>in</strong> Argument, das<br />
der Operator nimmt und <strong>in</strong> teletype gesetzte Worte bzw. Zeichen def<strong>in</strong>ieren<br />
den Operator selbst. Die Bedeutung der e<strong>in</strong>zelnen Argumente ist wie folgt:<br />
name: Beliebiger Name (z.B. Variablenname, Funktionsname) im aktuellen<br />
Kontext (wird im OO-Teil besprochen).<br />
qualified-name: Beliebiger “vollständiger” Name ohne Rücksicht auf den aktuellen<br />
Kontext (wird im OO-Teil besprochen).<br />
classname: Name e<strong>in</strong>er Klasse (wird im OO-Teil besprochen).<br />
namespace-name: Name e<strong>in</strong>es Namespaces (wird im OO-Teil besprochen).
3.2 Arithmetische Operatoren 57<br />
type: E<strong>in</strong>e beliebige Typenbezeichnung, egal ob es sich um e<strong>in</strong>en primitiven<br />
Datentyp, e<strong>in</strong>en zusammengesetzten Datentyp oder e<strong>in</strong>en benutzerdef<strong>in</strong>ierten<br />
Datentyp handelt.<br />
object: E<strong>in</strong>e beliebige Instanz e<strong>in</strong>er Klasse (siehe OO-Teil), Structure oder<br />
Union.<br />
member: Name e<strong>in</strong>es Members, egal ob es sich nun um den Member e<strong>in</strong>er<br />
Structure, e<strong>in</strong>er Union oder e<strong>in</strong>es Namespaces handelt.<br />
po<strong>in</strong>ter: E<strong>in</strong> beliebiger Po<strong>in</strong>ter.<br />
po<strong>in</strong>ter-to-member: E<strong>in</strong> Po<strong>in</strong>ter, der auf e<strong>in</strong>en Member zeigt.<br />
expr: E<strong>in</strong> beliebiger Ausdruck nach den Syntax- und Semantikregeln von<br />
C ++. Dies kann z.B. e<strong>in</strong>e Berechnung, wie var1 + var2 se<strong>in</strong> oder auch<br />
e<strong>in</strong> Funktionsaufruf, wie myFunc(), etc.<br />
expr-list: E<strong>in</strong>e Liste von expr, durch Kommas getrennt.<br />
lvalue: Bezeichnet ganz allgeme<strong>in</strong> “irgende<strong>in</strong>en Speicher im Memory”, also<br />
z.B. e<strong>in</strong>e Variable. Der Begriff lvalue bedeutet voll ausgeschrieben leftvalue,<br />
weil er z.B. auf der l<strong>in</strong>ken Seite e<strong>in</strong>er Zuweisung vorkommen kann.<br />
Die wirklich wichtige Eigenschaft e<strong>in</strong>es lvalues ist die, dass <strong>in</strong> ihm (eventuell<br />
nach Auswertung e<strong>in</strong>er expr, die zu e<strong>in</strong>em lvalue evaluiert) etwas<br />
gespeichert werden kann.<br />
Das bedeutet, dass z.B. e<strong>in</strong> Ausdruck<br />
var1 = var2 + var3 * var4;<br />
gemäß den Prioritäten <strong>in</strong> der Tabelle <strong>in</strong> folgender Reihenfolge ausgewertet<br />
wird:<br />
• Zuerst wird var3 mit var4 multipliziert, denn * hat mit 5 den <strong>in</strong> der<br />
Priorität am höchsten stehenden Rang.<br />
• Das Ergebnis aus dieser Multiplikation wird zu var2 addiert, denn die<br />
Addition hat mit 6 den <strong>in</strong> der Priorität am zweithöchsten stehenden Rang.<br />
• Danach wird das Endergebnis auf var1 zugewiesen, denn die Zuweisung<br />
kommt mit Rang 16 erst zu allerletzt dran.<br />
3.2 Arithmetische Operatoren<br />
In C ++ f<strong>in</strong>den sich als b<strong>in</strong>äre arithmetische Operatoren die vier Grundrechnungsarten<br />
(+, -, *, /) und der “modulo”-Operator (%), der den Divisionsrest<br />
berechnet. Der Begriff der b<strong>in</strong>ären arithmetischen Operatoren kommt daher,<br />
dass diese Operatoren immer zwei Operanden, e<strong>in</strong>en l<strong>in</strong>ken und e<strong>in</strong>en rechten,<br />
entgegennehmen. Ich glaube, es ist unnötig, hier e<strong>in</strong>e große Abhandlung<br />
zu schreiben, wie man z.B. die Addition zweier Zahlen darstellt, denn dies<br />
ist absolut <strong>in</strong>tuitiv.<br />
Sehr wohl möchte ich allerd<strong>in</strong>gs e<strong>in</strong>e paar Worte über das Verhalten <strong>in</strong><br />
Bezug auf die Datentypen, die bei e<strong>in</strong>er Operation verwendet werden, verlieren:
58 3. Operatoren<br />
Bis auf den “modulo”-Operator, der nur für Ganzzahlen def<strong>in</strong>iert ist,<br />
s<strong>in</strong>d alle b<strong>in</strong>ären arithmetischen Operatoren sowohl auf Ganzzahlen als auch<br />
auf Gleitkommazahlen anwendbar. Mischt man verschiedene Datentypen <strong>in</strong><br />
e<strong>in</strong>er Operation, <strong>in</strong>dem man z.B. e<strong>in</strong>en double-Wert zu e<strong>in</strong>em <strong>in</strong>t addiert, so<br />
wird <strong>in</strong>tern e<strong>in</strong>e so genannte implizite Typumwandlung vorgenommen. Diese<br />
Umwandlung erfolgt dergestalt, dass immer der mächtigste Datentyp für die<br />
Operation herangezogen wird. Auch das Ergebnis der Operation entspricht<br />
dann diesem mächtigsten Datentyp. Im Beispiel der Addition e<strong>in</strong>es double zu<br />
e<strong>in</strong>em <strong>in</strong>t wird also zuerst der <strong>in</strong>t <strong>in</strong> se<strong>in</strong> double-äquivalent umgewandelt,<br />
dann wird die Addition durchgeführt und das Ergebnis ist vom Typ double.<br />
Mischt man z.B. e<strong>in</strong>en short und e<strong>in</strong>en <strong>in</strong>t <strong>in</strong> e<strong>in</strong>er Operation, so ist der<br />
mächtigere Datentyp natürlich <strong>in</strong>t und das Spielchen läuft wie erwartet mit<br />
<strong>in</strong>t ab.<br />
Im Pr<strong>in</strong>zip ist das alles ganz logisch, trotzdem wird immer wieder e<strong>in</strong>e<br />
ganz wichtige Tatsache übersehen: Man muss diesen Umstand auch beim<br />
Zuweisen des Ergebnisses an e<strong>in</strong>e Variable beachten! Es passiert leider<br />
nur allzu oft, dass der Datentyp der Variable, die das Ergebnis hält,<br />
weniger mächtig ist, als das Ergebnis selbst. Damit muss das Ergebnis dann<br />
wieder <strong>in</strong> e<strong>in</strong>en weniger mächtigen Typ gewandelt werden, um <strong>in</strong> der Variable<br />
speicherbar zu se<strong>in</strong>. Bei dieser Operation besteht dann die Gefahr, dass<br />
das Fassungsvermögen des Ergebnistyps zu kle<strong>in</strong> ist, wodurch es zu e<strong>in</strong>er<br />
Verfälschung des Ergebnisses kommt.<br />
Begleitend zum Thema der impliziten Typumwandlungen empfehle ich die<br />
Lektüre von Kapitel 6 aus <strong>Softwareentwicklung</strong> <strong>in</strong> C.<br />
Neben den b<strong>in</strong>ären arithmetischen Operatoren gibt es auch noch die sogenannten<br />
unären arithmetischen Operatoren, also solche, die nicht zwei Operanden<br />
mite<strong>in</strong>ander verknüpfen, sondern im Gegensatz dazu nur auf e<strong>in</strong>en<br />
Operanden wirken. E<strong>in</strong> solcher unärer arithmetischer Operator ist natürlich<br />
das negative Vorzeichen, das man e<strong>in</strong>em Operanden voranstellt. Im Gegensatz<br />
zu C gibt es <strong>in</strong> C ++ auch das positive Vorzeichen als def<strong>in</strong>ierten unären<br />
arithmetischen Operator. Man mag nun sagen, dass dies vielleicht nicht<br />
notwendig ist, denn e<strong>in</strong> positives Vorzeichen tut ja eigentlich gar nichts, allerd<strong>in</strong>gs<br />
ist es aus Gründen der Vollständigkeit und <strong>in</strong> H<strong>in</strong>blick auf Operator<br />
Overload<strong>in</strong>g durchaus s<strong>in</strong>nvoll, dieses zuzulassen.<br />
Neben dem negativen und dem positiven Vorzeichen gibt es allerd<strong>in</strong>gs<br />
noch viel <strong>in</strong>teressantere unäre Operatoren, die sehr typisch für C ++ (und<br />
auch C) s<strong>in</strong>d und die für Neul<strong>in</strong>ge immer wieder e<strong>in</strong>en kle<strong>in</strong>en Stolperste<strong>in</strong><br />
darstellen. Die Rede ist hier von den pre- und post-<strong>in</strong>crement, sowie von den<br />
pre- und post-decrement Operatoren, die nur auf ganzzahlige Datentypen<br />
angewandt werden können.<br />
Der <strong>in</strong>crement-Operator wird als ++ dargestellt, der decrement-Operator<br />
als --. Ob es sich um e<strong>in</strong>e pre- oder post-Version des jeweiligen Operators<br />
handelt, wird dadurch bestimmt, ob er dem Operanden vorangestellt (pre-
3.3 Logische- und Vergleichsoperatoren 59<br />
<strong>in</strong>crement bzw. decrement) oder h<strong>in</strong>ten nachgestellt wird (post-<strong>in</strong>crement<br />
bzw. decrement). E<strong>in</strong>e Anwendung dieser Operatoren auf e<strong>in</strong>e Variable bewirkt<br />
e<strong>in</strong>e nachhaltige Änderung ihres Inhalts <strong>in</strong> Form e<strong>in</strong>er Zuweisung. Dies<br />
verrät uns auch e<strong>in</strong> Blick <strong>in</strong> die Operator-Tabelle: Diese Operatoren verlangen<br />
e<strong>in</strong>en lvalue als Operanden! Aber abgesehen von der Inhaltsänderung<br />
hat es noch etwas Besonderes mit diesen Operatoren auf sich, wie man am<br />
verschiedenen Rang der pre- und post-Versionen <strong>in</strong> der Tabelle sieht:<br />
• Pre-<strong>in</strong>crement bzw. pre-decrement bewirken, dass zuerst das Ergebnis der<br />
<strong>in</strong>crement- bzw. decrement-Operation berechnet (und gespeichert) wird<br />
und mit dem neuen Wert weitergearbeitet wird.<br />
• Post-<strong>in</strong>crement bzw. post-decrement bewirken, dass mit dem alten Wert<br />
weitergearbeitet wird und erst danach die <strong>in</strong>crement- bzw. decrement-<br />
Operation durchgeführt wird.<br />
Kl<strong>in</strong>gt kompliziert, ist es aber gar nicht, wenn man sich diese Zusammenhänge<br />
am Beispiel ansieht. Der Ausdruck<br />
var1 = var2++;<br />
wird so ausgewertet, dass zuerst e<strong>in</strong>e Zuweisung des Inhalts von var2 auf<br />
var1 stattf<strong>in</strong>det und danach var2 <strong>in</strong>krementiert wird. Nehmen wir z.B. an,<br />
dass vor der Auswertung dieses Ausdrucks var2 den Wert 5 enthält. Dann<br />
enthält nach Auswertung des Ausdrucks var1 den Wert 5 und var2 den Wert<br />
6. Betrachtet man den Ausdruck<br />
var1 = ++var2;<br />
so wird zuerst die <strong>in</strong>crement-Operation durchgeführt und dann das Ergebnis<br />
zugewiesen. Hätte var2 wieder vor der Auswertung den Wert 5, so würden<br />
nach Auswertung des Ausdrucks sowohl var1 als auch var2 den Wert 6 enthalten.<br />
E<strong>in</strong>e sehr genaue Abhandlung zum Thema der pre- und post-<strong>in</strong>crement bzw.<br />
-decrement Operatoren f<strong>in</strong>det sich im Buch <strong>Softwareentwicklung</strong> <strong>in</strong> C <strong>in</strong> Abschnitt<br />
5.2.2.<br />
3.3 Logische- und Vergleichsoperatoren<br />
Wie man der Tabelle entnehmen kann, stehen zum Vergleich von Werten<br />
<strong>in</strong> C ++ folgende Operatoren zur Verfügung: == prüft auf Gleichheit, != auf<br />
Ungleichheit, > prüft, ob der l<strong>in</strong>ke Wert größer als der rechte ist, < prüft,<br />
ob der l<strong>in</strong>ke Wert kle<strong>in</strong>er als der rechte ist, >= und
60 3. Operatoren<br />
Def<strong>in</strong>ition, dass 0 als false gilt, jedoch war nicht genau def<strong>in</strong>iert, was nun<br />
als true gilt. Irgendetwas ungleich 0 eben. Aus diesem Grund gibt es <strong>in</strong><br />
C ke<strong>in</strong>e exakte Def<strong>in</strong>ition, welchen Wert sie für true liefern. In C ++ jedoch<br />
gilt garantiert, dass die Operationen e<strong>in</strong> “echtes” C ++ true, also 1 liefern.<br />
In den meisten Fällen mag das unwichtig se<strong>in</strong>, allerd<strong>in</strong>gs gibt es Umstände,<br />
z.B. beim Abspeichern von boolean-Werten <strong>in</strong> bestimmten E<strong>in</strong>zelbits e<strong>in</strong>er<br />
Ganzzahl, wo diese genauere Def<strong>in</strong>ition von C ++ sehr nützlich se<strong>in</strong> kann.<br />
Vorsicht Falle: Der Vergleichsoperator == kann nur allzu leicht e<strong>in</strong>mal<br />
durch e<strong>in</strong>e Unachtsamkeit <strong>in</strong> e<strong>in</strong>em Programm zum Zuweisungsoperator =<br />
mutieren. Da die Zuweisung so def<strong>in</strong>iert ist, dass sie als Ergebnis den zugewiesenen<br />
Wert liefert, betrachtet das der Compiler ke<strong>in</strong>eswegs als Fehler.<br />
Soll er auch gar nicht, denn oft wird dies tatsächlich absichtlich gemacht.<br />
Moderne Compiler produzieren allerd<strong>in</strong>gs mittlerweile zum Glück Warn<strong>in</strong>gs<br />
über possible un<strong>in</strong>tended assignments, die man mit entsprechenden Klammerungen<br />
verh<strong>in</strong>dern kann.<br />
Als logische Verknüpfungsoperatoren f<strong>in</strong>det man <strong>in</strong> C ++ auch wieder die<br />
alten Bekannten, die es schon <strong>in</strong> C gab: && repräsentiert e<strong>in</strong> logisches AND,<br />
|| e<strong>in</strong> logisches OR und ! e<strong>in</strong> logisches NOT.<br />
Bis auf den Tipp, dass gerade bei Verknüpfungen mehrerer logischer Abfragen<br />
durch diese Operatoren e<strong>in</strong>e entsprechende Klammerung den Code<br />
sehr viel lesbarer macht als e<strong>in</strong>e e<strong>in</strong>fache Ane<strong>in</strong>anderreihung, möchte ich zu<br />
diesen Operatoren ke<strong>in</strong>e weiteren Worte verlieren. Sie s<strong>in</strong>d ganz e<strong>in</strong>fach <strong>in</strong>tuitiv<br />
anwendbar.<br />
Lesern, die <strong>in</strong> C ungeübt s<strong>in</strong>d, wird zum Thema der Vergleichsoperatoren<br />
und der logischen Verknüpfungen Abschnitt 5.3 aus <strong>Softwareentwicklung</strong> <strong>in</strong><br />
C noch Zusatz<strong>in</strong>formation geboten.<br />
3.4 Bitoperatoren<br />
Die Bit-Verknüpfungsoperatoren & (Bit-AND), | (Bit-OR), ^ (Bit-XOR) und<br />
~ (Bit-NOT oder auch Komplement bzw. 1-er Komplement) sowie die entsprechenden<br />
Bit-Shiftoperatoren > (Bitright-shift<br />
bzw SR) stehen <strong>in</strong> C ++ zur Manipulation von E<strong>in</strong>zelbits und<br />
Bitmasken <strong>in</strong> ganzzahligen Werten zur Verfügung.<br />
Leider hat sich vor allem <strong>in</strong> den letzten Jahren die Me<strong>in</strong>ung stark e<strong>in</strong>gebürgert,<br />
dass man dieses “masch<strong>in</strong>ennahe Zeug” ja sowieso nicht braucht<br />
und dass das nur für Assemblerprogrammierer <strong>in</strong>teressant ist. Dies ist mitnichten<br />
der Fall! Im Pr<strong>in</strong>zip begegnen Softwareentwicklern viel öfter sogenannte<br />
Flags, als auf den ersten Blick zu vermuten wäre (z.B. bei File-<br />
Operationen). Weiters hilft das Wissen um Bitrepräsentationen und Bitope-
3.6 Datentypabfragen und explizite Typecasts 61<br />
ratoren <strong>in</strong> gewissen Bereichen unglaublich viel, wenn es um effiziente Implementationen<br />
geht.<br />
Ich habe jetzt nicht vor, e<strong>in</strong>e lange Abhandlung über Bitoperatoren hier <strong>in</strong><br />
diesem Buch zu verewigen. Allerd<strong>in</strong>gs muss ich allen Lesern, die sich im<br />
Umgang mit Bitmasken und ähnlichen Konstrukten nicht ganz sicher fühlen,<br />
unbed<strong>in</strong>gt den dr<strong>in</strong>genden Rat geben, sich e<strong>in</strong>mal Abschnitt 5.4 im Buch<br />
<strong>Softwareentwicklung</strong> <strong>in</strong> C genau zu Gemüte zu führen! Dort wird auf die<br />
wichtigen Zusammenhänge sehr detailliert und beispielunterstützt e<strong>in</strong>gegangen.<br />
3.5 Zuweisungen<br />
Der Zuweisungs-Operator =, sowie die e<strong>in</strong>zelnen Zuweisungs-Kurzformen (*=,<br />
/=, %=, +=, -=, =, &=, |= und ^=) s<strong>in</strong>d <strong>in</strong> C ++ äquivalent zu C. Der<br />
Ausdruck<br />
var1 += var2;<br />
ist die Kurzform zu<br />
var1 = var1 + var2;<br />
Man sieht also, dass durch die Verwendung der Zuweisungs-Kurzformen<br />
Ausdrücke kürzer und kompakter formuliert werden können. Darum und weil<br />
die Verwendung der Kurzformen sowieso <strong>in</strong> C und C ++ absolut typisch ist,<br />
möchte ich unbed<strong>in</strong>gt anregen, diese Operatoren nicht zu ignorieren. Von<br />
vielen Neul<strong>in</strong>gen werden diese Operatoren als etwas Unnötiges erachtet und<br />
daher werden sie nicht verwendet. Im S<strong>in</strong>ne e<strong>in</strong>es guten Programmierstils<br />
möchte ich von dieser Vorgehensweise abraten. Außerdem möchte ich auch<br />
noch darauf h<strong>in</strong>weisen, dass es sehr wohl ziemliche Unterschiede <strong>in</strong> der Performance<br />
zwischen Kurzzuweisung und Operation mit nachfolgender Zuweisung<br />
geben kann. Der Beweis zu dieser Aussage wird später noch <strong>in</strong> Kapitel 12<br />
angetreten.<br />
3.6 Datentypabfragen und explizite Typecasts<br />
Es wurde bereits im Rahmen der Diskussion der arithmetischen Operatoren<br />
(siehe Abschnitt 3.2) besprochen, dass der Compiler gewisse implizite Umwandlungen<br />
von Datentypen vornimmt, wenn dies zur Durchführung e<strong>in</strong>er<br />
Operation notwendig ist. Sehr oft will man dies nicht dem Compiler überlassen,<br />
sondern selbst e<strong>in</strong>e Umwandlung erzw<strong>in</strong>gen. Dies geschieht durch<br />
sogenannte explizite Typecasts (oder auch explizite Casts). Die Gründe für<br />
explizite Casts s<strong>in</strong>d mannigfaltig, der Häufigste davon ist, dass es unter gewissen<br />
Umständen dem Compiler beim besten Willen nicht möglich ist, zu<br />
erraten, welcher Typ denn nun der geeignete ist, bzw. auch, wie denn nun
62 3. Operatoren<br />
die Umwandlung <strong>in</strong> diesen Typ erfolgen soll. Vor allem im OO-Teil werden<br />
wir noch viele Beispiele zu dieser Thematik kennen lernen.<br />
Im Unterschied zu C, das nur e<strong>in</strong>en e<strong>in</strong>zigen Cast-Operator kennt, gibt es<br />
<strong>in</strong> C ++ mehrere verschiedene Operatoren dafür. Außerdem existiert <strong>in</strong> C ++<br />
auch e<strong>in</strong> Mechanismus, zur Laufzeit Information über den Typ von Variablen<br />
zu erhalten, die sogenannte Run-Time-Type-Information (kurz: RTTI ).<br />
Ganz bewusst möchte ich bei der Besprechung von Typecasts nicht mit<br />
den sogenannten C-Style Casts beg<strong>in</strong>nen, die allen Lesern mit C-Vorbildung<br />
im Pr<strong>in</strong>zip bekannt s<strong>in</strong>d. Das Problem hierbei ist nämlich, dass diese C-<br />
Style Casts <strong>in</strong> C ++ eigentlich e<strong>in</strong>e Komb<strong>in</strong>ation aus mehreren Möglichkeiten<br />
s<strong>in</strong>d, wie man Typen umwandeln kann. Diese Möglichkeiten be<strong>in</strong>halten auch<br />
solche, die auf RTTI zurückgreifen, um Casts korrekt ausführen zu können.<br />
Daher möchte ich <strong>in</strong> diesem Kapitel zunächst Type-Identification und RTTI,<br />
danach die verschiedenen zur Verfügung stehenden Cast-Operatoren und erst<br />
am Ende den klassischen C-Style Cast besprechen.<br />
Obwohl das Thema der Casts <strong>in</strong> der Folge sehr genau besprochen wird,<br />
möchte ich trotzdem, quasi “zum Aufwärmen”, den Lesern Kapitel 13 aus<br />
<strong>Softwareentwicklung</strong> <strong>in</strong> C empfehlen.<br />
Vorsicht Falle: Entwickler, die e<strong>in</strong>ige Erfahrung mit C und mit älteren Versionen<br />
von C ++ haben, neigen dazu, bei weitem öfter als notwendig explizite<br />
Casts e<strong>in</strong>zusetzen. Egal, um welche Variante der <strong>in</strong> der Folge vorgestellten<br />
Casts es sich handelt, ungefährlich s<strong>in</strong>d sie im Pr<strong>in</strong>zip alle nicht. Neuere<br />
C ++ Versionen können <strong>in</strong> sehr vielen Situationen implizite Casts vornehmen,<br />
die von alten C ++ Versionen und <strong>in</strong>sbesondere auch von C noch nicht durchgeführt<br />
wurden.<br />
Um nun ke<strong>in</strong>e Zeitbomben <strong>in</strong> e<strong>in</strong> Programm e<strong>in</strong>zubauen, sollte man unbed<strong>in</strong>gt<br />
folgenden Tipp beherzigen: Casts sollen nur dort verwendet werden,<br />
wo sie unbed<strong>in</strong>gt notwendig s<strong>in</strong>d. Im Fall, dass man sich nicht sicher ist,<br />
ob man e<strong>in</strong>en Cast braucht oder nicht, schreibt man am besten nicht <strong>in</strong> vorauseilendem<br />
Gehorsam e<strong>in</strong>en expliziten Cast. Das schlimmste was dadurch<br />
passieren kann ist, dass sich der Compiler beschwert. Danach kann man den<br />
Cast immer noch ergänzen.<br />
3.6.1 Type Identification und Run-Time-Type-Information<br />
In diesem Abschnitt wird e<strong>in</strong> Feature von C ++ besprochen, dass es <strong>in</strong> C<br />
überhaupt nicht gibt: Die Möglichkeit, den Typ e<strong>in</strong>es Objekts (z.B. e<strong>in</strong>er<br />
Variable) herauszuf<strong>in</strong>den.<br />
Leider muss ich hier e<strong>in</strong>en kle<strong>in</strong>en Spagat <strong>in</strong> Bezug auf die Struktur dieses<br />
Buchs machen, denn bei den Themen Type Identification und RTTI bekommen<br />
wir es plötzlich mit Klassen zu tun, weil dies <strong>in</strong> der Natur der hierfür
3.6 Datentypabfragen und explizite Typecasts 63<br />
def<strong>in</strong>ierten Operatoren liegt. Würde ich dieses Thema hier aussparen und<br />
erst im OO-Teil des Buchs anschneiden, dann würde wichtige Information<br />
fehlen, die es erst ermöglicht, Casts richtig zu begreifen. Die Behandlung<br />
der Casts <strong>in</strong> den OO-Teil zu verlegen funktioniert auch nicht, denn ohne dieses<br />
Wissen würden wichtige Voraussetzungen fehlen, die jetzt schon benötigt<br />
werden. Also möchte ich e<strong>in</strong>en Kompromiss e<strong>in</strong>gehen: In der Folge werden<br />
hier die Pr<strong>in</strong>zipien der benötigten Mechanismen umrissen, ohne ganz genau<br />
auf deren Details e<strong>in</strong>zugehen. E<strong>in</strong>e ergänzende Abhandlung von RTTI wird<br />
<strong>in</strong> Abschnitt 15.6 nachgeliefert.<br />
Zurück zum Thema Type Identification: C ++ bietet die Möglichkeit, zur<br />
Laufzeit e<strong>in</strong>es Programms zu Typ<strong>in</strong>formationen z.B. von Variablen zu kommen.<br />
Dazu existiert der typeid-Operator, den wir uns am besten gleich<br />
e<strong>in</strong>mal am Beispiel ansehen (typeid_demo.cpp):<br />
1 // typeid demo . cpp − demo f o r <strong>C++</strong> type i d e n t i f i c a t i o n f e a t u r e s<br />
2<br />
3 #<strong>in</strong>clude <br />
4 #<strong>in</strong>clude < t y pe<strong>in</strong>fo><br />
5 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
6<br />
7 us<strong>in</strong>g std : : cout ;<br />
8 us<strong>in</strong>g std : : endl ;<br />
9<br />
10 struct TestStruct<br />
11 {<br />
12 <strong>in</strong>t just a member ;<br />
13 char one more member ;<br />
14 } ;<br />
15<br />
16 union TestUnion<br />
17 {<br />
18 <strong>in</strong>t j u s t a v a r i a n t ;<br />
19 char one more variant ;<br />
20 } ;<br />
21<br />
22 typedef struct TestStruct TypedefedTestStruct ;<br />
23 typedef union TestUnion TypedefedTestUnion ;<br />
24<br />
25 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
26 {<br />
27 <strong>in</strong>t32 t e s t v a r = 0;<br />
28<br />
29 cout
64 3. Operatoren<br />
46 typeid ( long long ) . name()
3.6 Datentypabfragen und explizite Typecasts 65<br />
Von Zeile 10 bis Zeile 23 f<strong>in</strong>det man die Deklaration e<strong>in</strong>er Structure, e<strong>in</strong>er<br />
Union und zwei entsprechende typedef-Statements, die später zu Demonstrationszwecken<br />
verwendet werden.<br />
Die Variable test_var, die <strong>in</strong> Zeile 27 def<strong>in</strong>iert wird, ist auch noch ke<strong>in</strong>e<br />
Besonderheit, allerd<strong>in</strong>gs das Statement <strong>in</strong> den Zeilen 29–30 enthält e<strong>in</strong>e<br />
Neuigkeit:<br />
• In Zeile 30 wird der typeid-Operator aufgerufen. Dieser Operator ist derjenige,<br />
der <strong>in</strong> unserer zusammenfassenden Operatoren-Tabelle als<br />
typeid(type) unter dem Begriff Type Identification verewigt wurde.<br />
• Dieser typeid-Operator liefert uns <strong>in</strong>terne, Compiler-implementationsspezifische<br />
bzw., je nach Typ und System, auch OS-implementationsspezifische<br />
Information zu e<strong>in</strong>em Datentyp. Diese Information wird als<br />
Objekt vom Typ type_<strong>in</strong>fo geliefert – hier s<strong>in</strong>d wir plötzlich beim OO-<br />
Teil angelangt.<br />
• Um nun nicht OO-Features von C ++ besprechen zu müssen, stelle ich e<strong>in</strong>fach<br />
<strong>in</strong> den Raum, dass man mit e<strong>in</strong>em Objekt zum Teil ähnlich umgeht,<br />
wie man es mit e<strong>in</strong>er Structure tut: E<strong>in</strong> Objekt besitzt ebenfalls Members<br />
und gleich wie bei e<strong>in</strong>er Structure wird auf diese mittels des Punkt-<br />
Operators zugegriffen. Members können bei e<strong>in</strong>em Objekt nicht nur Variablen,<br />
sondern auch sogenannte Methoden (im Pr<strong>in</strong>zip Funktionen) se<strong>in</strong>.<br />
Und genau um e<strong>in</strong>e solche Methode handelt es sich bei name(), wie sie <strong>in</strong><br />
Zeile 30 aufgerufen wird.<br />
• Die Methode name() liefert bei Aufruf e<strong>in</strong>en Str<strong>in</strong>g, der den <strong>in</strong>ternen,<br />
Compiler- und plattformabhängigen Namen e<strong>in</strong>es Typs repräsentiert. Dieser<br />
muss aus Implementationsgründen nämlich nicht derselbe se<strong>in</strong>, wie der<br />
Typname, den wir im Programm verwenden, wie man auch am Output<br />
sieht. Es verwendet z.B. das mit g++ compilierte Programm unter L<strong>in</strong>ux<br />
für e<strong>in</strong>en char <strong>in</strong>tern e<strong>in</strong>fach den Namen c.<br />
Natürlich kann man typeid nicht nur e<strong>in</strong>fach auf Typen anwenden, denn<br />
dies alle<strong>in</strong> wäre ziemlich s<strong>in</strong>nlos. Man kann typeid auch auf beliebige Expressions,<br />
also z.B. auf Variablen anwenden, wie man z.B. im Statement <strong>in</strong><br />
den Zeilen 64–65 sieht. Und genau dabei handelt es sich, wie auch <strong>in</strong> unserer<br />
Operatorentabelle zu sehen ist, um das so wichtige RTTI -Feature von<br />
C ++: Mittels RTTI ist es <strong>in</strong> C ++ möglich, zur Laufzeit den Typ, zu dem e<strong>in</strong>e<br />
Expression evaluiert, abzufragen.<br />
Und damit sieht man auch, warum es notwendig ist, typeid auf e<strong>in</strong>en Typ<br />
wie <strong>in</strong>t anwenden zu können: Nachdem ja <strong>in</strong>tern die Typen nicht mehr die<br />
uns bekannten Namen haben, sondern plattformabhängige Identifications,<br />
wäre es ansonsten unmöglich herauszuf<strong>in</strong>den, ob etwas z.B. jetzt wirklich<br />
vom Typ <strong>in</strong>t ist. Bestimmte Cast-Operatoren, die wir gleich <strong>in</strong> der Folge<br />
kennen lernen werden, verwenden die gelieferte Typ<strong>in</strong>formation (zu der <strong>in</strong>tern<br />
um e<strong>in</strong>iges mehr als nur der Name gehört, der hier abgefragt wird), um<br />
die Kompatibilität von Datentypen festzustellen, bevor sie ihre Umwandlung<br />
durchführen.
66 3. Operatoren<br />
Was sieht man an unserem kle<strong>in</strong>en Demoprogrämmchen noch, wenn man<br />
den Output näher betrachtet?<br />
• Die ersten 3 Zeilen des Outputs zeigen uns die schon besprochene Plattformabhängigkeit<br />
des Datentyps char, nämlich, dass nicht gesagt ist, ob<br />
er nun <strong>in</strong>tern als signed char oder unsigned char betrachtet wird. Wir<br />
sehen, dass char, signed char und unsigned char <strong>in</strong>tern unter drei verschiedenen<br />
Namen bekannt s<strong>in</strong>d, nämlich c, Sc und Uc.<br />
• Die zweiten 3 Zeilen des Outputs wiederum zeigen uns, dass dies beim Datentyp<br />
short sehr wohl wieder genau def<strong>in</strong>iert ist, denn der <strong>in</strong>terne Name<br />
von short und signed short ist derselbe (s), wogegen unsigned short,<br />
wie zu erwarten, den <strong>in</strong>ternen Namen Us trägt. Für die Datentypen <strong>in</strong>t,<br />
long und long long gilt dasselbe wie für short. Aus Gründen der Platzersparnis<br />
wurden die entsprechenden Programmzeilen hier nicht verewigt.<br />
Leser, die me<strong>in</strong>en Aussagen nicht ohne Kontrolle trauen, können gerne zur<br />
Überprüfung die entsprechenden Zeilen im Programm ergänzen :-).<br />
• Vergleicht man z.B. den <strong>in</strong>ternen Namen unseres mittels typedef <strong>in</strong><br />
user_types.h def<strong>in</strong>ierten Typs <strong>in</strong>t32 mit dem <strong>in</strong>ternen Namen e<strong>in</strong>es “echten”<br />
<strong>in</strong>t, so kommt man e<strong>in</strong>er Begriffs-Fehlverwendung aus den Urzeiten<br />
von C auf die Spur: Intern besitzen beide, also <strong>in</strong>t32 und <strong>in</strong>t denselben<br />
Typ, sie s<strong>in</strong>d also nicht nur kompatibel, sondern sie s<strong>in</strong>d tatsächlich<br />
äquivalent und dementsprechend zur Laufzeit nicht mehr vone<strong>in</strong>ander zu<br />
unterscheiden. Was sagt uns das? Nun, ganz e<strong>in</strong>fach: typedef dient nur<br />
dazu, dem Compiler e<strong>in</strong>en H<strong>in</strong>weis zu geben, dass da e<strong>in</strong> Typ <strong>in</strong>t32 verwendet<br />
wird, der aber <strong>in</strong> Wirklichkeit e<strong>in</strong> <strong>in</strong>t ist. Der Compiler nimmt das<br />
h<strong>in</strong> und macht <strong>in</strong>tern aus jedem <strong>in</strong>t32 e<strong>in</strong>en “echten” <strong>in</strong>t. Rekapitulieren<br />
wir nun noch kurz, was es zum Thema Def<strong>in</strong>ition und Deklaration zu sagen<br />
gibt, dann erkennen wir, dass es sich bei typedef nicht, wie suggeriert,<br />
um e<strong>in</strong>e Typ-Def<strong>in</strong>ition, sondern eigentlich nur um e<strong>in</strong>e Typ-Deklaration<br />
handelt! Von Rechts wegen müsste es also typedecl heißen und nicht<br />
typedef. Ich gebe ja zu, dass es sich hier um e<strong>in</strong>e Spitzf<strong>in</strong>digkeit handelt,<br />
aber man sieht daran, dass auch <strong>in</strong> der Computerwelt nicht immer alles so<br />
genau ist, wie es se<strong>in</strong> sollte.<br />
• In den letzten 4 Zeilen des Outputs erkennt man, was der hier verwendete<br />
Compiler aus Namen von zusammengesetzten Datentypen wie Unions<br />
und Structures macht: Der Name, der der Structure bzw. Union gegeben<br />
wurde, wird auch <strong>in</strong>tern beibehalten, allerd<strong>in</strong>gs bekommt er e<strong>in</strong>e Zahl als<br />
Präfix. Diese wird bei g++ zu Laufzeit-Optimierungszwecken verwendet.<br />
Ja, ok, jetzt gehe ich endgültig zu weit. Ich höre schon auf damit... :-).<br />
Mit dem Wissen über die Möglichkeiten des Herausf<strong>in</strong>dens von Information<br />
über Datentypen bewaffnet, haben wir jetzt das notwendige Handwerkszeug,<br />
um die verschiedenen Cast-Operatoren zu begreifen, die es <strong>in</strong> C ++ gibt. Also<br />
stürzen wir uns <strong>in</strong>s Vergnügen und widmen diesen Casts e<strong>in</strong>en näheren Blick.
3.6.2 Unchecked Cast<br />
3.6 Datentypabfragen und explizite Typecasts 67<br />
Der erste Cast-Operator, den wir betrachten, repräsentiert den sogenannten<br />
unchecked Cast. Dieser Cast ist, wie der Name schon sagt, e<strong>in</strong>er, bei dem ke<strong>in</strong>e<br />
Überprüfungen stattf<strong>in</strong>den. Daher ist er zwar e<strong>in</strong>erseits der Cast, mit dem<br />
man die gef<strong>in</strong>keltsten Konstrukte basteln kann, andererseits aber auch der<br />
gefährlichste Cast, den man anwenden kann. Beachtet man, dass der Operator<br />
dazu re<strong>in</strong>terpret_cast heißt, so kann man sich auch leicht ausmalen,<br />
was <strong>in</strong>tern passiert: Was auch immer sich h<strong>in</strong>ter e<strong>in</strong>em Datentyp verbirgt,<br />
wird e<strong>in</strong>fach anders <strong>in</strong>terpretiert. Es wird nicht e<strong>in</strong>mal versucht, e<strong>in</strong>e echte<br />
Umwandlung durchzuführen, sondern es wird ganz e<strong>in</strong>fach nur als anderer<br />
Typ behandelt. Damit kann man im Pr<strong>in</strong>zip alles <strong>in</strong> alles, ohne Rücksicht<br />
auf Verluste, umwandeln. Ganz so krass ist es nicht, denn zum<strong>in</strong>dest wird<br />
e<strong>in</strong>e m<strong>in</strong>imale Überprüfung vom Compiler vorgenommen, nämlich, ob die<br />
neue Typ<strong>in</strong>terpretation zum<strong>in</strong>dest überhaupt irgende<strong>in</strong>en S<strong>in</strong>n ergeben könnte.<br />
Die Gefahr, dass dabei wahnwitzigste D<strong>in</strong>ge passieren, ist natürlich riesig<br />
und die Portabilität von Programmen, die ausgiebig mit re<strong>in</strong>terpret_cast<br />
arbeiten, ist im Normalfall als nicht berauschend bis nicht gegeben zu bezeichnen.<br />
Da die tollsten Effekte (sowohl im positiven als auch im negativen S<strong>in</strong>n)<br />
bei e<strong>in</strong>em re<strong>in</strong>terpret_cast vor allem <strong>in</strong> Verb<strong>in</strong>dung mit Po<strong>in</strong>tern und den<br />
dah<strong>in</strong>terstehenden Daten zu erzielen s<strong>in</strong>d, möchte ich das Beispiel dazu bis<br />
zur Behandlung der Po<strong>in</strong>ter <strong>in</strong> C ++ verschieben.<br />
Kurz umrissen kann man sagen, dass re<strong>in</strong>terpret_cast dazu gedacht<br />
ist, zwischen Typen umzuwandeln, die im Pr<strong>in</strong>zip ke<strong>in</strong>en ursächlichen Zusammenhang<br />
haben (z.B. <strong>in</strong>t und po<strong>in</strong>ter). Dass man von solchen D<strong>in</strong>gen<br />
im Normalfall tunlichst die F<strong>in</strong>ger lassen sollte, werde ich noch öfter <strong>in</strong> diesem<br />
Buch erwähnen :-).<br />
Vorsicht Falle: Dadurch, dass bei e<strong>in</strong>em re<strong>in</strong>terpret_cast e<strong>in</strong>fach das<br />
Bitmuster des Speicherabbilds e<strong>in</strong>es Datums unangetastet bestehen bleibt<br />
und nur als anderer Datentyp <strong>in</strong>terpretiert wird, ergibt sich e<strong>in</strong>e ungeahnte<br />
Palette von Fehlermöglichkeiten. Fehler, die durch falsche Annahmen über<br />
die Interpretation von Datentypen zustande kommen, s<strong>in</strong>d unheimlich schwer<br />
zu f<strong>in</strong>den.<br />
Jedenfalls möchte ich hier e<strong>in</strong>e sehr starke Empfehlung abgeben: F<strong>in</strong>ger<br />
weg von re<strong>in</strong>terpret_cast, wenn es irgendwie möglich ist!!!<br />
3.6.3 Compiletime-checked Cast<br />
Der Compiletime-checked Cast, der durch den Operator static_cast<br />
repräsentiert wird, ist dazu gedacht, zwischen Typen umzuwandeln, die <strong>in</strong><br />
e<strong>in</strong>em irgendwie gearteten ursächlichen Zusammenhang zue<strong>in</strong>ander stehen.<br />
E<strong>in</strong> Beispiel hierfür wäre die Umwandlung e<strong>in</strong>es <strong>in</strong>t <strong>in</strong> e<strong>in</strong>en float. Für diese<br />
Umwandlung gibt es e<strong>in</strong>e genaue Vorschrift, nämlich wie e<strong>in</strong>- und dieselbe
68 3. Operatoren<br />
Zahl als <strong>in</strong>t und als float dargestellt wird. Wendet man e<strong>in</strong>en static_cast<br />
<strong>in</strong> die umgekehrte Richtung, also von float auf <strong>in</strong>t an, so wird der float<br />
Wert zum<strong>in</strong>dest bestmöglich <strong>in</strong> e<strong>in</strong>en <strong>in</strong>t umgewandelt, soll heißen, es werden<br />
die Kommastellen verworfen und der ganzzahlige Anteil wandert <strong>in</strong> den <strong>in</strong>t.<br />
Dieses Verhalten läßt sich e<strong>in</strong>fach mit folgendem Programm demonstrieren<br />
(static_cast_demo.cpp):<br />
1 // s t a t i c c a s t d e m o . cpp − small demo program f o r s t a t i c c a s t<br />
2<br />
3 #<strong>in</strong>clude <br />
4 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
5<br />
6 us<strong>in</strong>g std : : cout ;<br />
7 us<strong>in</strong>g std : : endl ;<br />
8<br />
9 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
10 {<br />
11 <strong>in</strong>t32 a n i n t v a r = 17;<br />
12 float c o n v e r t e d f l o a t v a r = static cast(a n i n t v a r ) ;<br />
13<br />
14 cout
3.6 Datentypabfragen und explizite Typecasts 69<br />
man, dass -1 <strong>in</strong>terpretiert als unsigned nicht wirklich so toll ist! Und hier<br />
kann auch der beste Compiler nichts machen, denn wie schon erwähnt, wird<br />
der Code zur Umwandlung zur Compiletime e<strong>in</strong>gesetzt. Zu dieser Zeit ist<br />
allerd<strong>in</strong>gs im Normalfall nicht bekannt, dass vielleicht später zur Laufzeit<br />
e<strong>in</strong> negativer Wert <strong>in</strong> e<strong>in</strong>en unsigned Wert verwandelt werden soll.<br />
Vorsicht Falle: Auch e<strong>in</strong> static_cast bewahrt nicht vor den schweren Fehlern,<br />
die beim Mischen von signed und unsigned Typen passieren können,<br />
denn zur Compiletime, zu der der entsprechende Umwandlungscode e<strong>in</strong>gesetzt<br />
wird, ist der umzuwandelnde Wert ja noch nicht überprüfbar! Aus<br />
diesem Grund muss ich zum wiederholten Mal darauf h<strong>in</strong>weisen, dass unvorsichtiges<br />
Mischen von Datentypen katastrophale Folgen haben kann und<br />
tunlichst unterlassen werden soll!<br />
3.6.4 Runtime-checked Cast<br />
Es gibt beim Arbeiten mit Klassen und Objekten <strong>in</strong> C ++ Fälle, die wir im OO-<br />
Teil noch kennen lernen werden, bei denen der Compiler bei der Übersetzung<br />
des Programms nicht wissen kann, mit welchem Typ man es zur Laufzeit <strong>in</strong><br />
e<strong>in</strong>em Ausdruck zu tun haben wird. In diesem Fall ist e<strong>in</strong> static_cast nicht<br />
verwendbar, da für e<strong>in</strong>en static_cast vom Compiler Code e<strong>in</strong>gesetzt wird,<br />
der die tatsächliche Umwandlung vornimmt. Wenn aber der Typ zu dieser<br />
Zeit noch unbekannt ist, welchen Code soll der Compiler dann e<strong>in</strong>setzen?<br />
Der Fall, dass man erst zur Laufzeit entscheiden kann, welchen Typ e<strong>in</strong><br />
Objekt nun wirklich hat, kommt <strong>in</strong> der OO-Programmierung relativ oft vor,<br />
also darf man nicht verh<strong>in</strong>dern, dass e<strong>in</strong>e eventuell zur Laufzeit korrekte<br />
Umwandlung bereits im Vorfeld vom Compiler abgelehnt wird, bloß weil er<br />
nicht feststellen kann, ob hier später korrekt oder <strong>in</strong>korrekt umgewandelt<br />
würde und welchen Code er e<strong>in</strong>setzen soll. Damit würde man die Flexibilität<br />
von C ++ stark e<strong>in</strong>schränken. Schlimmer noch, man würde sogar saubere<br />
Programmierung zugunsten von brute-force Ansätzen verh<strong>in</strong>dern!<br />
Aus diesem Grund wurde <strong>in</strong> C ++ der dynamic_cast e<strong>in</strong>geführt. Dieser<br />
sagt dem Compiler, dass er Code e<strong>in</strong>setzen soll, der erst zur Laufzeit entscheidet,<br />
ob e<strong>in</strong>e Umwandlung nun zulässig ist oder nicht. Sollte sie zur Laufzeit<br />
als zulässig erkannt werden, so wird sie entsprechend durchgeführt, wenn<br />
nicht, wird zur Laufzeit e<strong>in</strong> Fehler generiert. Die Syntax und Semantik e<strong>in</strong>es<br />
Aufrufs von dynamic_cast s<strong>in</strong>d dieselbe wie wir sie schon von static_cast<br />
kennen.<br />
Leser, die nun hoffen, dass e<strong>in</strong> dynamic_cast vor den schweren Fehlern<br />
bewahren würde, die man beim Mischen von signed und unsigned Typen<br />
machen kann, die muss ich leider enttäuschen. Der dynamic_cast kümmert<br />
sich nur um D<strong>in</strong>ge, die bei Klassenhierarchien bei der OO-Programmierung<br />
<strong>in</strong>teressant s<strong>in</strong>d und zu denen wir noch kommen werden. Er ist leider auf
70 3. Operatoren<br />
primitive Datentypen nicht e<strong>in</strong>mal anwendbar und der Versuch dieser Anwendung<br />
wird vom Compiler mit unfreundlichen Meldungen und Abbruch<br />
der Übersetzung quittiert. Ich gebe zu, dass es schön wäre, e<strong>in</strong>en Cast zur<br />
Verfügung zu haben, der zur Laufzeit e<strong>in</strong>e Plausibilitätsprüfung z.B. bei<br />
signed auf unsigned Casts durchführt, nur leider existiert dieser <strong>in</strong> C ++<br />
nicht.<br />
Durch die Natur der Anwendung von dynamic_cast im Kontext von Klassenhierarchien<br />
muss ich leider auch hier auf e<strong>in</strong> Beispiel verzichten und die<br />
Leser auf später vertrösten.<br />
3.6.5 Remove-const Cast<br />
Wir werden bei der Diskussion über Funktionen und im OO-Teil bei der Beschreibung<br />
von Methoden noch e<strong>in</strong>e Anwendung von const kennen lernen,<br />
die über das re<strong>in</strong>e Def<strong>in</strong>ieren von Konstanten h<strong>in</strong>ausgeht: Man kann Parameter<br />
und return-Values als const deklarieren, womit man e<strong>in</strong> ungewolltes<br />
Ändern derselben verh<strong>in</strong>dert.<br />
Manchmal, aber wirklich nur manchmal, gibt es Fälle, <strong>in</strong> denen man etwas<br />
als const deklariert, um damit auszudrücken, dass man ja wirklich nichts<br />
ändern will. Bloß gibt es die Situation, <strong>in</strong> der sich aus der Sichtweise e<strong>in</strong>es<br />
Entwicklers an e<strong>in</strong>em Objekt zwar äußerlich nichts ändert, wohl aber irgendwelche<br />
<strong>in</strong>ternen Kle<strong>in</strong>igkeiten umdef<strong>in</strong>iert werden. Damit sieht e<strong>in</strong> Objekt<br />
nach außen h<strong>in</strong> konstant aus und soll auch zur Beruhigung der Entwickler<br />
als solches deklariert werden. Diese Eigenschaft wird als logische Konstantheit<br />
oder auch logical Constness bezeichnet, im Gegensatz zur physischen<br />
Konstantheit oder auch physical Constness.<br />
Damit landet man allerd<strong>in</strong>gs <strong>in</strong> e<strong>in</strong>em kle<strong>in</strong>en Dilemma: Wenn etwas<br />
als const deklariert ist, aber <strong>in</strong>tern gar nicht so konstant ist, wie man es<br />
nach außen vorspielen will, wie realisiert man das? Genau dazu wurde der<br />
const_cast e<strong>in</strong>geführt, der es erlaubt, dass e<strong>in</strong> als const gekennzeichnetes<br />
Objekt nach Umwandlung trotzdem geändert werden kann. Es bleibt hierbei<br />
den Entwicklern überlassen, wie sie nach außen h<strong>in</strong> den E<strong>in</strong>druck der<br />
Konstanz aufrecht halten. E<strong>in</strong>es ist jedoch sicher: Nach außen h<strong>in</strong> muss der<br />
E<strong>in</strong>druck gewahrt bleiben, denn sonst erzeugt man e<strong>in</strong>e ungeahnte Fehlerquelle!<br />
Auch zu diesem Cast verschiebe ich das entsprechende Beispiel auf später,<br />
da mit den bis jetzt zur Verfügung stehenden Konstrukten die S<strong>in</strong>nhaftigkeit<br />
und die Gefahren noch nicht besonders e<strong>in</strong>drucksvoll darstellbar s<strong>in</strong>d.<br />
3.6.6 C-Style Casts<br />
Zu guter Letzt kommen wir zu der Gattung von Casts, die bereits aus C<br />
geläufig s<strong>in</strong>d. Daher auch der Name C-Style Cast. Der Grund, warum diese<br />
hier als letzte anstatt als erste Variante des Casts diskutiert werden, wurde
3.6 Datentypabfragen und explizite Typecasts 71<br />
bereits erwähnt: C-Style Casts s<strong>in</strong>d e<strong>in</strong>e Mischung aus den bisher erklärten<br />
verschiedenen Arten von Casts.<br />
Beim C-Style Cast stellt man den gewünschten Zieldatentyp <strong>in</strong> runden<br />
Klammern dem umzuwandelnden Datum voran. Es wird hierbei nicht def<strong>in</strong>iert,<br />
welche Art der Umwandlung stattf<strong>in</strong>det. E<strong>in</strong> C-Style Cast wird so<br />
lange vom Compiler ohne Kommentar akzeptiert, wie er durch irgende<strong>in</strong>e beliebige<br />
Komb<strong>in</strong>ation von static_cast, re<strong>in</strong>terpret_cast und const_cast<br />
durchführbar ist.<br />
Man sieht bei dieser Def<strong>in</strong>ition gleich die Gefahr: E<strong>in</strong> Entwickler kann<br />
nicht steuern, welche Art von Cast nun vom Compiler e<strong>in</strong>gesetzt wird.<br />
Es kann also passieren, dass aufgrund e<strong>in</strong>er Fehlannahme e<strong>in</strong>es Entwicklers<br />
über die Natur e<strong>in</strong>es Datentyps der Compiler gezwungen ist, e<strong>in</strong>en<br />
re<strong>in</strong>terpret_cast e<strong>in</strong>zusetzen, obwohl dies gar nicht beabsichtigt war. In<br />
diesem Fall hat man dann e<strong>in</strong> Programm erzeugt, das vielleicht gar nicht<br />
mehr wirklich portabel ist, obwohl man nach bestem Wissen und Gewissen<br />
glaubt, sauberen Code geschrieben zu haben!<br />
Ich möchte also hier nur kurz zeigen, wie e<strong>in</strong> C-Style Cast formuliert<br />
wird, jedoch nicht ohne die Warnung, dass man die F<strong>in</strong>ger davon lassen<br />
sollte (c_style_cast_demo.cpp):<br />
1 // c s t y l e c a s t d e m o . cpp − demo f o r the use o f C−s t y l e c a s t s<br />
2<br />
3 #<strong>in</strong>clude <br />
4 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
5<br />
6 us<strong>in</strong>g std : : cout ;<br />
7 us<strong>in</strong>g std : : endl ;<br />
8<br />
9 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
10 {<br />
11 unsigned char a c h a r a c t e r = ’ x ’ ;<br />
12<br />
13 // the c−s t y l e cast variant<br />
14 cout
72 3. Operatoren<br />
Wieso eigentlich kommt man im oberen Programm durch e<strong>in</strong>en e<strong>in</strong>fachen<br />
Cast plötzlich von e<strong>in</strong>em dargestellten Zeichen zu e<strong>in</strong>er dargestellten Zahl?<br />
Ne<strong>in</strong>, völlig falsch... es ist weder weiße noch schwarze Magie im Spiel und ich<br />
habe den Output auch nicht nachträglich im Buch verändert. Bei der kurzen<br />
Beschreibung von cout <strong>in</strong> Abschnitt 2.3 habe ich bereits ganz kurz erklärt,<br />
dass es im Pr<strong>in</strong>zip egal ist, was man ihm übergibt, denn es wird immer die<br />
Repräsentation gewählt, die e<strong>in</strong>em Datentyp entspricht. Bei e<strong>in</strong>em char ist<br />
die default Repräsentation eben, dass er als Zeichen ausgegeben wird, also<br />
hier als x. Bei e<strong>in</strong>em <strong>in</strong>t wird per Default die Darstellung als Zahl gewählt.<br />
Genau das haben wir uns hier zunutze gemacht: Der Cast auf <strong>in</strong>t32 bewirkt,<br />
dass cout als Datentyp eben e<strong>in</strong>en <strong>in</strong>t32 vorgesetzt bekommt, den es<br />
natürlich als Zahl darstellen will. Dass die dargestellte Zahl dem Character-<br />
Code von x auf der jeweiligen Zielplattform entspricht, versteht sich von<br />
selbst.<br />
Vorsicht Falle: Neben dem bereits erwähnten Problem, dass man die Art<br />
der Umwandlung nicht mehr <strong>in</strong> der Hand hat, gibt es e<strong>in</strong>en zweiten Grund,<br />
warum man e<strong>in</strong>en C-Style Cast nicht verwenden sollte: Se<strong>in</strong>e Auff<strong>in</strong>dbarkeit<br />
(oder besser nicht-Auff<strong>in</strong>dbarkeit) im Code bei nachträglichen Änderungen.<br />
Es ist e<strong>in</strong> Leichtes, automatisch nach bestimmten Vorkommen von<br />
static_cast und den anderen speziellen Operatoren zu suchen. Es ist unendlich<br />
mühsam und gleichzeitig fehlerträchtig, nach dem Vorkommen zweier<br />
runder Klammern mit e<strong>in</strong>em Datentyp dazwischen zu suchen, denn hier kann<br />
man <strong>in</strong> vielen Fällen nichts mehr automatisieren.<br />
Das bedeutet, dass die E<strong>in</strong>stellung vieler Entwickler, e<strong>in</strong>fach e<strong>in</strong>mal e<strong>in</strong>en<br />
C-Style Cast zu verwenden, weil er schneller geschrieben ist und diesen<br />
nachträglich gegen die “richtigen” Casts auszutauschen, gar nicht so toll ist.<br />
Durch die schlechte Automatisierbarkeit dieses Vorgangs bleiben im Regelfall<br />
dann die C-Style Casts e<strong>in</strong>fach im Code stehen und das ist garantiert nicht<br />
das, was man erreichen will!
4. Kontrollstrukturen<br />
Als Kontrollstrukturen bezeichnet man Konstrukte, die uns helfen, den Programmablauf<br />
zu bee<strong>in</strong>flussen, anstatt immer nur sequenziell von oben nach<br />
unten Zeile für Zeile auszuführen. Wie zu erwarten hat C ++ alle Kontrollstrukturen<br />
(z.B. if...else, while, etc.) von C übernommen. Neu h<strong>in</strong>zugekommen<br />
ist <strong>in</strong> C ++ e<strong>in</strong>zig das Exception-Handl<strong>in</strong>g, allerd<strong>in</strong>gs wird dieses<br />
erst im OO-Teil des Buchs genauer behandelt. Jedoch möchte ich trotzdem<br />
allen Lesern, die bereits C beherrschen, empfehlen, dieses Kapitel zum<strong>in</strong>dest<br />
zu überfliegen, denn es gibt <strong>in</strong> C ++ bei e<strong>in</strong>zelnen Kontrollstrukturen e<strong>in</strong>ige<br />
kle<strong>in</strong>e Fe<strong>in</strong>heiten, die <strong>in</strong> C nicht vorkommen. Vor allem geht es hierbei um<br />
Aspekte zur Def<strong>in</strong>ition von Variablen, die ja <strong>in</strong> C ++ überall im laufenden<br />
Code vorkommen können.<br />
E<strong>in</strong>e sehr genaue Abhandlung über Kontrollstrukturen <strong>in</strong> C f<strong>in</strong>det sich <strong>in</strong><br />
Kapitel 7 von <strong>Softwareentwicklung</strong> <strong>in</strong> C. Allen Lesern, die <strong>in</strong> C noch nicht<br />
ganz sattelfest s<strong>in</strong>d, möchte ich dieses Kapitel dr<strong>in</strong>gend empfehlen, da die<br />
Basics zu den Kontrollstrukturen hier nur sehr gestrafft abgehandelt werden.<br />
Ganz kurz zur Auffrischung möchte ich zwei Def<strong>in</strong>itionen wiederholen, die<br />
nachfolgend gebraucht werden:<br />
• E<strong>in</strong> atomares Statement <strong>in</strong> C ++ besteht aus e<strong>in</strong>er oder mehreren Expressions,<br />
gefolgt von e<strong>in</strong>em Strichpunkt. Oft wird auch der Begriff Programmzeile<br />
dafür verwendet.<br />
• Es gibt die Möglichkeit, mehrere atomare Statements zu e<strong>in</strong>em zusammengesetzten<br />
Statement oder auch Block zusammenzufassen. E<strong>in</strong>e Zusammenfassung<br />
zu e<strong>in</strong>em Block erreicht man, <strong>in</strong>dem man die zusammengehörigen<br />
e<strong>in</strong>zelnen Statements (atomare oder selbst bereits zusammengesetzte)<br />
durch geschwungene Klammern e<strong>in</strong>fasst.<br />
Wo auch immer <strong>in</strong> der Folge der Begriff Statement verwendet wird, kann<br />
sowohl e<strong>in</strong> e<strong>in</strong>zelnes Statement als auch e<strong>in</strong> Block stehen.
74 4. Kontrollstrukturen<br />
4.1 Selection Statements<br />
Unter dem Oberbegriff Selection Statements s<strong>in</strong>d die Statements zusammengefasst,<br />
die, je nach Ergebnis der Auswertung e<strong>in</strong>er Bed<strong>in</strong>gung, zu e<strong>in</strong>em<br />
bestimmten Programmteil verzweigen, also e<strong>in</strong>e Auswahl treffen.<br />
Wie zu erwarten treffen wir hier unsere alten Bekannten aus C, nämlich<br />
if...else und switch. Das if...else Konstrukt sieht formal folgendermaßen<br />
aus:<br />
if (condition)<br />
statement1<br />
else<br />
statement2<br />
Dazu zu sagen wäre noch, dass der else-Zweig optional ist, also weggelassen<br />
werden kann, wenn er nicht gebraucht wird. E<strong>in</strong> kle<strong>in</strong>es Beispiel möchte<br />
ich hier auch noch zeigen, das pr<strong>in</strong>zipiell e<strong>in</strong>mal die Verwendung von if<br />
demonstriert (if_demo.cpp):<br />
1 // if demo . cpp − t<strong>in</strong>y program that shows the use o f i f <strong>in</strong> <strong>C++</strong><br />
2<br />
3 #<strong>in</strong>clude <br />
4 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
5<br />
6 us<strong>in</strong>g std : : cout ;<br />
7 us<strong>in</strong>g std : : endl ;<br />
8<br />
9 const i nt8 DESIRED INT8 BYTES = 1;<br />
10 const i nt8 DESIRED UINT8 BYTES = 1;<br />
11 const i nt8 DESIRED INT16 BYTES = 2;<br />
12 const i nt8 DESIRED UINT16 BYTES = 2;<br />
13 const i nt8 DESIRED INT32 BYTES = 4;<br />
14 const i nt8 DESIRED UINT32 BYTES = 4;<br />
15 const i nt8 DESIRED INT64 BYTES = 8;<br />
16 const i nt8 DESIRED UINT64 BYTES = 8;<br />
17<br />
18 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
19 {<br />
20 i f ( ( sizeof ( i nt8 ) == DESIRED INT8 BYTES) &&<br />
21 ( sizeof ( u<strong>in</strong>t8 ) == DESIRED UINT8 BYTES) &&<br />
22 ( sizeof ( <strong>in</strong>t16 ) == DESIRED INT16 BYTES) &&<br />
23 ( sizeof ( u<strong>in</strong>t16 ) == DESIRED UINT16 BYTES) &&<br />
24 ( sizeof ( <strong>in</strong>t32 ) == DESIRED INT32 BYTES) &&<br />
25 ( sizeof ( u<strong>in</strong>t32 ) == DESIRED UINT32 BYTES) &&<br />
26 ( sizeof ( <strong>in</strong>t64 ) == DESIRED INT64 BYTES) &&<br />
27 ( sizeof ( u<strong>in</strong>t64 ) == DESIRED UINT64 BYTES) )<br />
28 {<br />
29 cout
4.1 Selection Statements 75<br />
41 // For the r e s t of the types analogous statements<br />
42 // have to be i n s e r t e d here . I leave t h i s up to you . . .<br />
43<br />
44 return (−1);<br />
45 }<br />
Ich denke, das Programm braucht wirklich ke<strong>in</strong>e große Erklärung, deshalb<br />
möchte ich an dieser Stelle nur e<strong>in</strong>en kle<strong>in</strong>en Tipp anbr<strong>in</strong>gen, der die Lesbarkeit<br />
von Code betrifft: In den Zeilen 28–31 sieht man, dass das Programm<br />
e<strong>in</strong>e alles ok Meldung ausgibt und durch Aufruf von return <strong>in</strong> Zeile 30<br />
term<strong>in</strong>iert, <strong>in</strong>dem es die ma<strong>in</strong>-Funktion verlässt. Das bedeutet, dass das Programm<br />
<strong>in</strong> diesem Fall sicherlich niemals Zeile 32 erreichen kann. Aus diesem<br />
Grund ist hier auch ke<strong>in</strong> else notwendig, obwohl die Zeilen von 32 weg bis<br />
zum Ende des Programms quasi e<strong>in</strong>en logischen else-Zweig darstellen. Diese<br />
Vorgangsweise ist sehr anzuraten, denn ansonsten gibt es Fälle, <strong>in</strong> denen<br />
durch ungeschickte if...else-Konstrukte e<strong>in</strong>e unübersichtliche Schachtelungshierarchie<br />
von Blöcken entsteht, was die Wartbarkeit und Änderbarkeit<br />
von Code deutlich verr<strong>in</strong>gert.<br />
Es gibt auch e<strong>in</strong>en besonderen Operator, der unter dem Namen conditional<br />
Expression bekannt ist und der im Pr<strong>in</strong>zip e<strong>in</strong>er sehr kurzen und prägnanten<br />
Form von if...else entspricht. Die Rede ist von<br />
condition ? true-expr : false-expr<br />
Hierbei stellt condition e<strong>in</strong>e Bed<strong>in</strong>gung dar, die evaluiert wird. Für den Fall,<br />
dass sie zu true evaluiert, wird mit der Auswertung der true-expr fortgefahren,<br />
ansonsten mit der false-expr. Sehr oft f<strong>in</strong>det man e<strong>in</strong>e solche conditional<br />
expression bei bed<strong>in</strong>gten Zuweisungen. Das Statement<br />
<strong>in</strong>t number = orig > MAX ? MAX : orig;<br />
weist z.B. der Variablen number den Wert MAX zu, sofern orig größer ist als<br />
dieser, ansonsten wird orig zugewiesen. Für Neul<strong>in</strong>ge ist diese Schreibweise<br />
zwar eher ungewohnt, aber ich möchte im S<strong>in</strong>ne von klarem und e<strong>in</strong>fach lesbarem<br />
Code allen Lesern empfehlen, sich mit diesem Statement anzufreunden<br />
und es auch wirklich zu verwenden.<br />
Hat man viele Alternativen, aus denen man aussuchen will, so kann<br />
es schnell passieren, dass e<strong>in</strong>e Reihe von (vielleicht sogar geschachtelten)<br />
if...else Statements ziemlich unübersichtlich wird. Aus diesem Grund<br />
gibt es das switch Konstrukt, das sich formal folgendermaßen präsentiert:<br />
switch(condition)<br />
{<br />
case const1 :<br />
statement1<br />
case const2 :<br />
statement2<br />
...<br />
default:<br />
statement-xx<br />
}
76 4. Kontrollstrukturen<br />
Ich habe hier bewusst nicht die zwar völlig korrekte, aber für Erklärungszwecke<br />
ebenso völlig s<strong>in</strong>nlose Schreibweise<br />
switch(condition)<br />
statement<br />
verwendet, denn darunter kann man sich nun wirklich nicht vorstellen, was<br />
switch eigentlich tut :-).<br />
Auch für die kurze Erklärung von switch schreiten wir am besten gleich<br />
zum Beispiel (switch_demo.cpp):<br />
1 // switch demo . cpp − t<strong>in</strong>y program that shows the use o f i f <strong>in</strong> <strong>C++</strong><br />
2<br />
3 #<strong>in</strong>clude <br />
4 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
5<br />
6 us<strong>in</strong>g std : : cout ;<br />
7 us<strong>in</strong>g std : : endl ;<br />
8<br />
9 const char SHOW = ’ s ’ ;<br />
10 const char QUIT = ’ q ’ ;<br />
11 const char EXIT = ’ x ’ ;<br />
12<br />
13 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
14 {<br />
15 char j u s t a c h a r = ’ q ’ ;<br />
16<br />
17 switch ( j u s t a c h a r )<br />
18 {<br />
19 case SHOW:<br />
20 cout
4.1 Selection Statements 77<br />
• Der default-Label steht speziell für alles Andere, was nicht direkt abgedeckt<br />
wurde.<br />
Vorsicht Falle: Oft versuchen ungeübte C ++ Entwickler, e<strong>in</strong>e Variable irgendwo<br />
mitten <strong>in</strong> der Reihe der case-Labels zu def<strong>in</strong>ieren und auch gleich<br />
explizit zu <strong>in</strong>itialisieren, weil C ++ die Def<strong>in</strong>ition von Variablen ja “überall”<br />
gestattet. Dies ist allerd<strong>in</strong>gs der direkte Weg zu e<strong>in</strong>em Compilerfehler, wie<br />
man sich leicht vorstellen kann:<br />
Die Def<strong>in</strong>ition ist dabei nicht das wirkliche Problem, denn der Compiler<br />
könnte hierbei e<strong>in</strong>fach den notwendigen zusätzlichen Speicher gleich zu<br />
Beg<strong>in</strong>n des Blocks reservieren. Das g<strong>in</strong>ge also noch eventuell ohne Fehlermeldung<br />
ab.<br />
Die explizite Initialisierung bei der Def<strong>in</strong>ition ist das Problem! Das würde<br />
nämlich bedeuten, dass der Code für die Initialisierung auch im Pr<strong>in</strong>zip übersprungen<br />
werden könnte, je nachdem, bei welchem case-Label der E<strong>in</strong>sprung<br />
erfolgt. Jetzt kann man natürlich behaupten, dass ja der Code für die Initialisierung<br />
auch zusammen mit dem Code für die Speicherreservierung vom<br />
Compiler nach oben gerückt werden könnte. Das geht aber leider nicht, denn<br />
wer sagt denn, dass zu Beg<strong>in</strong>n des Blocks schon alles bekannt ist, was für<br />
die Initialisierung gebraucht wird? Es könnte ja später noch etwas berechnet<br />
werden, was die Initialisierung verändert, weil Initialisierungswerte <strong>in</strong> C ++<br />
nicht notwendigerweise Konstanten se<strong>in</strong> müssen!<br />
Will man also e<strong>in</strong>e Variable irgendwo <strong>in</strong>nerhalb des Blocks def<strong>in</strong>ieren,<br />
der die case-Labels enthält, dann bleibt nichts anderes übrig, als entweder<br />
e<strong>in</strong>en eigenen Sub-Block zu schreiben, oder auf die explizite Initialisierung<br />
zu verzichten und stattdessen e<strong>in</strong>e normale Wertzuweisung vorzunehmen.<br />
E<strong>in</strong>en weiteren wichtigen Punkt zum Thema der Def<strong>in</strong>ition von Variablen<br />
gibt es, der noch nicht von C her bekannt ist: Es ist möglich, <strong>in</strong>nerhalb<br />
e<strong>in</strong>er condition e<strong>in</strong>e Variable zu def<strong>in</strong>ieren! Scope und Lifetime dieser Variable<br />
erstrecken sich dann auf den gesamten vom entsprechenden Konstrukt<br />
verwalteten Block. Z.B. im Fall e<strong>in</strong>es if ohne zugehöriges else auf den if-<br />
Zweig, im Fall e<strong>in</strong>es if...else auf if- und else-Zweig. Der Vorteil dieser<br />
Möglichkeit liegt auf der Hand: Scope und Lifetime von Variablen s<strong>in</strong>d, wo<br />
notwendig, auf ihren m<strong>in</strong>imalen Verwendungsbereich festgelegt, anstatt auf<br />
den gesamten umschließenden Block.<br />
Betrachten wir am besten am Beispiel, wie wir solche Variablendef<strong>in</strong>itionen<br />
<strong>in</strong>nerhalb von Conditions sehr s<strong>in</strong>nvoll e<strong>in</strong>setzen können<br />
(def<strong>in</strong>ition_<strong>in</strong>_condition_demo.cpp):<br />
1 // d e f i n i t i o n i n c o n d i t i o n d e m o . cpp − small program to show where<br />
2 // a d e f i n i t i o n i n s i d e a condition makes sense<br />
3<br />
4 #<strong>in</strong>clude <br />
5 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
6<br />
7 us<strong>in</strong>g std : : cout ;
78 4. Kontrollstrukturen<br />
8 us<strong>in</strong>g std : : endl ;<br />
9<br />
10 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
11 {<br />
12 const <strong>in</strong>t32 MODULUS = 3;<br />
13<br />
14 <strong>in</strong>t32 value = 8;<br />
15<br />
16 i f ( <strong>in</strong>t32 d i v r e s t = value % MODULUS)<br />
17 cout
4.2 Schleifen 79<br />
Vorsicht Falle: E<strong>in</strong> weiteres Problem wird durch Missverständnisse und/oder<br />
Schlampigkeiten bei der Implementation gewisser Compiler hervorgerufen:<br />
Manche Compiler beschränken den Scope e<strong>in</strong>er Variable, die <strong>in</strong><br />
der condition def<strong>in</strong>iert wird, fälschlicherweise nicht auf das vom Konstrukt<br />
verwaltete Statement, sondern auf den umschließenden Block! Dass damit<br />
der S<strong>in</strong>n dieser Möglichkeit ad absurdum geführt wird, versteht sich<br />
von selbst. Ob der verwendete Compiler e<strong>in</strong>er dieser nicht spezifikationsgemäß<br />
arbeitenden ist, läßt sich leicht mit folgendem Programm überprüfen<br />
(def<strong>in</strong>ition_compiler_test.cpp):<br />
1 // d e f i n i t i o n c o m p i l e r t e s t . cpp − small t e s t program to f i n d out<br />
2 // i f a compiler works c o r r e c t l y<br />
3<br />
4 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
5 {<br />
6 i f ( char t e s t v a r = ’ x ’ )<br />
7 ;<br />
8 char t e s t v a r = ’ y ’ ;<br />
9 return ( 0 ) ;<br />
10 }<br />
Abgesehen von Warn<strong>in</strong>gs wegen unbenutzter Variablen darf der Compiler<br />
ke<strong>in</strong>en Fehler erzeugen, wenn er korrekt arbeitet. Bemängelt der Compiler<br />
bei der Übersetzung e<strong>in</strong>e doppelte Def<strong>in</strong>ition von test_var, so hat man es<br />
mit e<strong>in</strong>em der falsch implementierten Sorte zu tun (und sollte sich eventuell<br />
den Umstieg auf e<strong>in</strong>en anderen überlegen :-)).<br />
4.2 Schleifen<br />
Die altbekannten while, do...while und for Schleifen s<strong>in</strong>d auch <strong>in</strong> C ++<br />
selbstverständlich vorhanden. E<strong>in</strong>e while Schleife sieht formal folgendermaßen<br />
aus:<br />
while(condition)<br />
statement<br />
In e<strong>in</strong>er while Schleife wird vor jedem e<strong>in</strong>zelnen Durchlauf die condition<br />
überprüft. Evaluiert diese zu true, so wird statement genau e<strong>in</strong>mal ausgeführt,<br />
also der Schleifenrumpf genau e<strong>in</strong>mal durchlaufen. Danach f<strong>in</strong>det<br />
die nächste Überprüfung der condition und e<strong>in</strong> eventueller erneuter Durchlauf<br />
statt, etc., bis die condition zu false evaluiert. Sobald dies der Fall ist,<br />
wird statement nicht mehr ausgeführt. Stattdessen wird mit der Ausführung<br />
beim ihm nachfolgenden Code fortgefahren.<br />
Dies bedeutet, dass es passieren kann, dass statement nicht e<strong>in</strong> e<strong>in</strong>ziges<br />
Mal ausgeführt wird, nämlich dann, wenn condition bereits beim ersten Mal<br />
zu false evaluiert.
80 4. Kontrollstrukturen<br />
Im Gegensatz zu e<strong>in</strong>er while Schleife wird bei e<strong>in</strong>er do...while Schleife<br />
statement immer zum<strong>in</strong>dest e<strong>in</strong>mal ausgeführt, wie sich leicht an der folgenden<br />
formalen Def<strong>in</strong>ition erkennen lässt:<br />
do<br />
statement<br />
while(condition)<br />
Die Überprüfung von condition f<strong>in</strong>det erst nach dem Ausführen von statement<br />
statt. Evaluiert sie zu true, wird statement erneut ausgeführt, so lange,<br />
bis condition zu false evaluiert.<br />
Zu guter Letzt kommen wir noch zum mächtigsten Schleifenkonstrukt <strong>in</strong><br />
C ++, nämlich zu den for Schleifen. E<strong>in</strong> Blick auf deren formale Def<strong>in</strong>ition<br />
ergibt folgendes Bild:<br />
for(<strong>in</strong>it;condition;step-expr)<br />
statement<br />
Anstatt nur e<strong>in</strong>e condition zu überprüfen, macht e<strong>in</strong>e for Schleife gleich<br />
bedeutend mehr, wie man <strong>in</strong> der Def<strong>in</strong>ition des Schleifenkopfs sieht: Beim<br />
ersten Erreichen der Schleife wird das <strong>in</strong>it Statement ausgeführt. Danach<br />
wird die condition überprüft. Evaluiert diese zu true, dann wird statement<br />
ausgeführt, gefolgt von step-expr. Damit s<strong>in</strong>d wir am Ende e<strong>in</strong>es Durchlaufs<br />
angelangt und es wird mit der Überprüfung von condition fortgefahren. Die<br />
Schleife durchläuft <strong>in</strong> gewohnter Manier die Abfolge vom Überprüfen der<br />
condition bis zum Ausführen der step-expr so oft, bis condition e<strong>in</strong>mal zu<br />
false evaluiert.<br />
Noch e<strong>in</strong>e Besonderheit zeichnet die for Schleife aus: Die e<strong>in</strong>zelnen<br />
Ausdrücke <strong>in</strong>it, condition und step-expr können auch leer gelassen werden,<br />
was dann im Extremfall sogar zu e<strong>in</strong>er (hoffentlich gewollten) Endlosschleife<br />
führen kann: for(;;).<br />
Gerade for Schleifen bereiten C-Neul<strong>in</strong>gen, ob der großen Flexibilität <strong>in</strong><br />
ihrer Anwendung, manchmal gewisse Probleme. Leser, die sich selbst zu<br />
den C-Neul<strong>in</strong>gen zählen, sollten aus diesem Grund wirklich die sehr genaue<br />
Behandlung dieser Schleifen <strong>in</strong> <strong>Softwareentwicklung</strong> <strong>in</strong> C <strong>in</strong> Abschnitt 7.5<br />
nachschlagen.<br />
Die bereits angesprochene Möglichkeit der Def<strong>in</strong>ition von Variablen an<br />
ungewöhnlichen Stellen ist bei for Schleifen besonders <strong>in</strong>teressant: Man kann<br />
benötigte Laufvariablen e<strong>in</strong>fach im <strong>in</strong>it Statement im Schleifenkopf def<strong>in</strong>ieren<br />
und sie haben damit e<strong>in</strong>en Scope und e<strong>in</strong>e Lifetime, die genau auf das von<br />
for kontrollierte Statement begrenzt ist.<br />
E<strong>in</strong> kurzes Demo Programm zur Verwendung der möglichen Variablendef<strong>in</strong>itionen<br />
im Schleifenkopf soll die <strong>in</strong> C ++ übliche “saubere” Schreibweise<br />
z.B. e<strong>in</strong>er Zählschleife demonstrieren (for_demo.cpp):
1 // for demo . cpp − small f o r−loop demo program<br />
2<br />
3 #<strong>in</strong>clude <br />
4 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
5<br />
6 us<strong>in</strong>g std : : cout ;<br />
7 us<strong>in</strong>g std : : endl ;<br />
8<br />
9 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
10 {<br />
11 for ( <strong>in</strong>t16 count = 0 ; count < 3 ; count++)<br />
12 cout
82 4. Kontrollstrukturen<br />
23 cout
9 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
10 {<br />
11 <strong>in</strong>t32 s p a g h e t t i c o u n t e r = 0;<br />
12<br />
13 nirvana :<br />
14 cout
5. Funktionen<br />
Pr<strong>in</strong>zipiell ist e<strong>in</strong>e Funktion e<strong>in</strong>e Zusammenfassung mehrerer Anweisungen zu<br />
e<strong>in</strong>em aufrufbaren Ganzen. Weiters nimmt e<strong>in</strong>e Funktion Parameter entgegen<br />
und liefert e<strong>in</strong>en return-Wert. Die Def<strong>in</strong>ition e<strong>in</strong>er Funktion sieht formal<br />
folgendermaßen aus:<br />
retval function-name(param-list)<br />
{<br />
block-of-code<br />
}<br />
Hierbei bezeichnet retval den return-Wert der Funktion, wobei bei der Def<strong>in</strong>ition<br />
hier e<strong>in</strong>fach e<strong>in</strong> Datentyp (z.B. <strong>in</strong>t) angegeben wird. Der (sprechende!!!)<br />
Name e<strong>in</strong>er Funktion wird durch function-name def<strong>in</strong>iert und dieser<br />
ist gefolgt von e<strong>in</strong>er Parameterliste, hier durch param-list gekennzeichnet, <strong>in</strong><br />
runden Klammern. Diese gesamte erste Zeile bezeichnet man auch als Funktionskopf<br />
. Der Code, der ausgeführt wird, wenn e<strong>in</strong>e Funktion aufgerufen<br />
wird, steht als Block (also durch geschwungene Klammern e<strong>in</strong>gefasst) <strong>in</strong> Anschluss<br />
an den Kopf. Diesen Block bezeichnet man auch als Funktionsrumpf .<br />
Die Parameterübergabe beim Aufruf von Funktionen erfolgt gleich wie <strong>in</strong><br />
C immer als call-by-value. Das bedeutet, dass immer zuerst e<strong>in</strong>e vollständige<br />
Evaluierung der Ausdrücke stattf<strong>in</strong>det, die als Parameter übergeben werden<br />
und dass das Ergebnis dieser Evaluierung als Wert an die Funktion übergeben<br />
wird. Sollte man z.B. e<strong>in</strong>e Variable als Parameter an e<strong>in</strong>e Funktion<br />
übergeben, so kann <strong>in</strong>nerhalb der Funktion problemlos der Wert des Parameters<br />
beliebig verändert werden, der Wert der Variable außerhalb ändert sich<br />
garantiert nicht!<br />
Das Liefern e<strong>in</strong>es return-Wertes erfolgt über e<strong>in</strong> explizites return Statement,<br />
das genau dort <strong>in</strong> der Funktion steht, wo der Rücksprung erfolgen<br />
soll. Es kann auch mehrere return Statements <strong>in</strong> e<strong>in</strong>er Funktion geben, die<br />
je nach Kontrollfluss des Programms (z.B. durch if...else) alternativ den<br />
Rücksprung veranlassen. Es wird garantiert, dass der Rücksprung aus e<strong>in</strong>er<br />
Funktion genau an der Stelle des return Statements stattf<strong>in</strong>det.<br />
Nicht so selten gibt es auch Fälle, dass Funktionen gar ke<strong>in</strong>en return-Wert<br />
liefern, weil sie eigentlich nur als Prozeduren gebraucht werden, die irgende<strong>in</strong>e<br />
Aufgabe (z.B. Bildschirmanzeige) erfüllen. Für diese Fälle gibt es den
86 5. Funktionen<br />
“Datentyp” void. Daher kommt auch der <strong>in</strong> C und C ++ geläufige Name<br />
e<strong>in</strong>er void-Funktion, womit e<strong>in</strong>e re<strong>in</strong>e Prozedur geme<strong>in</strong>t ist. Will man den<br />
Rücksprung aus e<strong>in</strong>er void Funktion veranlassen, so geschieht das mittels<br />
e<strong>in</strong>es “re<strong>in</strong>en” return Statements, also ohne Angabe e<strong>in</strong>es Return-Values.<br />
Im Gegensatz zu “echten” Funktionen, bei denen ja durch die Deklaration<br />
zw<strong>in</strong>gend vorgeschrieben ist, dass sie e<strong>in</strong>en Wert e<strong>in</strong>es gewissen Typs liefern,<br />
muss bei void Funktionen ke<strong>in</strong> explizites return Statement existieren. Sobald<br />
das Ende des Funktionsrumpfes erreicht ist, wird bei void Funktionen<br />
der automatische Rücksprung zum Aufrufer veranlasst.<br />
Der wichtigste Punkt bei Funktionen ist nicht, wie viele glauben, die technische<br />
Abhandlung, wie man sie def<strong>in</strong>iert und aufruft. Viel wichtiger ist die<br />
richtige und s<strong>in</strong>nvolle Verwendung von Funktionen, um Software sauber<br />
zu strukturieren und Abstraktionslevels zu schaffen. Um dies gut zu<br />
schaffen, braucht es e<strong>in</strong>iges an Erfahrung. Man muss das Pr<strong>in</strong>zip der schrittweisen<br />
Verfe<strong>in</strong>erung beim Herangehen an e<strong>in</strong>e Problemstellung erst e<strong>in</strong>mal<br />
wirklich ver<strong>in</strong>nerlicht haben, um <strong>in</strong> der Lage zu se<strong>in</strong>, Code sauber zu strukturieren.<br />
Beim Arbeiten mit Funktionen geht es immer darum, Geme<strong>in</strong>samkeiten<br />
zu f<strong>in</strong>den und diese auf s<strong>in</strong>nvoll parametrisierte Codestücke abzubilden.<br />
Lesern, die hier noch möglicherweise e<strong>in</strong> kle<strong>in</strong>eres oder größeres Defizit haben,<br />
möchte ich Kapitel 8 aus <strong>Softwareentwicklung</strong> <strong>in</strong> C zur E<strong>in</strong>stimmung<br />
auf das Kommende wärmstens empfehlen.<br />
Ich möchte mich hier absichtlich zum jetzigen Zeitpunkt nicht extrem detailliert<br />
über Abstraktions- und Struktur-Aspekte von Funktionen auslassen,<br />
denn wir werden im OO-Teil des Buchs erst die sogenannten Methoden kennen<br />
lernen. Obwohl diese auf den ersten Blick den hier besprochenen Funktionen<br />
täuschend ähnlich sehen, stellen sie doch h<strong>in</strong>ter den Kulissen durch ihre<br />
Kontextbezogenheit e<strong>in</strong> viel mächtigeres Konstrukt als “normale” Funktionen<br />
dar. Deshalb wird dieses Kapitel vor allem auf technische Aspekte von<br />
Funktionen e<strong>in</strong>gehen und nicht so sehr auf softwaretechnologische und z.T.<br />
philosophische Betrachtungen zu deren richtiger Anwendung. All das wird<br />
dann dafür umso exzessiver bei der Besprechung von Klassen, Objekten und<br />
Methoden nachgeholt :-).<br />
Allen Lesern, die geübt im Umgang mit C s<strong>in</strong>d und die jetzt denken, dass<br />
Funktionen ja sowieso e<strong>in</strong> alter Hut s<strong>in</strong>d, möchte ich e<strong>in</strong>dr<strong>in</strong>glich widersprechen!<br />
C ++ hat durch das Konzept des Overload<strong>in</strong>gs e<strong>in</strong>ige nette Features und<br />
auch Stolperste<strong>in</strong>e auf Lager, die aus C völlig unbekannt s<strong>in</strong>d!<br />
Wenden wir uns zuerst e<strong>in</strong>mal an e<strong>in</strong>em Beispiel den Features von Funktionen<br />
<strong>in</strong> C ++ zu, bevor wir <strong>in</strong> die Tiefen vordr<strong>in</strong>gen und dort mit e<strong>in</strong> paar<br />
netten Fallen konfrontiert werden (function_demo.cpp):<br />
1 // function demo . cpp − demonstration o f f u n c t i o n s <strong>in</strong> <strong>C++</strong><br />
2<br />
3 #<strong>in</strong>clude <br />
4 #<strong>in</strong>clude ” u s e r t y p e s . h”
5<br />
6 us<strong>in</strong>g std : : cout ;<br />
7 us<strong>in</strong>g std : : endl ;<br />
8<br />
9 // function d e c l a r a t i o n s<br />
10 <strong>in</strong>t32 square ( <strong>in</strong>t32 num) ;<br />
11 double square (double num) ;<br />
12 void show ( <strong>in</strong>t32 num) ;<br />
13 void show (double num) ;<br />
14<br />
5. Funktionen 87<br />
15 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
16 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
17 {<br />
18 <strong>in</strong>t32 a number = 4;<br />
19 double one more number = 4 . 0 ;<br />
20<br />
21 <strong>in</strong>t32 a square = square ( a number ) ;<br />
22 double one more square = square ( one more number ) ;<br />
23<br />
24 show ( a square ) ;<br />
25 show ( one more square ) ;<br />
26<br />
27 // i m p l i c i t conversions can a l s o take place :<br />
28 <strong>in</strong>t16 and one more = 3;<br />
29 show ( square ( and one more ) ) ;<br />
30 }<br />
31<br />
32 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
33 <strong>in</strong>t32 square ( <strong>in</strong>t32 num)<br />
34 {<br />
35 return (num ∗ num) ;<br />
36 }<br />
37<br />
38 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
39 double square (double num)<br />
40 {<br />
41 return (num ∗ num) ;<br />
42 }<br />
43<br />
44 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
45 void show ( <strong>in</strong>t32 num)<br />
46 {<br />
47 cout
88 5. Funktionen<br />
In den Zeilen 10–13 unseres Programms kann man erkennen, wie man Funktionen<br />
deklariert: Man schreibt die Signatur der Funktionen, gefolgt von<br />
e<strong>in</strong>em Strichpunkt. Dies ist e<strong>in</strong>fach e<strong>in</strong> H<strong>in</strong>weis an den Compiler, dass es<br />
e<strong>in</strong>e Funktion unter e<strong>in</strong>em bestimmten Namen, mit e<strong>in</strong>em bestimmten Parametersatz<br />
und mit e<strong>in</strong>em bestimmten return-Wert irgendwo gibt.<br />
Genau <strong>in</strong> diesen Deklarationszeilen fällt auch schon auf, dass es <strong>in</strong> C ++<br />
mit Funktionen etwas auf sich hat, was es <strong>in</strong> C e<strong>in</strong>fach nicht gibt: In den<br />
Zeilen 10 und 11, sowie <strong>in</strong> den Zeilen 12 und 13 s<strong>in</strong>d jeweils zwei Funktionen<br />
deklariert, die denselben Namen haben! Sie unterscheiden sich nur durch<br />
ihre Parameter und durch den return-Wert. Genau diese Möglichkeit wird als<br />
Overload<strong>in</strong>g bezeichnet. Die genauen Regeln für Overload<strong>in</strong>g von Funktionen<br />
s<strong>in</strong>d Folgende:<br />
• Overload<strong>in</strong>g von Funktionen bedeutet, dass es mehrere Funktionen mit<br />
demselben Namen geben kann.<br />
• Die e<strong>in</strong>zelnen Funktionen, die an e<strong>in</strong>em Overload<strong>in</strong>g beteiligt s<strong>in</strong>d, müssen<br />
sich zum<strong>in</strong>dest <strong>in</strong> e<strong>in</strong>em Parameter vone<strong>in</strong>ander unterscheiden.<br />
• Es ist nicht von Belang, ob sich die Funktionen <strong>in</strong> der Anzahl der Parameter<br />
oder <strong>in</strong> den Typen oder <strong>in</strong> beiden Punkten unterscheiden.<br />
• E<strong>in</strong> Overload<strong>in</strong>g von Funktionen, die denselben Namen und denselben Parametersatz<br />
besitzen und sich ausschließlich im return-Wert vone<strong>in</strong>ander<br />
unterscheiden, ist nicht möglich.<br />
Um Overload<strong>in</strong>g überhaupt erst möglich zu machen, laufen schematisch gesehen<br />
compiler<strong>in</strong>tern folgende Prozesse ab:<br />
• Bei der Deklaration von Funktionen (explizit oder implizit bei der Def<strong>in</strong>ition)<br />
wird das sogenannte Name-Mangl<strong>in</strong>g durchgeführt. Das bedeutet,<br />
dass der Name jeder Funktion e<strong>in</strong>fach <strong>in</strong>tern verlängert wird, <strong>in</strong>dem er um<br />
den Parametersatz und um den return-Wert erweitert wird. Hierbei s<strong>in</strong>d<br />
allerd<strong>in</strong>gs nicht die Namen der Parameter, sondern nur deren Typen <strong>in</strong>teressant.<br />
Wie nun genau e<strong>in</strong>e Funktion <strong>in</strong>tern nach dem Name-Mangl<strong>in</strong>g aussieht<br />
ist compilerabhängig! Um sich leichter etwas vorstellen zu können,<br />
nehmen wir e<strong>in</strong> mögliches Beispiel, wie e<strong>in</strong> Compiler das Name-Mangl<strong>in</strong>g<br />
durchführen könnte. E<strong>in</strong>e Funktion<br />
<strong>in</strong>t myFunction(<strong>in</strong>t count,double value)<br />
könnte durch das Name-Mangl<strong>in</strong>g <strong>in</strong>tern unter dem Namen<br />
myFunctionid_i<br />
bekannt se<strong>in</strong>. Der Name ergibt sich e<strong>in</strong>fach daraus, dass an den Funktionsnamen<br />
der Reihe nach die <strong>in</strong>ternen Bezeichner der e<strong>in</strong>zelnen Datentypen<br />
angehängt werden, gefolgt von e<strong>in</strong>em Underl<strong>in</strong>e und dem <strong>in</strong>ternen Bezeichner<br />
für den Typ des return-Wertes. Dies bedeutet nun, dass der Compiler<br />
tatsächlich <strong>in</strong>tern die Namensgleichheiten aufhebt und lauter Funktionen<br />
mit verschiedenen Namen erzeugt. Dies ist auch notwendig, denn ansonsten<br />
könnte der L<strong>in</strong>ker se<strong>in</strong>e Arbeit nicht mehr verrichten. Dieser weiß ja
5. Funktionen 89<br />
vom Overload<strong>in</strong>g <strong>in</strong> Wirklichkeit gar nichts. Ihm wird nur noch Object-<br />
Code mit symbolischen Namen zur endgültigen Adressauflösung gegeben.<br />
Deshalb muss der Compiler dafür sorgen, dass im Object-Code garantiert<br />
ke<strong>in</strong>e Namensgleichheiten mehr vorkommen.<br />
• Wenn e<strong>in</strong>e Funktion aufgerufen wird, dann analysiert der Compiler die<br />
Aufrufparameter und sucht aus den ihm bekannten Funktionen des entsprechenden<br />
Namens diejenige, die “am besten” zum Parametersatz des<br />
Aufrufs passt. Zum Beispiel wird <strong>in</strong> unserem Demoprogramm <strong>in</strong> Zeile 21<br />
die Funktion square mit e<strong>in</strong>em <strong>in</strong>t32 als Parameter aufgerufen. Dieser<br />
Aufruf passt am besten zur Variante von square, die <strong>in</strong> Zeile 10 deklariert<br />
und ab Zeile 33 def<strong>in</strong>iert ist. Daher wird genau dieser Funktionsaufruf<br />
e<strong>in</strong>gesetzt.<br />
• Dass nur der Parametersatz zur Unterscheidung herangezogen wird und<br />
nicht auch der return-Wert (worüber übrigens schon des Öfteren diskutiert<br />
wurde), ist auch leicht e<strong>in</strong>zusehen. Im laufenden Code ist nämlich<br />
nicht wirklich immer ersichtlich, welcher return-Wert nun erwartet wird.<br />
Es ist ja z.B. auch möglich, dass jemand e<strong>in</strong>fach <strong>in</strong> unserem Programm<br />
square aufruft, aber das Ergebnis ke<strong>in</strong>eswegs verwendet, weder durch e<strong>in</strong>e<br />
Zuweisung, noch <strong>in</strong> e<strong>in</strong>er Berechnung. Und woher soll der Compiler dann<br />
se<strong>in</strong>e Information bekommen, welche der overloaded Funktionen er jetzt<br />
e<strong>in</strong>setzen soll?<br />
Neben dem Overload<strong>in</strong>g gibt es bei Funktionen <strong>in</strong> C ++ noch e<strong>in</strong> sehr angenehmes<br />
Feature, das Entwickler dabei unterstützt, sauberen Code zu schreiben:<br />
Funktionen können sogenannte default Parameter besitzen. Am Beispiel sieht<br />
das folgendermaßen aus (default_param_demo.cpp):<br />
1 // default param demo . cpp − demo o f d e f a u l t parameters<br />
2<br />
3 #<strong>in</strong>clude <br />
4 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
5<br />
6 us<strong>in</strong>g std : : cout ;<br />
7 us<strong>in</strong>g std : : endl ;<br />
8<br />
9 // function d e c l a r a t i o n s<br />
10 void show ( <strong>in</strong>t32 num, bool show prefix = f a l s e ) ;<br />
11<br />
12 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
13 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
14 {<br />
15 show ( 1 7 ) ;<br />
16 show ( 1 7 , false ) ;<br />
17 show ( 1 7 , true ) ;<br />
18 }<br />
19<br />
20 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
21 void show ( <strong>in</strong>t32 num, bool show prefix )<br />
22 {<br />
23 i f ( show prefix )<br />
24 cout
90 5. Funktionen<br />
Dieses Demoprogrämmchen erzeugt dann folgenden Output:<br />
17<br />
17<br />
the value i s : 1 7<br />
In Zeile 10 sieht man, wie man e<strong>in</strong>e Funktion mit default Parametern deklariert:<br />
Man stellt e<strong>in</strong>em Parameter ganz e<strong>in</strong>fach e<strong>in</strong> = gefolgt vom gewünschten<br />
default Wert nach. Dies liest sich quasi wie e<strong>in</strong>e Zuweisung. Wirft man e<strong>in</strong>en<br />
näheren Blick auf Zeile 15, dann sieht man auch, was es mit den default<br />
Parametern auf sich hat. Es wird nämlich <strong>in</strong> Zeile 15 die Funktion show<br />
nur mit e<strong>in</strong>em Parameter aufgerufen, obwohl sie aber mit zwei Parametern<br />
deklariert (und def<strong>in</strong>iert) ist. Der Compiler setzt hier e<strong>in</strong>fach <strong>in</strong>tern für den<br />
zweiten Parameter den <strong>in</strong> der Deklaration angegebenen default Wert (hier<br />
false) e<strong>in</strong>. Zeile 16 zeigt e<strong>in</strong>en vollständig expliziten Aufruf, der, ebenso wie<br />
Zeile 17, ke<strong>in</strong>en Gebrauch vom default Parameter macht.<br />
Die folgenden Regeln gelten bei der Arbeit mit default Parametern:<br />
• Es kann beliebig viele default Parameter geben, diese müssen aber immer<br />
alle am Ende der Parameterliste stehen. Es ist natürlich auch problemlos<br />
möglich, alle Parameter e<strong>in</strong>er Funktion als default Parameter zu deklarieren.<br />
E<strong>in</strong>e Deklaration<br />
void func(<strong>in</strong>t par1, <strong>in</strong>t par2 = 0, <strong>in</strong>t par3 = 0, <strong>in</strong>t par4 = 0);<br />
ist also gültig, woh<strong>in</strong>gegen die Deklaration<br />
void func(<strong>in</strong>t par1, <strong>in</strong>t par2 = 0, <strong>in</strong>t par3, <strong>in</strong>t par4 = 0);<br />
zu e<strong>in</strong>em Compilerfehler führt, denn par2 ist e<strong>in</strong> default Parameter, der<br />
nachfolgende par3 allerd<strong>in</strong>gs nicht. Wie soll der Compiler also z.B. bei<br />
e<strong>in</strong>em Aufruf<br />
func(11,22,33);<br />
nun erraten können, ob 22 nun für par2 und 33 für par3 genommen werden<br />
soll, oder vielleicht doch eher der default Wert für par2, 22 für par3 und<br />
33 für par4?<br />
• Neben der eben erwähnten Regel, dass default Parameter allesamt am Ende<br />
der Liste stehen müssen, gibt es noch e<strong>in</strong>e zweite Regel, die Ambiguitäten<br />
bei der Auflösung verh<strong>in</strong>dert: Default Parameter werden von l<strong>in</strong>ks nach<br />
rechts der Reihe nach mit übergebenen Parameterwerten belegt, bis alle<br />
übergebenen Parameter aufgebraucht s<strong>in</strong>d. Haben wir z.B. folgende Funktion:<br />
void func(<strong>in</strong>t par1 = 0, <strong>in</strong>t par2 = 0, <strong>in</strong>t par3 = 0)<br />
dann führt e<strong>in</strong> Aufruf<br />
func(1,2);<br />
dazu, dass für par1 der Wert 1 und für par2 der Wert 2 übergeben wird.<br />
Für par3 wird der default Wert 0 aus der Deklaration e<strong>in</strong>gesetzt.<br />
Die Angabe e<strong>in</strong>es default Parameters wird nur <strong>in</strong> der Deklaration benötigt,<br />
nicht aber <strong>in</strong> der Def<strong>in</strong>ition. Der Compiler ist dafür verantwortlich, den
5. Funktionen 91<br />
korrekten Parameter beim Aufruf der Funktion e<strong>in</strong>zusetzen. Wenn man den<br />
default Parameter auch <strong>in</strong> der Def<strong>in</strong>ition angibt, dann führt dies bei vielen<br />
Compilern zu e<strong>in</strong>em Fehler.<br />
So viele Möglichkeiten übersichtlichen Code zu schreiben sich <strong>in</strong> C ++<br />
durch Overload<strong>in</strong>g und default Parameter ergeben, so viele Fallen tun sich<br />
auch auf, wenn man nicht ganz genau weiß, was man macht! Das folgende<br />
Programm demonstriert, wie man den Compiler so weit br<strong>in</strong>gen kann, dass er<br />
freiwillig ke<strong>in</strong>e Auflösung gewisser Funktionsaufrufe mehr vornimmt, sondern<br />
sich beschwert, dass er ja nicht Gedanken lesen kann um zu erraten, welchen<br />
Aufruf man nun beabsichtigt hat (overload<strong>in</strong>g_problems.cpp):<br />
1 // overload<strong>in</strong>g problems . cpp − demo o f problems with overload<strong>in</strong>g<br />
2<br />
3 #<strong>in</strong>clude <br />
4 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
5<br />
6 us<strong>in</strong>g std : : cout ;<br />
7 us<strong>in</strong>g std : : endl ;<br />
8<br />
9 // function d e c l a r a t i o n s<br />
10 void show ( <strong>in</strong>t32 num, bool show prefix = f a l s e ) ;<br />
11 void show ( float num, bool show prefix = f a l s e ) ;<br />
12 void show (double num) ;<br />
13<br />
14 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
15 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
16 {<br />
17 float f l o a t v a l = 1 2 . 5 ;<br />
18 <strong>in</strong>t32 i n t v a l = 20;<br />
19 <strong>in</strong>t16 a n o t h e r i n t v a l = 10;<br />
20 <strong>in</strong>t64 one more <strong>in</strong>t val = 10;<br />
21<br />
22 show ( i n t v a l ) ;<br />
23 show ( f l o a t v a l ) ;<br />
24 show ( a n o t h e r i n t v a l ) ;<br />
25 show ( 1 2 . 5 ) ;<br />
26<br />
27 show ( one more <strong>in</strong>t val ) ; // ambiguity problem !<br />
28 // problem solved by f o r c i n g a c e r t a i n i n t e r p r e t a t i o n<br />
29 show ( static cast(one more <strong>in</strong>t val ) ) ;<br />
30<br />
31 show ( 1 2 . 5 , false ) ; // ambiguity problem !<br />
32 // problem solved by f o r c i n g a c e r t a i n i n t e r p r e t a t i o n<br />
33 show ( static cast(12.5), f a l s e ) ;<br />
34 return ( 0 ) ;<br />
35 }<br />
36<br />
37 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
38 void show ( <strong>in</strong>t32 num, bool show prefix = f a l s e )<br />
39 {<br />
40 cout
92 5. Funktionen<br />
51 cout
5. Funktionen 93<br />
paar für den Exponenten reserviert, fehlen also bei der Mantisse und<br />
bewirken, je nach Zahl, e<strong>in</strong> Abschneiden des <strong>in</strong>t64 Wertes.<br />
Zum Glück kann man als Entwickler e<strong>in</strong>greifen und dem Compiler <strong>in</strong> se<strong>in</strong>er<br />
dunklen Stunde der Unentschlossenheit mitteilen, was man eigentlich<br />
will. Dies geschieht <strong>in</strong> Zeile 29, denn durch den expliziten Cast hat es der<br />
Compiler wieder mit e<strong>in</strong>em <strong>in</strong>t32 als Parameter zu tun. Was er damit<br />
anfangen soll, weiß er und setzt den Aufruf der <strong>in</strong> Zeile 10 deklarierten<br />
Funktion e<strong>in</strong>.<br />
Zeile 31: Auch diese Zeile treibt den Compiler zur Verzweiflung, denn er steht<br />
vor der Wahl, e<strong>in</strong>en double entweder zu e<strong>in</strong>em float oder zu e<strong>in</strong>em<br />
<strong>in</strong>t32 verkommen zu lassen, um e<strong>in</strong>e der Funktionen deklariert <strong>in</strong> den<br />
Zeilen 10 und 11 aufrufen zu können. Beides ist natürlich aus bekannten<br />
Gründen nicht problemlos, also beschwert sich der Compiler lieber und<br />
überlässt den Entwicklern die Entscheidung. Die Lösung des Problems<br />
ist, wie anzunehmen war, wieder e<strong>in</strong> expliziter Cast. In Zeile 33 sieht<br />
man den entsprechenden korrigierten Aufruf.<br />
Alle Aussagen, die ich hier über den Aufruf bestimmter Funktionen aus e<strong>in</strong>er<br />
Reihe von Varianten getroffen habe, lassen sich leicht verifizieren, <strong>in</strong>dem man<br />
die beiden problematischen Zeilen auskommentiert, die zum Ambiguitätsproblem<br />
führen und danach das Programm übersetzt. Der Output, der dann<br />
generiert wird, sieht folgendermaßen aus:<br />
show ( <strong>in</strong>t32 , bool ) c a l l e d<br />
20<br />
show ( f l o a t , bool ) c a l l e d<br />
12.5<br />
show ( <strong>in</strong>t32 , bool ) c a l l e d<br />
10<br />
show ( double ) c a l l e d<br />
12.5<br />
show ( <strong>in</strong>t32 , bool ) c a l l e d<br />
10<br />
show ( f l o a t , bool ) c a l l e d<br />
12.5<br />
Das Regelwerk, nach dem der Compiler versucht, den bestmöglichen Funktionsaufruf<br />
aus e<strong>in</strong>er Reihe von Alternativen zu f<strong>in</strong>den, ist relativ komplex.<br />
Jedoch ist es so ausgelegt, dass immer die Variante e<strong>in</strong>gesetzt wird, bei der<br />
ke<strong>in</strong>e gefährlichen Umwandlungen auftreten. Kann der Compiler ke<strong>in</strong>e solche<br />
Variante f<strong>in</strong>den und steht vor e<strong>in</strong>er Reihe von Alternativen, die alle nicht<br />
optimal s<strong>in</strong>d, so muss man als Entwickler durch e<strong>in</strong>en expliziten Cast e<strong>in</strong>greifen.<br />
Vorsicht Falle: Gerade C ++ Neul<strong>in</strong>ge neigen oft dazu, Overload<strong>in</strong>g zu<br />
übertreiben und für viele verschiedene Datentypen, die eigentlich kompatibel<br />
wären, eigene Funktionen zu schreiben, anstatt sich auf die Kompatibilität<br />
zu verlassen. Dies kann schnell zu Überraschungen führen, wenn man sich<br />
zu wenig Gedanken über die Auswirkungen macht. Deshalb möchte ich hier<br />
drei Tipps geben:
94 5. Funktionen<br />
• Das genaue Verstehen der Interna und der Zusammenhänge der e<strong>in</strong>zelnen<br />
Datentypen ist e<strong>in</strong>e der wichtigsten Voraussetzungen für e<strong>in</strong>e saubere<br />
Entwicklung! Viele Entwickler, nicht nur Neul<strong>in</strong>ge, haben hier e<strong>in</strong> großes<br />
Wissensdefizit, weil sie die Interna als “unnötigen Ballast” betrachten, den<br />
man “heutzutage sowieso nicht mehr braucht”. Diese E<strong>in</strong>stellung ist grundfalsch!<br />
• Niemals das Overload<strong>in</strong>g übertreiben! Im Fall unserer show Funktion wäre<br />
es klug gewesen, e<strong>in</strong>e Variante mit <strong>in</strong>t64 und e<strong>in</strong>e mit long double zu<br />
schreiben. Damit hat man für Gleitkomma und Integer Typen immer den<br />
mächtigsten Datentyp zur Verfügung und der Compiler hat ke<strong>in</strong>e Probleme<br />
mehr.<br />
• Das Mischen von Overload<strong>in</strong>g und default Parametern ist nach Möglichkeit<br />
zu vermeiden, denn es kann sehr leicht unbeabsichtigte Ambiguitäten und/oder<br />
unsauberen Code zur Folge haben. E<strong>in</strong> Beispiel für solchen schlechten<br />
Programmierstil ist auch <strong>in</strong> unserem Demoprogramm zu f<strong>in</strong>den, denn es<br />
gibt e<strong>in</strong>e show Funktion mit e<strong>in</strong>em double Parameter und e<strong>in</strong>e zweite mit<br />
e<strong>in</strong>em float und e<strong>in</strong>em zusätzlichen default Parameter. Stattdessen wäre<br />
e<strong>in</strong>e e<strong>in</strong>zige Funktion mit e<strong>in</strong>em long double und zusätzlichem default<br />
Parameter sehr viel besser gewesen!<br />
Vorsicht Falle: Ja, gleich noch e<strong>in</strong>e. Der Deklaration von Funktionen<br />
kommt durch das Overload<strong>in</strong>g <strong>in</strong> C ++ e<strong>in</strong>e unglaublich wichtige Rolle zu, weil<br />
erst dadurch der Compiler alle Alternativen kennt. Welche tollen Effekte<br />
das Vergessen e<strong>in</strong>er e<strong>in</strong>zigen Deklaration hervorrufen kann, sieht man an<br />
folgendem Programm (miss<strong>in</strong>g_decl_problem.cpp):<br />
1 // miss<strong>in</strong>g decl problem . cpp − demo o f a problem that a r i s e s when<br />
2 // a function d e c l a r a t i o n i s miss<strong>in</strong>g<br />
3<br />
4 #<strong>in</strong>clude <br />
5 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
6<br />
7 us<strong>in</strong>g std : : cout ;<br />
8 us<strong>in</strong>g std : : endl ;<br />
9<br />
10 // function d e c l a r a t i o n s<br />
11 void show ( <strong>in</strong>t16 num) ;<br />
12<br />
13 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
14 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
15 {<br />
16 show ( 1 2 3 4 5 6 7 . 5 ) ; // here the c a l l to show ( <strong>in</strong>t16 ) i s taken ! ! ! !<br />
17 return ( 0 ) ;<br />
18 }<br />
19<br />
20 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
21 void show ( <strong>in</strong>t16 num)<br />
22 {<br />
23 cout
27 void show (double num)<br />
28 {<br />
29 cout
96 5. Funktionen<br />
24 {<br />
25 cout
1 // i n l i n e f u n c t i o n d e m o . cpp − demo o f i n l i n e f u n c t i o n s<br />
2<br />
3 #<strong>in</strong>clude <br />
4 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
5<br />
6 us<strong>in</strong>g std : : cout ;<br />
7 us<strong>in</strong>g std : : endl ;<br />
8<br />
5. Funktionen 97<br />
9 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
10 <strong>in</strong>l<strong>in</strong>e double square (double num)<br />
11 {<br />
12 return (num ∗ num) ;<br />
13 }<br />
14<br />
15 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
16 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
17 {<br />
18 cout
98 5. Funktionen<br />
1 // i n l i n e f u n c d i f f i c u l t . cpp − demo f o r i n l i n e f u n c t i o n s that<br />
2 // are d i f f i c u l t f o r the compiler<br />
3<br />
4 #<strong>in</strong>clude <br />
5 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
6<br />
7 us<strong>in</strong>g std : : cout ;<br />
8 us<strong>in</strong>g std : : endl ;<br />
9<br />
10 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
11 <strong>in</strong>l<strong>in</strong>e u<strong>in</strong>t64 f a c t ( u<strong>in</strong>t64 num)<br />
12 {<br />
13 return (num < = 1 ? 1 : (num ∗ f a c t (num − 1 ) ) ) ;<br />
14 }<br />
15<br />
16 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
17 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
18 {<br />
19 cout
5. Funktionen 99<br />
E<strong>in</strong>satz. Dieser Umstand ist e<strong>in</strong>fach zu erkennen, wenn man bedenkt, dass<br />
auch <strong>in</strong>l<strong>in</strong>e Funktionen e<strong>in</strong>en e<strong>in</strong>deutigen Funktionspo<strong>in</strong>ter besitzen und<br />
z.B. static Variablen <strong>in</strong> ihnen vorkommen dürfen.<br />
Zur Verwendung von <strong>in</strong>l<strong>in</strong>e Funktionen möchte ich folgende Vorgehensweise<br />
anregen:<br />
• Es sollen ausschließlich Funktionen als <strong>in</strong>l<strong>in</strong>e deklariert werden, die wirklich<br />
außerordentlich oft im Code aufgerufen werden.<br />
• Zusätzlich zum oftmaligen Aufruf ist auch die ausführbare Codemenge von<br />
Funktionen e<strong>in</strong> Thema: Es sollen ausschließlich Funktionen als <strong>in</strong>l<strong>in</strong>e<br />
deklariert werden, die e<strong>in</strong>en sehr kurzen Funktionsrumpf besitzen. Bei<br />
“langen” Funktionen ist <strong>in</strong>l<strong>in</strong>e s<strong>in</strong>nlos.<br />
• Wenn e<strong>in</strong>e Funktion als <strong>in</strong>l<strong>in</strong>e deklariert wird, sollte man sofort anhand<br />
von Tests überprüfen, ob sich tatsächlich der gewünschte Laufzeitgew<strong>in</strong>n<br />
e<strong>in</strong>gestellt hat. Wenn nicht, dann sollte man die <strong>in</strong>l<strong>in</strong>e Deklaration wieder<br />
rückgängig machen, weil sie <strong>in</strong> diesem Fall nichts br<strong>in</strong>gt.<br />
E<strong>in</strong> kurzer Exkurs zum Thema Optimierung: Es ist mit <strong>in</strong>l<strong>in</strong>e Funktionen<br />
nun e<strong>in</strong>e Maßnahme bekannt, wie man e<strong>in</strong>e gewisse Laufzeitoptimierung<br />
erreichen kann. Jedoch muss ich unbed<strong>in</strong>gt noch e<strong>in</strong> paar allgeme<strong>in</strong>e<br />
Worte zum Thema Performancesteigerung verlieren, denn gerade <strong>in</strong> diesem<br />
Bereich wird unheimlich viel verbrochen, was jeglichen Regeln vernünftiger<br />
<strong>Softwareentwicklung</strong> widerspricht.<br />
Pr<strong>in</strong>zipiell gibt es zwei Kriterien, die Performance von Software zu beurteilen:<br />
Subjektive Performance: Bei der subjektiven Performance kommt es nur darauf<br />
an, wie schnell sich e<strong>in</strong> Programm anfühlt, nicht unbed<strong>in</strong>gt, wie<br />
schnell es tatsächlich ist. Wenn man z.B. bei e<strong>in</strong>em Programm lange<br />
warten muss, bis dann plötzlich e<strong>in</strong>e vollständige Darstellung von etwas<br />
Komplexem angezeigt wird, so wird dies als unangenehm langsam<br />
empfunden. Wenn allerd<strong>in</strong>gs dasselbe Programm Stück für Stück dieses<br />
komplexe Etwas aufbaut, sodass man den Aufbau laufend mitverfolgen<br />
kann, so wird dies als schneller empfunden. Es kann sogar vorkommen,<br />
dass die Gesamtzeit, die das Programm bis zur fertigen Anzeige braucht,<br />
beim subjektiv langsameren System kürzer ist. Objektiv gesehen also<br />
wäre das als langsam empfundene System eigentlich das schnellere.<br />
Objektive Performance: Bei der objektiven Performance kommt es auf re<strong>in</strong>e<br />
Fakten an, <strong>in</strong> unserem Fall eben Zeitmessungen. Die Geschw<strong>in</strong>digkeit der<br />
Abarbeitung e<strong>in</strong>er Aufgabe ist e<strong>in</strong> direktes Produkt der Effizienz e<strong>in</strong>er<br />
Lösung.<br />
Beide Aspekte, subjektive und objektive Performance summieren sich zum<br />
Gesamte<strong>in</strong>druck des Systems. Maßnahmen, die die subjektive Performance<br />
steigern, werden überall dort e<strong>in</strong>gesetzt, wo man ke<strong>in</strong>e echte, also objektive<br />
Steigerung mehr erreichen kann.
100 5. Funktionen<br />
Wenden wir uns aber jetzt der objektiven Performance zu, denn diese<br />
ist e<strong>in</strong>erseits wichtiger, andererseits schwerer zu beherrschen, wenn man<br />
nicht sauber arbeitet. Insbesondere wirken sich hier Designfehler extrem aus!<br />
Falsch gewählte Datenstrukturen, die nicht für das zu lösende Problem geeignet<br />
s<strong>in</strong>d, s<strong>in</strong>d die ultimativen Killer. Genauso verhält es sich mit verworrenen<br />
Programmen, die nur so vor Seiteneffekten strotzen. In diesen werden z.B.<br />
dieselben Abfragen immer wieder durchgeführt, weil man sich nie sicher se<strong>in</strong><br />
kann, ob sich seit der letzten Abfrage vielleicht nicht doch etwas “h<strong>in</strong>ten<br />
herum” geändert hat.<br />
Um jetzt nicht gleich e<strong>in</strong> ganzes Buch zum Thema Optimierung zu schreiben,<br />
will ich nur e<strong>in</strong> paar Aspekte anführen, die man im H<strong>in</strong>terkopf behalten<br />
sollte:<br />
• Der wichtigste Punkt bei der Optimierung ist, dass man niemals im Widerspruch<br />
zu den Grundregeln e<strong>in</strong>er sauberen, <strong>in</strong>tuitiven, wartbaren, erweiterbaren<br />
und modularen Softwarearchitektur stehen darf. Nur zu oft<br />
sieht man, dass Programme so lange “mit der Brechstange” optimiert werden,<br />
bis sich im Code niemand mehr auskennt. Bei der nächsten Änderung<br />
macht sich große Verzweiflung breit und vor lauter Seiteneffekten hat man<br />
das Resultat, das bereits zuvor angesprochen wurde: Die Software wird<br />
immer langsamer!<br />
• E<strong>in</strong> altbekanntes Faktum bei der Optimierung ist, dass Speicherbedarfsund<br />
Rechenzeitoptimierung nur allzu oft im Widerspruch zue<strong>in</strong>ander stehen.<br />
Es ist niemandem geholfen, wenn e<strong>in</strong> Programm e<strong>in</strong> wenig schneller<br />
wird, sich aber dafür als enormer Speicherfresser bemerkbar macht. Durch<br />
die Wahl geeigneter Datenstrukturen kann man allerd<strong>in</strong>gs e<strong>in</strong>e gute Balance<br />
herstellen.<br />
• Bevor man überhaupt den Code angreift, um etwas zu optimieren, ist<br />
es notwendig zu wissen, was man eigentlich optimiert. Oftmals wird der<br />
Fehler gemacht, Programmteile auf Geschw<strong>in</strong>digkeit zu optimieren, die so<br />
selten aufgerufen werden, dass sie nur e<strong>in</strong>en sehr kle<strong>in</strong>en Bruchteil der<br />
Gesamtrechenzeit benötigen. Damit hat man dann zwar sehr viel Arbeit<br />
<strong>in</strong>vestiert, nur der Erfolg lässt zu wünschen übrig. Es ist unbed<strong>in</strong>gt<br />
notwendig, e<strong>in</strong>en objektiven Test durchführen, um herauszuf<strong>in</strong>den, welche<br />
Vorgänge im Programm <strong>in</strong>sgesamt am meisten Laufzeit <strong>in</strong> Anspruch nehmen.<br />
Zumeist ist es so, dass zwischen 70% und 90% der Laufzeit von e<strong>in</strong>er<br />
Handvoll Funktionen verbraucht werden, weil diese e<strong>in</strong>fach so oft aufgerufen<br />
werden. Optimiert man an der richtigen Stelle, hat man auch schon<br />
mit kle<strong>in</strong>en Verbesserungen e<strong>in</strong>e relativ große Steigerung der Gesamtperformance<br />
erreicht.<br />
• Am allerwichtigsten überhaupt ist es, die <strong>in</strong>sgesamt ausgeführte Codemenge<br />
kurz zu halten. Dies kann man nur durch Wahl der geeigneten<br />
Datenstrukturen und Algorithmen erreichen. Gerade hierbei werden die<br />
gravierendsten Fehler gemacht, denn oft wird um wenige Zeilen Code ge-
5. Funktionen 101<br />
feilscht (=l<strong>in</strong>eare Ersparnis), obwohl e<strong>in</strong> Algorithmus mit Komplexität<br />
O(n 2 ) (=quadratischer Verlust) oder schlimmer im E<strong>in</strong>satz ist.<br />
Um das Gesagte zu untermauern, möchte ich hier e<strong>in</strong>e kle<strong>in</strong>e Geschichte<br />
erzählen, die sich im Jahr 1990 ereignet hat: Es wurde e<strong>in</strong> Paket zur Berechnung<br />
e<strong>in</strong>er speziellen Regression auf e<strong>in</strong>er für damals sehr leistungsfähigen<br />
Unix-Workstation entwickelt. Leider war e<strong>in</strong> sehr naiver Entwickler<br />
am Werk, der ke<strong>in</strong>e Rücksicht auf die Beschaffenheit der Daten nahm<br />
und wahnwitzigste Matrix-Operationen, mit e<strong>in</strong>er Laufzeitkomplexität von<br />
O(n 2 ) bis O(n 3 ), je nach Art der Berechnung, implementierte.<br />
Ergebnis: E<strong>in</strong> Programmlauf dauerte im Durchschnitt zwei Tage. Der<br />
verzweifelte Entwickler <strong>in</strong>vestierte viel Zeit <strong>in</strong> sehr naive Laufzeitoptimierung<br />
(hier e<strong>in</strong> paar Statements, dort e<strong>in</strong> paar...), aber der Erfolg war nur<br />
m<strong>in</strong>imal. Der beste Wert wurde mit 1 1/2 Tagen Laufzeit erreicht.<br />
Nachdem der mittlerweile völlig entnervte Entwickler aufgab, wurde das<br />
Programm auf se<strong>in</strong>e Komplexität untersucht und e<strong>in</strong>em entsprechenden<br />
kompletten Redesign unterzogen. Besonderes Augenmerk wurde hierbei<br />
auf <strong>in</strong>terne Datenstrukturen gelegt. Der Zeitaufwand vom Beg<strong>in</strong>n des<br />
Redesigns bis zur fertigen Implementation betrug weniger als die Hälfte<br />
der zuvor zur s<strong>in</strong>nlosen Optimierung <strong>in</strong>vestierten Zeit. Der Erfolg war<br />
aber durchschlagend: Die Komplexität aller Berechnungen lag nunmehr<br />
bei O(n). Damit wurde die Laufzeit des Programms, mit denselben Daten<br />
wie zuvor, auf weniger als e<strong>in</strong>e M<strong>in</strong>ute reduziert!<br />
Als kle<strong>in</strong>es Detail am Rande wäre hier noch zu erwähnen, dass das s<strong>in</strong>nvoll<br />
optimierte Programm sauber und gut lesbar war. Ganz im Gegensatz zu<br />
dem Meisterwerk, das durch den verzweifelten ersten Optimierungsversuch<br />
entstand, denn dieses war durch das Herausquetschen der letzten kle<strong>in</strong>en<br />
Instruktionen e<strong>in</strong>fach gar nicht mehr lesbar.<br />
• Damit Komplexitätsverbesserungen garantiert den gewünschten Effekt<br />
br<strong>in</strong>gen, sollte man auf jeden Fall auch auf die worst-Case Komplexität<br />
e<strong>in</strong>es Algorithmus losgehen, nicht nur auf den Durchschnitt (außer, man<br />
kann den worst-Case garantiert ausschalten). Das bekannteste Beispiel<br />
e<strong>in</strong>es Algorithmus mit O(log(n)) average-Komplexität, die allerd<strong>in</strong>gs zu<br />
O(n 2 ) im worst-Case entarten kann, ist Quicksort. Wenn man diesem die<br />
“falschen” Daten füttert ist es ganz und gar nicht mehr so quick :-).<br />
• Performancetests sollten immer auch auf viel zu ger<strong>in</strong>g ausgestatteten Systemen<br />
mit gleichzeitig viel zu großer Belastung stattf<strong>in</strong>den. Auf diese Art<br />
lassen sich leichter unangenehme Effekte f<strong>in</strong>den (auch sehr geeignet für den<br />
Test auf subjektive Performance!).<br />
• Während der Phase der Optimierungen muss man immer darauf achten,<br />
nur e<strong>in</strong>e Sache auf e<strong>in</strong>mal zu optimieren und dann e<strong>in</strong>en Vergleichstest<br />
durchzuführen. Der Grund dafür ist e<strong>in</strong>fach zu erkennen: Wenn man <strong>in</strong><br />
e<strong>in</strong>em Programm an mehreren Baustellen gleichzeitig arbeitet, lässt sich<br />
nicht mehr sagen, von welcher der Maßnahmen nun welcher Effekt ausgeht.
102 5. Funktionen<br />
Es kann sogar passieren, dass sich die Effekte verschiedener Maßnahmen<br />
gegenseitig aufheben und damit hat man viel Arbeit umsonst <strong>in</strong>vestiert!<br />
• Wann ist nun der Zeitpunkt für Optimierungen gekommen? Pr<strong>in</strong>zipiell<br />
bereits beim Design, denn dieses entscheidet über die Komplexität des Codes.<br />
Weitere Performancetests und Optimierungsschritte sollten bei sehr<br />
oft verwendeten Basis- und Utility-Klassen möglichst früh stattf<strong>in</strong>den. Module,<br />
die bereits e<strong>in</strong>en sehr hohen Abstraktionslevel haben, kann man zum<br />
Schluss der Entwicklung optimieren.
6. Po<strong>in</strong>ter und References<br />
C ist schon berühmt und nicht m<strong>in</strong>der berüchtigt für se<strong>in</strong>e Po<strong>in</strong>ter, die e<strong>in</strong><br />
unheimlich mächtiges aber auch gleichzeitig gefährliches Werkzeug darstellen.<br />
C ++ hat nicht nur die Po<strong>in</strong>ter von se<strong>in</strong>er Urmutter C übernommen,<br />
sondern auch den schlechten Ruf, der ihnen anlastet. Ich kann allerd<strong>in</strong>gs nur<br />
immer wieder betonen, dass nicht die Existenz von Po<strong>in</strong>tern das Problem ist,<br />
sondern dass Unwissenheit und Unverständnis sowie Leichts<strong>in</strong>n bei der<br />
Verwendung von Po<strong>in</strong>tern die wahren Schuldigen an deren schlechtem Ruf<br />
s<strong>in</strong>d. Bei Fehlverwendung von Po<strong>in</strong>tern eröffnen sich ungeahnte und besonders<br />
heimtückische Fehlermöglichkeiten! Probleme, die durch die<br />
Fehlverwendung von Po<strong>in</strong>tern entstehen, s<strong>in</strong>d üblicherweise außerordentlich<br />
schwer zu lokalisieren, denn nur zu oft hat der Fehler selbst noch ke<strong>in</strong>e sofort<br />
merkbaren Auswirkungen. Die Katastrophe tritt nicht selten erst viel<br />
später, zu irgende<strong>in</strong>em völlig unbestimmten Zeitpunkt, ans Tageslicht. Dann<br />
nämlich, wenn e<strong>in</strong> Programm endgültig se<strong>in</strong>em durche<strong>in</strong>ander gekommenen<br />
Heap ohne e<strong>in</strong>en direkt erkennbaren Grund zum Opfer fällt und entweder<br />
s<strong>in</strong>nlose Ergebnisse produziert oder abstürzt.<br />
Neben den altbekannten Po<strong>in</strong>tern bietet C ++ auch die sogenannten References<br />
an, die es <strong>in</strong> C nicht gibt. Man könnte References auch salopp als<br />
versteckte Po<strong>in</strong>ter bezeichnen. In jedem Fall s<strong>in</strong>d References wunderbar als<br />
E<strong>in</strong>stieg <strong>in</strong> die Welt der Adressen geeignet und werden daher <strong>in</strong> der Folge<br />
zuerst behandelt, bevor wir uns den Po<strong>in</strong>tern <strong>in</strong> ihrer Hardcore-Variante<br />
zuwenden.<br />
6.1 References<br />
Wie C, so unterstützt auch C ++ bei Funktionsaufrufen ausschließlich callby-value<br />
zur Parameterübergabe. Manchmal will man allerd<strong>in</strong>gs auch e<strong>in</strong>en<br />
call-by-reference erreichen, also e<strong>in</strong>en übergebenen Parameter nachhaltig im<br />
aufrufenden Programmteil verändern. Die e<strong>in</strong>zige Möglichkeit, dies zu bewerkstelligen,<br />
ist der Umweg über e<strong>in</strong>e Adresse, die auf die Orig<strong>in</strong>alvariable<br />
verweist, auf die sich der Parameter bezieht.<br />
Neben e<strong>in</strong>em gewollten nachhaltigen Verändern von Variablen gibt es auch<br />
noch e<strong>in</strong>en anderen Grund, der e<strong>in</strong>en call-by-reference notwendig macht: E<strong>in</strong><br />
call-by-value z.B. mit e<strong>in</strong>er struct als Parameter führt sehr oft zu e<strong>in</strong>em
104 6. Po<strong>in</strong>ter und References<br />
gewaltigen Laufzeit-Overhead, denn es muss ja der gesamte Inhalt der struct<br />
kopiert werden und das kann e<strong>in</strong>e ganze Menge se<strong>in</strong>. Selbiges gilt für den<br />
return-Wert. Es ist also auch <strong>in</strong> diesem Fall sehr s<strong>in</strong>nvoll, e<strong>in</strong>fach über e<strong>in</strong>e<br />
Adresse Bezug auf das Orig<strong>in</strong>al zu nehmen.<br />
All das, was ich hier gerade angeführt habe, macht man <strong>in</strong> C mit Po<strong>in</strong>tern<br />
“per Hand”. In C ++ hat man aus mehreren Gründen, die uns z.T. im OO-Teil<br />
noch begegnen werden, den expliziten Po<strong>in</strong>tern e<strong>in</strong> weiteres Konstrukt zur<br />
Seite gestellt, die sogenannten References. Diese stellen, wenn man so will,<br />
implizite Po<strong>in</strong>ter dar, bei denen man sich nicht als Entwickler selbst um die<br />
Dereferenzierung bei der Zuweisung kümmern muss. Diese passiert versteckt<br />
h<strong>in</strong>ter den Kulissen. Auch gibt es bei References ke<strong>in</strong>e Po<strong>in</strong>terarithmetik.<br />
Genehmigen wir uns zum E<strong>in</strong>stieg e<strong>in</strong> erstes kle<strong>in</strong>es Beispiel, das die Verwendung<br />
von References demonstriert (first_reference_demo.cpp):<br />
1 // f i r s t r e f e r e n c e d e m o . cpp − demo o f r e f e r e n c e s <strong>in</strong> <strong>C++</strong><br />
2<br />
3 #<strong>in</strong>clude <br />
4 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
5<br />
6 us<strong>in</strong>g std : : cout ;<br />
7 us<strong>in</strong>g std : : endl ;<br />
8<br />
9 void changeVariable ( <strong>in</strong>t32 &var ) ;<br />
10<br />
11 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
12 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
13 {<br />
14 <strong>in</strong>t32 my var = 12;<br />
15 <strong>in</strong>t32 &my ref = my var ;<br />
16<br />
17 // my var and my ref r e f e r to the same <strong>in</strong>t32 <strong>in</strong> memory<br />
18 // t h e r e f o r e they share the same value . . .<br />
19 cout
46 void changeVariable ( <strong>in</strong>t32 &var )<br />
47 {<br />
48 var /= 2;<br />
49 }<br />
6.1 References 105<br />
Der Output dieses Programms zeigt sehr schnell, was es mit References so<br />
auf sich hat:<br />
my var : 1 2 , my ref : 1 2<br />
my var : 2 4 , my ref : 2 4<br />
my var : 4 8 , my ref : 4 8<br />
my var : 2 4 , my ref : 2 4<br />
my var : 1 2 , my ref : 1 2<br />
Beg<strong>in</strong>nen wir mit der Analyse des Programms <strong>in</strong> Zeile 14: Dort wird e<strong>in</strong>e<br />
ganz normale <strong>in</strong>t32 Variable namens my_var def<strong>in</strong>iert. In der folgenden Zeile<br />
15 allerd<strong>in</strong>gs f<strong>in</strong>det sich e<strong>in</strong>e Variablendef<strong>in</strong>ition, die uns bisher noch nicht<br />
untergekommen ist. Genau dies ist die Def<strong>in</strong>ition e<strong>in</strong>er Reference. Um e<strong>in</strong>e<br />
Variable als Reference Variable zu def<strong>in</strong>ieren, wird dem Variablennamen bei<br />
der Def<strong>in</strong>ition e<strong>in</strong> & vorangestellt. Worauf die Reference zeigt, wird durch deren<br />
Initialisierung festgelegt. In unserem Fall referenziert my_ref die Variable<br />
my_var, das bedeutet, beide Variablen beziehen sich auf denselben Platz im<br />
Speicher!<br />
In den Zeilen 19–30 sieht man, dass man tatsächlich beide Variablen alternativ<br />
verwenden kann. Egal, was man mit e<strong>in</strong>er von ihnen macht, das<br />
Ergebnis ist immer <strong>in</strong> der jeweils anderen auch zu sehen. Was auch sonst,<br />
beziehen sich die beiden doch auf dieselbe Speicherstelle. Das Schöne im Gegensatz<br />
zu “echten” Po<strong>in</strong>tern ist hierbei, dass man sich überhaupt nicht um<br />
die Dereferenzierung kümmern muss. E<strong>in</strong>e Reference Variable wird e<strong>in</strong>fach<br />
ganz gleich im laufenden Code verwendet wie e<strong>in</strong>e normale Variable, der Rest<br />
passiert h<strong>in</strong>ter den Kulissen.<br />
Für Entwickler, die bisher nur mit C zu tun hatten, mag dieses Verhalten<br />
ungewohnt se<strong>in</strong>, vor allem liest sich z.B. die Zeile 15 für solche Entwickler etwas<br />
komisch. Dort wird my_ref im Pr<strong>in</strong>zip auf e<strong>in</strong>e Adresse <strong>in</strong>itialisiert, aber<br />
die Schreibweise suggeriert irgendwie, dass e<strong>in</strong>e Zuweisung des Inhalts stattf<strong>in</strong>det.<br />
Dieses komische Gefühl verschw<strong>in</strong>det allerd<strong>in</strong>gs sehr schnell, wenn<br />
man sich erst e<strong>in</strong>mal an die Arbeit mit den wirklich sehr praktischen References<br />
gewöhnt hat.<br />
Vielfach ist <strong>in</strong> der Literatur erwähnt, dass es sich bei References im Pr<strong>in</strong>zip<br />
um alternative Namen handelt. Betrachtet man unser Beispiel, so versteht<br />
man auch sehr schnell, wieso es zu dieser Bezeichnung kommt. Jedoch<br />
verschleiert der Begriff der alternativen Namen völlig, dass ja eigentlich e<strong>in</strong>e<br />
Reference Variable viel mehr als nur quasi e<strong>in</strong> Alias ist. Sie ist tatsächlich<br />
e<strong>in</strong>e alternative Zugriffsmöglichkeit. Aus diesem Grund möchte ich allen<br />
Lesern ans Herz legen, den Begriff der alternativen Namen nicht zu verwenden.<br />
In den Zeilen 46–49 ist e<strong>in</strong>e Funktion def<strong>in</strong>iert, die von e<strong>in</strong>er Reference<br />
Gebrauch macht, um e<strong>in</strong>en call-by-reference, also e<strong>in</strong> nachhaltiges Verändern
106 6. Po<strong>in</strong>ter und References<br />
e<strong>in</strong>er Variable beim Aufrufenden zu erreichen. Wie man <strong>in</strong> Zeile 33 sieht,<br />
funktioniert dies auch wie erwartet. Dass man auch e<strong>in</strong>e Reference e<strong>in</strong>em<br />
call-by-reference übergeben kann und dass dabei nicht plötzlich e<strong>in</strong>e Doppel-<br />
Reference mit schlimmen Folgen daraus wird, sieht man <strong>in</strong> Zeile 38.<br />
Obwohl es aus der Besprechung des Beispielprogramms eigentlich klar<br />
se<strong>in</strong> sollte, möchte ich es hier trotzdem explizit erwähnen, um ke<strong>in</strong>e Missverständnisse<br />
aufkommen zu lassen: Ke<strong>in</strong> e<strong>in</strong>ziger Operator wirkt auf<br />
e<strong>in</strong>e Reference selbst! Egal, welche Operatoren man auf e<strong>in</strong>e Reference<br />
Variable anwendet, sie wirken immer ausschließlich auf den<br />
Inhalt, der sich h<strong>in</strong>ter der Reference verbirgt! Es gibt also ke<strong>in</strong>e<br />
Möglichkeit, z.B. so etwas wie Po<strong>in</strong>terarithmetik (wird später noch genau<br />
erklärt) mit References durchzuführen.<br />
In der Natur von References liegt auch, dass man sie nicht e<strong>in</strong>fach z.B.<br />
mit e<strong>in</strong>em Zahlenwert <strong>in</strong>itialisieren kann. References s<strong>in</strong>d ja quasi versteckte<br />
Zeiger auf e<strong>in</strong>en Speicherplatz. Aus diesem Grund muss die Initialisierung<br />
immer mit e<strong>in</strong>em sogenannten lvalue erfolgen, denn nur dieser garantiert<br />
ja, dass dah<strong>in</strong>ter auch Speicherplatz existiert, der beschrieben werden kann.<br />
Und genau diese Eigenschaft führt uns zu e<strong>in</strong>em weiteren Punkt: References<br />
müssen unbed<strong>in</strong>gt immer bei ihrer Def<strong>in</strong>ition explizit <strong>in</strong>itialisiert<br />
werden!. Der Grund ist e<strong>in</strong>fach: Es wurde bereits erwähnt, dass ke<strong>in</strong> e<strong>in</strong>ziger<br />
Operator auf die Reference wirkt, sondern alle nur auf den Inhalt. Das<br />
bedeutet auch, dass es ja gar ke<strong>in</strong>e Möglichkeit gibt, das Ziel der Referenz<br />
nachträglich zu verändern, denn e<strong>in</strong>e Zuweisung will ja den Inhalt und nicht<br />
die Referenz zuweisen! Also kommt der expliziten Initialisierung e<strong>in</strong>er Referenz<br />
e<strong>in</strong>e besondere Rolle zu: Sie ist der e<strong>in</strong>zige Zeitpunkt, zu dem man <strong>in</strong><br />
der Lage ist, ihr Ziel zu setzen.<br />
E<strong>in</strong> kle<strong>in</strong>es Detail am Rande: Es war bereits davon die Rede, dass unter<br />
dem Begriff der expliziten Initialisierung das Setzen e<strong>in</strong>es Wertes bei der<br />
Def<strong>in</strong>ition geme<strong>in</strong>t ist und nicht das Zuweisen e<strong>in</strong>es Wertes irgendwann nach<br />
der Def<strong>in</strong>ition. Am Verhalten der Initialisierung von References sieht man<br />
e<strong>in</strong>deutig, dass auch der Compiler <strong>in</strong>tern e<strong>in</strong>e explizite Initialisierung ganz<br />
anders <strong>in</strong>terpretiert als e<strong>in</strong>e Zuweisung. Ansonsten wäre es nämlich nicht<br />
möglich, e<strong>in</strong>e Referenz überhaupt auf e<strong>in</strong>e Speicherstelle zeigen zu lassen,<br />
denn die Zuweisung bewirkt ja etwas gänzlich Anderes!<br />
Vorsicht Falle: Leider sieht man immer wieder, dass durch Unwissenheit<br />
oder Schlamperei besonders tolle Zeitbomben wie die folgende implementiert<br />
werden (reference_problems_demo.cpp):<br />
1 // reference problems demo . cpp − p o s s i b l e problems with r e f e r e n c e s<br />
2<br />
3 #<strong>in</strong>clude <br />
4 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
5<br />
6 <strong>in</strong>t32 &returnDeadVariable ( ) ;<br />
7<br />
8 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
9 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )
10 {<br />
11 <strong>in</strong>t32 my var = returnDeadVariable ( ) ;<br />
12 <strong>in</strong>t32 &my ref = returnDeadVariable ( ) ;<br />
13<br />
14 return ( 0 ) ;<br />
15 }<br />
16<br />
6.1 References 107<br />
17 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
18 <strong>in</strong>t32 &returnDeadVariable ( )<br />
19 {<br />
20 <strong>in</strong>t32 a n a u t o v a r i a b l e = 17;<br />
21<br />
22 return ( a n a u t o v a r i a b l e ) ; // NEVER ! ! ! ! ! ! ! ! ! ! ! ! ! !<br />
23 }<br />
E<strong>in</strong> Blick auf die Funktion, die <strong>in</strong> den Zeilen 18–23 implementiert wird, zeigt,<br />
was man niemals tun darf: In Zeile 22 wird die Reference auf e<strong>in</strong>e Variable<br />
als return-Wert geliefert, bloß hat diese Variable zum Zeitpunkt des Rücksprungs<br />
ihre Lifetime bereits h<strong>in</strong>ter sich! Welchen Wert die Variable my_var<br />
<strong>in</strong> Zeile 11 zugewiesen bekommt, steht <strong>in</strong> den Sternen. Leider passiert es<br />
sehr häufig, dass der Speicher, der von der mittlerweile “toten” Variable<br />
benutzt wurde, noch nicht überschrieben wurde. Dementsprechend wird <strong>in</strong><br />
vielen Fällen my_var tatsächlich noch den beabsichtigten Wert (<strong>in</strong> unserem<br />
Fall 17) enthalten. Dies ist ke<strong>in</strong>esfalls garantiert! Jedoch suggeriert e<strong>in</strong> solches<br />
durch Zufall entstandenes Verhalten, dass das Programm verme<strong>in</strong>tlich<br />
fehlerlos funktioniert – das tut es aber nicht!<br />
Viel Schlimmeres noch passiert <strong>in</strong> Zeile 12: Dort wird tatsächlich die Reference<br />
auf e<strong>in</strong>e tote Variable e<strong>in</strong>er anderen Reference Variable zugewiesen. Damit<br />
hat man gut versteckt e<strong>in</strong>en Po<strong>in</strong>ter auf “irgendetwas”. Hat man Glück,<br />
dann verabschiedet sich das Programm beim ersten Zugriffsversuch auf diesen<br />
Speicher mit e<strong>in</strong>er Segmentation Violation. Hat man Pech, was leider auch<br />
relativ oft der Fall ist, dann gehört der falsch referenzierte Speicher irgende<strong>in</strong>er<br />
anderen Variable im eigenen Programm. Damit sieht das Betriebssystem<br />
ke<strong>in</strong>e Veranlassung, e<strong>in</strong>e wie auch immer geartete Schutzverletzung zu melden.<br />
Und schon hat man die tollste Zeitbombe <strong>in</strong> e<strong>in</strong> Programm e<strong>in</strong>gebaut,<br />
die man sich vorstellen kann. Zu irgende<strong>in</strong>em völlig unvorhersagbaren Zeitpunkt<br />
wird sich das Programm an irgende<strong>in</strong>er völlig unvorhersagbaren Stelle<br />
sehr komisch benehmen und die nächste schlaflose Nacht zur Fehlersuche<br />
kündigt sich an.<br />
Das schlimmste Problem bei solchen Fehlern ist, dass Ursache und Auswirkung<br />
<strong>in</strong> ke<strong>in</strong>em erkennbaren Verhältnis zue<strong>in</strong>ander stehen. Daher s<strong>in</strong>d<br />
sie auch außerordentlich schwer zu lokalisieren! Zum Glück warnen heutige<br />
Compiler bei solchen bombigen Programmen, dass e<strong>in</strong>e Reference auf e<strong>in</strong>e<br />
lokale Variable als return-Wert vielleicht gar ke<strong>in</strong>e so gute Idee ist. Bloß, was<br />
hilft die beste Warn<strong>in</strong>g, wenn sie von Entwicklern ignoriert wird?<br />
Sehr s<strong>in</strong>nvoll e<strong>in</strong>setzbar s<strong>in</strong>d References auch, wenn man e<strong>in</strong>e struct bei<br />
e<strong>in</strong>em Funktionsaufruf als Parameter übergeben will. In C ++ f<strong>in</strong>det bekannterweise<br />
immer e<strong>in</strong> call-by-value statt. Das bedeutet, dass e<strong>in</strong>e Kopie e<strong>in</strong>er
108 6. Po<strong>in</strong>ter und References<br />
struct gemacht werden muss, um e<strong>in</strong>en solchen durchführen zu können. Neben<br />
großen E<strong>in</strong>bußen bei der Performance kann man dabei auch leicht über<br />
das deep- und shallow-clone Problem stolpern. Mit den hier besprochenen<br />
References kann man diese Probleme sehr elegant <strong>in</strong> den Griff bekommen,<br />
wie das folgende Beispiel zeigt (struct_reference_demo.cpp):<br />
1 // s t r u c t r e f e r e n c e d e m o . cpp − demo how to use s t r u c t s as params<br />
2<br />
3 #<strong>in</strong>clude <br />
4 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
5<br />
6 us<strong>in</strong>g std : : cout ;<br />
7 us<strong>in</strong>g std : : endl ;<br />
8<br />
9 struct TestStruct<br />
10 {<br />
11 <strong>in</strong>t32 a member ;<br />
12 <strong>in</strong>t32 another member ;<br />
13 } ;<br />
14<br />
15 void show ( struct TestStruct & t h e s t r u c t ) ;<br />
16 void changeStruct ( struct TestStruct & t h e s t r u c t ) ;<br />
17<br />
18 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
19 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
20 {<br />
21 struct TestStruct a s t r u c t ;<br />
22<br />
23 a s t r u c t . a member = 17;<br />
24 a s t r u c t . another member = 20;<br />
25<br />
26 show ( a s t r u c t ) ;<br />
27 changeStruct ( a s t r u c t ) ;<br />
28 show ( a s t r u c t ) ;<br />
29<br />
30 return ( 0 ) ;<br />
31 }<br />
32<br />
33 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
34 void show ( struct TestStruct & t h e s t r u c t )<br />
35 {<br />
36 cout
6.1 References 109<br />
werden müsste, um den Funktionsaufruf durchzuführen. Dementsprechend<br />
f<strong>in</strong>det der Aufruf dieser Funktion, z.B. <strong>in</strong> Zeile 26, ohne jegliche explizite<br />
Po<strong>in</strong>terkonstrukte e<strong>in</strong>fach nur durch Übergabe der entsprechenden struct<br />
Variable statt.<br />
Obwohl dies alle<strong>in</strong> schon e<strong>in</strong>e merkliche Verbesserung im Vergleich zu C<br />
darstellt, ist es allerd<strong>in</strong>gs noch immer nicht der Weisheit letzter Schluss, wie<br />
uns die Funktion changeStruct <strong>in</strong> den Zeilen 41–45 zeigt: Hier wird der<br />
Inhalt der übergebenen Structure nachhaltig verändert. Dies funktioniert<br />
natürlich, weil wir es ja mit e<strong>in</strong>em call-by-reference zu tun haben. Dadurch,<br />
dass es sich um e<strong>in</strong>e Funktion mit dem Namen changeStruct handelt, ist<br />
auch für alle Entwickler zu erwarten, dass sich etwas ändert und dementsprechend<br />
ist dieses Verhalten noch <strong>in</strong>tuitiv. Wer aber h<strong>in</strong>dert z.B. die Funktion<br />
show daran, die Structure zu verändern? Müssen sich alle Entwickler ab jetzt<br />
bei jedem Aufruf e<strong>in</strong>er Funktion, die e<strong>in</strong>en struct Parameter nimmt, Sorgen<br />
machen, dass etwas nachhaltig geändert wird, auch wenn sie es nicht erwarten?<br />
Hoffentlich nicht, denn damit würden Unmengen an Code geschrieben<br />
werden müssen, um auf diesen Umstand auch entsprechend Rücksicht zu nehmen.<br />
Tatsächlich gibt es e<strong>in</strong>e Möglichkeit, wie man e<strong>in</strong>e unerwartete Veränderung<br />
verh<strong>in</strong>dern kann: Man verwendet e<strong>in</strong>e explizit konstante Referenz. Wie<br />
das geht, zeigt das folgende Beispiel (struct_ref_clean_demo.cpp):<br />
1 // s t r u c t r e f c l e a n d e m o . cpp − demo f o r clean use o f s t r u c t r e f s<br />
2<br />
3 #<strong>in</strong>clude <br />
4 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
5<br />
6 us<strong>in</strong>g std : : cout ;<br />
7 us<strong>in</strong>g std : : endl ;<br />
8<br />
9 struct TestStruct<br />
10 {<br />
11 <strong>in</strong>t32 a member ;<br />
12 <strong>in</strong>t32 another member ;<br />
13 } ;<br />
14<br />
15 void show ( const struct TestStruct & t h e s t r u c t ) ;<br />
16<br />
17 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
18 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
19 {<br />
20 struct TestStruct a s t r u c t ;<br />
21<br />
22 a s t r u c t . a member = 17;<br />
23 a s t r u c t . another member = 20;<br />
24<br />
25 show ( a s t r u c t ) ;<br />
26 return ( 0 ) ;<br />
27 }<br />
28<br />
29 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
30 void show ( const struct TestStruct & t h e s t r u c t )<br />
31 {<br />
32 cout
110 6. Po<strong>in</strong>ter und References<br />
Bei der Deklaration von show <strong>in</strong> Zeile 15 und bei der Def<strong>in</strong>ition dieser Funktion<br />
<strong>in</strong> den Zeilen 30–34 sieht man, wie man e<strong>in</strong> ungewolltes nachhaltiges<br />
Verändern des Inhalts e<strong>in</strong>er Variable verh<strong>in</strong>dern kann: Man deklariert den<br />
Reference Parameter als const. Dadurch teilt man dem Compiler mit, dass<br />
ke<strong>in</strong>e Änderung stattf<strong>in</strong>den wird und jeder Versuch e<strong>in</strong>es schreibenden Zugriffs<br />
auf diesen Parameter wird entsprechend mit e<strong>in</strong>er Fehlermeldung quittiert.<br />
Leider stehen viele Entwickler immer noch auf dem Standpunkt, dass<br />
const nicht notwendig ist, denn “sie verändern ja sowieso nichts”. Dieser<br />
Standpunkt ist sehr schlecht, denn durch die Deklaration als const signalisiert<br />
man diese Absicht explizit und dadurch kann es auch bei späteren<br />
Änderungen zu ke<strong>in</strong>en Missverständnissen kommen.<br />
Vom Compiler wird e<strong>in</strong>e const Reference tatsächlich als etwas völlig<br />
Verschiedenes zu e<strong>in</strong>er normalen Reference gesehen, wie folgendes Demoprogramm<br />
zeigt (const_ref_demo.cpp):<br />
1 // const ref demo . cpp − demo f o r const r e f e r e n c e s<br />
2<br />
3 #<strong>in</strong>clude <br />
4 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
5<br />
6 us<strong>in</strong>g std : : cout ;<br />
7 us<strong>in</strong>g std : : endl ;<br />
8<br />
9 struct TestStruct<br />
10 {<br />
11 <strong>in</strong>t32 a member ;<br />
12 <strong>in</strong>t32 another member ;<br />
13 } ;<br />
14<br />
15 void show ( const struct TestStruct & t h e s t r u c t ) ;<br />
16 void changeStruct ( struct TestStruct & t h e s t r u c t ) ;<br />
17<br />
18<br />
19 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
20 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
21 {<br />
22 <strong>in</strong>t32 my var = 17;<br />
23 const <strong>in</strong>t32 & f i r s t c o n s t r e f = my var ;<br />
24 // the f o l l o w i n g d e f i n i t i o n works , but i s deprecated<br />
25 const <strong>in</strong>t32 & s e c o n d c o n s t r e f = 12;<br />
26 // t h i s i n i t i a l i z a t i o n r e s u l t s <strong>in</strong> a compiler e r r o r<br />
27 <strong>in</strong>t32 & t h i r d c o n s t r e f = s e c o n d c o n s t r e f ;<br />
28 // t h i s bad hack i s p o s s i b l e , but . . .<br />
29 <strong>in</strong>t32 & h a c k c o n s t r e f = const cast(s e c o n d c o n s t r e f ) ;<br />
30<br />
31 cout
42 a s t r u c t . a member = 17;<br />
43 a s t r u c t . another member = 20;<br />
44<br />
45 show ( a s t r u c t ) ;<br />
46<br />
6.1 References 111<br />
47 // assignment of r e f e r e n c e i s f a s t e r than copy<strong>in</strong>g s t r u c t<br />
48 struct TestStruct & a s s i g n e d s t r u c t = a s t r u c t ;<br />
49<br />
50 // t h i s assignment i s ok<br />
51 const struct TestStruct & c o n s t s t r u c t r e f = a s t r u c t ;<br />
52<br />
53 // t h i s one r e s u l t s <strong>in</strong> a compiler e r r o r<br />
54 struct TestStruct & a n o t h e r s t r u c t r e f = c o n s t s t r u c t r e f ;<br />
55<br />
56 return ( 0 ) ;<br />
57 }<br />
58<br />
59 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
60 void show ( const struct TestStruct & t h e s t r u c t )<br />
61 {<br />
62 struct TestStruct &bad hack =<br />
63 const cast(t h e s t r u c t ) ;<br />
64<br />
65 bad hack . a member = 3;<br />
66<br />
67 cout
112 6. Po<strong>in</strong>ter und References<br />
Compiler legt h<strong>in</strong>ter den Kulissen e<strong>in</strong>e temporäre Variable an, deren Lifetime<br />
genau der Lifetime der Reference entspricht, <strong>in</strong>itialisiert diese Variable<br />
auf den angegebenen Wert und <strong>in</strong>itialisiert die Reference so, dass sie auf<br />
die temporäre Variable zeigt.<br />
Jetzt stellt sich natürlich die Frage, warum der Compiler bei const References<br />
so etwas macht, bei normalen References aber <strong>in</strong> so e<strong>in</strong>em Fall<br />
e<strong>in</strong>en Fehler meldet. Die Antwort ist simpel: Es wurde bei der Def<strong>in</strong>ition<br />
von C ++ befunden, dass im Fall von normalen References dieses Verhalten<br />
fehleranfällig und gefährlich ist.<br />
Me<strong>in</strong>e Me<strong>in</strong>ung dazu ist allerd<strong>in</strong>gs noch etwas pragmatischer, denn ich stehe<br />
auf dem Standpunkt, dass die S<strong>in</strong>nhaftigkeit von compilergenerierten<br />
temporären Variablen sowieso sehr fraglich ist. Deshalb wurde dieser Fall<br />
auch im Demoprogramm als deprecated (wörtl.: missbilligt) gekennzeichnet.<br />
• In Zeile 27 sieht man, was man <strong>in</strong> jedem Fall nicht machen darf: Man darf<br />
e<strong>in</strong>e “normale” Reference natürlich nicht mit e<strong>in</strong>er const Reference <strong>in</strong>itialisieren,<br />
denn damit würde man ja den Schreibschutz aufheben. Dagegen<br />
hat dann der Compiler e<strong>in</strong> wirksames Mittel, nämlich e<strong>in</strong>e Fehlermeldung.<br />
• Zeile 29 zeigt e<strong>in</strong>drucksvoll zwei D<strong>in</strong>ge: Erstens, dass sich immer Mittel<br />
und Wege f<strong>in</strong>den, wie man durch e<strong>in</strong>en bösen Hack den Compiler überreden<br />
kann, etwas doch zu übersetzen. Zweitens, dass tatsächlich e<strong>in</strong>e temporäre<br />
Variable h<strong>in</strong>ter der <strong>in</strong> Zeile 25 def<strong>in</strong>ierten const Reference existiert, denn<br />
sonst könnte man die Zuweisung <strong>in</strong> Zeile 35 nicht ungestraft durchführen.<br />
Wie bereits erwähnt, kann man mittels e<strong>in</strong>es const_cast e<strong>in</strong>e schreibgeschützte<br />
Variable zwanglos von ihrem Schreibschutz befreien. Diese<br />
Möglichkeit sollte man allerd<strong>in</strong>gs nur sehr wohl überlegt und so selten<br />
wie möglich <strong>in</strong> Anspruch nehmen. Die Verwendung, wie sie <strong>in</strong> diesem<br />
Beispiel gezeigt wird, ist absolut katastrophal und hat <strong>in</strong> ke<strong>in</strong>em vernünftigen<br />
Programm etwas verloren.<br />
• Bereits erwähnt wurde gerade Zeile 35 des Programms. Durch den bösen<br />
Hack des Elim<strong>in</strong>ierens des Schreibschutzes ist es tatsächlich möglich, e<strong>in</strong>e<br />
Konstante zu verändern! Hier wurde dieses Konstrukt ausschließlich<br />
deswegen verewigt, weil sich damit demonstrieren lässt, dass sich h<strong>in</strong>ter<br />
second_const_ref tatsächlich e<strong>in</strong>e temporäre Variable versteckt. Der<br />
Output aus den Zeilen 37–39 zeigt es (wenn man zuerst die richtigen Zeilen<br />
auskommentiert, damit das Programm überhaupt compiliert werden<br />
kann). Niemals darf so etwas <strong>in</strong> e<strong>in</strong>em vernünftigen Programm <strong>in</strong> dieser<br />
Form vorkommen!!!<br />
Vorsicht Falle: Auf das hier beschriebene Verhalten, dass sich h<strong>in</strong>ter<br />
e<strong>in</strong>er Konstanten e<strong>in</strong>e temporäre Variable versteckt, darf man sich ke<strong>in</strong>esfalls<br />
verlassen. Obwohl das Verhalten so im Standard niedergeschrieben<br />
ist, kann es, je nach Compiler, se<strong>in</strong>, dass sich das Programm <strong>in</strong> diesem<br />
Punkt anders verhält, als hier beschrieben. Grundsätzlich lässt sich für die
6.1 References 113<br />
alltägliche Praxis sagen, dass das Resultat dieses bösen Hacks undef<strong>in</strong>iert<br />
ist.<br />
Ich habe diesen Hack hier vor allem deswegen demonstriert, weil ich bereits<br />
<strong>in</strong> vielen Programmen gesehen habe, dass durch e<strong>in</strong>e unvorsichtige Anwendung<br />
des const_cast solche Operationen unabsichtlich vorkommen.<br />
• E<strong>in</strong>e weitere angenehme Seite von References ist <strong>in</strong> Zeile 48 zu sehen: Führt<br />
man e<strong>in</strong>e Referenzzuweisung e<strong>in</strong>er struct durch, so ist dies natürlich deutlich<br />
effizienter, als e<strong>in</strong>e Kopie der gesamten struct zu machen, was bei<br />
e<strong>in</strong>er Wertzuweisung passieren würde. Jedoch muss man sich <strong>in</strong> diesem<br />
Fall immer dessen bewusst se<strong>in</strong>, dass jeder schreibende Zugriff auch e<strong>in</strong>e<br />
Veränderung des Orig<strong>in</strong>als hervorruft! Ist dies nicht erwünscht, so ist die<br />
Reference entsprechend als const zu deklarieren, wie dies <strong>in</strong> Zeile 51 zu<br />
sehen ist.<br />
• Zeile 54 demonstriert, dass der Compiler sehr wohl const ernst nimmt,<br />
denn er weigert sich, diese Zuweisung e<strong>in</strong>er const Structure auf e<strong>in</strong>e normale<br />
durchzuführen, obwohl h<strong>in</strong>ter der const Structure ja e<strong>in</strong>e normale<br />
steht und diese nur durch die Zuweisung zu const mutiert ist.<br />
• In den Zeilen 62–63 sieht man denselben Hack, der uns bereits zuvor begegnet<br />
ist: Hier wird bösartigerweise durch e<strong>in</strong>en const_cast der Schreibschutz<br />
von the_struct aufgehoben. Sehr, sehr selten gibt es Fälle, wo dies<br />
aus der Programmlogik heraus legitim ist. Der hier demonstrierte Fall ist<br />
e<strong>in</strong>deutig ke<strong>in</strong>er davon! Wir werden später im OO-Teil noch e<strong>in</strong>ige wenige<br />
Fälle kennen lernen, wo dieser Cast se<strong>in</strong>e Berechtigung hat.<br />
Vorsicht Falle: Auch wenn sich References bei ihrer Verwendung im Code<br />
sehr ähnlich anfühlen wie normale Variablen, so verstecken sich dah<strong>in</strong>ter<br />
doch Po<strong>in</strong>ter, die auf e<strong>in</strong>e Adresse zeigen. Damit kann man natürlich auch<br />
die entsprechenden Fehler machen, wie sie z.B. <strong>in</strong> unserem vorigen Demoprogramm<br />
gezeigt wurden. Ich kann also nur sehr nachdrücklich den Tipp<br />
geben, References vorsichtig und vorausschauend zu verwenden. Das Wichtigste<br />
bei References ist, den Mechanismus, der dah<strong>in</strong>tersteckt, genauestens<br />
verstanden zu haben, um nicht <strong>in</strong> e<strong>in</strong>e böse Falle zu tappen.<br />
Vorsicht Falle: Obwohl der const_cast erfunden wurde, um den Schreibschutz<br />
aufzuheben, der z.B. über e<strong>in</strong>e Reference mittels const verhängt wurde,<br />
verbergen sich dah<strong>in</strong>ter ungeahnte Fehlermöglichkeiten. Welcher Entwickler<br />
ist denn schon darauf gefasst, dass sich plötzlich e<strong>in</strong>e als const deklarierte<br />
Variable bzw. e<strong>in</strong> Parameter nachhaltig verändert? E<strong>in</strong> solches<br />
Verhalten führt garantiert zu vielen Nachtschichten, die mit Fehlersuche zugebracht<br />
werden, weil sich e<strong>in</strong> Programm plötzlich außerordentlich komisch<br />
verhält.<br />
Deshalb hier e<strong>in</strong>e lebensnotwendige Grundregel beim Arbeiten mit<br />
const_cast: Der E<strong>in</strong>satz von const_cast ist dann und NUR DANN
114 6. Po<strong>in</strong>ter und References<br />
gestattet, wenn sich aus der äußeren Sicht am Inhalt e<strong>in</strong>es als const<br />
deklarierten Objekts nichts verändert!<br />
E<strong>in</strong>e typische Anwendung für e<strong>in</strong>e versteckte Veränderung, die “außenstehende”<br />
Entwickler nicht bemerken, ist z.B. das Cach<strong>in</strong>g von Daten h<strong>in</strong>ter<br />
den Kulissen. Je nachdem, was man genau bezwecken will, gibt es auch noch<br />
die alternative Möglichkeit, e<strong>in</strong>zelne Variablen als mutable zu deklarieren.<br />
Dies wird <strong>in</strong> Abschnitt 15.1 noch genauer behandelt.<br />
Vorsicht Falle: Wo auch immer sich aufgrund der Semantik e<strong>in</strong>er Funktion<br />
an e<strong>in</strong>em Parameter nach außen h<strong>in</strong> nichts ändern soll, aber aus technischen<br />
Gründen die Verwendung e<strong>in</strong>er Reference wünschenswert oder notwendig ist<br />
(z.B. bei e<strong>in</strong>er Structure als Parameter), soll man diesen Parameter immer als<br />
const deklarieren. Damit signalisiert man nach außen, dass man nicht beabsichtigt,<br />
etwas zu verändern. Deklariert man e<strong>in</strong>en solchen Parameter nicht<br />
als const, so werden sich alle Entwickler, die diese Funktion verwenden, auf<br />
e<strong>in</strong>e mögliche Änderung e<strong>in</strong>stellen und auf diese Art unnötig komplizierten<br />
Code erzeugen.<br />
Es gibt den noch schlimmeren Fall, dass man e<strong>in</strong>e Funktion, die e<strong>in</strong>en Parameter<br />
irrtümlich nicht als const deklariert hat, gar nicht verwenden kann,<br />
weil man selbst nur e<strong>in</strong>e const Reference <strong>in</strong> Händen hält. Das Schlimmste,<br />
was dann herauskommen kann, ist die notgedrungene Verwendung e<strong>in</strong>es<br />
const_cast, um den Aufruf überhaupt tätigen zu können. Haben allerd<strong>in</strong>gs<br />
e<strong>in</strong>mal solche bösen Hacks <strong>in</strong> den Code E<strong>in</strong>gang gefunden, dann bahnen sich<br />
viele unabwendbare Katastrophen an, denn wer soll sich noch auskennen,<br />
welches const ernst geme<strong>in</strong>t ist und welches nicht?<br />
6.2 Po<strong>in</strong>ter<br />
Nach der Diskussion über References s<strong>in</strong>d wir nun endgültig bei der Hardcore<br />
Variante des Hantierens mit Adressen angekommen, nämlich bei den Po<strong>in</strong>tern.<br />
Po<strong>in</strong>ter s<strong>in</strong>d im Pr<strong>in</strong>zip etwas unheimlich E<strong>in</strong>faches: Es s<strong>in</strong>d Variablen,<br />
deren Inhalt e<strong>in</strong>e Adresse ist. Genau diese E<strong>in</strong>fachheit macht sie auch so<br />
mächtig, man kann nämlich damit auf quasi beliebige Inhalte im Speicher<br />
zeigen. Diese Inhalte können verschiedenster Natur se<strong>in</strong>, nämlich sowohl<br />
Speicherstellen für Daten als auch E<strong>in</strong>sprungadressen von Funktionen bzw.<br />
Methoden. Natürlich kann man mit Adressen auch beliebig rechnen und auf<br />
diese Art Blöcke verwalten.<br />
Dieselbe E<strong>in</strong>fachheit, die Po<strong>in</strong>ter so mächtig macht, macht sie gleichzeitig<br />
auch so gefährlich <strong>in</strong> den Händen ungeübter und/oder unvorsichtiger Entwickler.<br />
Wie bereits bei den Datentypen diskutiert wurde, ist <strong>in</strong>tern <strong>in</strong> e<strong>in</strong>em<br />
Computer praktisch alles e<strong>in</strong>fach nur Interpretationssache. Im Fall von<br />
Po<strong>in</strong>tern wird die Interpretation dessen, worauf die Adresse zeigt, noch bei
6.2 Po<strong>in</strong>ter 115<br />
weitem stärker den Entwicklern überlassen, als dies bei anderen Datentypen<br />
der Fall ist.<br />
Neben der Möglichkeit, gezielt über Adressen Daten ansprechen zu können,<br />
bietet die Arbeit mit Po<strong>in</strong>tern noch e<strong>in</strong> ganz besonderes Feature, das ohne<br />
Po<strong>in</strong>ter (bzw. zum<strong>in</strong>dest e<strong>in</strong> sehr ähnliches Konstrukt) gar nicht realisierbar<br />
wäre: Die dynamische Speicherverwaltung. Es ist möglich, zur Laufzeit e<strong>in</strong>es<br />
Programms zu entscheiden, wie viel Speicher man für gewisse Aufgaben<br />
braucht, diesen Speicher anzufordern und auch wieder freizugeben. Diese<br />
Möglichkeit ist für praktisch alle real-World Anwendungen lebensnotwendig<br />
und ohne diese Möglichkeit ist es nicht denkbar, vernünftig funktionierende<br />
Software zu entwickeln. E<strong>in</strong>es muss man aber bei dynamischem Memory-<br />
Management <strong>in</strong> C ++ unbed<strong>in</strong>gt immer im H<strong>in</strong>terkopf behalten: Man darf<br />
nicht nur e<strong>in</strong>fach Speicher zur Laufzeit dynamisch anfordern, man muss auch<br />
unbed<strong>in</strong>gt die Verantwortung für die saubere Verwaltung übernehmen! Der<br />
Compiler und das Betriebssystem unterstützen Entwickler hierbei nur sehr<br />
rudimentär.<br />
Nachdem bekannt ist, was man mit Po<strong>in</strong>tern machen kann und bevor ich zur<br />
genauen Beschreibung komme, wie man das machen kann, möchte ich gerne<br />
allen Lesern die Gewissensfrage nach ihren Vorkenntnissen zu Po<strong>in</strong>tern <strong>in</strong> C<br />
stellen. Denen, die sich nur rudimentär etwas darunter vorstellen können,<br />
möchte ich e<strong>in</strong>dr<strong>in</strong>glichst raten, dass sie Kapitel 10 aus <strong>Softwareentwicklung</strong><br />
<strong>in</strong> C genauestens lesen! Dort wird anfängergerecht und akribisch auf den sauberen<br />
Umgang mit Po<strong>in</strong>tern e<strong>in</strong>gegangen und es werden alle möglichen Fehler<br />
skizziert, die bei unsachgemäßer Handhabung passieren können. E<strong>in</strong>iges was<br />
dort beschrieben wird, wird <strong>in</strong> C ++ bereits durch References abgedeckt, das<br />
bedeutet aber nicht, dass es für das Verständnis nicht ebenso wichtig wäre!<br />
Natürlich werde ich hier <strong>in</strong> der Folge den Umgang mit Po<strong>in</strong>tern und alle<br />
Stolperste<strong>in</strong>e nochmals aufzeigen. Jedoch muss ich gerade bei Po<strong>in</strong>tern<br />
erfahrungsgemäß den Standpunkt vertreten: Doppelt hält besser!<br />
6.2.1 Po<strong>in</strong>ter und Adressen<br />
Wie bereits erwähnt, speichert e<strong>in</strong>e Po<strong>in</strong>tervariable e<strong>in</strong>e Adresse. Diese<br />
Adresse zeigt auf e<strong>in</strong>e Speicherstelle, genauer gesagt wird sie so <strong>in</strong>terpretiert,<br />
dass sie auf die Startadresse e<strong>in</strong>es Speicherblocks zeigt. Nehmen wir<br />
beispielsweise e<strong>in</strong>en <strong>in</strong>t32, der bei uns ja so def<strong>in</strong>iert ist, dass er 4 Bytes<br />
belegt. E<strong>in</strong> Po<strong>in</strong>ter, der auf diesen <strong>in</strong>t32 zeigt, zeigt also <strong>in</strong> Realität auf das<br />
erste der vier für ihn reservierten Bytes.<br />
Was sagt uns das? Egal, ob wir nun auf e<strong>in</strong>en <strong>in</strong>t8, <strong>in</strong>t32, <strong>in</strong>t64, double,<br />
e<strong>in</strong>e Structure oder sonst etwas e<strong>in</strong>en Po<strong>in</strong>ter zeigen lassen, er speichert immer<br />
nur die Startadresse! Die Interpretation, was an dieser Stelle steht, ist<br />
mehr oder weniger den Entwicklern überlassen. Dies br<strong>in</strong>gt uns auch gleich<br />
zu e<strong>in</strong>er sehr wichtigen Eigenschaft von Po<strong>in</strong>tern: Es gibt ke<strong>in</strong>e Möglichkeit,<br />
zur Laufzeit herauszuf<strong>in</strong>den, wie viel Speicher an der Stelle,
116 6. Po<strong>in</strong>ter und References<br />
auf die e<strong>in</strong> Po<strong>in</strong>ter zeigt, regulär reserviert ist! Es gibt nicht e<strong>in</strong>mal<br />
die Möglichkeit, zur Laufzeit herauszuf<strong>in</strong>den, ob e<strong>in</strong> Po<strong>in</strong>ter<br />
überhaupt auf e<strong>in</strong>e reguläre Adresse zeigt!<br />
Vorsicht Falle: Schlamperei beim Entwickeln und daraus resultierende<br />
Po<strong>in</strong>ter, die entweder auf Speicherstellen zeigen, auf die sie nicht zeigen sollen<br />
oder auch Fehl<strong>in</strong>terpretationen von Datentypen, gehören zu den schlimmsten<br />
Fehlern, die man machen kann. Schlimm s<strong>in</strong>d diese Fehler aus zwei Gründen:<br />
Erstens, weil sie sich nur allzu oft nicht sofort bemerkbar machen. Zweitens,<br />
weil im Normalfall ke<strong>in</strong> direkter Zusammenhang zwischen Ursache und Wirkung<br />
erkennbar ist. Gerade Typ-Fehl<strong>in</strong>terpretationen s<strong>in</strong>d außerordentlich<br />
schwer <strong>in</strong> e<strong>in</strong>em fehlerhaften Programm zu lokalisieren.<br />
Als erstes Beispiel zum Thema Po<strong>in</strong>ter versuchen wir e<strong>in</strong>mal, den Mechanismus<br />
der References, den wir bereits kennen gelernt haben, quasi “zu Fuß”<br />
zu implementieren. In C wäre diese Art der Implementation sogar notwendig,<br />
denn C kennt ke<strong>in</strong>e References (first_po<strong>in</strong>ter_demo.cpp):<br />
1 // f i r s t p o i n t e r d e m o . cpp − small demo o f p o i n t e r s <strong>in</strong> <strong>C++</strong><br />
2<br />
3 #<strong>in</strong>clude <br />
4 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
5<br />
6 us<strong>in</strong>g std : : cout ;<br />
7 us<strong>in</strong>g std : : endl ;<br />
8<br />
9 void changeVariable ( <strong>in</strong>t32 ∗ var ) ;<br />
10<br />
11 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
12 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
13 {<br />
14 <strong>in</strong>t32 my var = 12;<br />
15 <strong>in</strong>t32 ∗ my ptr = &my var ;<br />
16<br />
17 // my var and my ptr r e f e r to the same <strong>in</strong>t32 <strong>in</strong> memory<br />
18 // t h e r e f o r e they share the same value . . .<br />
19 cout
41 ” , ∗ my ptr : ”
118 6. Po<strong>in</strong>ter und References<br />
auslesen kann, sondern ebensogut auf diesem Weg e<strong>in</strong>en Wert zuweisen kann,<br />
zeigt sich <strong>in</strong> den Zeilen 29–31.<br />
Auch e<strong>in</strong>en call-by-reference kann man mittels Po<strong>in</strong>tern “zu Fuß” machen,<br />
wie Zeile 34 beweist: Die Funktion changeVariable nimmt als Parameter<br />
e<strong>in</strong>en Po<strong>in</strong>ter, der mittels &my_var übergeben wird. Damit zeigt der Parameter<br />
var der Funktion changeVariable auf die Speicherstelle von my_var<br />
und jede Änderung des Inhalts <strong>in</strong>nerhalb der Funktion wirkt sich natürlich<br />
auch außerhalb aus.<br />
E<strong>in</strong>es sieht man an diesem Programm allerd<strong>in</strong>gs auch: Alle Konstrukte,<br />
bei denen es im Pr<strong>in</strong>zip nur auf die Referenzierung von Speicherstellen ohne<br />
Aspekte dynamischer Speicherverwaltung ankommt, s<strong>in</strong>d mit References bei<br />
weitem eleganter lösbar. Deshalb rate ich <strong>in</strong> solchen Fällen unbed<strong>in</strong>gt dazu,<br />
für diese Aufgaben Po<strong>in</strong>ter nicht zu verwenden, auch wenn man sie aus C<br />
gewohnt ist.<br />
Vorsicht Falle: Auf e<strong>in</strong>e besondere Falle möchte ich hier auch noch aufmerksam<br />
machen, die wir bereits bei den References besprochen haben. Es<br />
hat auch bei Po<strong>in</strong>tern katastrophale Auswirkungen, wenn e<strong>in</strong> Po<strong>in</strong>ter als<br />
return-Wert aus e<strong>in</strong>er Funktion geliefert wird, der auf e<strong>in</strong>e Variable zeigt, die<br />
ihre Lifetime bereits h<strong>in</strong>ter sich hat.<br />
Vorsicht Falle: Es wird dr<strong>in</strong>gend angeraten, e<strong>in</strong>en Po<strong>in</strong>ter, den man nicht<br />
mehr <strong>in</strong> Verwendung hat, sofort explizit auf 0 zu setzen. Da 0 per Def<strong>in</strong>ition<br />
garantiert ke<strong>in</strong>e gültige Adresse repräsentiert, führt jeder Versuch des Dereferenzierens<br />
e<strong>in</strong>es solchen Po<strong>in</strong>ters garantiert zu e<strong>in</strong>er Segmentation Violation.<br />
Damit bemerkt man auf jeden Fall sofort, wenn man e<strong>in</strong>en Programmfehler<br />
e<strong>in</strong>gebaut hat. Ohne explizites Setzen auf 0 ist dies nicht garantiert!<br />
Konvention: NULL vs. 0. Leser, die Erfahrung mit C haben, werden sich<br />
gefragt haben, warum ich hier 0 als ungültige Adresse erwähne und nicht<br />
NULL, wie es <strong>in</strong> C üblich ist. Das NULL Macro, das sich <strong>in</strong> C e<strong>in</strong>gebürgert<br />
hat, kann <strong>in</strong> C ++ e<strong>in</strong> paar größere oder kle<strong>in</strong>ere Problemchen verursachen:<br />
Im Macro ist nämlich NULL vom Typ her als void * def<strong>in</strong>iert. E<strong>in</strong> C ++<br />
Compiler mit se<strong>in</strong>en, im Vergleich zu C, besseren Typüberprüfungs- und<br />
Umwandlungsstrategien sieht das dann so:<br />
• Die Zuweisung von 0 (also der Zahl 0) ist problemlos möglich, denn hierbei<br />
kann e<strong>in</strong>e implizite Umwandlung stattf<strong>in</strong>den. Es ist nämlich ke<strong>in</strong>e Konversion<br />
von e<strong>in</strong>em Po<strong>in</strong>tertyp auf e<strong>in</strong>en anderen notwendig.<br />
• Die Zuweisung von NULL würde e<strong>in</strong>e implizite Umwandlung von e<strong>in</strong>em<br />
Po<strong>in</strong>ter-Typ auf e<strong>in</strong>en anderen mit sich br<strong>in</strong>gen. Dies wird def<strong>in</strong>itiv als<br />
gefährlich erachtet.
6.2 Po<strong>in</strong>ter 119<br />
Wenn man aus guter alter Gewohnheit die F<strong>in</strong>ger nicht vom NULL Macro<br />
lassen kann, so sollte man NULL e<strong>in</strong>fach als 0 def<strong>in</strong>ieren (sollte dies nicht<br />
ohneh<strong>in</strong> bereits <strong>in</strong> e<strong>in</strong>em der Headers zum C ++ Compiler geschehen se<strong>in</strong>).<br />
6.2.2 Dynamische Memory Verwaltung<br />
Nach der Kurzbesprechung des Referenzmechanismus “zu Fuß”, der nun wirklich<br />
nicht das Berauschendste ist, wofür man <strong>in</strong> C ++ Po<strong>in</strong>ter verwenden kann<br />
bzw. soll, wenden wir uns jetzt dem Thema zu, das im Pr<strong>in</strong>zip die allerwichtigste<br />
Anwendung von Po<strong>in</strong>tern <strong>in</strong> Programmen darstellt: Die dynamische<br />
Memory Verwaltung.<br />
Unter dem Begriff der dynamischen Memory Verwaltung versteht man<br />
das Anfordern, Verwalten und Freigeben von Speicher je nach Bedarf zur<br />
Laufzeit. Ok, je nach Bedarf stimmt vielleicht nicht ganz, jedem Computer<br />
geht e<strong>in</strong>mal der Speicher aus :-).<br />
Ich möchte gleich zu Beg<strong>in</strong>n auf e<strong>in</strong>en ganz wichtigen Unterschied<br />
zwischen C und C ++ h<strong>in</strong>weisen: In C wurden die Funktionen malloc, realloc<br />
und free im Rahmen der dynamischen Memory Verwaltung verwendet. Aus<br />
sehr guten Gründen soll man genau von diesen Funktionen <strong>in</strong> C ++ tunlichst<br />
die F<strong>in</strong>ger lassen, außer für ganz spezielle Anwendungen. Dann muss man<br />
allerd<strong>in</strong>gs sehr genau wissen, was man tut! E<strong>in</strong>e Abhandlung zu diesem<br />
Thema f<strong>in</strong>det sich <strong>in</strong> Abschnitt 12.3.<br />
Als Ersatz stehen <strong>in</strong> C ++ die Operatoren new und delete zur Verfügung,<br />
die wir <strong>in</strong> Kürze genau unter die Lupe nehmen werden.<br />
An e<strong>in</strong>em kurzen Beispiel lässt sich jedenfalls am e<strong>in</strong>fachsten erklären, wie<br />
man pr<strong>in</strong>zipiell mit dynamischer Memory Verwaltung umgeht<br />
(first_dynamic_memory_demo.cpp):<br />
1 // first dynamic memory demo . cpp − p r i n c i p l e s o f dynamic memory<br />
2 // management <strong>in</strong> <strong>C++</strong><br />
3<br />
4 #<strong>in</strong>clude <br />
5 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
6<br />
7 us<strong>in</strong>g std : : cout ;<br />
8 us<strong>in</strong>g std : : endl ;<br />
9<br />
10 void show ( const <strong>in</strong>t32 array [ ] , u<strong>in</strong>t32 length ) ;<br />
11<br />
12 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
13 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
14 {<br />
15 // request memory f o r a s i n g l e element<br />
16 <strong>in</strong>t32 ∗ dyn var = new <strong>in</strong>t32 ;<br />
17 ∗dyn var = 10;<br />
18 cout
120 6. Po<strong>in</strong>ter und References<br />
24 // f r e e memory aga<strong>in</strong> us<strong>in</strong>g the s i n g l e v e r s i o n d e l e t e<br />
25 delete dyn var ;<br />
26<br />
27 dyn var = 0;<br />
28<br />
29 const u<strong>in</strong>t32 ARRAY LENGTH = 10;<br />
30<br />
31 // request memory f o r the array with new<br />
32 <strong>in</strong>t32 ∗ dyn array = new <strong>in</strong>t32 [ARRAY LENGTH] ;<br />
33<br />
34 for ( u<strong>in</strong>t32 count = 0; count < ARRAY LENGTH; count++)<br />
35 dyn array [ count ] = count ;<br />
36<br />
37 show ( dyn array ,ARRAY LENGTH) ;<br />
38<br />
39 <strong>in</strong>t32 ∗ i t e r a t i o n p t r = dyn array ;<br />
40 cout
6.2 Po<strong>in</strong>ter 121<br />
bei unseren kle<strong>in</strong>en Testprogrammen der Speicher nicht ausgehen wird<br />
und lassen diesen Aspekt außer Acht.<br />
C Programmierern, die noch malloc gewohnt s<strong>in</strong>d, das die Größe <strong>in</strong><br />
Bytes gesagt bekommen muss, wird new zu Beg<strong>in</strong>n etwas ungewohnt<br />
vorkommen. Aber damit lernt man schnell zu leben, weil es doch e<strong>in</strong>e<br />
deutliche Verbesserung darstellt.<br />
Vorsicht Falle: Der new Operator ist <strong>in</strong> se<strong>in</strong>er Standard-Implementation<br />
so def<strong>in</strong>iert, dass e<strong>in</strong>e implizite Initialisierung des Speichers nicht<br />
garantiert ist! Das bedeutet, dass unbed<strong>in</strong>gt immer e<strong>in</strong>e explizite Zuweisung<br />
e<strong>in</strong>es Wertes erfolgen muss, da man es sonst mit irgende<strong>in</strong>em<br />
zufälligen Wert zu tun hat. Dies erfolgt z.B. für unseren <strong>in</strong> Zeile 16<br />
angeforderten Speicher für e<strong>in</strong>en <strong>in</strong>t32 <strong>in</strong> Zeile 17.<br />
Vorsicht Falle: Entwicklern, die im Umgang mit Po<strong>in</strong>tern ungeübt<br />
s<strong>in</strong>d, passiert es leider nur zu gerne, dass sie den dereference Operator,<br />
also den *, bei e<strong>in</strong>er Zuweisung e<strong>in</strong>es Wertes vergessen. Schreibt man<br />
z.B. <strong>in</strong> Zeile 17 das Statement dyn_var = 10;, so führt das zu e<strong>in</strong>em<br />
Compilerfehler. Diese Zuweisung würde nämlich bedeuten, dass man<br />
dem Po<strong>in</strong>ter dyn_var die Adresse 10 zuweisen will und das ist nicht<br />
zulässig.<br />
Zeile 20: Speicher, den man dynamisch mittels new anfordert, bleibt so lange<br />
für das Programm reserviert, bis er explizit wieder freigegeben wird (ich<br />
klammere hier e<strong>in</strong>e mögliche Implementation von C ++ mittels Garbage-<br />
Collector bewusst aus). Das bedeutet, dass man sich unbed<strong>in</strong>gt immer<br />
selbst darum kümmern muss, dass alle angeforderten Speicherblöcke dem<br />
System auch wieder durch Aufruf des delete Operators zurückgegeben<br />
werden. In unserem Fall bewirkt der Aufruf delete dyn_var das Freigeben<br />
des zuvor angeforderten <strong>in</strong>t32.<br />
Vorsicht Falle: Der Operator delete ist so def<strong>in</strong>iert, dass er natürlich<br />
nur Speicher wieder freigeben kann, der mit new angefordert wurde. Daher<br />
darf man delete nur mit e<strong>in</strong>em Base-Po<strong>in</strong>ter aufrufen, den man von<br />
new bekommen hat. Ruft man delete mit irgende<strong>in</strong>em anderen als e<strong>in</strong>em<br />
von new erhaltenen Base-Po<strong>in</strong>ter auf, so endet dieser Aufruf mit<br />
e<strong>in</strong>er Segmentation Violation.<br />
Vorsicht Falle: Niemals darf man den Base-Po<strong>in</strong>ter e<strong>in</strong>es Speicherblocks<br />
verlieren! Sollte dies passieren, so kann der Speicher nicht mehr<br />
freigegeben werden (womit sollte man denn delete aufrufen?) und damit<br />
wächst das Programm beständig! Wie bereits erwähnt: Speicher, der mit<br />
new angefordert wird, wird vom System nicht automatisch wieder freigegeben!<br />
Vergisst man e<strong>in</strong> delete, so gammelt der betreffende Block bis
122 6. Po<strong>in</strong>ter und References<br />
zum Beenden des Programms im Speicher herum. So e<strong>in</strong> Beenden des<br />
Programms kann dann natürlich auch passieren, weil ke<strong>in</strong> Speicher mehr<br />
da ist und das Programm deshalb vom System abgewürgt wird :-).<br />
Vorsicht Falle: In C ++ ist delete so def<strong>in</strong>iert, dass man es auch mit<br />
e<strong>in</strong>em 0 Po<strong>in</strong>ter ungestraft aufrufen darf. In diesem Fall passiert e<strong>in</strong>fach<br />
gar nichts. Allerd<strong>in</strong>gs... manche (zum Glück wenige) Compiler bzw.<br />
Laufzeitumgebungen sehen das anders. In solchen Fällen endet delete<br />
auf e<strong>in</strong>en 0 Po<strong>in</strong>ter mit e<strong>in</strong>er Segmentation Violation!<br />
Das bedeutet, dass e<strong>in</strong> Aufruf von delete ohne vorherige Abfrage auf<br />
0 auf der jeweiligen Plattform aus Sicherheitsgründen getestet werden<br />
muss. Aus diesem Grund habe ich <strong>in</strong> diesem Buch dort, wo ke<strong>in</strong>e besondere<br />
Unübersichtlichkeit dadurch entsteht, die Abfragen auf 0 <strong>in</strong> den<br />
Beispielen verewigt. In der Praxis wird dies normalerweise nicht gemacht.<br />
Zeile 22: Hier sieht man, dass man natürlich e<strong>in</strong>e Po<strong>in</strong>ter Variable beliebig<br />
oft verwenden kann. Nachdem der Speicher, der sich h<strong>in</strong>ter dyn_var versteckt<br />
hat, freigegeben wurde, kann man auch wieder neuen anfordern.<br />
Die Anforderung mittels new sieht allerd<strong>in</strong>gs hier etwas anders aus, als<br />
zuvor <strong>in</strong> Zeile 16! Hier wird e<strong>in</strong>e Anforderung von Speicher mit e<strong>in</strong>er expliziten<br />
Initialisierung komb<strong>in</strong>iert. Der Wert, auf den <strong>in</strong>itialisiert werden<br />
soll, wird dem Datentyp <strong>in</strong> runden Klammern nachgestellt. Dies ist auch<br />
die von mir empfohlene Art der Speicheranforderung, da hierbei immer<br />
garantiert e<strong>in</strong> <strong>in</strong>itialisierter Speicherblock geliefert wird. Der Output,<br />
der <strong>in</strong> Zeile 23 generiert wird, beweist, dass die Initialisierung erfolgreich<br />
war.<br />
Zeile 27: Hier sieht man, was man unbed<strong>in</strong>gt tun soll, wenn e<strong>in</strong>e Po<strong>in</strong>ter Variable<br />
nicht mehr auf e<strong>in</strong>en existenten Speicherblock zeigt: Man setzt<br />
den Po<strong>in</strong>ter explizit auf 0! Damit ist garantiert, dass jeglicher Zugriff,<br />
schreibend oder lesend, zu e<strong>in</strong>er Segmentation Violation führt. Ansonsten<br />
könnte es se<strong>in</strong>, und das ist gar nicht so selten, dass man unbeabsichtigt<br />
irgendwann “irgende<strong>in</strong>en” Speicher verändert. Solche Fehler zu<br />
f<strong>in</strong>den ist, wie schon öfter besprochen, der größte Spaß im Alltag von<br />
Entwicklern.<br />
Zeile 32: Wenn man schon dynamisch Speicher anfordern will, dann natürlich<br />
nicht immer nur Element für Element, das wäre absurd. Sehr oft braucht<br />
man Platz für viele Elemente auf e<strong>in</strong>mal. Dies funktioniert natürlich<br />
auch, <strong>in</strong>dem man gleich e<strong>in</strong> ganzes Array anfordert. Der entsprechende<br />
Aufruf von new[] ist <strong>in</strong>tuitiv: Man stellt e<strong>in</strong>fach die Anzahl der<br />
gewünschten Elemente des Arrays <strong>in</strong> eckigen Klammern dem Datentypen<br />
nach. Der Base-Po<strong>in</strong>ter, der von new geliefert wird, zeigt dann auf<br />
den Anfang des dynamischen Arrays, also auf das erste Element desselben.
6.2 Po<strong>in</strong>ter 123<br />
Zeilen 34–35: Hier sieht man, dass man mit e<strong>in</strong>em dynamischen Array, das<br />
durch e<strong>in</strong>en Po<strong>in</strong>ter repräsentiert wird, gleich umgehen kann, wie es schon<br />
von e<strong>in</strong>em statischen Array bekannt ist: Man kann zum Zugriff auf e<strong>in</strong><br />
bestimmtes Element e<strong>in</strong>fach den Index dieses Elements <strong>in</strong> eckigen Klammern<br />
dem Po<strong>in</strong>ter nachstellen.<br />
Nicht nur, dass sich e<strong>in</strong> Po<strong>in</strong>ter und e<strong>in</strong>e (statische) Array-Variable <strong>in</strong><br />
dieser Beziehung gleich verhalten, sie werden <strong>in</strong>tern tatsächlich beide<br />
durch e<strong>in</strong>en Po<strong>in</strong>ter realisiert! Man kann also e<strong>in</strong>e (statische) Array-<br />
Variable auch e<strong>in</strong>fach e<strong>in</strong>em Po<strong>in</strong>ter zuweisen, sofern dieser den korrekten<br />
Typ hat. Durch diese Zuweisung zeigt dann der Po<strong>in</strong>ter direkt auf das<br />
statische Array. Sehr oft sieht man vor allem bei Parameterlisten, dass<br />
die beiden Arten von Arrays vom Typ her beliebig austauschbar s<strong>in</strong>d.<br />
Vorsicht Falle: Auch wenn sich statische und dynamische Arrays gleich<br />
verhalten und <strong>in</strong>tern gleich repräsentiert werden, gibt es e<strong>in</strong>en riesigen<br />
Unterschied, wie (und auch wo) der Speicher für sie bereitgestellt wird.<br />
Es darf dementsprechend niemals versucht werden, den Speicher, der für<br />
e<strong>in</strong> statisches Array allokiert ist, mittels delete freizugeben, denn dies<br />
führt geradewegs <strong>in</strong>s Verderben.<br />
Vorsicht Falle: Es gibt ke<strong>in</strong>e Möglichkeit, zur Laufzeit herauszuf<strong>in</strong>den,<br />
für wie viele Elemente nun Speicher angefordert wurde! Daher muss man<br />
selbst Sorge dafür tragen, dass man nicht über die Grenzen e<strong>in</strong>es Arrays<br />
h<strong>in</strong>ausschreibt.<br />
Zeilen 39–41: Auch die Po<strong>in</strong>ter-Arithmetik funktioniert <strong>in</strong> C ++ ganz gleich<br />
wie <strong>in</strong> C: Wenn man zu e<strong>in</strong>em Po<strong>in</strong>ter e<strong>in</strong>e Zahl addiert, dann bewirkt<br />
man, dass der Po<strong>in</strong>ter um genau so viele Elemente (nicht Bytes!!!) im<br />
Array vorrückt, wie die Zahl besagt. Subtrahiert man e<strong>in</strong>e Zahl, dann<br />
geht das Spielchen natürlich <strong>in</strong> die Gegenrichtung. In Zeile 41 machen<br />
wir uns dies zunutze, <strong>in</strong>dem e<strong>in</strong>fach der Wert 5 zum iteration_ptr<br />
addiert wird, woraufh<strong>in</strong> der Po<strong>in</strong>ter auf das sechste Element (also das<br />
mit Index 5) zeigt. Dereferenziert man diese Adresse, so kommt man<br />
zum dah<strong>in</strong>terstehenden Wert.<br />
Vorsicht Falle: Oft wird von Neul<strong>in</strong>gen der Fehler gemacht, e<strong>in</strong>e Anweisung<br />
wie *(iteration_ptr + 5) nicht zu klammern, also stattdessen<br />
*iteration_ptr + 5 h<strong>in</strong>zuschreiben. Diese zwei Statements haben allerd<strong>in</strong>gs<br />
e<strong>in</strong>e grundverschiedene Bedeutung:<br />
*(iteration_ptr + 5) bedeutet, dass zuerst der Po<strong>in</strong>ter um 5 Elemente<br />
versetzt wird und danach der dort gespeicherte Wert dereferenziert wird.<br />
*iteration_ptr + 5 bedeutet im Gegensatz dazu, dass zuerst der Po<strong>in</strong>ter<br />
dereferenziert wird und danach zu diesem Wert 5 addiert wird.
124 6. Po<strong>in</strong>ter und References<br />
Zeile 44: Egal, ob man e<strong>in</strong> e<strong>in</strong>zelnes Element oder e<strong>in</strong> Array anfordert, man<br />
muss sich um die Freigabe des Speichers kümmern, wenn er nicht mehr<br />
gebraucht wird. Und hier besteht e<strong>in</strong> kle<strong>in</strong>er Unterschied zwischen e<strong>in</strong>em<br />
dynamisch angeforderten E<strong>in</strong>zelelement und e<strong>in</strong>em Array: Bei e<strong>in</strong>em Array<br />
muss der delete[] Operator aufgerufen werden, wie <strong>in</strong> Zeile 44 zu<br />
sehen.<br />
Vorsicht Falle: Nicht nur von Neul<strong>in</strong>gen wird oft der Unterschied zwischen<br />
delete und delete[] ignoriert. Dies geht auch zumeist ohne<br />
gröbere Probleme ab, jedoch kann das nicht garantiert werden! H<strong>in</strong>ter<br />
den beiden Formen von delete verstecken sich nämlich tatsächlich<br />
verschiedene Implementationen, die auch von den Entwicklern selbst im<br />
Zuge e<strong>in</strong>es Overload<strong>in</strong>gs geschrieben se<strong>in</strong> könnten.<br />
Deshalb möchte ich hier e<strong>in</strong>e sehr e<strong>in</strong>dr<strong>in</strong>gliche Warnung aussprechen,<br />
auch wenn mancherorts <strong>in</strong> der Literatur (unrichtigerweise) Gegenteiliges<br />
behauptet wird: delete und delete[] s<strong>in</strong>d nicht äquivalent<br />
und dürfen auch nicht als äquivalent angesehen werden, obwohl<br />
sie sich oberflächlich von außen betrachtet gleich zu verhalten<br />
sche<strong>in</strong>en. Durch die Verwendung von delete wird das Freigeben<br />
e<strong>in</strong>es E<strong>in</strong>zelelements veranlasst, durch die Verwendung von<br />
delete[] wird das Freigeben e<strong>in</strong>es Arrays veranlasst. Wo auch<br />
immer e<strong>in</strong> e<strong>in</strong>faches new verwendet wird, muss der Speicher<br />
auch mit e<strong>in</strong>em e<strong>in</strong>fachen delete freigegeben werden. Bei Verwendung<br />
e<strong>in</strong>es new ...[...] muss der Speicher auch mit e<strong>in</strong>em<br />
delete[]freigegeben werden.<br />
Vorsicht Falle: Genauso, wie man nicht feststellen kann, wie viel Speicher<br />
allokiert wurde, lässt sich auch nicht feststellen, ob nun e<strong>in</strong> Array<br />
oder nur e<strong>in</strong> E<strong>in</strong>zelelement allokiert wurde. Das bedeutet, dass man ohne<br />
selbst gemerkte Zusatz<strong>in</strong>formation zur Laufzeit nicht mehr herausf<strong>in</strong>den<br />
kann, welche der delete Varianten man nun nehmen muss.<br />
Vorsicht Falle: Nicht nur Neul<strong>in</strong>ge sitzen manchmal e<strong>in</strong>em folgenschweren<br />
Irrtum auf: Ob etwas nun für den Compiler bzw. zur Laufzeit e<strong>in</strong> Array<br />
darstellt oder nicht, ist durch die Art der Anforderung bestimmt,<br />
nicht durch die Anzahl der Elemente! Auch wenn new <strong>in</strong>t32[1] nur<br />
Speicher für e<strong>in</strong> Element liefert, stellt das Ergebnis doch e<strong>in</strong> dynamisches<br />
Array dar und muss mit delete[] gelöscht werden! Auch hier ist<br />
die Verwendung des e<strong>in</strong>fachen delete nicht zulässig!<br />
Zeile 45: Hier treffen wir wieder Altbekanntes: Auch für Po<strong>in</strong>ter, die e<strong>in</strong><br />
dynamisches Array repräsentieren, gilt, dass sie nach Gebrauch auf 0<br />
gesetzt werden sollten.
6.2 Po<strong>in</strong>ter 125<br />
Zeilen 51–56: Die Funktion show, die hier def<strong>in</strong>iert, ist demonstriert die Austauschbarkeit<br />
von Po<strong>in</strong>ter- und Array Variablen. Hier wird als erster<br />
Parameter e<strong>in</strong> Array entgegengenommen (const, denn wir wollen ja daran<br />
nichts ändern!). In Zeile 54 wird durch dieses Array e<strong>in</strong>fach mittels<br />
Po<strong>in</strong>terarithmetik durchgegangen.<br />
Der Grund, warum e<strong>in</strong> Array-Parameter genommen wurde, liegt auf der<br />
Hand: Man will damit signalisieren, dass man den Parameter wie e<strong>in</strong><br />
Array behandelt, auch wenn dieses eventuell nur die Länge 1 hätte.<br />
Der Grund, warum Po<strong>in</strong>terarithmetik trotzdem funktioniert, ist die bereits<br />
erwähnte Austauschbarkeit von statischen Arrays und Po<strong>in</strong>tern, da<br />
auch e<strong>in</strong> statisches Array h<strong>in</strong>ter den Kulissen als Po<strong>in</strong>ter dargestellt wird.<br />
Der Ausdruck *array++ bedeutet: Dereferenziere zuerst den Po<strong>in</strong>ter und<br />
liefere damit se<strong>in</strong>en Wert. Danach zähle den Po<strong>in</strong>ter (nicht den Wert!!!)<br />
um e<strong>in</strong> Element weiter. Dadurch wird das Array Schritt für Schritt durchgegangen.<br />
Der Grund, warum e<strong>in</strong>e Veränderung des Parameters array funktioniert,<br />
obwohl er durch const mit e<strong>in</strong>em Schreibschutz versehen wurde, ist e<strong>in</strong>fach:<br />
const bezieht sich auf den Inhalt, der sich h<strong>in</strong>ter dem Po<strong>in</strong>ter<br />
verbirgt, nicht auf die Po<strong>in</strong>tervariable, die ja nur e<strong>in</strong>e Kopie der Adresse<br />
enthält (call-by-value!). Wollte man e<strong>in</strong>em Element des übergebenen Arrays<br />
e<strong>in</strong>en neuen Wert zuweisen, dann würde sich natürlich der Compiler<br />
beschweren.<br />
6.2.3 Str<strong>in</strong>gs<br />
In C ++ werden, gleich wie <strong>in</strong> C, Str<strong>in</strong>gs e<strong>in</strong>fach durch statische oder dynamische<br />
char Arrays repräsentiert. E<strong>in</strong>e besondere Eigenschaft von Str<strong>in</strong>gs<br />
ist hierbei allerd<strong>in</strong>gs wichtig: Sie müssen immer mit e<strong>in</strong>em ’\0’ Character<br />
abgeschlossen werden!<br />
Im Pr<strong>in</strong>zip war das auch schon alles, was es dazu zu sagen gibt und ich<br />
halte mich derzeit noch bewusst bedeckt mit weiteren Ausführungen und<br />
e<strong>in</strong>em Demoprogramm dazu, denn gerade Str<strong>in</strong>gs s<strong>in</strong>d e<strong>in</strong>e der ganz typischen<br />
Anwendungen, bei denen uns durch die Objektorientiertheit von C ++<br />
die Arbeit deutlich erleichtert wird und die Gefahr von Fehlern deutlich verr<strong>in</strong>gert<br />
wird. E<strong>in</strong>e saubere, zeitgemäße OO-Repräsentation von Str<strong>in</strong>gs wird<br />
<strong>in</strong> Abschnitt 16.5 besprochen.<br />
6.2.4 Funktionspo<strong>in</strong>ter<br />
Wie bereits erwähnt wurde, kann man mit Po<strong>in</strong>tern nicht nur auf Daten<br />
zugreifen, sondern man kann auch Po<strong>in</strong>ter auf Funktionen zeigen lassen. Auf<br />
Umweg über solche Po<strong>in</strong>ter kann man diese Funktionen dann aufrufen.<br />
Funktionspo<strong>in</strong>ter s<strong>in</strong>d allerd<strong>in</strong>gs nicht ganz ungefährlich. Genau deswegen<br />
möchte ich hier nur erwähnen, dass es Funktionspo<strong>in</strong>ter gibt, dass jedoch
126 6. Po<strong>in</strong>ter und References<br />
durch die Objektorientierung von C ++ im Gegensatz zu C nur noch sehr wenige<br />
E<strong>in</strong>satzgebiete derselben existieren. Es gibt viel sauberere Konstrukte,<br />
die dieselbe Aufgabe erfüllen. Salopp gesagt sollte man eigentlich im normalen<br />
Entwicklungsalltag die F<strong>in</strong>ger von Funktionspo<strong>in</strong>tern lassen. Deshalb<br />
wurde die Diskussion über sie auch sehr weit nach h<strong>in</strong>ten verlegt, nämlich <strong>in</strong><br />
Abschnitt 15.3 :-).<br />
6.2.5 Besondere Aspekte von Po<strong>in</strong>tern<br />
Po<strong>in</strong>ter s<strong>in</strong>d pr<strong>in</strong>zipiell mit e<strong>in</strong>em hohen Fehlerrisiko verbunden, aber sie s<strong>in</strong>d<br />
aus vielerlei Gründen <strong>in</strong> C ++ nicht wegzudenken. Was auch immer bisher zu<br />
Po<strong>in</strong>tern gesagt wurde und <strong>in</strong> der Folge noch gesagt wird, gehört zum täglichen<br />
Brot von C ++ Entwicklern. Allerd<strong>in</strong>gs möchte ich an dieser Stelle noch<br />
unbed<strong>in</strong>gt darauf h<strong>in</strong>weisen, dass im OO-Teil des Buchs e<strong>in</strong>ige Möglichkeiten<br />
besprochen werden, wie man potentielle Fehlerquellen elim<strong>in</strong>ieren kann, <strong>in</strong>dem<br />
man Po<strong>in</strong>ter sauber <strong>in</strong> Classes kapselt, anstatt sie “wild” im laufenden<br />
Programmcode zu verwenden. Trotz aller Warnungen möchte ich trotzdem<br />
alle Leser dazu motivieren, sich mit Po<strong>in</strong>tern ausgiebig zu spielen, denn dies<br />
ist die beste Möglichkeit, e<strong>in</strong> gutes Gefühl dafür zu bekommen. Es sollten<br />
jedoch die derzeitigen “Spielereien” der Leser nicht <strong>in</strong> wichtigen (also produktmäßigen)<br />
Programmcode e<strong>in</strong>fließen. Ok, genug gewarnt, kommen wir<br />
wieder zurück zum Thema der besonderen Aspekte von Po<strong>in</strong>tern.<br />
Call-by-reference auf Po<strong>in</strong>ter. Auch Po<strong>in</strong>tervariablen können natürlich<br />
Teilnehmer an e<strong>in</strong>em call-by-reference se<strong>in</strong>, der den Inhalt des Po<strong>in</strong>ters selbst,<br />
also die Adresse auf die er zeigt, nachhaltig verändert. Wie das typisch<br />
für C ++ mittels References gelöst wird, sieht man an folgendem Programm<br />
(ptr_call_by_ref_demo.cpp):<br />
1 // p t r c a l l b y r e f d e m o . cpp − c a l l by r e f e r e n c e with p o i n t e r s<br />
2<br />
3 #<strong>in</strong>clude <br />
4 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
5<br />
6 us<strong>in</strong>g std : : cout ;<br />
7 us<strong>in</strong>g std : : endl ;<br />
8<br />
9 void assignClone ( <strong>in</strong>t32 ∗& dst , const <strong>in</strong>t32 s r c [ ] , u<strong>in</strong>t32 length ) ;<br />
10<br />
11 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
12 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
13 {<br />
14 const u<strong>in</strong>t32 ARRAY LENGTH = 10;<br />
15 <strong>in</strong>t32 ∗ s r c a r r a y = new <strong>in</strong>t32 [ARRAY LENGTH] ;<br />
16 <strong>in</strong>t32 ∗ d s t a r r a y = 0;<br />
17<br />
18 for ( u<strong>in</strong>t32 count = 0; count < ARRAY LENGTH; count++)<br />
19 s r c a r r a y [ count ] = count ;<br />
20<br />
21 assignClone ( d s t a r r a y , s r c a r r a y ,ARRAY LENGTH) ;<br />
22<br />
23 <strong>in</strong>t32 ∗ s r c i t e r a t i o n p t r = s r c a r r a y ;<br />
24 <strong>in</strong>t32 ∗ d s t i t e r a t i o n p t r = d s t a r r a y ;
25 for ( u<strong>in</strong>t32 count = 0 ; count < ARRAY LENGTH; count++)<br />
26 cout
128 6. Po<strong>in</strong>ter und References<br />
4. src wird um e<strong>in</strong> Element weitergerückt.<br />
5. dst wird um e<strong>in</strong> Element weitergerückt.<br />
Und nun zum negativen Aspekt...<br />
Vorsicht Falle: Weil wir es bei dst mit e<strong>in</strong>er Reference zu tun haben,<br />
dürfen wir diese natürlich niemals direkt bei unseren Kopierschritten <strong>in</strong> Zeile<br />
49 verwenden. Damit würden wir nämlich auch die Adresse im Orig<strong>in</strong>al<br />
nachhaltig verändern (was ja <strong>in</strong> e<strong>in</strong>em anderen Zusammenhang, nämlich bei<br />
der Zuweisung des angeforderten Speichers, erwünscht ist). Also müssen wir<br />
e<strong>in</strong>e lokale Kopie davon machen, was <strong>in</strong> Zeile 47 passiert.<br />
Vorsicht Falle: Noch etwas wird bei solcherlei Konstrukten leider nur viel zu<br />
gern vergessen: Der Speicher, der <strong>in</strong> der Funktion assignClone angefordert<br />
wird, muss natürlich auch irgendwo wieder freigegeben werden, wenn er nicht<br />
mehr gebraucht wird. Dass das nur irgendwo beim Aufrufer stattf<strong>in</strong>den kann,<br />
ist auch klar. Dass das aber wirklich unbed<strong>in</strong>gt auch dort stattf<strong>in</strong>den muss,<br />
wird gerne e<strong>in</strong>fach unter den Tisch gekehrt... natürlich nur <strong>in</strong> Programmen<br />
anderer Leute :-). Bei uns wird explizit der Speicher für den Clone <strong>in</strong> Zeile 31<br />
wieder freigegeben.<br />
Die Zeile 36, <strong>in</strong> der beide eben freigegebenen Po<strong>in</strong>ter auf 0 gesetzt werden,<br />
ersche<strong>in</strong>t hier als Overkill. Jedoch tut die Zeile niemandem weh und<br />
außerdem kann es ja se<strong>in</strong>, dass durch e<strong>in</strong>e spätere Programmänderung nach<br />
Zeile 33 noch e<strong>in</strong>iges an Code dazukommt, der eventuell diese beiden Variablen<br />
irrtümlich versuchen würde zu verwenden, im Glauben, dass dah<strong>in</strong>ter<br />
noch Speicher allokiert ist. E<strong>in</strong>e solche “unnötige” Zeile kann also e<strong>in</strong>e wertvolle<br />
Zukunfts<strong>in</strong>vestition se<strong>in</strong>!<br />
Mehrfachpo<strong>in</strong>ter. Wie bereits bei Arrays gezeigt wurde, dass diese mehrdimensional<br />
se<strong>in</strong> können, so verhält es sich natürlich auch bei den Po<strong>in</strong>tern.<br />
Um dies zu demonstrieren, schnappen wir uns e<strong>in</strong>fach das letzte Demoprogramm<br />
und ändern es dergestalt ab, dass wir es mit e<strong>in</strong>er Matrix zu tun<br />
haben (multi_ptr_demo.cpp):<br />
1 // multi ptr demo . cpp − a short demo f o r multi−p o i n t e r s<br />
2<br />
3 #<strong>in</strong>clude <br />
4 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
5<br />
6 us<strong>in</strong>g std : : cout ;<br />
7 us<strong>in</strong>g std : : endl ;<br />
8<br />
9 void assignClone ( <strong>in</strong>t32 ∗∗& dst , const <strong>in</strong>t32 ∗ const ∗ s r c ,<br />
10 u<strong>in</strong>t32 num rows , u<strong>in</strong>t32 num cols ) ;<br />
11<br />
12 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
13 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
14 {<br />
15 const u<strong>in</strong>t32 NUMROWS = 5;
16 const u<strong>in</strong>t32 NUM COLS = 7;<br />
17<br />
6.2 Po<strong>in</strong>ter 129<br />
18 <strong>in</strong>t32 ∗∗ src matrix = new <strong>in</strong>t32 ∗ [NUMROWS] ;<br />
19 for ( u<strong>in</strong>t32 row count = 0; row count < NUMROWS; row count++)<br />
20 {<br />
21 src matrix [ row count ] = new <strong>in</strong>t32 [NUM COLS] ;<br />
22 for ( u<strong>in</strong>t32 col count = 0; col c o unt < NUM COLS; col c o unt++)<br />
23 src matrix [ row count ] [ col count ] =<br />
24 ( ( row count + 1) ∗ 100) + c o l c o unt + 1;<br />
25 }<br />
26<br />
27 <strong>in</strong>t32 ∗∗ dst matrix = 0;<br />
28<br />
29 assignClone ( dst matrix , src matrix ,NUMROWS,NUM COLS) ;<br />
30<br />
31 for ( u<strong>in</strong>t32 row count = 0; row count < NUMROWS; row count++)<br />
32 {<br />
33 for ( u<strong>in</strong>t32 col count = 0; col c o unt < NUM COLS; col c o unt++)<br />
34 cout
130 6. Po<strong>in</strong>ter und References<br />
für sich hält dann die Reihen. Bleiben wir aber bei unserer ersten Def<strong>in</strong>ition,<br />
denn diese wurde im Programm implementiert.<br />
In Zeile 18 wird e<strong>in</strong>mal das Array angelegt, das die Reihen repräsentiert.<br />
Wenn man sich überlegt, dass e<strong>in</strong>e Reihe quasi e<strong>in</strong> Array von <strong>in</strong>t32 Werten<br />
ist, also e<strong>in</strong> <strong>in</strong>t32* dann ergibt sich für die Reihen, dass diese vom Typ e<strong>in</strong><br />
<strong>in</strong>t32**, also e<strong>in</strong> Po<strong>in</strong>ter auf (viele) Po<strong>in</strong>ter auf <strong>in</strong>t32 ist.<br />
In der äußeren Schleife, deren Kopf <strong>in</strong> Zeile 19 zu f<strong>in</strong>den ist, wird für jede<br />
e<strong>in</strong>zelne Reihe e<strong>in</strong>mal e<strong>in</strong> Array angelegt, das die Werte halten kann, also die<br />
Spalten. Dies passiert <strong>in</strong> Zeile 21. In der <strong>in</strong>neren Schleife von Zeile 22–24<br />
werden nur noch den e<strong>in</strong>zelnen Zellen Werte zugewiesen.<br />
Lassen wir kurz noch die Funktion außer Acht, die den Clone erzeugt und<br />
wenden wir uns den Zeilen zu, die den von der Matrix belegten Speicher dann<br />
auch wieder freigeben. Dies passiert immer von <strong>in</strong>nen nach außen. Zuerst<br />
werden die e<strong>in</strong>zelnen Reihen <strong>in</strong> e<strong>in</strong>er Schleife freigegeben. Danach erst wird<br />
das Array freigegeben, das die e<strong>in</strong>zelnen Reihen gehalten hat. Dass dies nur<br />
<strong>in</strong> dieser Reihenfolge geht, lässt sich leicht nachvollziehen, denn wenn man<br />
das äußere Array freigibt, dann hat man die Base-Po<strong>in</strong>ter für die <strong>in</strong>neren<br />
Arrays damit verloren.<br />
Vorsicht Falle: E<strong>in</strong>er der ganz typischen Fehler von Neul<strong>in</strong>gen ist es, anzunehmen,<br />
dass e<strong>in</strong> delete[] auf das äußerste Array automatisch auch die<br />
e<strong>in</strong>zelnen <strong>in</strong>neren Arrays löscht, also quasi alles aufräumt. Dies ist ke<strong>in</strong>eswegs<br />
der Fall! Vergisst man, die e<strong>in</strong>zelnen <strong>in</strong>neren Arrays zu löschen, dann<br />
erzeugt man e<strong>in</strong> tolles Programm, das nach genügend langer Laufzeit zu e<strong>in</strong>em<br />
guten Test wird, wie sich e<strong>in</strong> Rechner verhält, dem langsam aber sicher<br />
der Speicher ausgeht :-).<br />
Wenn wir uns nun noch kurz der assignClone Funktion <strong>in</strong> Zeile 51 zuwenden,<br />
die im Pr<strong>in</strong>zip genau nach dem Pr<strong>in</strong>zip funktioniert, das bereits<br />
zuvor zum Anlegen der Matrix beschrieben wurde, dann fällt uns noch e<strong>in</strong>e<br />
Besonderheit auf: Der zweite Parameter, also das src Array ist nun wirklich<br />
sehr komisch deklariert. Was um alles <strong>in</strong> der Welt soll der Ausdruck<br />
const <strong>in</strong>t32 *const *src<br />
bedeuten? Überlegen wir e<strong>in</strong>mal ganz kurz, was es z.B. mit e<strong>in</strong>em Po<strong>in</strong>ter<br />
auf e<strong>in</strong>en <strong>in</strong>t32 so auf sich hat. Im Pr<strong>in</strong>zip s<strong>in</strong>d ja daran zwei verschiedene<br />
Typen beteiligt, nämlich e<strong>in</strong>erseits e<strong>in</strong> Po<strong>in</strong>ter und andererseits der <strong>in</strong>t32,<br />
auf den er zeigt. Um die volle Kontrolle zu haben, will man jeden Teil für<br />
sich konstant machen können oder auch nicht. Und genau dies ist <strong>in</strong> C ++<br />
auch möglich, denn e<strong>in</strong>e Variable<br />
const <strong>in</strong>t32 *var;<br />
stellt e<strong>in</strong>en (nicht konstanten) Po<strong>in</strong>ter auf e<strong>in</strong>en konstanten <strong>in</strong>t32 dar. Dagegen<br />
stellt e<strong>in</strong>e Variable<br />
<strong>in</strong>t32 *const var;
6.2 Po<strong>in</strong>ter 131<br />
e<strong>in</strong>en konstanten Po<strong>in</strong>ter auf e<strong>in</strong>en (nicht konstante) <strong>in</strong>t32 dar. Wenn man<br />
dies weitersp<strong>in</strong>nt, ist naturgemäß e<strong>in</strong>e Variable<br />
const <strong>in</strong>t32 *const var;<br />
e<strong>in</strong> konstanter Po<strong>in</strong>ter auf e<strong>in</strong>en konstanten <strong>in</strong>t32. Genau so etwas brauchen<br />
wir hier, nur dass var selbst e<strong>in</strong> Array ist. Wir wollen ja e<strong>in</strong>en Doppelpo<strong>in</strong>ter<br />
an die Funktion übergeben, bei dem weder die e<strong>in</strong>zelnen Werte, die er hält,<br />
also die Reihen, noch die Werte, die von diesen gehalten werden, also die<br />
tatsächlichen Zellen, verändert werden können. Also haben wir es hier mit<br />
e<strong>in</strong>em Po<strong>in</strong>ter auf e<strong>in</strong> konstantes Array auf konstante Werte zu tun.<br />
Da ich weiß, dass viele Entwickler e<strong>in</strong> kle<strong>in</strong>es Problem damit haben, const<br />
korrekt <strong>in</strong> komplizierteren Konstrukten e<strong>in</strong>zusetzen, wie z.B. References auf<br />
Po<strong>in</strong>ter-Variablen, etc. Deshalb hier e<strong>in</strong> kle<strong>in</strong>er Tipp: const ist l<strong>in</strong>ksb<strong>in</strong>dend,<br />
soll heißen, es bezieht sich pr<strong>in</strong>zipiell immer auf den l<strong>in</strong>ks von ihm stehenden<br />
Teil des Typs. Dass man const auch quasi rechtsb<strong>in</strong>dend verwenden kann, ist<br />
nur e<strong>in</strong>e Konvention, die der verbesserten Lesbarkeit dienen soll. Sollte also<br />
nach dieser Konvention const ganz l<strong>in</strong>ks stehen, so wird es so ausgewertet,<br />
als ob es rechts neben dem ersten Ausdruck vorkommen würde. Verwirrend?<br />
Ke<strong>in</strong> Problem, mit e<strong>in</strong>em kle<strong>in</strong>en Beispiel ist alles klar:<br />
Der Ausdruck<br />
const <strong>in</strong>t32 *var<br />
ist äquivalent zu<br />
<strong>in</strong>t32 const *var<br />
und sollte, wenn man ganz korrekt se<strong>in</strong> will, auch so geschrieben werden.<br />
Nur hat sich <strong>in</strong> Entwicklerkreisen über lange Zeit die erstere Schreibweise<br />
e<strong>in</strong>gebürgert, deshalb möchte ich, wo es nicht notwendig ist, damit auch<br />
nicht brechen.<br />
Vorsicht Falle: Von Neul<strong>in</strong>gen, aber auch leider von erfahreneren Entwicklern,<br />
werden immer wieder völlig unzulässige Annahmen über die Kompatibilität<br />
von Mehrfacharrays und Mehrfachpo<strong>in</strong>tern getroffen. Diese beiden<br />
Typen s<strong>in</strong>d nicht notwendigerweise gegene<strong>in</strong>ander austauschbar, denn es<br />
gibt ke<strong>in</strong>e genaue Def<strong>in</strong>ition, wie mehrdimensionale Arrays nun <strong>in</strong>tern speichermäßig<br />
organisiert s<strong>in</strong>d.<br />
Vorsicht Falle: E<strong>in</strong>es sieht man am Beispiel mit den Mehrfachpo<strong>in</strong>tern sehr<br />
deutlich: Es ist wirklich nicht allzu schwierig, Fehler bei deren Behandlung<br />
e<strong>in</strong>zubauen! Aus diesem Grund s<strong>in</strong>d auch solche Konstrukte im laufenden Code<br />
von C ++ Programmen verpönt und sollten unbed<strong>in</strong>gt durch entsprechende<br />
Classes ersetzt werden, die solches Verhalten kapseln. Zu diesem Thema<br />
werde ich im OO-Teil noch mehr als nur e<strong>in</strong> paar Worte verlieren :-).<br />
Po<strong>in</strong>ter und Typecasts. Po<strong>in</strong>ter halten per Def<strong>in</strong>ition e<strong>in</strong>fach Adressen.<br />
Dass auch noch e<strong>in</strong> Typ mit angegeben wird, hat damit zu tun, dass man<br />
sie richtig behandeln will. Man will ja im Fall e<strong>in</strong>es <strong>in</strong>dizierten Zugriffs das
132 6. Po<strong>in</strong>ter und References<br />
richtige Element erwischen und nicht e<strong>in</strong>fach irgende<strong>in</strong> Byte. Selbiges gilt<br />
für die Po<strong>in</strong>terarithmetik, bei der auch elementweise und nicht byteweise<br />
gerechnet wird.<br />
Adressen s<strong>in</strong>d immer gleich, denn dem Computer ist es vollkommen egal,<br />
ob e<strong>in</strong>e Adresse nun auf e<strong>in</strong>en <strong>in</strong>t32, double oder sonst etwas zeigt. Die Interpretation<br />
bleibt dem Programm überlassen. Was sagt uns das? Nun, ganz<br />
e<strong>in</strong>fach: Mittels Typecasts können wir sehr e<strong>in</strong>fach e<strong>in</strong>e Neu<strong>in</strong>terpretation<br />
des dah<strong>in</strong>terstehenden Werts erreichen. Allerd<strong>in</strong>gs hat der Compiler def<strong>in</strong>itiv<br />
etwas dagegen, dass man e<strong>in</strong>fach wild Po<strong>in</strong>ter e<strong>in</strong>es Typs Po<strong>in</strong>tern e<strong>in</strong>es<br />
anderen Typs zuweist, weil er ja die Gefahr der Fehl<strong>in</strong>terpretation kennt. Das<br />
Statement<br />
u<strong>in</strong>t8 *my_ptr = your_ptr;<br />
wird vom Compiler nur dann übersetzt, wenn auch your_ptr e<strong>in</strong> u<strong>in</strong>t8 * ist.<br />
Ist er das nicht, sondern z.B. e<strong>in</strong> <strong>in</strong>t32 *, dann quittiert das der Compiler<br />
mit e<strong>in</strong>er netten Meldung, dass man doch aufpassen soll, was man tut. Es<br />
wird von e<strong>in</strong>em C ++ Compiler z.B. nicht e<strong>in</strong>mal e<strong>in</strong> impliziter Cast zwischen<br />
e<strong>in</strong>em char*, unsigned char* und e<strong>in</strong>em signed char* durchgeführt, da ja<br />
nicht def<strong>in</strong>iert ist, ob nun e<strong>in</strong> char signed oder unsigned ist.<br />
Wie allerd<strong>in</strong>gs schon bekannt ist, gibt es Mittel und Wege, den Compiler<br />
zu überreden, gewisse D<strong>in</strong>ge zu akzeptieren, nämlich entsprechende Casts.<br />
Im Pr<strong>in</strong>zip kann man damit fast beliebigste Neu<strong>in</strong>terpretationen erzw<strong>in</strong>gen,<br />
nur muss man wirklich ganz genau wissen, was man macht! Sehen wir uns<br />
e<strong>in</strong>fach e<strong>in</strong>mal e<strong>in</strong> Standardbeispiel an, das zeigt, wie man zu den e<strong>in</strong>zelnen<br />
Bytes e<strong>in</strong>es “größeren” Ganzzahlenwertes, also z.B. e<strong>in</strong>es <strong>in</strong>t32 kommt<br />
(bytes_of_<strong>in</strong>t32_demo.cpp):<br />
1 // bytes of <strong>in</strong>t32 demo . cpp − e x t r a c t the s i n g l e bytes o f an <strong>in</strong>t32<br />
2<br />
3 #<strong>in</strong>clude <br />
4 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
5<br />
6 us<strong>in</strong>g std : : cout ;<br />
7 us<strong>in</strong>g std : : endl ;<br />
8<br />
9 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
10 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
11 {<br />
12 <strong>in</strong>t32 num for extraction = 4;<br />
13 u<strong>in</strong>t8 ∗ i n t 3 2 i t e r a t o r =<br />
14 re<strong>in</strong>terpret cast(&num for extraction ) ;<br />
15 for ( u<strong>in</strong>t32 count = 0; count < 4; count++)<br />
16 cout
6.2 Po<strong>in</strong>ter 133<br />
Auf e<strong>in</strong>er Sun würde die Reihenfolge der Bytes genau umgekehrt se<strong>in</strong>. Dies<br />
hängt mit den <strong>in</strong>ternen Architekturen dieser Rechner zusammen, denn e<strong>in</strong>em<br />
Intel liegt e<strong>in</strong>e little Endian und e<strong>in</strong>er Sun e<strong>in</strong>e big Endian Byte Order<br />
zugrunde.<br />
Ich möchte mich hier nicht näher über little- und big Endian auslassen, wohl<br />
aber möchte ich erwähnen, dass dieses Wissen sehr wichtig se<strong>in</strong> kann! Leser,<br />
die mit diesen Begriffen nichts anfangen können, möchte ich auf Kapitel 13<br />
von <strong>Softwareentwicklung</strong> <strong>in</strong> C verweisen, wo e<strong>in</strong> sehr ähnliches Beispiel mit<br />
genauerer Erklärung gebracht wird.<br />
Nach diesem kle<strong>in</strong>en Exkurs <strong>in</strong> die Prozessorarchitekturen zurück zum eigentlichen<br />
Thema, nämlich dem Erzw<strong>in</strong>gen e<strong>in</strong>er anderen Interpretation e<strong>in</strong>es<br />
Datums als der, die der Typ eigentlich vorgeben würde. Wir haben bereits<br />
den re<strong>in</strong>terpret_cast kennen gelernt und dabei erfahren, dass durch diesen<br />
e<strong>in</strong>e Neu<strong>in</strong>terpretation “ohne Rücksicht auf Verluste” und ohne jeglichen<br />
Umwandlungsversuch von Seiten des Compilers stattf<strong>in</strong>det. Das machen wir<br />
uns <strong>in</strong> den Zeilen 13–14 zunutze, denn dort erzw<strong>in</strong>gen wir e<strong>in</strong>e Neu<strong>in</strong>terpretation<br />
e<strong>in</strong>es <strong>in</strong>t32* als u<strong>in</strong>t8*. Dies bedeutet <strong>in</strong>tern, dass die Adresse,<br />
die die Variable <strong>in</strong>t32_iterator hält auf num_for_extraction zeigt. Allerd<strong>in</strong>gs<br />
wird der Inhalt dieser Speicherstelle nicht als <strong>in</strong>t32 <strong>in</strong>terpretiert,<br />
sondern als u<strong>in</strong>t8. Das bedeutet, dass nur e<strong>in</strong> e<strong>in</strong>ziges Byte genommen und<br />
als u<strong>in</strong>t8 <strong>in</strong>terpretiert wird anstatt aller 4 Bytes, die geme<strong>in</strong>sam die Variable<br />
num_for_extraction ausmachen.<br />
Wenn man nun also <strong>in</strong> e<strong>in</strong>er Schleife, wie <strong>in</strong> den Zeilen 15–18 gezeigt, die<br />
4 Bytes des <strong>in</strong>t32 h<strong>in</strong>tere<strong>in</strong>ander ausliest, dann hat man genau das erreicht,<br />
was man wollte. Natürlich ist <strong>in</strong> Zeile 17 wieder e<strong>in</strong> static_cast des u<strong>in</strong>t8<br />
auf u<strong>in</strong>t32 notwendig, denn ansonsten würde ke<strong>in</strong>e Zahl, sondern irgende<strong>in</strong><br />
kryptischer Character am Bildschirm dargestellt werden.<br />
Es gibt genügend Fälle, bei denen zur Compiletime noch gar nicht bekannt<br />
ist, welcher Po<strong>in</strong>tertyp dann zur Laufzeit als Parameter an e<strong>in</strong>e Funktion<br />
übergeben wird. Im OO-Teil werden uns solche mehrfach begegnen.<br />
Jetzt wissen wir aber, dass der Compiler beim impliziten Umwandeln recht<br />
restriktiv ist und uns damit das Leben schwer machen kann, welchen Parametertyp<br />
wir denn jetzt eigentlich für unsere Funktion deklarieren. Dafür gibt<br />
es <strong>in</strong> C ++ e<strong>in</strong>en ganz besonderen untypisierten Po<strong>in</strong>ter, nämlich den void *.<br />
Dieser Po<strong>in</strong>tertyp ist so def<strong>in</strong>iert, dass jeder beliebige Po<strong>in</strong>ter vom Compiler<br />
ohne Fehlermeldung oder Warn<strong>in</strong>g implizit <strong>in</strong> ihn umgewandelt werden kann.<br />
In unserer Funktion können wir dann zur Laufzeit (sofern wir wissen, womit<br />
wir zu tun haben :-)) mittels e<strong>in</strong>es re<strong>in</strong>terpret_cast wieder die gewünschte<br />
Interpretation herstellen.<br />
Dass ohne expliziten Cast bei e<strong>in</strong>em void * ke<strong>in</strong>e Index-Operationen und<br />
auch ke<strong>in</strong>e Po<strong>in</strong>terarithmetik möglich s<strong>in</strong>d, versteht sich von selbst. Diese<br />
Operationen s<strong>in</strong>d ja so def<strong>in</strong>iert, dass sie immer auf Elementbasis und nicht<br />
auf Bytebasis rechnen. Wenn man aber nicht weiß, welches Element sich
134 6. Po<strong>in</strong>ter und References<br />
dah<strong>in</strong>ter versteckt, dann kann man auch nicht rechnen. Entsprechend wird<br />
sich der Compiler bei e<strong>in</strong>em Versuch der Anwendung von Po<strong>in</strong>terarithmetik<br />
oder Indizierung auf e<strong>in</strong>en void * beschweren. Ebenso verhält es sich mit<br />
delete: Es ist nicht auf e<strong>in</strong>en void * aufrufbar. Die Gründe dafür werden<br />
<strong>in</strong> Abschnitt 10.2.3 noch kurz erläutert.<br />
Vorsicht Falle: Das Spielchen mit dem Erzw<strong>in</strong>gen der Neu<strong>in</strong>terpretation<br />
mittels re<strong>in</strong>terpret_cast kann man nicht nur bei Po<strong>in</strong>tern spielen, sondern<br />
sogar unglückseligerweise zwischen Po<strong>in</strong>tern und Ganzzahlentypen. Ist im<br />
Pr<strong>in</strong>zip auch irgendwie logisch, denn e<strong>in</strong>e Adresse ist ja auch nur irgende<strong>in</strong>e<br />
Ganzzahl. Nur leider funktioniert das mit dem Cast zwischen z.B. <strong>in</strong>t<br />
und Po<strong>in</strong>tern nicht so toll, denn wer garantiert uns, dass e<strong>in</strong> <strong>in</strong>t auf e<strong>in</strong>er<br />
bestimmten Plattform gleich lang ist, wie e<strong>in</strong> Po<strong>in</strong>ter? Leider gibt es immer<br />
noch ziemlich viel (zumeist historische) Software, die diese Annahme e<strong>in</strong>fach<br />
als gegeben h<strong>in</strong>nimmt. Allerd<strong>in</strong>gs ist das grundfalsch!<br />
Deshalb ist es unbed<strong>in</strong>gt notwendig, folgende Regel e<strong>in</strong>zuhalten: Es darf<br />
niemals zwischen e<strong>in</strong>em Po<strong>in</strong>ter und e<strong>in</strong>em, wie auch immer gearteten<br />
Ganzzahldatentyp, z.B. <strong>in</strong>t gecastet werden! Dies ergibt nämlich<br />
unter Garantie Software mit e<strong>in</strong>er wunderbar e<strong>in</strong>programmierten Zeitbombe.
7. Der Preprocessor<br />
In C ++ kommt dem Preprocessor e<strong>in</strong>e viel kle<strong>in</strong>ere Rolle zu, als dies noch <strong>in</strong> C<br />
der Fall war. Viele alltägliche D<strong>in</strong>ge, die noch <strong>in</strong> C mit Preprocessor Macros<br />
erledigt werden mussten, s<strong>in</strong>d <strong>in</strong> C ++ <strong>in</strong> der Sprache selbst enthalten, z.B.<br />
Konstanten, <strong>in</strong>l<strong>in</strong>e Funktionen, etc. Es hat auch sehr gute Gründe, warum<br />
diese Konstrukte <strong>in</strong> die Sprache selbst E<strong>in</strong>zug gehalten haben. Deshalb muss<br />
ich gleich vorausschicken, dass man auf Preprocessor Macros nur so spärlich<br />
wie möglich zurückgreifen sollte, um nicht die Robustheit und Wartbarkeit<br />
des Codes aufs Spiel zu setzen.<br />
Pr<strong>in</strong>zipiell ist der Preprocessor etwas ganz E<strong>in</strong>faches:<br />
• Er ist e<strong>in</strong> eigenes Programm, das vor dem Compiler aufgerufen wird.<br />
• Der Preprocessor geht den Source durch und nimmt <strong>in</strong> ihm textuelle<br />
Ersetzungen nach gewissen Regeln vor. Der Output des Preprocessors ist<br />
selbst C ++ Source-Code, der dann dem Compiler zur Übersetzung gegeben<br />
wird.<br />
• Der Preprocessor weiß nichts über Typen, Scope, Lifetime und andere sprachimmanente<br />
Regeln von C ++. Aus diesem Grund entstehen auch Probleme<br />
mit der Robustheit und Wartbarkeit von Code, wenn man sich unnötig<br />
auf Spielchen mit Preprocessor Macros e<strong>in</strong>lässt.<br />
Anweisungen, die für den Preprocessor gedacht s<strong>in</strong>d, beg<strong>in</strong>nen immer mit e<strong>in</strong>em<br />
# <strong>in</strong> der ersten Spalte e<strong>in</strong>er Zeile. E<strong>in</strong> Beispiel für e<strong>in</strong>e solche Anweisung<br />
ist die<br />
#<strong>in</strong>clude<br />
Anweisung, die wir bereits verwendet haben. Auffällig bei dieser Anweisung<br />
ist, dass sie nicht durch e<strong>in</strong>en Strichpunkt abgeschlossen wird. Auch dies ist<br />
e<strong>in</strong>e Eigenheit des Preprocessors: Preprocessor Anweisungen werden nicht<br />
mit e<strong>in</strong>em Strichpunkt abgeschlossen, sondern enden mit dem Ende der Zeile!<br />
Sollte e<strong>in</strong>e Anweisung zu lang werden, sodass man e<strong>in</strong>en Zeilenumbruch<br />
machen will oder muss, so funktioniert das, <strong>in</strong>dem man den Zeilenumbruch<br />
“escaped”. Dies geschieht durch e<strong>in</strong>en Backslash als letztes Zeichen der Zeile.<br />
In der Folge werden die Features des Preprocessors genauer beleuchtet,<br />
die man im Programmieralltag braucht. Auf besondere Anweisungen, die<br />
z.B. erlauben, <strong>in</strong>l<strong>in</strong>e Assemblercode e<strong>in</strong>zub<strong>in</strong>den oder die besondere compilerspezifische<br />
D<strong>in</strong>ge veranlassen (Stichwort #pragma), wird bewusst verzichtet.<br />
Erstens s<strong>in</strong>d diese Anweisungen abhängig vom verwendeten Compiler
136 7. Der Preprocessor<br />
und der Zielplattform, zweitens sollte man sie wirklich nur <strong>in</strong> sehr speziellen<br />
Fällen verwenden, die im “normalen” Alltag e<strong>in</strong>es Softwareentwicklers<br />
außerordentlich selten vorkommen.<br />
Leser, die bisher noch gar nichts mit dem Preprocessor <strong>in</strong> C zu tun hatten,<br />
möchte ich an dieser Stelle kurz auf Kapitel 16 von <strong>Softwareentwicklung</strong> <strong>in</strong> C<br />
h<strong>in</strong>weisen.<br />
7.1 Include Files<br />
Die Preprocessor Anweisung zum E<strong>in</strong>fügen von Include Files haben wir bereits<br />
gleich zu Beg<strong>in</strong>n des Buchs kennen gelernt und durchgehend <strong>in</strong> unseren<br />
Programmen verwendet:<br />
#<strong>in</strong>clude file_identifier<br />
Statt dem Begriff des Files habe ich hier absichtlich den Begriff<br />
file_identifier verwendet, denn wir haben es hier mit zwei verschiedenen<br />
Formen zu tun, wie man Files e<strong>in</strong>fügt. E<strong>in</strong>erseits gibt es die sogenannten<br />
System Headers, also solche, die im Suchpfad des Compilers gefunden werden.<br />
Zu diesen gehören <strong>in</strong> jedem Fall alle Standard Headers für e<strong>in</strong>e Plattform.<br />
Je nach Projekt kann man natürlich auch e<strong>in</strong>es der Projekt Include-<br />
Subdirectories <strong>in</strong> den Suchpfad aufnehmen. Damit werden die dort enthaltenen<br />
Headers auch als System Headers gefunden. Neben diesen Headers<br />
gibt es auch noch die Project Headers, also solche, die nicht im Suchpfad des<br />
Compilers stehen.<br />
Folgende Konventionen gibt es für die entsprechenden #<strong>in</strong>clude Anweisungen:<br />
System Headers: Diese werden <strong>in</strong>kludiert, <strong>in</strong>dem man den Filenamen des<br />
Headers <strong>in</strong> spitze Klammern e<strong>in</strong>fasst. Außerdem ist <strong>in</strong> C ++ die Konvention,<br />
dass man bei System Headers die Extension .h nicht angibt,<br />
wie bereits <strong>in</strong> Abschnitt 2.3 genauer erklärt wurde. Die Anweisung<br />
#<strong>in</strong>clude <br />
bewirkt also e<strong>in</strong> E<strong>in</strong>fügen des Files iostream aus dem Suchpfad des Compilers<br />
an der Stelle im File, an der das #<strong>in</strong>clude Statement steht. Es ist<br />
allerd<strong>in</strong>gs nicht gesagt, dass das File e<strong>in</strong>fach nur iostream heißt, obwohl<br />
dies zumeist der Fall ist. Der Compiler könnte auch <strong>in</strong>tern mit Headers<br />
mit beliebigen Extensions arbeiten, die er selbst verwaltet.<br />
Project Headers: Diese werden <strong>in</strong>kludiert, <strong>in</strong>dem man den Filenamen des<br />
Headers <strong>in</strong> doppelte Anführungszeichen e<strong>in</strong>fasst. Bei diesen Headers darf<br />
die Extension .h nicht weggelassen werden. Auch dazu kennen wir bereits<br />
e<strong>in</strong>en Kandidaten:<br />
#<strong>in</strong>clude "user_types.h"<br />
bewirkt also e<strong>in</strong> E<strong>in</strong>fügen des Files user_types.h an der Stelle im File,<br />
an der das #<strong>in</strong>clude Statement steht. Project Headers werden immer
7.2 Bed<strong>in</strong>gte Übersetzung 137<br />
im aktuellen Subdirectory gesucht und unterliegen nicht der Suche im<br />
Systempfad.<br />
Wie bereits erwähnt, nimmt der Preprocessor e<strong>in</strong>zig und alle<strong>in</strong> textuelle Ersetzungen<br />
vor. Das bedeutet also, dass tatsächlich an der Stelle, an der<br />
e<strong>in</strong>e #<strong>in</strong>clude Anweisung steht, das <strong>in</strong>kludierte File textuell e<strong>in</strong>gesetzt wird.<br />
Dadurch ergibt sich zweierlei:<br />
1. Unnötige #<strong>in</strong>clude Anweisungen s<strong>in</strong>d zu vermeiden, da sie die Compiletime<br />
sehr verlängern können.<br />
2. In Headers dürfen ausschließlich Deklarationen vorkommen, da bei Def<strong>in</strong>itionen<br />
die Gefahr der duplicate Def<strong>in</strong>ition besteht. Solche dürfen nur<br />
<strong>in</strong> .cpp Files vorkommen, die dann zum Programm dazugel<strong>in</strong>kt werden.<br />
7.2 Bed<strong>in</strong>gte Übersetzung<br />
Der Preprocessor unterstützt auch drei verschiedene Anweisungen zum Überprüfen<br />
von Bed<strong>in</strong>gungen:<br />
#if constant<br />
true-code<br />
#else<br />
false-code<br />
#endif<br />
Mit dieser Anweisung wird die Zahlenkonstante constant auf e<strong>in</strong>en Wert ungleich<br />
0 überprüft. Falls dies zutrifft, wird der Code im Programm belassen,<br />
der oben durch true-code bezeichnet ist und false-code wird entfernt. Falls<br />
dies nicht zutrifft, dann wird true-code entfernt und false-code wird im Programm<br />
belassen. Das #else und false-code s<strong>in</strong>d optional, #endif ist allerd<strong>in</strong>gs<br />
unbed<strong>in</strong>gt vonnöten, um das Ende der #if Anweisung anzuzeigen. Die<br />
Anweisung<br />
#ifdef identifier<br />
true-code<br />
#else<br />
false-code<br />
#endif<br />
überprüft, ob das Preprocessor Macro identifier bereits def<strong>in</strong>iert wurde (siehe<br />
unten). Wenn ja, bleibt true-code im Programm, ansonsten false-code.<br />
Auch hier wieder ist der #else Zweig optional. Analog dazu verhält sich die<br />
Anweisung<br />
#ifndef identifier<br />
true-code<br />
#else<br />
false-code<br />
#endif
138 7. Der Preprocessor<br />
Hier wird allerd<strong>in</strong>gs überprüft, ob identifier noch nicht def<strong>in</strong>iert wurde.<br />
Die letzte Anweisung wird auch verwendet, um Headers vor unbeabsichtigter<br />
Doppel<strong>in</strong>klusion und daraus resultierenden Fehlermeldungen des Compilers<br />
zu schützen. Header-Files sollten immer durch die folgenden Preprocessor<br />
Anweisungen e<strong>in</strong>gefasst se<strong>in</strong>:<br />
#ifndef filename_h___<br />
#def<strong>in</strong>e filename_h___<br />
... complete code of header ...<br />
#endif<br />
Hier steht filename_h___ für den spezifischen Namen des Headers. Im Fall<br />
unseres Files user_types.h würde also das entspechende Konstrukt folgendermaßen<br />
aussehen:<br />
#ifndef user_types_h___<br />
#def<strong>in</strong>e user_types_h___<br />
... complete code of header ...<br />
#endif<br />
7.3 Macros<br />
Die Macros werden hier absichtlich an letzter Stelle bei der Behandlung des<br />
Preprocessors erwähnt, da sie <strong>in</strong> C ++ nur noch e<strong>in</strong>e untergeordnete Bedeutung<br />
besitzen. Pr<strong>in</strong>zipiell gibt es drei Arten von Macros. E<strong>in</strong>e e<strong>in</strong>fache<br />
Def<strong>in</strong>ition e<strong>in</strong>es Identifiers haben wir bereits zuvor kennen gelernt, nämlich<br />
die Anweisung<br />
#def<strong>in</strong>e user_types_h___<br />
Diese bewirkt, dass e<strong>in</strong> Identifier namens user_types_h___ def<strong>in</strong>iert wird.<br />
Dieser ist dann mit #ifdef bzw. #ifndef überprüfbar. Neben dieser e<strong>in</strong>fachen<br />
Def<strong>in</strong>ition e<strong>in</strong>es Identifiers gibt es auch die Def<strong>in</strong>ition von nicht parametrisierten<br />
Macros. Z.B. def<strong>in</strong>iert die Anweisung<br />
#def<strong>in</strong>e MAX_LENGTH 255<br />
e<strong>in</strong>e Constant namens MAX_LENGTH, die den Wert 255 besitzt. Überall im<br />
Code, wo der Compiler auf e<strong>in</strong>e Verwendung von MAX_LENGTH stößt, wird<br />
diese textuell durch den Wert 255 ersetzt. Allerd<strong>in</strong>gs ist diese Art der Konstantendef<strong>in</strong>ition<br />
<strong>in</strong> C ++ verpönt, denn es gibt das typensichere Konstrukt<br />
der const Variablen, die stattdessen verwendet werden sollen.<br />
Weiters gibt es auch noch die Möglichkeit, parametrisierte Macros zu<br />
def<strong>in</strong>ieren. Z.B. wird durch<br />
#def<strong>in</strong>e SWAP(a,b) { <strong>in</strong>t swap = a; a = b; b = swap; }<br />
e<strong>in</strong> Macro, das das Vertauschen zweier Variablen<strong>in</strong>halte veranlasst. Jedoch<br />
wird auch von dieser Verwendung von Macros <strong>in</strong> C ++ abgeraten, da es das<br />
typensichere Konstrukt der <strong>in</strong>l<strong>in</strong>e Funktionen als Alternative gibt.<br />
Vorsicht Falle: Das zuvor gezeigte SWAP Macro ist sogar recht bösartig,<br />
denn wenn man die Klammerung der Ausdrücke zu e<strong>in</strong>em Block vergisst,
7.3 Macros 139<br />
dann kann man nette Überraschungen erleben! Lesern, denen nicht geläufig<br />
ist, warum dies der Fall ist, möchte ich wärmstens die Lektüre von Abschnitt<br />
16.1.2 aus <strong>Softwareentwicklung</strong> <strong>in</strong> C ans Herz legen!
Teil II<br />
Objektorientierte Konzepte von <strong>C++</strong>
8. Objektorientierung Allgeme<strong>in</strong><br />
Ich habe ganz bewusst bisher im Buch ke<strong>in</strong> umfangreicheres Beispiel besprochen,<br />
da die wichtigsten Features, die C ++ eigentlich erst ausmachen, <strong>in</strong> der<br />
Objektorientierung der Sprache liegen. Um nicht manche Leser dazu anzuregen,<br />
imperatives C ++ zu programmieren anstatt sauberes, echt objektorientiertes<br />
C ++, wollte ich mit e<strong>in</strong>em größeren Beispiel abwarten, bis zum<strong>in</strong>dest<br />
die wichtigsten OO-Konstrukte von C ++ bekannt s<strong>in</strong>d.<br />
E<strong>in</strong>es möchte ich gleich vorausschicken: Zu wissen, wie man Klassen und<br />
Objekte def<strong>in</strong>iert und wie man technisch gesehen damit arbeitet, macht noch<br />
lange ke<strong>in</strong>e Objektorientierung! Objektorientierung ist e<strong>in</strong> Denkmodell, das<br />
von C ++ unterstützt wird. Durch diese Unterstützung kann man e<strong>in</strong> sauberes<br />
OO-Design auch elegant <strong>in</strong> C ++ implementieren. Das bedeutet aber ke<strong>in</strong>eswegs,<br />
dass Code, der irgendwelche OO-Features verwendet, auch wirklich<br />
sauberer objektorientierter Code ist!<br />
Genau aus diesem Grund f<strong>in</strong>de ich es wichtig, erst e<strong>in</strong>mal die Grundgedanken<br />
der Objektorientierung zu diskutieren und Beispiele dafür zu br<strong>in</strong>gen,<br />
bevor wir überhaupt dazu kommen, wie diese Grundgedanken von der Sprache<br />
unterstützt werden. Ke<strong>in</strong>e Aktivität im gesamten Entwicklungszyklus<br />
bee<strong>in</strong>flusst e<strong>in</strong> Produkt so stark, wie das Erstellen der Architektur und des<br />
Designs. Im Pr<strong>in</strong>zip ist dies e<strong>in</strong>e B<strong>in</strong>senweisheit, wenn man sich überlegt,<br />
wie seit Jahrtausenden Bauwerke entstehen: Zuerst wird e<strong>in</strong> Plan gemacht,<br />
der die Basis für das Bauwerk darstellt. Der Plan ergibt sich daraus, dass die<br />
Anforderungen für e<strong>in</strong> Bauwerk <strong>in</strong> puncto Platzbedarf, Verwendungszweck,<br />
Stabilität, u.v.m. <strong>in</strong> e<strong>in</strong>e Architektur umgesetzt werden. Die Ästhetik des<br />
Bauwerks ist e<strong>in</strong> direktes Produkt aus se<strong>in</strong>em Design mit den gegebenen<br />
Randbed<strong>in</strong>gungen. Erst wenn der Plan gezeichnet und baustatische Berechnungen<br />
erfolgreich verlaufen s<strong>in</strong>d, wird mit dem tatsächlichen Bau begonnen.<br />
Die gesamte Struktur des Bauwerks steht zu diesem Zeitpunkt vollständig<br />
fest!<br />
Software steht vom Komplexitätsgrad e<strong>in</strong>em Bauwerk <strong>in</strong> ke<strong>in</strong>ster Weise<br />
nach. Wieso also wird hier genau die wichtigste Phase des Designs und<br />
des Entwurfs der Architektur so oft nur äußerst rudimentär durchgeführt?<br />
E<strong>in</strong>e mögliche Antwort und für mich bisher die e<strong>in</strong>zig logische Erklärung ist<br />
folgende:
144 8. Objektorientierung Allgeme<strong>in</strong><br />
Dadurch, dass man beim Schreiben e<strong>in</strong>es Programms mit Text zu tun<br />
hat, den man <strong>in</strong> den Computer e<strong>in</strong>tippt und den man nach Bedarf ändern<br />
kann, wird suggeriert, dass Änderungen problemlos s<strong>in</strong>d und dass man jederzeit<br />
fehlende Teile ergänzen und fehlerhafte Teile ausbessern kann. Beim<br />
Bau e<strong>in</strong>es Gebäudes ist jedem klar, dass es nachträglich nicht möglich ist,<br />
e<strong>in</strong>fach mitten aus dem Gebäude e<strong>in</strong> Stockwerk herauszunehmen und durch<br />
e<strong>in</strong> anderes zu ersetzen.<br />
Jedoch wird bei Betrachtungen zur leichten Änderbarkeit von Software<br />
e<strong>in</strong> Faktum übersehen: Bei e<strong>in</strong>em Computerprogramm verhält es sich nicht<br />
grundlegend anders als bei e<strong>in</strong>em Gebäude! Will man an e<strong>in</strong>em Konzept e<strong>in</strong>es<br />
Programms etwas ändern, dann s<strong>in</strong>d nicht e<strong>in</strong>fach nur e<strong>in</strong> paar Zeilen Text<br />
auszubessern! Es kann passieren, dass sich e<strong>in</strong>e Konzeptänderung durch e<strong>in</strong><br />
gesamtes Programm zieht! Hat man also beim Entwickeln die Planung vernachlässigt,<br />
so passiert es nur allzu oft, dass am Fundament etwas geändert<br />
werden muss, wodurch man alles destabilisiert, was auf dem “alten” Fundament<br />
aufbaut. Führt man die Änderung nicht durch, ist das Fundament<br />
auch <strong>in</strong>stabil, denn die notwendige Änderung wurde ja nicht e<strong>in</strong>fach zum<br />
Spaß erfunden. Somit kommt man <strong>in</strong> e<strong>in</strong> Dilemma, aus dem sich so leicht<br />
ke<strong>in</strong> Ausweg f<strong>in</strong>den lässt, denn e<strong>in</strong> komplettes Redesign und e<strong>in</strong> Neuaufbau<br />
der bisher existierenden Software wäre oftmals die e<strong>in</strong>zig s<strong>in</strong>nvolle Lösung,<br />
ist aber mitunter viel zu zeitaufwändig.<br />
Jede Applikation, die man entwickelt, besteht aus vielen verschiedenen<br />
Abstraktionsschichten. Die oberste Schicht ist diejenige, die die Anwender<br />
zu sehen bekommen. Betrachten wir diese oberste Schicht e<strong>in</strong>mal als die<br />
tatsächliche Applikation (z.B. e<strong>in</strong>e Textverarbeitung) und betrachten wir die<br />
darunter liegenden Schichten als deren Fundament (z.B. Datenspeicherung,<br />
Netzwerk-Kommunikation, etc.). Bleiben wir weiters beim nahe liegenden<br />
Vergleich zwischen Software und Gebäuden. Dann würde sich sauber entworfene<br />
Software mit klarer und <strong>in</strong> sich abgeschlossener Architektur ähnlich<br />
wie <strong>in</strong> Abbildung 8.1 visualisieren lassen, wobei die e<strong>in</strong>zelnen Kästchen für<br />
die entworfenen Module stehen.<br />
Applikation<br />
Abbildung 8.1: Saubere und robuste Softwarearchitektur
8. Objektorientierung Allgeme<strong>in</strong> 145<br />
Jedoch liegt nur allzu vielen Softwarepaketen diese saubere Architektur<br />
nicht zugrunde (auch wenn nur die Allerwenigsten dies zugeben)! Die Architektur<br />
e<strong>in</strong>er Lösung, bei der <strong>in</strong> der Designphase gespart wurde und die daher<br />
entsprechend viele kle<strong>in</strong>e und größere Konzeptänderungen während der Entwicklung<br />
erfahren hat, mutiert zu e<strong>in</strong>em <strong>in</strong>stabilen Monster, bestehend aus<br />
wild angeordneten Pseudo-Modulen, wie es <strong>in</strong> Abbildung 8.2 skizziert ist.<br />
Applikation<br />
Abbildung 8.2: Mangelhafte (oft reale!) Softwarearchitektur<br />
Ich würde wirklich nur allzu gern sagen, dass Abbildung 8.2 nur Polemik<br />
me<strong>in</strong>erseits ist und dass ich hier die Realität stark überzeichnet habe. Leider<br />
aber ist das nicht der Fall, manchmal ist es sogar noch etwas schlimmer. Sogar<br />
sehr namhafte Softwarefirmen produzieren teure Lösungen, deren Architektur<br />
genau wie <strong>in</strong> Abbildung 8.2 dargestellt aussieht. Und das trotz aller ISO- und<br />
anderer Qualitätsrichtl<strong>in</strong>ien, die sie sich auf die Fahnen heften.<br />
Nun stellt sich natürlich die Frage, wie man es besser machen kann und<br />
wie man verh<strong>in</strong>dern kann, dass es zu solchen Auswüchsen kommt. Ich kann<br />
hier nur raten, wirklich ausreichend viel Zeit auf die Design- und Architekturphase<br />
zu verwenden und diese Phase erst abzuschließen, wenn das Design<br />
vollkommen schlüssig ist und ke<strong>in</strong>e Fragen mehr offen lässt. Weiters ist zum<br />
Erstellen e<strong>in</strong>es guten Designs auch sehr viel Erfahrung nötig. Um diese zu<br />
sammeln, braucht man viel Zeit und der Weg dorth<strong>in</strong> ist oft e<strong>in</strong> ste<strong>in</strong>iger.<br />
Häufig wird man Designs verwerfen müssen, weil sie nicht tragfähig s<strong>in</strong>d,<br />
aber das liegt <strong>in</strong> der Natur e<strong>in</strong>es jeden Lernprozesses. Ich kann <strong>in</strong> der Folge<br />
die Grundpr<strong>in</strong>zipien des OO-Designs so abhandeln, wie ich glaube, dass<br />
sie am leichtesten verständlich und nachvollziehbar s<strong>in</strong>d. Damit können Leser,<br />
die sich bisher noch nicht allzu ausführlich mit OO-Design beschäftigt<br />
haben, die ersten E<strong>in</strong>stiegshürden leichter meistern. Ich möchte jedoch alle<br />
Leser bitten, e<strong>in</strong>es im H<strong>in</strong>terkopf zu behalten: Es gibt ke<strong>in</strong>e Kochrezepte,<br />
die man e<strong>in</strong>fach befolgen kann und die zw<strong>in</strong>gend zu e<strong>in</strong>em guten Ergebnis<br />
führen! Der Entwurf e<strong>in</strong>er sauberen Architektur ist der schwierigste und
146 8. Objektorientierung Allgeme<strong>in</strong><br />
kreativ anspruchsvollste Prozess im gesamten Zyklus der <strong>Softwareentwicklung</strong><br />
und gehört entsprechend lang und sorgfältig geübt!<br />
8.1 Module und Abläufe<br />
Der wichtigste Teil, der ganz am Anfang des Designprozesses steht und sich<br />
<strong>in</strong> verschiedenen Ausprägungen durch den Prozess zieht, ist das Erfassen des<br />
Problems mit all se<strong>in</strong>en Abläufen und Randbed<strong>in</strong>gungen. Schafft man es,<br />
e<strong>in</strong> Problem so lange <strong>in</strong> immer allgeme<strong>in</strong>ere Gruppen von Teilproblemen zu<br />
zerlegen, bis man auf e<strong>in</strong>er vollkommen abstrakten Ebene angekommen ist,<br />
hat man e<strong>in</strong>e gute Chance, dass das Konzept tragfähig ist und dass man<br />
das gesamte System sehr sauber <strong>in</strong> Module zerlegen kann. Sehr oft gibt es<br />
vor allem bei Neul<strong>in</strong>gen Probleme, e<strong>in</strong>en Prozess wirklich vollständig zu<br />
beschreiben und gleichzeitig auf e<strong>in</strong>er Abstraktionsebene zu bleiben. Ich<br />
möchte an dieser Stelle ke<strong>in</strong>e lange theoretische Abhandlung über diese Pr<strong>in</strong>zipien<br />
schreiben und ich möchte vor allem nicht formal oder technisch werden<br />
und mich schon gar nicht auf e<strong>in</strong>e Programmiersprache festlegen. Stattdessen<br />
möchte ich e<strong>in</strong>fach an e<strong>in</strong>em völlig alltäglichen Beispiel die Phase der Analyse<br />
der Abläufe und der Identifikation von Modulen demonstrieren. Bevor ich<br />
dazu komme noch e<strong>in</strong> Tipp an alle Leser: Alles, was <strong>in</strong> der Folge beschrieben<br />
wird, kl<strong>in</strong>gt unglaublich banal, wenn man e<strong>in</strong>fach jetzt nur weiterliest.<br />
Dass es banal kl<strong>in</strong>gt, wenn man das Ergebnis liest, bedeutet aber ke<strong>in</strong>eswegs,<br />
dass der Weg zu diesem Ergebnis ebenso banal ist! Me<strong>in</strong> Ratschlag<br />
wäre, das folgende Beispiel zuerst selbst e<strong>in</strong>mal mittels Papier und Bleistift<br />
durchzuspielen und dann das Ergebnis mit dem hier abgedruckten zu vergleichen.<br />
Mit e<strong>in</strong> wenig Selbstkritik werden sicherlich e<strong>in</strong>ige Leser erkennen,<br />
dass sie bei der Analyse etwas vergessen haben, Abstraktionsebenen nicht<br />
e<strong>in</strong>gehalten haben (soll heißen, im Detailliertheitsgrad der Beschreibung unregelmäßig<br />
arbeiten) oder vielleicht sogar falsche Annahmen über die Natur<br />
gewisser Gegebenheiten getroffen haben.<br />
8.1.1 Der Weg zum Arbeitsplatz – e<strong>in</strong> kle<strong>in</strong>es Beispiel<br />
In diesem Abschnitt soll e<strong>in</strong> ganz e<strong>in</strong>facher Prozess analysiert werden und<br />
alle Beteiligten (Personen und D<strong>in</strong>ge) sowie die Zusammenhänge zwischen<br />
ihnen herausgearbeitet werden. Der e<strong>in</strong>fache Prozess, um den es geht, ist<br />
e<strong>in</strong> typischer Ablauf, den die meisten Leser täglich h<strong>in</strong>ter sich br<strong>in</strong>gen: Der<br />
Beg<strong>in</strong>n e<strong>in</strong>es Arbeitstages, vom Kl<strong>in</strong>geln des Weckers weg bis zur Ankunft am<br />
Arbeitsplatz.<br />
E<strong>in</strong> erster schneller Durchgang liefert uns e<strong>in</strong>mal e<strong>in</strong>e ganz grobe Beschreibung<br />
der Beteiligten und Abläufe:<br />
1. Der Wecker geht ab und weckt (hoffentlich :-)) die arbeitswütige Person<br />
viel zu früh am Morgen auf.
8.1 Module und Abläufe 147<br />
2. Die aufgeweckte Person versucht verzweifelt, den lästigen Wecker abzustellen<br />
und schafft dies auch nach e<strong>in</strong>igen kle<strong>in</strong>eren Fehlschlägen.<br />
3. Mit langsamen, vorsichtigen Bewegungen steigt die mittlerweile gar nicht<br />
mehr so arbeitswütige Person aus dem Bett und tappt <strong>in</strong> Richtung Badezimmer.<br />
4. Im Badezimmer f<strong>in</strong>det die allmorgendliche Prozedur statt, um sich frisch<br />
zu machen.<br />
5. Nach der Morgentoilette schon <strong>in</strong> etwas zurechnungsfähigerem Zustand<br />
macht sich unsere Person daran, <strong>in</strong> die Küche zu gehen und e<strong>in</strong> Frühstück<br />
zu richten.<br />
6. Das Frühstück wird hastig verschlungen, weil es wieder e<strong>in</strong>mal e<strong>in</strong> wenig<br />
zu spät ist, um es <strong>in</strong> Ruhe zu sich zu nehmen.<br />
7. Die Person macht sich auf den Weg zum Arbeitsplatz. Hier gibt es mehrere<br />
verschiedene Möglichkeiten, pr<strong>in</strong>zipiell passiert dies entweder zu Fuß<br />
oder mit e<strong>in</strong>em wie auch immer gearteten Fahrzeug.<br />
8. Beim Gebäude angekommen, geht unsere arbeitswillige Person h<strong>in</strong>e<strong>in</strong><br />
und steuert zielstrebig den Arbeitsplatz an.<br />
Vorsicht Falle: E<strong>in</strong>ige Leser werden es schon bemerkt haben, aber ich<br />
b<strong>in</strong> mir sicher, dass es nicht allen aufgefallen ist: Die arbeitswillige Person<br />
verbreitet am Arbeitsplatz knisternde Erotik, denn sie kommt nackt dort<br />
an :-). Wie man sieht, ist es also wirklich auch bei denn banalsten Abläufen<br />
möglich, böse Schlampigkeitsfehler zu begehen.<br />
Aus diesem groben Ablauf kann man nun relativ e<strong>in</strong>fach auch die direkten<br />
offensichtlichsten Beteiligten mit ihren Eigenschaften extrahieren:<br />
Person: In unserem Beispiel ist die Person der agierende Teil und sie hat<br />
(bezogen auf unser Beispiel) folgende Eigenschaften:<br />
• Sie kann schlafen oder munter se<strong>in</strong>.<br />
• Sie kann sitzen, liegen, stehen, gehen und sich auch mit e<strong>in</strong>em Fahrzeug<br />
fortbewegen.<br />
• Sie kann Frühstück machen und Essen zu sich nehmen.<br />
• Sie kann den Wecker abstellen und sich frisch machen.<br />
Wecker: Bezogen auf das Beispiel ist e<strong>in</strong> Wecker e<strong>in</strong> D<strong>in</strong>g, das läuten kann<br />
und dessen Läuten man abstellen kann.<br />
Badezimmer: Das Badezimmer ist e<strong>in</strong> Raum, <strong>in</strong> dem man sich frisch machen<br />
kann.<br />
Küche: Die Küche ist e<strong>in</strong> Raum, <strong>in</strong> dem man sich etwas zu essen richten<br />
kann. Eventuell kann man <strong>in</strong> ihr auch essen, oder man muss dazu <strong>in</strong><br />
e<strong>in</strong>en anderen Raum gehen.<br />
Frühstück: Das Frühstück ist e<strong>in</strong>e Mahlzeit mit folgenden Eigenschaften:<br />
• Man nimmt sie nach dem Aufstehen e<strong>in</strong>. Ich habe jetzt bewusst nicht<br />
<strong>in</strong> der Früh geschrieben, denn das Empf<strong>in</strong>den, was das nun <strong>in</strong> puncto<br />
Uhrzeit bedeutet, ist sehr subjektiv :-).
148 8. Objektorientierung Allgeme<strong>in</strong><br />
• Sie besteht aus mehreren verschiedenen festen und flüssigen Nahrungsmitteln<br />
<strong>in</strong> verschiedenen Zuständen (z.B. warm/kalt). Die Zusammenstellung<br />
ist beliebig.<br />
Fahrzeug: Die Beteiligung des Fahrzeugs wird hier erwähnt für den Fall,<br />
dass die Person nicht zu Fuß zur Arbeit geht. Es gibt verschiedenste<br />
Ausprägungen von Fahrzeugen, z.B. Fahrrad, Auto, Bus, etc., die alle<br />
ganz verschiedene Eigenschaften haben. E<strong>in</strong>e Eigenschaft ist ihnen allen<br />
geme<strong>in</strong>sam: sie s<strong>in</strong>d Fortbewegungshilfen, die man entsprechend ihrer<br />
anderen Eigenschaften verwenden kann.<br />
Gebäude: E<strong>in</strong> Gebäude ist e<strong>in</strong> beliebiges Bauwerk mit vielen möglichen Eigenschaften.<br />
E<strong>in</strong>e der Grundeigenschaften ist, dass es e<strong>in</strong>en oder mehrere<br />
Räume be<strong>in</strong>haltet und (hoffentlich...) zum<strong>in</strong>dest e<strong>in</strong>en E<strong>in</strong>- bzw. Ausgang<br />
hat.<br />
Arbeitsplatz: Obwohl der Arbeitsplatz zuvor implizit <strong>in</strong> e<strong>in</strong> Gebäude verlegt<br />
wurde, muss das natürlich nicht se<strong>in</strong>. E<strong>in</strong> Arbeitsplatz ist e<strong>in</strong> beliebiger<br />
Platz mit der e<strong>in</strong>zigen Eigenschaft, dass man dort zum<strong>in</strong>dest zeitweise<br />
se<strong>in</strong>e Arbeit verrichtet.<br />
Ich habe zuvor bewusst ganz e<strong>in</strong>fach h<strong>in</strong>geschrieben, dass man <strong>in</strong> das<br />
Gebäude h<strong>in</strong>e<strong>in</strong>geht und den Arbeitsplatz ansteuert, um zu zeigen, wie<br />
leicht man e<strong>in</strong>en Designfehler begehen kann! Würde man diese Analyse<br />
hier nicht durchführen, kann es nur allzu leicht passieren, dass e<strong>in</strong>e Eigenschaft<br />
e<strong>in</strong>es Arbeitsplatzes ist, dass er <strong>in</strong> e<strong>in</strong>em Gebäude liegt. Das<br />
ist ganz e<strong>in</strong>fach falsch! Man stelle sich vor, dass e<strong>in</strong>e solche Fehlannahme<br />
E<strong>in</strong>gang <strong>in</strong> e<strong>in</strong>e Softwarearchitektur f<strong>in</strong>det und plötzlich muss man<br />
mittels dieser Architektur e<strong>in</strong>en Förster modellieren, der se<strong>in</strong>e Arbeit im<br />
Wald verrichtet... womit sofort die nächste Fehlannahme auffällt: Es<br />
wurde auch impliziert, dass der Weg zur Arbeit zu e<strong>in</strong>em Gebäude führt.<br />
Das ist aber auch nicht zw<strong>in</strong>gend!<br />
Vorsicht Falle: Man sieht, selbst bei e<strong>in</strong>er sehr groben Modellierung e<strong>in</strong>es<br />
völlig alltäglichen Ablaufs kann es schon zu Fehlern kommen. Je detaillierter<br />
man Abläufe beschreibt, umso mehr Chancen hat man, solche Fehler auch<br />
wirklich sofort zu lokalisieren und zu korrigieren. Dies macht man so lange,<br />
bis man im Groben e<strong>in</strong>mal e<strong>in</strong> schlüssiges Modell von Beteiligten und Interaktionen<br />
zwischen diesen hat. Die Abstraktionsebene darf man dabei nicht<br />
verändern, man steht immer noch als Beobachter außerhalb des Problems<br />
und analysiert das Gesamtmodell.<br />
Im Normalfall modelliert man auch nicht nur e<strong>in</strong>en e<strong>in</strong>zelnen Ablauf, wie<br />
es hier der Fall ist, sondern man modelliert e<strong>in</strong>e ganze Reihe von sogenannten<br />
Use-Cases. Jeder Use-Case ist e<strong>in</strong> ganz bestimmter Prozess, der mit bzw. <strong>in</strong><br />
e<strong>in</strong>em System durchführbar se<strong>in</strong> muss. Aus der Analyse aller dieser Use-<br />
Cases ergibt sich dann das grobe Gesamtmodell.<br />
Sobald das Modell schlüssig ist, geht man e<strong>in</strong>en Schritt <strong>in</strong> die Tiefe.<br />
Je nach Problemstellung gibt es verschiedene Interpretationen, was nun e<strong>in</strong>
8.1 Module und Abläufe 149<br />
Schritt ist und was <strong>in</strong> die Tiefe bedeutet. In den allermeisten Fällen funktioniert<br />
es wunderbar, wenn man sich die e<strong>in</strong>zelnen Aktoren vornimmt und<br />
Abläufe der Reihe nach, detailliert aus ihrer Sicht heraus, beschreibt. Durch<br />
die Verfe<strong>in</strong>erung <strong>in</strong> der Beschreibung der Abläufe f<strong>in</strong>det man wieder e<strong>in</strong>e<br />
Fülle neuer Aktoren, die <strong>in</strong> dieser Abstraktionsebene Wichtigkeit besitzen.<br />
Um nun nicht gleich e<strong>in</strong> gesamtes Buch darüber zu schreiben, wie man <strong>in</strong> der<br />
Früh aufsteht und zur Arbeit kommt, möchte ich den Ablauf des sich frisch<br />
Machens aus Punkt 4 oben als Teilbeispiel herausgreifen.<br />
Die Vorbed<strong>in</strong>gung <strong>in</strong> unserem groben Ablauf war, dass man sich im Badezimmer<br />
frisch macht. Das bedeutet, dass man dieses e<strong>in</strong>mal betreten muss.<br />
Genau damit haben wir e<strong>in</strong>e wichtige Eigenschaft des Badezimmers gefunden,<br />
die <strong>in</strong> Wirklichkeit e<strong>in</strong>e Eigenschaft aller Räume ist: sie s<strong>in</strong>d abgegrenzt<br />
und es gibt e<strong>in</strong>e Möglichkeit, sie zu betreten. Die Abgrenzung e<strong>in</strong>es Raumes<br />
ist im Normalfall e<strong>in</strong>e Mauer, die Möglichkeit, ihn zu betreten, ist e<strong>in</strong>e<br />
Öffnung. E<strong>in</strong>e Mauer kann wiederum aus verschiedenen Materialien bestehen,<br />
verschieden dick, groß und gefärbt se<strong>in</strong>, e<strong>in</strong>e Öffnung <strong>in</strong> der Mauer kann<br />
durch e<strong>in</strong>e Tür verschlossen se<strong>in</strong>, diese kann wiederum verschiedenste Ausprägungen<br />
haben, etc.<br />
Man kann leicht erkennen, dass man nur durch den kle<strong>in</strong>en Aspekt, dass<br />
man e<strong>in</strong>en Raum betreten muss, e<strong>in</strong>en Use-Case gefunden hat, der bereits<br />
wieder das Herausarbeiten vieler verschiedener Beteiligter mit ihren Eigenschaften<br />
zur Folge hat, die im übergeordneten groben Fall noch ke<strong>in</strong>e direkte<br />
Rolle gespielt haben. Diesen Prozess, der sich über viele Ebenen zieht, nennt<br />
man schrittweise Verfe<strong>in</strong>erung des Modells und genau diese schrittweise Verfe<strong>in</strong>erung<br />
wendet man <strong>in</strong> der Analyse und Designphase von Software so lange<br />
an, bis man auf e<strong>in</strong>em Level angelangt ist, der ke<strong>in</strong>e weitere Verfe<strong>in</strong>erung<br />
mehr zulässt oder benötigt.<br />
Vorsicht Falle: Auch diesen Exkurs habe ich gerade absichtlich gemacht,<br />
um zu zeigen, wie schnell man e<strong>in</strong> Ziel aus den Augen verlieren kann. Die<br />
Absicht war eben noch, den Prozess des sich frisch Machens zu beschreiben.<br />
Bereits beim ersten kle<strong>in</strong>en Teilaspekt dieses Prozesses, nämlich dem Betreten<br />
des Badezimmers, habe ich die gerade dort noch gültige Abstraktionsebene<br />
verlassen und begonnen, über Räume, Mauern und Türen zu philosophieren.<br />
Man sieht also, dass die schrittweise Verfe<strong>in</strong>erung sicherlich die Vorgangsweise<br />
der Wahl se<strong>in</strong> wird, um das Gesamtproblem <strong>in</strong> den Griff zu bekommen.<br />
Jedoch sieht man auch, dass Diszipl<strong>in</strong> e<strong>in</strong>e der wichtigsten Eigenschaften<br />
ist, die man braucht, um s<strong>in</strong>nvoll und zielgerichtet zu arbeiten. Man darf<br />
erst den nächsten Schritt <strong>in</strong> der Verfe<strong>in</strong>erung tun, wenn man e<strong>in</strong>e Ebene<br />
abgehandelt hat, sonst verliert man sich <strong>in</strong> Details und verliert damit den<br />
Überblick über das zu lösende Problem.<br />
Also – zurück zum Ausgangspunkt! Wie macht man sich morgens frisch:<br />
1. Man betritt das Badezimmer.<br />
2. Man duscht sich.
150 8. Objektorientierung Allgeme<strong>in</strong><br />
3. Man trocknet sich ab.<br />
4. Man putzt sich die Zähne.<br />
5. Man verlässt das Badezimmer.<br />
Was erkennt man aus dieser groben Beschreibung:<br />
• E<strong>in</strong>e Eigenschaft e<strong>in</strong>er Person ist es offensichtlich, dass sie Zähne hat, denn<br />
was sollte man denn sonst putzen.<br />
• E<strong>in</strong>e Eigenschaft des Badezimmers ist, dass dar<strong>in</strong> e<strong>in</strong> Handtuch vorhanden<br />
ist, sonst kann man sich nicht abtrocknen. Damit ist nicht ausgesagt, dass<br />
das Handtuch e<strong>in</strong>e Eigenschaft des Badezimmers ist, denn man könnte es<br />
ja auch beim Betreten mit h<strong>in</strong>e<strong>in</strong> genommen haben. Im Pr<strong>in</strong>zip ist nicht<br />
e<strong>in</strong>mal gesagt, dass zum Abtrocknen e<strong>in</strong> Handtuch notwendig ist, denn<br />
man könnte ja auch so lange stehen bleiben, bis man trocken ist. Der<br />
Ablauf des Abtrocknens verdient also im Modellierungsprozess noch e<strong>in</strong>e<br />
nähere Betrachtung, was die Anforderungen betrifft.<br />
• E<strong>in</strong>e Eigenschaft unseres Badezimmers ist, dass es e<strong>in</strong>e Dusche gibt... oje...<br />
wieder e<strong>in</strong> Fehldesign. Es gibt auch Badezimmer ohne Dusche! Dort funktioniert<br />
der gerade angeführte Prozess zwar nicht mehr vollständig, weil<br />
man sich nicht duschen, sondern nur e<strong>in</strong> Vollbad nehmen kann oder sich<br />
überhaupt nur waschen kann. Trotzdem darf man aus der Prozessbeschreibung<br />
nicht zw<strong>in</strong>gend schließen, dass auch alles Geforderte e<strong>in</strong>e Eigenschaft<br />
von beteiligten Parteien ist!<br />
Um wirklich e<strong>in</strong>en s<strong>in</strong>nvollen Verfe<strong>in</strong>erungsschritt vollziehen zu können, werden<br />
die e<strong>in</strong>zelnen Prozesse, die vom Betreten bis zum Verlassen des Badezimmers<br />
stattf<strong>in</strong>den, e<strong>in</strong>mal alle e<strong>in</strong>zeln verfe<strong>in</strong>ert, denn mehr Information als<br />
gerade eben lässt sich aus dem groben Ablauf nicht extrahieren. Beispielhaft<br />
für die anderen Teilprozesse nehmen wir uns e<strong>in</strong>fach das Zähne Putzen zur<br />
Brust:<br />
1. Man nimmt die Zahnbürste <strong>in</strong> die Hand. Wichtig hierbei ist, dass man<br />
ke<strong>in</strong>e direkte Aussage treffen kann, wo die Zahnbürste hergenommen<br />
wird. Sie könnte e<strong>in</strong>fach daliegen oder auch <strong>in</strong> e<strong>in</strong>em eigenen Kästchen<br />
se<strong>in</strong>. Egal, wie es auch immer ist, der Aufenthaltsort der Zahnbürste ist<br />
ke<strong>in</strong>e Eigenschaft der Zahnbürste selbst. Entsprechend ist auch hier e<strong>in</strong><br />
eigener Prozess zu modellieren, der <strong>in</strong> Bezug zum Aufenthaltsort steht.<br />
2. Man nimmt das Behältnis mit der Zahnpasta. Hier ist wieder zu bedenken,<br />
dass es ke<strong>in</strong>e Eigenschaft ist, dass Zahnpasta <strong>in</strong> e<strong>in</strong>er Tube vorhanden<br />
ist. Auch andere Behältnisse s<strong>in</strong>d möglich! Wie schon bei der<br />
Zahnbürste, so wird auch hier wieder ke<strong>in</strong>e Annahme getroffen, wo die<br />
Zahnpasta zu f<strong>in</strong>den ist.<br />
3. Man schmiert Zahnpasta auf die Zahnbürste. Wie dies genau stattf<strong>in</strong>det,<br />
ist abhängig vom Behältnis. Wenn wir als Beispiel e<strong>in</strong>e Tube nehmen,<br />
dann muss diese zuerst geöffnet werden, danach wird die Zahnpasta herausgedrückt<br />
(und landet hoffentlich auf der Zahnbürste und nicht am<br />
Boden :-)) und danach wird die Tube wieder verschlossen.
8.1 Module und Abläufe 151<br />
4. Man befeuchtet die Zahnbürste mit der <strong>in</strong>zwischen darauf bef<strong>in</strong>dlichen<br />
Zahnpasta. Dazu braucht man Wasser, also muss es irgendwo e<strong>in</strong>e Wasserleitung<br />
geben. Wie man diese nun wieder zu bedienen hat, damit man<br />
auch wirklich Wasser bekommt, ist e<strong>in</strong>e der Eigenschaften der spezifischen<br />
“Implementation” der Wasserleitung mit entsprechendem Wasserhahn.<br />
5. Man öffnet den Mund.<br />
6. Man steckt die Zahnbürste <strong>in</strong> den Mund und putzt. Das Putzen alle<strong>in</strong> ist<br />
wieder e<strong>in</strong> eigens zu beschreibender Prozess, denn dabei s<strong>in</strong>d genau betrachtet<br />
viele Schritte vonnöten: Zahnbürste auf und ab bewegen, Stelle<br />
der Bewegung ändern, Mund weiter öffnen und die Innenseite putzen,<br />
etc.<br />
7. Nach dem Putzen spült man den Mund mit Wasser aus. Auch dieser Prozess<br />
wird wieder extra beschrieben, denn auch hier spielt die Bedienung<br />
des Wasserhahns e<strong>in</strong>e Rolle, sowie auch, ob man e<strong>in</strong>en Zahnputzbecher<br />
benutzt oder nicht.<br />
8. Man re<strong>in</strong>igt die Zahnbürste mit Wasser.<br />
9. Man stellt die Zahnbürste zurück.<br />
Vorsicht Falle: Welche Leser haben es bemerkt? Ich habe zu Demonstrationszwecken<br />
wieder e<strong>in</strong>en Fehler <strong>in</strong> den obigen Ablauf e<strong>in</strong>gebaut...<br />
Nach der obigen Beschreibung nämlich geht unsere Person mit der Zahnpasta<br />
<strong>in</strong> der Hand <strong>in</strong> die Arbeit, denn nach Schritt 3 fehlt schlicht und<br />
ergreifend das Zurückstellen der Zahnpasta an ihren Platz.<br />
Wichtig bei den e<strong>in</strong>zelnen Prozessbeschreibungen ist nämlich, dass Vorund<br />
Nachbed<strong>in</strong>gungen e<strong>in</strong>gehalten werden. Die Vor- und Nachbed<strong>in</strong>gungen<br />
bei genau dieser Beschreibung des Zähne Putzens s<strong>in</strong>d e<strong>in</strong>fach, dass die<br />
Person vorher weder Zahnbürste noch Zahnpasta <strong>in</strong> der Hand hat, dass es<br />
Zahnbürste und Zahnpasta überhaupt gibt, dass die Zahnpasta nicht leer ist<br />
und dass nach dem Putzen wieder alles aus der Hand gelegt wird. Weiters<br />
ist natürlich e<strong>in</strong>e Vorbed<strong>in</strong>gung, dass die Person zum<strong>in</strong>dest e<strong>in</strong>e Hand frei<br />
haben muss.<br />
Man sieht, dass es auch bei den banalsten Prozessen e<strong>in</strong>e Fülle von Vorund<br />
Nachbed<strong>in</strong>gungen gibt. Wenn man diese alle explizit <strong>in</strong> die Prozessbeschreibung<br />
aufnimmt, dann wird alles sehr schnell sehr unübersichtlich. Aus<br />
diesem Grund ist e<strong>in</strong>e der üblichen Vorgehensweisen, dass man die Bed<strong>in</strong>gungen<br />
explizit vor der Prozessbeschreibung anführt und dann den Prozess<br />
auf die E<strong>in</strong>haltung dieser Bed<strong>in</strong>gungen überprüft.<br />
Durch die genauere Beschreibung des Zähne Putzens haben wir wieder<br />
neue Beteiligte mit ihren Eigenschaften identifiziert:<br />
Zahnbürste: Ob diese nun elektrisch ist oder nicht, spielt <strong>in</strong> unserer Betrachtung<br />
hier ke<strong>in</strong>e besondere Rolle. In jedem Fall hat sie wie auch immer<br />
geartete Borsten, die zum Putzen dienen und ist irgendwie vernünftig
152 8. Objektorientierung Allgeme<strong>in</strong><br />
genug mit der Hand angreifbar, dass man sie auch wirklich zum Putzen<br />
verwenden kann.<br />
Zahnpasta: Die Zahnpasta ist e<strong>in</strong>e wie auch immer geartete Substanz, die<br />
entweder zäh bis dickflüssig oder pulverförmig se<strong>in</strong> muss, ansonsten eignet<br />
sie sich nicht zum Putzen der Zähne. Natürlich muss sie auch sonstige,<br />
dem Putzvorgang zuträgliche Eigenschaften haben, die sie als Re<strong>in</strong>igungssubstanz<br />
auszeichnen. Schuhcreme ist vielleicht nicht die geeignetste<br />
Substanz zum Zähne putzen, obwohl sie die Eigenschaft erfüllt,<br />
zäh zu se<strong>in</strong> :-).<br />
Behälter: In unserem Beispiel haben wir e<strong>in</strong>en Behälter identifiziert, <strong>in</strong> dem<br />
man Zahnpasta aufbewahren kann. Dieser muss entsprechend dicht se<strong>in</strong><br />
und weiters e<strong>in</strong>en Verschluss besitzen, um ihn zu öffnen. Die Öffnung<br />
muss s<strong>in</strong>nvoll genug an die Eigenschaften des Behälters und der Zahnpasta<br />
angepasst se<strong>in</strong>, dass man auch Zahnpasta entnehmen kann. Ich<br />
möchte mir hier nähere Eigenschaften e<strong>in</strong>es allgeme<strong>in</strong>en Behälters ersparen,<br />
da wir <strong>in</strong> Kürze noch darauf kommen werden, was es damit auf sich<br />
hat.<br />
Tube: E<strong>in</strong>e Tube ist e<strong>in</strong> besonderer Behälter. Je nach Größe der Tube kann<br />
man dar<strong>in</strong> s<strong>in</strong>nvoll Zahnpasta aufbewahren und diese auch je nach Verschluss<br />
dosiert entnehmen.<br />
Kästchen: E<strong>in</strong>e Möglichkeit, wo man Zahnbürste und Zahnpasta aufbewahren<br />
kann, ist e<strong>in</strong> Kästchen. Bei genauerer Überlegung ist auch e<strong>in</strong><br />
Kästchen e<strong>in</strong> Behälter, nur eben mit anderen Eigenschaften als der, den<br />
wir für die Zahnpasta brauchen.<br />
Wasser: Wasser ist e<strong>in</strong>e Flüssigkeit mit besonderen Eigenschaften. Ich<br />
möchte hier nicht näher darauf e<strong>in</strong>gehen, welche Eigenschaften dies jetzt<br />
s<strong>in</strong>d. In jedem Fall ist es e<strong>in</strong>e Flüssigkeit.<br />
Wasserleitung: Dies ist e<strong>in</strong>e allgeme<strong>in</strong>e Leitung, durch die (üblicherweise)<br />
Flüssigkeit geleitet wird, mit den entsprechenden Eigenschaften, dass sie<br />
dicht ist, dass sie auch verzweigt se<strong>in</strong> kann, etc. und vor allem, dass sie<br />
auf das Leiten von Wasser ausgelegt ist.<br />
Wasserhahn: Dieser bef<strong>in</strong>det sich an zum<strong>in</strong>dest e<strong>in</strong>er Wasserleitung und zwar<br />
immer am Ende e<strong>in</strong>er Verzweigung jeder der beteiligten Leitungen. Er<br />
besitzt die Eigenschaften, dass er auf- und zugedreht se<strong>in</strong> kann, im Falle<br />
mehrerer Wasserleitungen (z.B. kalt/warm) besitzt er auch die Mischer-<br />
Eigenschaft, etc.<br />
Mund: Bisher haben wir identifiziert, dass es Zähne gibt, die geputzt werden<br />
müssen. Jetzt haben wir auch explizit identifiziert, dass diese im Mund<br />
zu f<strong>in</strong>den s<strong>in</strong>d und dass e<strong>in</strong>e Person e<strong>in</strong>en Mund besitzt. Abgesehen von<br />
anderen Eigenschaften kann man den Mund öffnen und schließen und<br />
zwar stufenlos.<br />
Ich glaube, es ist beim Durchspielen des Beispiels klar geworden, wie man<br />
Schritt für Schritt Abläufe analysiert, Beteiligte identifiziert, Eigenschaften<br />
f<strong>in</strong>det und weiter schrittweise verfe<strong>in</strong>ert. An dieser Stelle möchte ich den
8.1 Module und Abläufe 153<br />
Begriff des Moduls e<strong>in</strong>führen, der <strong>in</strong> der Informatik (und nicht nur dort)<br />
folgendermaßen def<strong>in</strong>iert ist:<br />
• E<strong>in</strong> Modul ist e<strong>in</strong>e vollkommen <strong>in</strong> sich abgeschlossene E<strong>in</strong>heit mit genau<br />
def<strong>in</strong>ierter Funktionalität.<br />
• Für diese Funktionalität gilt ausnahmslos die Regel, dass e<strong>in</strong> Modul immer<br />
nur e<strong>in</strong>e e<strong>in</strong>zige, se<strong>in</strong>em Abstraktionslevel entsprechende Aufgabengruppe<br />
erledigen darf.<br />
Anm.: Es ist hier absichtlich von Aufgabengruppen die Rede und nicht<br />
von E<strong>in</strong>zelaufgaben, zu diesen kommen wir noch <strong>in</strong> e<strong>in</strong>em anderen Zusammenhang.<br />
• E<strong>in</strong> Modul besitzt e<strong>in</strong>e genau def<strong>in</strong>ierte Schnittstelle zur Außenwelt, über<br />
die (und ausschließlich nur über die!!!) andere Module mit ihm kommunizieren<br />
können.<br />
• Die Schnittstelle des Moduls darf immer nur exakt se<strong>in</strong>em Aufgabengebiet<br />
<strong>in</strong>nerhalb se<strong>in</strong>es Abstraktionslevels entsprechen und niemals Implementationsdetails<br />
nach außen preisgeben, die <strong>in</strong> diesem Level belanglos s<strong>in</strong>d.<br />
Anders betrachtet beschreibt die Modulschnittstelle, was man mit ihm<br />
machen kann, niemals aber, wie dies implementiert ist.<br />
• Durch den Begriff der vollkommen <strong>in</strong> sich abgeschlossenen Funktionalität<br />
ist zwar schon alles gesagt, aber weil es so wichtig ist, möchte ich es hier<br />
noch e<strong>in</strong>mal anführen: E<strong>in</strong> Modul darf ke<strong>in</strong>e direkte Abhängigkeit zu besonderen<br />
Implementationen von Codeteilen außerhalb des Moduls haben.<br />
Es ist ausschließlich die Verwendung von anderen Modulen über ihre def<strong>in</strong>ierten<br />
Schnittstellen erlaubt.<br />
• E<strong>in</strong> Modul kann aus beliebig vielen Submodulen bestehen, die <strong>in</strong> ihrem<br />
Abstraktionslevel um e<strong>in</strong>e Stufe tiefer liegen. Das impliziert, dass alle<br />
Module immer hierarchisch nach ihren Abstraktionslevels strukturiert s<strong>in</strong>d.<br />
Nehmen wir zur Demonstration das Teilbeispiel des Zähneputzens und betrachten<br />
die dort identifizierten Beteiligten und Abläufe. Wenn wir den hier<br />
def<strong>in</strong>ierten Modulbegriff auf dieses Konglomerat anwenden (soll heißen, wir<br />
suchen Funktionalitätsgruppen), so ergibt sich Folgendes:<br />
Zahnpflege-Modul: In diesem ist alle Funktionalität zu f<strong>in</strong>den, die mit der<br />
Zahnpflege zu tun hat. Dies be<strong>in</strong>haltet, wie man e<strong>in</strong>e Zahnbürste gebraucht,<br />
dass es Zahnpasta gibt und wie man mit ihr umgeht, etc.<br />
Behälter-Modul: Dieses Modul fasst alle Funktionalität zusammen, die mit<br />
Behältern zu tun hat, nämlich, welche es gibt (Tube, Becher, Kasten),<br />
wie man mit ihnen umgeht, etc.<br />
Wasserversorgungs-Modul: Dieses Modul fasst alles zusammen, was man zur<br />
Wasserversorgung an Funktionalität braucht, also Wasserleitung, Wasserhahn,<br />
wie man diesen bedient, etc.<br />
Mensch: Dieses Modul fasst das gesamte Zusammenspiel der E<strong>in</strong>zelteile des<br />
Menschen (Hände, Mund, etc.) zusammen und auch, wie e<strong>in</strong> Mensch mit<br />
se<strong>in</strong>er Umwelt umgeht (greifen, sehen, etc.).
154 8. Objektorientierung Allgeme<strong>in</strong><br />
Die Schnittstellen und Abhängigkeiten der e<strong>in</strong>zelnen Module s<strong>in</strong>d auch leicht<br />
identifizierbar, wie am Beispiel des Menschen und der Zahnpflege kurz demonstriert<br />
werden soll:<br />
• E<strong>in</strong> Mensch verwendet das Zahnpflege-Modul und das Wasserversorgungs-<br />
Modul, um sich die Zähne zu putzen. Die Schnittstelle des Menschen zur<br />
Außenwelt s<strong>in</strong>d se<strong>in</strong>e S<strong>in</strong>nesorgane, die Hände zum Greifen, die Be<strong>in</strong>e und<br />
Füße zum Gehen, etc.<br />
• Das Zahnpflege-Modul stellt als Schnittstelle nach außen e<strong>in</strong>e Zahnbürste<br />
und die Zahnpasta zur Verfügung. Weiters ist e<strong>in</strong>e der Randbed<strong>in</strong>gungen<br />
zur richtigen Verwendung dieses Moduls, dass es e<strong>in</strong> Wasserversorgungs-<br />
Modul geben muss, denn sonst funktioniert die Zahnpflege nicht vernünftig.<br />
Auch diese Bed<strong>in</strong>gung ist im Pr<strong>in</strong>zip e<strong>in</strong> Teil der Modulschnittstelle,<br />
obwohl damit nicht direkt irgendwelche möglichen Aufrufe verbunden s<strong>in</strong>d.<br />
Um nicht das Ziel aus den Augen zu verlieren – was haben wir bisher eigentlich<br />
erreicht, außer dass wir wissen, dass man sich <strong>in</strong> der Früh die Zähne<br />
putzt und wie das funktioniert? Wir haben das Problem <strong>in</strong> überschaubare<br />
Teilprobleme und Abläufe zerlegt. Das notwendige Wissen bei e<strong>in</strong>er weiteren<br />
Zerlegung jedes gefundenen Moduls beschränkt sich nur noch darauf, dass<br />
es auch andere Teile gibt, die etwas zur Verfügung stellen (=Module mit<br />
Schnittstellen), nicht aber, wie diese das <strong>in</strong>tern machen. Man will ja auch<br />
nicht genau wissen, wie e<strong>in</strong> Wasserhahn <strong>in</strong>tern konstruiert ist, wenn man nur<br />
das Wasser aufdrehen will. Es genügt, dass man weiß, dass man am Hahn<br />
dreht und dann r<strong>in</strong>nt Wasser heraus. Ok, ich weiß schon, es gibt auch E<strong>in</strong>handmischer,<br />
bei denen ke<strong>in</strong> Hahn mehr zum Drehen existiert, aber das ist<br />
jetzt nicht Thema der Diskussion :-).<br />
So nett das mit den überschaubaren Teilproblemen auch kl<strong>in</strong>gt und so<br />
hilfreich diese Vorgangsweise auch ist, irgendwie kann das aber trotzdem noch<br />
nicht alles gewesen se<strong>in</strong>. Bei der <strong>Softwareentwicklung</strong> will man ja nicht nur<br />
e<strong>in</strong> Problem analysieren, sondern man will ja auch e<strong>in</strong> <strong>in</strong>formatisches Modell<br />
bilden, <strong>in</strong> dem man die Abläufe implementiert. Am schönsten wäre es, wenn<br />
man die reale Welt gleich direkt im Computer nachbilden könnte, denn dann<br />
ist das <strong>in</strong>formatische Modell sicher am leichtesten zu verstehen und auch zu<br />
implementieren. Am schönsten wäre es doch, wenn man im Computer gleich<br />
die zu Beg<strong>in</strong>n auf Seite 147 identifizierten Beteiligten modellieren könnte<br />
und diese direkt mite<strong>in</strong>ander <strong>in</strong>teragieren lassen könnte. Dann nämlich hätte<br />
man gleich <strong>in</strong> der Analysephase des Problems e<strong>in</strong> Modell geschaffen, das ohne<br />
weitere “Übersetzung” implementierbar ist. Versuchen wir e<strong>in</strong>mal e<strong>in</strong> kurzes<br />
Bra<strong>in</strong>storm<strong>in</strong>g, wie man sich so e<strong>in</strong> Modell vorstellen könnte:<br />
• Es gibt e<strong>in</strong>en Menschen.<br />
– E<strong>in</strong> Mensch hat e<strong>in</strong>en Kopf.<br />
• Im Kopf gibt es e<strong>in</strong> Gehirn.<br />
• Im Kopf gibt es Augen, mit denen der Mensch sieht.
8.2 Klassen und Objekte 155<br />
• Im Kopf gibt es e<strong>in</strong>en Mund, durch den e<strong>in</strong> Mensch Nahrung zu sich<br />
nehmen kann, atmen kann und sprechen kann.<br />
· Im Mund gibt es Zähne. Diese s<strong>in</strong>d hart und man kann mit ihnen<br />
beißen.<br />
· Im Mund gibt es e<strong>in</strong>e Zunge. Diese ist beweglich, usw.<br />
· etc.<br />
• etc.<br />
– E<strong>in</strong> Mensch hat e<strong>in</strong>en Rumpf.<br />
– E<strong>in</strong> Mensch hat Arme.<br />
– E<strong>in</strong> Mensch hat Hände, wobei diese durch die Arme mit dem Rumpf<br />
verbunden s<strong>in</strong>d.<br />
– E<strong>in</strong> Mensch hat Be<strong>in</strong>e.<br />
– E<strong>in</strong> Mensch hat Füße, wobei diese durch die Be<strong>in</strong>e mit dem Rumpf<br />
verbunden s<strong>in</strong>d.<br />
– Die Tätigkeiten e<strong>in</strong>es Menschen werden zentral gesteuert.<br />
• Die Steuerung erfolgt durch Signale aus dem Gehirn.<br />
• Das Gehirn kann direkt mit allen e<strong>in</strong>zelnen Bestandteilen des Menschen<br />
kommunizieren. Dabei können Bewegungen hervorgerufen werden,<br />
etc.<br />
• etc.<br />
– E<strong>in</strong> Mensch kann gehen.<br />
– etc.<br />
• Es gibt Gebäude.<br />
– E<strong>in</strong> Gebäude ist unterteilt <strong>in</strong> Räume.<br />
– etc.<br />
• etc.<br />
Ich habe bewusst das Wort Bra<strong>in</strong>storm<strong>in</strong>g verwendet, denn dieses Modell<br />
ist noch nicht vollständig tragfähig für e<strong>in</strong>e Implementation, weil es nicht<br />
sauber genug strukturiert ist. Unter dem Begriff der sauberen Strukturierung<br />
wird im S<strong>in</strong>ne der Objektorientiertheit e<strong>in</strong>e saubere semantische Strukturierung<br />
nach E<strong>in</strong>zelteilen, Eigenschaften, Tätigkeiten und verschiedenartigen<br />
Zusammenhängen verstanden, die e<strong>in</strong>en geme<strong>in</strong>samen Abstraktionslevel besitzen.<br />
In welchen Kategorien und Modellen hierbei die Erf<strong>in</strong>der von OO Programmiersprachen<br />
gedacht haben und welche Ausdrucksmittel dabei zur Modellierung<br />
zur Verfügung stehen, ist das Thema des folgenden Abschnitts, der<br />
uns damit endlich wirklich zum Kern der OO <strong>Softwareentwicklung</strong> br<strong>in</strong>gt.<br />
8.2 Klassen und Objekte<br />
In der Natur der menschlichen Denkweise liegt das Begreifen der Welt als<br />
e<strong>in</strong>e Ansammlung von e<strong>in</strong>zelnen Objekten, die Eigenschaften haben und mite<strong>in</strong>ander<br />
<strong>in</strong> irgendwie gearteter Verb<strong>in</strong>dung stehen. Bei den Eigenschaften
156 8. Objektorientierung Allgeme<strong>in</strong><br />
unterscheidet man im täglichen Leben völlig <strong>in</strong>tuitiv zwischen solchen, die<br />
<strong>in</strong> der Natur e<strong>in</strong>es Objekts liegen (z.B. hat e<strong>in</strong> Mensch e<strong>in</strong>en Kopf, e<strong>in</strong>en<br />
Rumpf, Arme, Be<strong>in</strong>e, etc.) und solchen, die derzeitig gültige, besondere Ausprägungen<br />
von möglichen naturgegebenen Eigenschaften s<strong>in</strong>d (z.B. gute oder<br />
schlechte Laune). Objekte können Aktionen ausführen, entweder selbsttätig<br />
oder <strong>in</strong> Verb<strong>in</strong>dung mit anderen Objekten (z.B. e<strong>in</strong> Mensch fährt mit e<strong>in</strong>em<br />
Auto). Um e<strong>in</strong>e Aktion auszuführen gibt es Voraussetzungen, z.B. kann nur<br />
jemand mit e<strong>in</strong>em Auto fahren, der auch weiß, wie man das tut. Das bedeutet,<br />
dass die Fähigkeit, mit e<strong>in</strong>em Auto zu fahren, ke<strong>in</strong>e naturgegebene<br />
Eigenschaft e<strong>in</strong>es jeden Menschen ist.<br />
Abstrahiert man diese Betrachtungen und versucht e<strong>in</strong> allgeme<strong>in</strong>es Ausdrucksmittel<br />
für solche Denkmuster zu f<strong>in</strong>den, dann landet man pr<strong>in</strong>zipiell<br />
e<strong>in</strong>mal bei folgendem Ergebnis:<br />
• Es gibt Klassen. Diese fassen allgeme<strong>in</strong> alle naturgegebenen Möglichkeiten<br />
e<strong>in</strong>er bestimmten Gruppe von Objekten zusammen.<br />
• Es gibt Objekte. Diese s<strong>in</strong>d e<strong>in</strong>zelne Instanzen von bestimmten Klassen<br />
mit e<strong>in</strong>em bestimmten Status. Damit stellt e<strong>in</strong> Objekt e<strong>in</strong>e besondere<br />
Ausprägung der durch die Klasse vorgegebenen Möglichkeiten dar.<br />
Nach diesem Begriff wäre zum Beispiel e<strong>in</strong> Mensch aus dem zuvor besprochenen<br />
Beispiel e<strong>in</strong>e Klasse, die alle se<strong>in</strong>e naturgegebenen Eigenschaften und<br />
Fähigkeiten beschreibt. Die Person, die <strong>in</strong> der Früh aufsteht und zur Arbeit<br />
geht bzw. fährt, ist e<strong>in</strong> e<strong>in</strong>e Instanz e<strong>in</strong>es Menschen, also e<strong>in</strong> Objekt. Die besonderen<br />
Eigenschaften, die diese Person aus der Fülle der Möglichkeiten des<br />
Menschen hat, s<strong>in</strong>d durch die Instanz bestimmt. Zum Beispiel kann sich unsere<br />
Person (hoffentlich :-)) die Zähne putzen. Auch e<strong>in</strong> neugeborenes K<strong>in</strong>d<br />
ist e<strong>in</strong>e Person und damit der Klasse Mensch zugehörig. Mit dem Zähne<br />
putzen ist es allerd<strong>in</strong>gs beim Neugeborenen noch nicht so weit her.<br />
Damit wären wir nun endlich bei den Grundkonstrukten von OO Programmiersprachen<br />
gelandet, die es erlauben, e<strong>in</strong>e für den Menschen natürliche<br />
Modellbildung vorzunehmen. In allen OO Programmiersprachen gibt es<br />
Klassen und Objekte mit genau der Bedeutung, wie sie soeben besprochen<br />
wurde.<br />
Vorsicht Falle: Leider zieht sich durch die gesamte Literatur e<strong>in</strong>e völlig<br />
verwaschene Def<strong>in</strong>ition durch, <strong>in</strong> der die Begriffe Klasse und Objekt nur allzu<br />
oft als Synonym verwendet werden. Dies ist grundfalsch! Ich möchte<br />
e<strong>in</strong>dr<strong>in</strong>glichst davor warnen, die Begriffe Klasse und Objekt mite<strong>in</strong>ander zu<br />
vermischen, denn <strong>in</strong> diesen Begriffen liegt wichtige Semantik, die sich im Programmdesign<br />
niederschlägt und damit e<strong>in</strong> völlig falsches bzw. unverständliches<br />
Design ergibt!<br />
Wenn e<strong>in</strong>e Klasse e<strong>in</strong>e Eigenschaft hat, so ist dies etwas Naturgegebenes,<br />
was alle ihre Instanzen, also Objekte, <strong>in</strong> ihren Grundanlagen besitzen.<br />
Wenn e<strong>in</strong> Objekt e<strong>in</strong>e Eigenschaft hat, so ist dies e<strong>in</strong>e besondere Ausprägung<br />
bzw. e<strong>in</strong> Status, der sich nur auf diese e<strong>in</strong>e Instanz bezieht. E<strong>in</strong>e
8.2 Klassen und Objekte 157<br />
solche Ausprägung folgt immer den naturgegebenen Möglichkeiten, die e<strong>in</strong>e<br />
Klasse vorgibt.<br />
Umgelegt auf programmiersprachliche Ebene kann man auch folgende Def<strong>in</strong>ition<br />
geben:<br />
• E<strong>in</strong>e Klasse entspricht e<strong>in</strong>em Datentyp.<br />
• E<strong>in</strong> Objekt entspricht e<strong>in</strong>er Variable.<br />
OO Software folgt dem Modell, dass es <strong>in</strong> e<strong>in</strong>em Programm beliebig viele<br />
Objekte und Interaktionen zwischen diesen Objekten gibt. Durch die Interaktionen<br />
wird der Programmfluss bestimmt. Alle grundsätzlich möglichen<br />
Interaktionen mit e<strong>in</strong>em Objekt s<strong>in</strong>d durch se<strong>in</strong>e Klasse vorgegeben. Um<br />
jetzt nicht gleich e<strong>in</strong>e ganze Person zu modellieren, wie sie im obigen Beispiel<br />
vorkam, nehmen wir als kle<strong>in</strong>es und e<strong>in</strong>faches Beispiel me<strong>in</strong> Telefon am<br />
Schreibtisch:<br />
Me<strong>in</strong> Telefon am Schreibtisch ist e<strong>in</strong> Objekt, nämlich e<strong>in</strong>e Instanz e<strong>in</strong>er<br />
allgeme<strong>in</strong>en Klasse Telefon. Diese Klasse, die die Eigenschaften e<strong>in</strong>es Telefons<br />
und die möglichen Interaktionen mit e<strong>in</strong>em solchen bestimmt, kann<br />
vere<strong>in</strong>facht folgendermaßen aussehen:<br />
Bestandteile des Telefons:<br />
• E<strong>in</strong> Telefon besitzt e<strong>in</strong>en Telefonhörer.<br />
• E<strong>in</strong> Telefon besitzt e<strong>in</strong>e Gabel, auf der der Telefonhörer liegt.<br />
• E<strong>in</strong> Telefon besitzt e<strong>in</strong>en numerischen Tastenblock.<br />
• E<strong>in</strong> Telefon besitzt e<strong>in</strong>e Leitung zum Telefonnetz.<br />
• E<strong>in</strong> Telefon besitzt e<strong>in</strong>e Kl<strong>in</strong>gel.<br />
Mögliche Interaktionen mit dem Telefon aus Benutzersicht:<br />
• Man kann e<strong>in</strong>e Nummer wählen, die man anrufen will. Das geschieht<br />
über den numerischen Ziffernblock. Damit das Telefon e<strong>in</strong>e Wahl auch<br />
akzeptiert, darf der Hörer nicht auf der Gabel liegen.<br />
• Man kann angerufen werden. In diesem Fall macht sich das Telefon<br />
lautstark bemerkbar. Dies geschieht über die Kl<strong>in</strong>gel.<br />
• Man kann e<strong>in</strong>en Anruf annehmen. Dazu hebt man den Hörer von der<br />
Gabel.<br />
• Man kann mit jemandem sprechen. Dies geschieht über den Telefonhörer,<br />
der <strong>in</strong> diesem Fall nicht auf der Gabel liegen darf.<br />
• Man kann e<strong>in</strong> Gespräch beenden. Dies geschieht, <strong>in</strong>dem man den Telefonhörer<br />
auf die Gabel legt.<br />
Andere mögliche Interaktionen mit dem Telefon:<br />
• Das Telefon <strong>in</strong>teragiert mit dem Telefonnetz. Mögliche Interaktionen<br />
s<strong>in</strong>d sowohl die Nummernwahl als auch e<strong>in</strong> Signal für e<strong>in</strong>en e<strong>in</strong>gehenden<br />
Anruf, sowie das Gespräch selbst.<br />
• Das Telefon <strong>in</strong>teragiert mit dem Hörer. E<strong>in</strong>gehende Sprachsignale vom<br />
Telefonnetz werden an ihn weitergeleitet und Sprachsignale vom Hörer<br />
werden an das Telefonnetz weitergereicht.
158 8. Objektorientierung Allgeme<strong>in</strong><br />
• Das Telefon <strong>in</strong>teragiert mit dem Ziffernblock. Für jede gedrückte Taste<br />
wird e<strong>in</strong> e<strong>in</strong>deutiger Impuls empfangen.<br />
• Das Telefon <strong>in</strong>teragiert mit der Gabel, die sich als e<strong>in</strong>facher e<strong>in</strong>/aus<br />
Schalter präsentiert.<br />
• Das Telefon <strong>in</strong>teragiert mit der Kl<strong>in</strong>gel. Es kann diese zum Läuten<br />
veranlassen.<br />
Vorsicht Falle: Ich kann es e<strong>in</strong>fach nicht lassen... Weil sich alles so banal<br />
liest und dazu verleitet, das Geschriebene als sowieso klar h<strong>in</strong>zunehmen,<br />
musste ich schon wieder e<strong>in</strong>en groben Designfehler e<strong>in</strong>bauen, der sich im<br />
Lauf e<strong>in</strong>er Entwicklung als echte Zeitbombe entpuppen kann.<br />
Im Beispiel wird davon ausgegangen, dass der Hörer nicht auf der Gabel<br />
liegen darf, wenn man mit dem Gegenüber spricht oder e<strong>in</strong>e Nummer wählen<br />
will. Das stimmt zwar, aber es ist nicht die ganze Wahrheit: In Wirklichkeit<br />
darf die Gabel nicht gedrückt se<strong>in</strong>, egal ob durch den Hörer, den F<strong>in</strong>ger<br />
des Telefonierenden, den Fuß des Telefonierenden, der gerade am Tisch liegt,<br />
oder sonst etwas. Modelliert man die Klasse so, dass nur der Hörer dafür<br />
verantwortlich zeichnet, dass die Gabel gedrückt ist, dann ist das e<strong>in</strong> glattes<br />
Fehldesign, das der Realität nicht entspricht. Wir wollen <strong>in</strong> unserem Modell<br />
aber die Realität nachbilden!<br />
Solcherart Fehler schleichen sich nur allzu leicht <strong>in</strong> e<strong>in</strong> Design e<strong>in</strong> und<br />
müssen später teuerst mit Programmänderungen oder irgendwelchen Workarounds<br />
wieder korrigiert werden. Zumeist aber werden bei solchen Korrekturen<br />
später nur Spezialfälle korrigiert, die gerade wichtig s<strong>in</strong>d. Damit ist zwar<br />
e<strong>in</strong> zu dieser Zeit wichtiger Fall abgedeckt, aber leider entfernt sich das Modell<br />
noch weiter von der Realität, was die nächsten gröberen Probleme dann<br />
gleich nach sich zieht. Aus diesem Grund kann ich nur e<strong>in</strong>en ganz wichtigen<br />
Ratschlag geben:<br />
Das erarbeitete Modell muss laufend mit der Realität verglichen werden,<br />
ob es nicht vielleicht von dieser abweicht. Jede Abweichung muss sofort korrigiert<br />
werden, denn Fehler im Programmdesign potenzieren sich im Lauf der<br />
Zeit. Vor allem verliert man die Vorteile des OO Ansatzes, denn man muss<br />
plötzlich beg<strong>in</strong>nen, von der Realität auf das Modell zu übersetzen, anstatt e<strong>in</strong>e<br />
direkte Abbildung zu haben!<br />
Nach Korrektur des Designfehlers lesen sich die entsprechenden Punkte<br />
folgendermaßen:<br />
Mögliche Interaktionen mit dem Telefon aus Benutzersicht:<br />
• Man kann e<strong>in</strong>e Nummer wählen, die man anrufen will. Das geschieht<br />
über den numerischen Ziffernblock. Damit das Telefon e<strong>in</strong>e Wahl auch<br />
akzeptiert, darf die Gabel nicht gedrückt se<strong>in</strong>.<br />
• Man kann e<strong>in</strong>en Anruf annehmen. Dazu hebt man den Hörer ab. Die<br />
Gabel darf nicht gedrückt se<strong>in</strong>.
8.2 Klassen und Objekte 159<br />
• Man kann mit jemandem sprechen. Dies geschieht über den Telefonhörer.<br />
Die Gabel darf nicht gedrückt se<strong>in</strong>.<br />
• Man kann e<strong>in</strong> Gespräch beenden. Dies geschieht, <strong>in</strong>dem man die Gabel<br />
drückt.<br />
Zurück zum Ausgangspunkt – wir haben jetzt e<strong>in</strong>e primitive Klasse Telefon.<br />
Davon wird e<strong>in</strong>e Instanz erzeugt, also e<strong>in</strong> Objekt, das das Telefon auf me<strong>in</strong>em<br />
Schreibtisch darstellt. Damit diese e<strong>in</strong>e Instanz funktioniert, müssen folgende<br />
D<strong>in</strong>ge passieren:<br />
• Das Telefon muss an das Telefonnetz angeschlossen werden. Und schon<br />
haben wir den nächsten Fehler im Design der Telefonklasse entdeckt: Es<br />
fehlt e<strong>in</strong>e Interaktionsmöglichkeit, die es erlauben würde, dass man das<br />
Telefon ans Netz anschließt. Diese Möglichkeit muss im Design der Klasse<br />
ergänzt werden.<br />
• Das Telefon wird auf den Schreibtisch gestellt. Das bedeutet, dass wir<br />
e<strong>in</strong>e Instanz e<strong>in</strong>es Schreibtischs brauchen, die genau me<strong>in</strong>en Schreibtisch<br />
repräsentiert. Diese Instanz ist von der Klasse Schreibtisch, die wiederum<br />
auf jeden Fall e<strong>in</strong>e Interaktion “stelle e<strong>in</strong> beliebiges Objekt darauf” unterstützt.<br />
Dass das darauf gestellte Objekt zu schwer se<strong>in</strong> könnte und der<br />
Schreibtisch dann als Reaktion alle vier Be<strong>in</strong>e von sich streckt, lassen wir<br />
im Augenblick e<strong>in</strong>mal außer Acht. Im Design des Schreibtischs gehört e<strong>in</strong>e<br />
solche Randbed<strong>in</strong>gung allerd<strong>in</strong>gs sehr wohl modelliert!<br />
• Das Telefonnetz muss wissen, welche Nummer me<strong>in</strong>em Telefon zugeordnet<br />
wird und wo dieses genau am Netz hängt, damit es e<strong>in</strong>en Anruf korrekt<br />
durchleitet.<br />
Vorsicht Falle: Ne<strong>in</strong>, diesmal habe ich ke<strong>in</strong>en Designfehler e<strong>in</strong>gebaut. Allerd<strong>in</strong>gs<br />
möchte ich vor e<strong>in</strong>em solchen warnen, der nur allzu leicht passiert,<br />
wenn man ungeübt ist: Neul<strong>in</strong>ge (und leider auch manchmal erfahrene Entwickler)<br />
begehen gerne den Fehler, dass die Telefonnummer nicht dem Telefonnetz<br />
bekannt ist, wie oben beschrieben, sondern dem Objekt des speziellen<br />
Telefons selbst zu eigen ist. Es ist allerd<strong>in</strong>gs ke<strong>in</strong>e Eigenschaft der Klasse Telefon,<br />
dass es e<strong>in</strong>e Nummer besitzt. Vielmehr ist es e<strong>in</strong>e Eigenschaft der<br />
Klasse Telefonnetz, dass jedem Telefon e<strong>in</strong>e Nummer zugeordnet ist! Die<br />
Verantwortlichkeit liegt also beim Netz und niemals beim Telefon.<br />
Wozu es führt, wenn man e<strong>in</strong>en so folgenschweren Fehler begeht, lässt sich<br />
leicht erkennen, wenn man sich überlegt, dass man e<strong>in</strong> gesamtes Netz mit<br />
vielen daran hängenden Telefonen modellieren soll: Wie ruft man jemanden<br />
an? Man wählt e<strong>in</strong>e Nummer und teilt diese dem Netz mit. Und was tut<br />
das Netz dann? Wenn jedes Telefon se<strong>in</strong>e eigene Nummer selbst weiß, dann<br />
muss das Netz der Reihe nach alle Telefone um die Nummern fragen, bis<br />
es das richtige gefunden hat. Dass das nicht funktionieren kann, ist leicht<br />
e<strong>in</strong>zusehen.
160 8. Objektorientierung Allgeme<strong>in</strong><br />
Es ist also zw<strong>in</strong>gend notwendig, dass man während des gesamten Designprozesses<br />
immer e<strong>in</strong>en klaren Blick für die Verantwortlichkeiten der e<strong>in</strong>zelnen<br />
Klassen und Objekte hat und diese auch immer wieder auf ihre S<strong>in</strong>nhaftigkeit<br />
überprüft.<br />
E<strong>in</strong>e Kle<strong>in</strong>igkeit fehlt uns allerd<strong>in</strong>gs noch, um s<strong>in</strong>nvoll OO Design betreiben<br />
zu können. Es lässt sich leicht überlegen, das es recht mühsam und vor<br />
allem unnötig se<strong>in</strong> kann, für jeden unterschiedlichen Telefontyp e<strong>in</strong>e eigene<br />
Klasse zu designen, die im Grunde genommen fast dasselbe tut wie alle anderen<br />
speziellen Telefonklassen, nur dass e<strong>in</strong> paar Kle<strong>in</strong>igkeiten verändert s<strong>in</strong>d.<br />
Stellen wir uns e<strong>in</strong>fach vor, dass auf e<strong>in</strong>em anderen Schreibtisch e<strong>in</strong> Telefon<br />
steht, das zusätzlich zum numerischen Block noch e<strong>in</strong>en weiteren frei programmierbaren<br />
Tastenblock besitzt. Es wäre doch wirklich nicht besonders<br />
s<strong>in</strong>nvoll, nur deswegen alles <strong>in</strong>cl. Hörer, Ziffernblock, Kabel zum Telefonnetz,<br />
etc. vollständig neu zu designen, bloß weil e<strong>in</strong> Teil dazugekommen ist.<br />
Genauso s<strong>in</strong>nlos wäre es, das gesamte Design des e<strong>in</strong>facheren Telefons zu<br />
kopieren und beim komplizierteren Telefon dann die noch fehlende Funktionalität<br />
zu ergänzen. Man stelle sich nur e<strong>in</strong>mal vor, das Design wurde oft<br />
genug kopiert und plötzlich ergibt sich aus welchen Gründen auch immer e<strong>in</strong>e<br />
Änderung im Design der Grundfunktionalität e<strong>in</strong>es Telefons. Dann müssten<br />
alle Telefonklassen überarbeitet werden (von realen Implementationen und<br />
den dabei <strong>in</strong> der Natur der Sache liegenden Fehlerbehebungen will ich hier<br />
noch gar nicht sprechen :-)).<br />
Was man sich wünschen würde wäre folgende Möglichkeit:<br />
• Man entwirft e<strong>in</strong> Design für e<strong>in</strong>e Klasse Telefon, das die M<strong>in</strong>imalversion<br />
e<strong>in</strong>es Telefons beschreibt, wie wir das zuvor gemacht haben.<br />
• Sollte man e<strong>in</strong> anderes Telefon wünschen, also z.B. e<strong>in</strong>es mit e<strong>in</strong>em zusätzlichen<br />
Tastenblock oder mit e<strong>in</strong>em Display oder mit beidem, dann verwendet<br />
man die bereits existente Klasse und erweitert sie nur um die zusätzlichen<br />
Eigenschaften und Interaktionsmöglichkeiten.<br />
Diese Art der Wiederverwendbarkeit ist, wie zu erwarten, e<strong>in</strong>es der essentiellen<br />
Konzepte der OO <strong>Softwareentwicklung</strong> und wird durch den Mechanismus<br />
der Ableitung realisiert. E<strong>in</strong>e solche Ableitung funktioniert folgendermaßen:<br />
• Es gibt e<strong>in</strong>e Basisklasse, die e<strong>in</strong>e Grundfunktionalität def<strong>in</strong>iert, die allen<br />
Vertretern dieser Klasse zu eigen ist. Im Falle unseres Telefons ist dies<br />
die m<strong>in</strong>imale Funktionalität, die alles zum Thema anrufen und angerufen<br />
werden zur Verfügung stellt.<br />
• Es kann beliebig viele Klassen geben, die von der Basisklasse abgeleitet<br />
s<strong>in</strong>d. Durch die Ableitung erben sie automatisch die Eigenschaften der<br />
Basisklasse. Genau dadurch erspart man sich das Kopieren.<br />
Anm.: In der Fachliteratur f<strong>in</strong>det sich sehr oft der englische Begriff Inheritance<br />
für die Vererbung.
8.3 Richtige Verwendung der OO Mechanismen 161<br />
• Abgeleitete Klassen erben nicht nur die Eigenschaften der Basisklasse, sie<br />
können auch deren Eigenschaften anpassen oder neue Eigenschaften h<strong>in</strong>zufügen.<br />
Ich spreche hier absichtlich von h<strong>in</strong>zufügen, denn es ist pr<strong>in</strong>zipiell<br />
im OO Modell nicht vorgesehen, Eigenschaften wegzunehmen. Technisch<br />
wäre dies zwar je nach verwendeter OO Programmiersprache mehr schlecht<br />
als recht machbar, aber es ist absolut unsauber und auch wirklich s<strong>in</strong>nlos.<br />
Denn wenn man Eigenschaften wegnehmen muss, dann hat man bereits <strong>in</strong><br />
der Basisklasse e<strong>in</strong>en Designfehler und sollte diesen dort schnellstens korrigieren!<br />
Polemisch formuliert: Man verwendet ja auch nicht e<strong>in</strong>en Ferrari<br />
als Basismodell für e<strong>in</strong> Auto und montiert dann die entsprechenden Teile<br />
ab, um daraus e<strong>in</strong>en Kle<strong>in</strong>wagen zu bauen.<br />
Im Falle e<strong>in</strong>es Telefons mit e<strong>in</strong>em zusätzlichen frei programmierbaren Ziffernblock<br />
kann man dieses von der Basisklasse e<strong>in</strong>es Telefons ableiten und<br />
der abgeleiteten Klasse e<strong>in</strong>fach die Eigenschaft des zusätzlichen Ziffernblocks<br />
spendieren. Damit erhält man ohne großes Neudesign das gewünschte<br />
verbesserte Telefon. Das abgeleitete Telefon funktioniert also, was die<br />
Grundeigenschaften betrifft, gleich wie jedes andere Telefon auch, nur kann<br />
es durch das H<strong>in</strong>zufügen des Ziffernblocks noch etwas mehr als das Standardmodell.<br />
• Es ist nicht notwendig, dass man immer von e<strong>in</strong>er Basisklasse ableitet.<br />
Man kann auch von bereits abgeleiteten Klassen weiter ableiten. Auf diese<br />
Art kann man schrittweise, je nach Notwendigkeit, Eigenschaften anpassen<br />
und h<strong>in</strong>zufügen.<br />
• Im S<strong>in</strong>ne der Allgeme<strong>in</strong>heit ist es möglich, dass e<strong>in</strong>e Klasse nicht nur von<br />
e<strong>in</strong>er e<strong>in</strong>zigen Klasse abgeleitet ist (=s<strong>in</strong>gle Inheritance), sondern gleichzeitig<br />
von mehreren Klassen. Dies wird als Mehrfachvererbung (=multiple<br />
Inheritance) bezeichnet. C ++ unterstützt multiple Inheritance, manche<br />
andere Programmiersprachen (z.B. Java) nicht. Der Grund, warum nicht<br />
alle OO Programmiersprachen multiple Inheritance unterstützen, ist die<br />
große Gefahr, die bei Fehlverwendung von diesem Konstrukt ausgeht. Im<br />
weiteren Verlauf dieses Buchs werden potentielle Stolperste<strong>in</strong>e durch multiple<br />
Inheritance noch mehrfach diskutiert werden. Als kle<strong>in</strong>e Vorwarnung<br />
möchte ich hier nur sagen: Multiple Inheritance, vorsichtig und gezielt e<strong>in</strong>gesetzt,<br />
eröffnet sehr elegante Möglichkeiten, zu e<strong>in</strong>em sauberen Design zu<br />
kommen. Jedoch führen schon die kle<strong>in</strong>sten Unachtsamkeiten sehr schnell<br />
zu riesigen, schwer korrigierbaren Katastrophen.<br />
Um niemanden vorzeitig <strong>in</strong> Versuchung zu führen, erspare ich mir an dieser<br />
Stelle e<strong>in</strong> Beispiel, wie man Mehrfachvererbung e<strong>in</strong>setzen könnte :-).<br />
8.3 Richtige Verwendung der OO Mechanismen<br />
Das Schwierigste bei der OO <strong>Softwareentwicklung</strong> ist die semantisch richtige<br />
Verwendung der Mechanismen, die gerade im Rahmen der Klassen und Objekte<br />
erklärt wurden. Um e<strong>in</strong> wirklich sauberes Modell zu entwickeln, das die
162 8. Objektorientierung Allgeme<strong>in</strong><br />
Realität <strong>in</strong> e<strong>in</strong> Design abbildet, ohne die zur Verfügung stehenden Mechanismen<br />
missbräuchlich zu verwenden oder gar völlig zu vergewaltigen, braucht<br />
es sehr viel Erfahrung. In der Folge möchte ich deshalb e<strong>in</strong> paar Eckpfeiler<br />
als Startpunkt für das Sammeln von Erfahrung diskutieren um den Lernprozess<br />
diesbezüglich e<strong>in</strong> wenig zu beschleunigen. Ich möchte jedoch zum<br />
wiederholten Male darauf h<strong>in</strong>weisen, dass Übung im Umgang mit OO <strong>Softwareentwicklung</strong><br />
das Wichtigste überhaupt ist und durch ke<strong>in</strong>e Erklärung der<br />
Welt zu ersetzen ist!<br />
Fassen wir e<strong>in</strong>mal kurz alles zusammen, was bisher bekannt ist und<br />
ergänzen die notwendigen Puzzleteilchen, die noch fehlen, um die Mechanismen<br />
<strong>in</strong> der Praxis anwenden zu können:<br />
• Module s<strong>in</strong>d <strong>in</strong> sich abgeschlossene funktionale E<strong>in</strong>heiten. Sie enthalten<br />
e<strong>in</strong>e Sammlung von Klassen, die diese E<strong>in</strong>heit aufspannen.<br />
• Klassen s<strong>in</strong>d e<strong>in</strong>e Möglichkeit, die Eigenschaften e<strong>in</strong>es Objekttyps <strong>in</strong> der<br />
realen Welt zu modellieren. Zur Modellierung stehen folgende Mechanismen<br />
zur Verfügung:<br />
– Klassen bestimmen, welche Daten e<strong>in</strong> Objekt dieses Typs hält. Dies geschieht<br />
durch die Möglichkeit der Deklaration von sogenannten Member-<br />
Variablen, also Variablen, die e<strong>in</strong>er Instanz dieser Klasse zu eigen s<strong>in</strong>d.<br />
Member-Variablen s<strong>in</strong>d pr<strong>in</strong>zipiell <strong>in</strong>nerhalb e<strong>in</strong>es Objekts gekapselt und<br />
von außen nicht direkt zugreifbar. Welche Möglichkeiten man dabei hat,<br />
die Kapselung zu bee<strong>in</strong>flussen, ist von Programmiersprache zu Programmiersprache<br />
leicht verschieden. Für das Grundpr<strong>in</strong>zip ist dies derzeit<br />
allerd<strong>in</strong>gs noch belanglos.<br />
– Klassen bestimmen, welche Daten allen Objekten dieses Typs geme<strong>in</strong>sam<br />
s<strong>in</strong>d. Dies geschieht durch die Möglichkeit der Def<strong>in</strong>ition (nicht<br />
Deklaration!) von sogenannten Class-Member-Variablen. Solche Class-<br />
Members haben die Eigenschaft, dass sich alle Instanzen e<strong>in</strong> und dieselbe<br />
Variable “teilen” und geme<strong>in</strong>sam darauf zugreifen. Zum Beispiel kann<br />
es aus verschiedenen Gründen <strong>in</strong>teressant se<strong>in</strong>, wie viele Instanzen e<strong>in</strong>er<br />
Klasse zu e<strong>in</strong>em gewissen Zeitpunkt gerade im System existieren. Dazu<br />
kann man e<strong>in</strong>en Class-Member def<strong>in</strong>ieren, der e<strong>in</strong>en Counter darstellt.<br />
Jede Instanz, die erzeugt wird, <strong>in</strong>krementiert diesen und jede Instanz,<br />
die wieder zerstört wird, dekrementiert diesen.<br />
Anm.: Vorsicht ist <strong>in</strong> multithreaded Umgebungen geboten, damit man<br />
bei der Verwendung von Class-Members nicht <strong>in</strong> Synchronisationsprobleme<br />
läuft.<br />
– Klassen bestimmen, welche Interaktionen e<strong>in</strong> Objekt dieses Typs unterstützt.<br />
Dies geschieht durch die Deklaration von sogenannten Methoden<br />
oder auch Zugriffsmethoden. E<strong>in</strong>e Methode ist, salopp gesprochen,<br />
e<strong>in</strong>e Funktion, die e<strong>in</strong>er Instanz e<strong>in</strong>er Klasse eigen ist und direkt über<br />
e<strong>in</strong>e Instanz dieser Klasse aufgerufen wird. Manchmal werden Methoden<br />
auch als Member-Funktionen bezeichnet, allerd<strong>in</strong>gs ist der Begriff der<br />
Methode der häufiger verwendete, deshalb möchte ich dabei bleiben.
8.3 Richtige Verwendung der OO Mechanismen 163<br />
– Wie es bei den Daten die Class-Members gibt, so gibt es bei den Interaktionen<br />
die Class-Methods. Diese haben dieselbe Eigenschaft wie<br />
Class-Members, nämlich, dass sie sich nicht auf e<strong>in</strong>e bestimmte Instanz<br />
beziehen, sondern losgelöst von den E<strong>in</strong>zelobjekten s<strong>in</strong>d. Wie leicht e<strong>in</strong>zusehen<br />
ist, kann mittels Class-Methods auch nur auf Class-Members zugegriffen<br />
werden und nicht auf die <strong>in</strong>stanzbezogenen Member-Variablen,<br />
denn es fehlt ja der Bezug zu e<strong>in</strong>er bestimmten Instanz e<strong>in</strong>er Klasse.<br />
• Klassen können von e<strong>in</strong>er oder mehreren sogenannten Elternklassen abgeleitet<br />
se<strong>in</strong>. Damit erben sie deren Eigenschaften und können selbst neue<br />
h<strong>in</strong>zufügen bzw. die geerbten Eigenschaften nach Bedarf (s<strong>in</strong>nvoll!) modifizieren.<br />
Die entstehende Ableitungshierarchie kann beliebig tief se<strong>in</strong>.<br />
• Objekte s<strong>in</strong>d Instanzen von Klassen. Damit ergibt sich automatisch, dass<br />
jede Instanz e<strong>in</strong>er Klasse e<strong>in</strong>en eigenen Satz von Member-Variablen besitzt,<br />
wie sie durch die Klasse deklariert wurden. Außerdem beziehen sich die<br />
Methoden, die <strong>in</strong> der Klasse deklariert wurden, immer auf e<strong>in</strong> bestimmtes<br />
Objekt. Sie s<strong>in</strong>d quasi lokal zum Objekt <strong>in</strong> dem S<strong>in</strong>n, dass sie immer mit<br />
dem Variablensatz der Instanz arbeiten, auf der sie aufgerufen werden.<br />
Ich weiß schon – E<strong>in</strong>steiger <strong>in</strong> die OO Programmierung wünschen sich gerade<br />
die Instanz Klaus Schmaranz der Klasse Buchautor, die abgeleitet von der<br />
Klasse Mensch ist, um auf diesem Objekt die Methode rückt ihm den Kopf<br />
zurecht aufzurufen. Mit e<strong>in</strong>em kle<strong>in</strong>en Beispiel ist die Verwirrung aber schnell<br />
zu beheben, also sehen wir uns e<strong>in</strong>fach an, wie das Modell aussieht, das es<br />
unterstützt, me<strong>in</strong>en Kopf zurechtzurücken:<br />
• Es gibt e<strong>in</strong>e Klasse Head. Unter anderem hat diese Klasse folgende Eigenschaften:<br />
– E<strong>in</strong>e der Membervariablen, die dar<strong>in</strong> deklariert s<strong>in</strong>d, ist <strong>in</strong>_place und<br />
diese ist vom Typ bool.<br />
– E<strong>in</strong>e der Methoden, die dar<strong>in</strong> deklariert s<strong>in</strong>d, ist putInPlace. Aufruf<br />
derselben bewirkt e<strong>in</strong> Zurechtrücken, falls dies notwendig ist.<br />
• Es gibt e<strong>in</strong>e Klasse Human. Unter anderem hat diese Klasse folgende Eigenschaften:<br />
– E<strong>in</strong>e der Membervariablen, die dar<strong>in</strong> deklariert s<strong>in</strong>d, ist name und diese<br />
hält den Namen des Menschen.<br />
– E<strong>in</strong>e weitere der Membervariablen, die dar<strong>in</strong> deklariert s<strong>in</strong>d, ist the_head<br />
und diese ist von der Klasse Head.<br />
– E<strong>in</strong>e der Methoden, die dar<strong>in</strong> deklariert s<strong>in</strong>d, ist putHeadInPlace. Aufruf<br />
derselben bewirkt e<strong>in</strong> Zurechtrücken des Kopfs, falls dies nötig ist.<br />
– E<strong>in</strong>er der Class-Members, die dar<strong>in</strong> def<strong>in</strong>iert s<strong>in</strong>d, ist <strong>in</strong>stance_counter.<br />
Dieser hält die derzeitige Anzahl von derzeit existierenden Menschen.<br />
• Es gibt e<strong>in</strong>e Klasse Author, die von Human abgeleitet ist. In ihr werden<br />
die zusätzlichen Members (Variablen und Methoden) deklariert, die e<strong>in</strong>en<br />
Menschen zum Buchautor machen. Auch wenn böse Zungen behaupten,<br />
dass diese Klasse nun zu Unrecht die Eigenschaft von Human geerbt hat,
164 8. Objektorientierung Allgeme<strong>in</strong><br />
e<strong>in</strong>en Kopf zu besitzen – ich habe bereits erwähnt, dass es nicht möglich<br />
ist, Eigenschaften e<strong>in</strong>fach wegzudef<strong>in</strong>ieren :-).<br />
• Neben vielen anderen Instanzen von Author gibt es auch irgendwo e<strong>in</strong>e<br />
Instanz, die Klaus Schmaranz entspricht, was sich durch den entsprechenden<br />
E<strong>in</strong>trag im name Member äußert. Auf dieser Instanz ruft man<br />
putHeadInPlace auf, was alle notwendigen Schritte setzt, um wieder Hoffnung<br />
zu schöpfen.<br />
Um dieses Kapitel nun endgültig abzurunden, möchte ich hier noch die zw<strong>in</strong>genden<br />
Grundregeln zur Verwendung der OO Mechanismen angeben:<br />
In unseren Beispielen haben wir gesehen, dass e<strong>in</strong>e spezielle Telefonklasse<br />
von e<strong>in</strong>er allgeme<strong>in</strong>en Klasse Telefon abgeleitet ist. Ebenso war e<strong>in</strong> Buchautor<br />
von e<strong>in</strong>er Klasse Mensch abgeleitet. Kl<strong>in</strong>gt auch logisch, denn das<br />
spezielle Telefon ist e<strong>in</strong> Telefon, nur mit Zusätzen und e<strong>in</strong> Autor ist auch<br />
sicher e<strong>in</strong> Mensch, nur mit zusätzlichen Eigenschaften. Das genau ist auch<br />
die semantische Bedeutung e<strong>in</strong>er Ableitung:<br />
E<strong>in</strong>e Ableitung e<strong>in</strong>er Klasse repräsentiert e<strong>in</strong>e IS-A Relation.<br />
Wir haben auch gesehen, dass der Kopf e<strong>in</strong>es Menschen <strong>in</strong> e<strong>in</strong>er Membervariable<br />
gespeichert wurde und das genau entspricht auch deren semantischer<br />
Bedeutung:<br />
E<strong>in</strong>e Membervariable repräsentiert e<strong>in</strong>e HAS-A Relation.<br />
Diese beiden Def<strong>in</strong>itionen gelten natürlich auch als Umkehrschluss:<br />
• Wenn etwas durch IS-A im Modell beschreibbar ist, dann ist e<strong>in</strong>e<br />
Ableitung das korrekte semantische Mittel zur Modellierung.<br />
• Wenn etwas durch HAS-A im Modell beschreibbar ist, dann ist<br />
e<strong>in</strong>e Membervariable das korrekte semantische Mittel zur Modellierung.<br />
• Zu diesen beiden Regeln gibt es ke<strong>in</strong>e e<strong>in</strong>zige Ausnahme! Jeder<br />
e<strong>in</strong>zelne Verstoß gegen e<strong>in</strong>e dieser beiden Regeln führt unweigerlich<br />
zur Katastrophe!!!<br />
Vorsicht Falle: Nicht nur von Neul<strong>in</strong>gen wird allzu oft der Fehler begangen,<br />
Ableitungen falsch e<strong>in</strong>zusetzen, nämlich <strong>in</strong> der Art, dass so etwas wie<br />
e<strong>in</strong>e IS-LIKE-A Relation modelliert wird. E<strong>in</strong> solches Konstrukt stellt e<strong>in</strong>en<br />
groben Designfehler dar, der sich im Lauf der Designphase und später bei<br />
der Implementierung bitter rächt.<br />
Nur um e<strong>in</strong> Beispiel zu nennen, das ich leider sogar <strong>in</strong> der Literatur als<br />
s<strong>in</strong>nvolle Ableitung erwähnt gefunden habe:<br />
• Es gibt e<strong>in</strong>e Klasse Array, die e<strong>in</strong>e saubere Implementation e<strong>in</strong>es dynamischen<br />
Arrays darstellt.<br />
• Es wird e<strong>in</strong>e Klasse Stack entworfen. Weil es gerade so gut dazupasst,<br />
dass man die Elemente des Stacks <strong>in</strong> e<strong>in</strong>em Array speichert, wird Stack<br />
von Array abgeleitet.
8.3 Richtige Verwendung der OO Mechanismen 165<br />
Dieses Design sagt also aus, dass e<strong>in</strong> Stack e<strong>in</strong> Array ist, und genau das<br />
ist vollkommener Nonsens! E<strong>in</strong> Stack verwendet e<strong>in</strong> Array zur Speicherung,<br />
also darf hier niemals e<strong>in</strong>e Ableitung herangezogen werden! Der Stack bekommt<br />
e<strong>in</strong>fach e<strong>in</strong>e Membervariable der Klasse Array, die er zur Speicherung<br />
verwendet.<br />
Die wahnwitzige Idee, e<strong>in</strong>en Stack von e<strong>in</strong>em Array abzuleiten, führt dazu,<br />
dass der Stack alle Eigenschaften e<strong>in</strong>es Arrays erbt, also auch solche, die<br />
überhaupt nicht <strong>in</strong> se<strong>in</strong>er Natur liegen, wie z.B. <strong>in</strong>dizierte Zugriffe auf die<br />
e<strong>in</strong>zelnen Elemente. Damit wird dann noch vielleicht mit viel Würgen dem<br />
Stack die unerwünschte Funktionalität mittels Vergewaltigung von OO Konzepten<br />
abgewöhnt und schon ist der Wald-und-Wiesen-Hack perfekt, der e<strong>in</strong>em<br />
spätestens bei der nächsten Änderung der Array Klasse um die Ohren<br />
fliegt.
9. Klassen <strong>in</strong> <strong>C++</strong><br />
Nachdem nun zum<strong>in</strong>dest <strong>in</strong> der Theorie bekannt ist, was wir von Klassen<br />
und Objekten erwarten, wird es Zeit zu zeigen, wie diese Konzepte <strong>in</strong> C ++<br />
umgesetzt s<strong>in</strong>d.<br />
Vorsicht Falle: Obwohl ich noch nicht e<strong>in</strong>mal begonnen habe, die technischen<br />
Details von Klassen zu besprechen, möchte ich schon den ersten H<strong>in</strong>weis<br />
auf die gefährlichste Falle überhaupt geben, die sich C ++ Neul<strong>in</strong>gen auftut.<br />
Leider zeigt die Erfahrung, dass sehr viele Entwickler nur Teile der Konzepte<br />
ver<strong>in</strong>nerlicht haben, die C ++ bietet. Speziellere Konstrukte werden als<br />
“das brauche ich nicht” abgetan und e<strong>in</strong>fach ignoriert. Nur e<strong>in</strong>es von vielen<br />
typischen Beispielen für dieses Problem ist das Ignorieren der Existenz<br />
von virtual Ableitungen, zu denen wir später noch kommen werden, mit<br />
entsprechend absolut fatalen Folgen.<br />
Die große Gefahr dabei, die sogenannten Spezialitäten zu ignorieren, ist,<br />
dass dadurch oftmals Code entsteht, der aus technischen Gründen später wieder<br />
gravierend geändert oder sogar vollständig verworfen werden muss. Prom<strong>in</strong>ente<br />
Vertreter von kommerziell ausgelieferten Libraries, bei denen solche<br />
Pannen passiert s<strong>in</strong>d, gibt es e<strong>in</strong>ige, allerd<strong>in</strong>gs möchte ich hier ke<strong>in</strong>en davon<br />
namentlich nennen. Es genügt, zu wissen, dass es von praktisch allen wirklich<br />
großen Herstellern von Software und Entwicklungsumgebungen solche<br />
Wunderwerke gab und, leider muss man auch das sagen, immer noch gibt.<br />
Ich möchte also alle Leser ausdrücklichst bitten, alle <strong>in</strong> der Folge vorgestellten<br />
Konstrukte wirklich genauestens anzusehen und auch zu begreifen.<br />
Erst wenn man wirklich den S<strong>in</strong>n h<strong>in</strong>ter allem verstanden hat, ist man<br />
<strong>in</strong> der Lage, echtes C ++ zu schreiben, ohne <strong>in</strong> technische Fallen zu stolpern!<br />
Man kann nicht nur im Design vieles falsch machen, auch <strong>in</strong> der Umsetzung<br />
lauern noch mannigfaltige Gefahren!!!<br />
Um nun niemanden zu entmutigen: Es ist absolut erwünscht und sehr<br />
wichtig, sich mit allem e<strong>in</strong>mal zu spielen und entsprechende Fehler zu machen.<br />
Aus Fehlern lernt man (manchmal sogar nur aus Fehlern). Solche Fehler<br />
sollen allerd<strong>in</strong>gs im Rahmen der Lern- und Spielphase passieren und nicht<br />
bei ernsthaften Projekten!
168 9. Klassen <strong>in</strong> <strong>C++</strong><br />
9.1 Besonderheiten von Structures <strong>in</strong> <strong>C++</strong><br />
Aus Abschnitt 2.4.2 s<strong>in</strong>d Structures als Mittel zur Zusammenfassung von<br />
Daten zu e<strong>in</strong>em Aggregat-Datentypen bekannt. Es wurde auch bereits darauf<br />
h<strong>in</strong>gewiesen, dass sich noch mehr h<strong>in</strong>ter Structures versteckt, als dort<br />
behandelt wurde. Genau dieses Mehr möchte ich hier als Überleitung zum<br />
allgeme<strong>in</strong>en Konzept der Classes <strong>in</strong> C ++ verwenden. Sehen wir uns dazu<br />
gleich e<strong>in</strong> kle<strong>in</strong>es Beispiel an, das uns auf den Weg zu den Pr<strong>in</strong>zipien der<br />
OO Entwicklung <strong>in</strong> C ++ br<strong>in</strong>gt (struct_method_demo.cpp):<br />
1 // struct method demo . cpp − small demo , how methods can be<br />
2 // implemented f o r s t r u c t u r e s<br />
3<br />
4 #<strong>in</strong>clude <br />
5 #<strong>in</strong>clude < c s t r i n g><br />
6<br />
7 us<strong>in</strong>g std : : cout ;<br />
8 us<strong>in</strong>g std : : endl ;<br />
9<br />
10 struct Name<br />
11 {<br />
12 char ∗ f i r s t n a m e ;<br />
13 char ∗ last name ;<br />
14<br />
15 void i n i t ( const char ∗ f i r s t n a m e , const char ∗ last name ) ;<br />
16 void setFirstName ( const char ∗ f i r s t n a m e ) ;<br />
17 void setLastName ( const char ∗ last name ) ;<br />
18 const char ∗ getFirstName ( ) ;<br />
19 const char ∗ getLastName ( ) ;<br />
20 } ;<br />
21<br />
22 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
23 void Name : : i n i t ( const char ∗ f i r s t n a m e , const char ∗ last name )<br />
24 {<br />
25 i f ( ! f i r s t n a m e )<br />
26 f i r s t n a m e = 0;<br />
27 else<br />
28 {<br />
29 f i r s t n a m e = new char [ s t r l e n ( f i r s t n a m e ) + 1 ] ;<br />
30 strcpy ( f i r s t n a m e , f i r s t n a m e ) ;<br />
31 }<br />
32 i f ( ! last name )<br />
33 last name = 0;<br />
34 else<br />
35 {<br />
36 last name = new char [ s t r l e n ( last name ) + 1 ] ;<br />
37 strcpy ( last name , last name ) ;<br />
38 }<br />
39 }<br />
40<br />
41 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
42 void Name : : setFirstName ( const char ∗ f i r s t n a m e )<br />
43 {<br />
44 i f ( f i r s t n a m e )<br />
45 delete [ ] f i r s t n a m e ;<br />
46 i f ( ! f i r s t n a m e )<br />
47 f i r s t n a m e = 0;<br />
48 else<br />
49 {<br />
50 f i r s t n a m e = new char [ s t r l e n ( f i r s t n a m e ) + 1 ] ;<br />
51 strcpy ( f i r s t n a m e , f i r s t n a m e ) ;<br />
52 }
53 }<br />
54<br />
9.1 Besonderheiten von Structures <strong>in</strong> <strong>C++</strong> 169<br />
55 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
56 void Name : : setLastName ( const char ∗ last name )<br />
57 {<br />
58 i f ( last name )<br />
59 delete [ ] last name ;<br />
60 i f ( ! last name )<br />
61 last name = 0;<br />
62 else<br />
63 {<br />
64 last name = new char [ s t r l e n ( last name ) + 1 ] ;<br />
65 strcpy ( last name , last name ) ;<br />
66 }<br />
67 }<br />
68<br />
69 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
70 const char ∗Name : : getFirstName ( )<br />
71 {<br />
72 return ( f i r s t n a m e ) ;<br />
73 }<br />
74<br />
75 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
76 const char ∗Name : : getLastName ( )<br />
77 {<br />
78 return ( last name ) ;<br />
79 }<br />
80<br />
81 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
82 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
83 {<br />
84 Name one name ;<br />
85 one name . i n i t ( ”John” , ”Doe” ) ;<br />
86<br />
87 Name another name ;<br />
88 another name . i n i t ( ”Joe” , ”Smith” ) ;<br />
89<br />
90 cout
170 9. Klassen <strong>in</strong> <strong>C++</strong><br />
se Art der Deklaration macht aus e<strong>in</strong>er allgeme<strong>in</strong> aufrufbaren Funktion e<strong>in</strong>e<br />
auf die Structure bezogene Methode. Der Begriff auf die Structure bezogen<br />
bedeutet, dass e<strong>in</strong>e solche Methode auch nur direkt auf e<strong>in</strong>e Instanz e<strong>in</strong>er<br />
solchen Structure aufgerufen werden kann. Wie e<strong>in</strong> solcher Aufruf funktioniert,<br />
lässt sich leicht erraten, wenn wir daran denken, wie Member-Variablen<br />
e<strong>in</strong>er struct angesprochen werden. Es wird e<strong>in</strong>fach der Punkt-Operator dazu<br />
herangezogen (bzw. im Falle von Po<strong>in</strong>tern natürlich der Pfeil-Operator).<br />
Die entsprechenden Aufrufe der Methoden sieht man <strong>in</strong> Zeile 85 und <strong>in</strong> der<br />
Folge. Zum Beispiel bedeutet das Statement <strong>in</strong> Zeile 85: Rufe die Methode<br />
<strong>in</strong>it auf der Variable one_name mit den Parametern "John" und "Doe" auf.<br />
E<strong>in</strong> Unterschied zu C fällt <strong>in</strong> Zeile 83 auf: Anstatt <strong>in</strong> gewohnter Manier<br />
für den Typ struct Name zu schreiben, steht hier e<strong>in</strong>fach Name als Datentyp.<br />
Dies funktioniert <strong>in</strong> C ++ und es macht das sonst <strong>in</strong> C zur leichteren Lesbarkeit<br />
<strong>in</strong> diesem Zusammenhang verwendete typedef überflüssig.<br />
E<strong>in</strong> wichtiges Detail zu Methoden wurde noch nicht besprochen: Die Deklaration<br />
derselben erfolgt <strong>in</strong>nerhalb der struct, wie aber def<strong>in</strong>iert man sie?<br />
Stellen wir uns vor, es gäbe <strong>in</strong> unserem Programm neben der struct Name<br />
auch noch e<strong>in</strong>e struct Address. Stellen wir uns weiters vor, dass s<strong>in</strong>nvollerweise<br />
die Initialisierung bei beiden über die Methode <strong>in</strong>it erfolgt. Wie teilen<br />
wir dann dem Compiler mit, welche der Def<strong>in</strong>itionen der <strong>in</strong>it Methoden nun<br />
zu welcher struct gehört? Dass man dies nicht über verschiedene Parameterlisten<br />
realisieren kann, versteht sich von selbst, denn das würde e<strong>in</strong>e grobe<br />
E<strong>in</strong>schränkung darstellen.<br />
Des Rätsels Lösung zeigt sich z.B. <strong>in</strong> Zeile 23: Man verwendet den sogenannten<br />
Scope-Operator (::), der folgendermaßen funktioniert: Man gibt<br />
den Namen der struct an, gefolgt von ::, gefolgt vom Namen der Methode.<br />
Damit liest sich Zeile 23 so: Die Funktion <strong>in</strong>it mit dem Scope auf<br />
die struct Name und den entsprechenden Parametern sieht folgendermaßen<br />
aus... Das bedeutet, dass man mittels des Scope Operators die entsprechende<br />
Zuordnung bei der Def<strong>in</strong>ition vornimmt. Bei der Deklaration ist diese Zuordnung<br />
natürlich nicht notwendig, denn durch das Auftreten der Deklaration<br />
<strong>in</strong>nerhalb der struct ist dem Compiler die Zugehörigkeit sowieso klar.<br />
Für Leser, denen der Cod<strong>in</strong>g Standard, der diesem Buch zugrunde liegt,<br />
noch nicht geläufig ist, mag die Deklaration der Members <strong>in</strong> den Zeilen 12 und<br />
13 etwas komisch anmuten. Jedoch schreibt der Cod<strong>in</strong>g Standard vor, dass<br />
Member Variablen mit e<strong>in</strong>em Unterstrich enden, um sie sofort als Members<br />
identifizieren zu können und von auto Variablen auf e<strong>in</strong>en Blick unterscheiden<br />
zu können. Sieht man sich Zeile 26 an, so sieht man auch gleich, was<br />
es mit Members so auf sich hat. In der Def<strong>in</strong>ition der Methode Name::<strong>in</strong>it<br />
werden die beiden Members first_name_ und last_name_ verwendet, als ob<br />
sie <strong>in</strong>nerhalb des Scopes der Methode def<strong>in</strong>iert wären. Genau das ist auch<br />
der Fall und das ist e<strong>in</strong>e weitere Eigenschaft des Scope-Operators. Dadurch,<br />
dass <strong>in</strong>it zum Scope von struct Name gehört, gehören auch die Members der<br />
struct Name zum aktuellen Scope und können daher ohne weitere Umschwei-
9.2 E<strong>in</strong>fache Klassen 171<br />
fe direkt angesprochen werden. Zeile 26 bedeutet also Folgendes: Weise dem<br />
Member first_name_ der struct Name den Wert 0 zu.<br />
Vorsicht Falle: E<strong>in</strong> Teil der gängigen Praxis, die aus dem Cod<strong>in</strong>g Standard<br />
dieses Buchs resultiert, ist, dass es problemlos vorkommen kann, dass<br />
Parameter und Members quasi denselben Namen haben, nur dass eben Members<br />
durch e<strong>in</strong>en Unterstrich am Ende gekennzeichnet s<strong>in</strong>d. Wenn man den<br />
Cod<strong>in</strong>g Standard gewohnt ist, dann ist dies e<strong>in</strong> sehr angenehmes Feature,<br />
denn man muss nicht mit Gewalt e<strong>in</strong>en neuen Namen für e<strong>in</strong>en Parameter<br />
erf<strong>in</strong>den, der dann vielleicht e<strong>in</strong> wenig holprig ausfällt.<br />
Der Stolperste<strong>in</strong> bei dieser Praxis, der sich am Anfang auftut, ist, dass<br />
man noch nicht <strong>in</strong>tuitiv beim Lesen auf den Unterstrich triggert und somit<br />
z.B. die Zeile 30 durch schlampiges Lesen auf den ersten Blick komisch<br />
aussieht. Der erste Parameter von strcpy, also first_name_, bezeichnet<br />
nämlich den entsprechenden Member und der zweite Parameter, also<br />
first_name, bezeichnet den Übergabeparameter der <strong>in</strong>it Methode.<br />
Ich kann allerd<strong>in</strong>gs versichern, dass die Gewöhnungsphase an diesen Cod<strong>in</strong>g<br />
Standard, <strong>in</strong> der man solche Flüchtigkeitsfehler beim Lesen macht, sehr<br />
kurz ist. Ich kenne viele Leute, die genau nach diesem Cod<strong>in</strong>g Standard<br />
arbeiten und die sehr bald nicht das ger<strong>in</strong>gste Problem mehr beim Lesen<br />
solcher Programmzeilen hatten, weil der Underl<strong>in</strong>e am Ende <strong>in</strong> Fleisch und<br />
Blut übergegangen ist.<br />
Ich möchte nun an dieser Stelle ke<strong>in</strong>e näheren Ausführungen mehr über<br />
Structures br<strong>in</strong>gen, da wir alles, was hier gesagt werden könnte, bei “echten”<br />
Classes wieder treffen werden. Structures s<strong>in</strong>d <strong>in</strong> C ++ tatsächlich Classes, bei<br />
denen alle Members standardmäßig public s<strong>in</strong>d. Was das bedeutet, sehen<br />
wir im kommenden Abschnitt, <strong>in</strong> dem wir uns endlich offiziell den Klassen und<br />
Objekten, mit all ihren Stärken (und auch Stolperste<strong>in</strong>en), <strong>in</strong> C ++ zuwenden.<br />
9.2 E<strong>in</strong>fache Klassen<br />
Klassen <strong>in</strong> C ++ realisieren das Konzept, dass Entwickler neue Datentypen<br />
erstellen können, die genauso e<strong>in</strong>fach und s<strong>in</strong>nvoll verwendet werden können,<br />
wie e<strong>in</strong>gebaute Datentypen (z.B. <strong>in</strong>t). H<strong>in</strong>ter allen Datentypen steckt immer<br />
e<strong>in</strong> Gesamtkonzept, z.B. gehören zu e<strong>in</strong>em <strong>in</strong>t natürlich auch die entsprechenden<br />
Operationen +, -, *, /, etc. Weiters ist def<strong>in</strong>iert, wie e<strong>in</strong> <strong>in</strong>t z.B. <strong>in</strong><br />
e<strong>in</strong>en float oder double umgewandelt werden kann. Solche Gesamtkonzepte<br />
lassen sich vollständig auch mit Klassen verwirklichen.<br />
Um nun nicht die üblichen Datums-, Vektor-, Matrix- und andere Klassen<br />
zur Erklärung heranzuziehen, die <strong>in</strong> verschiedensten Formen <strong>in</strong> mannigfaltigen<br />
Büchern und Tutorials zu Tode diskutiert wurden, möchte ich die<br />
Grundpr<strong>in</strong>zipien an e<strong>in</strong>em durchgehenden Beispiel aufbauend erklären. Die-
172 9. Klassen <strong>in</strong> <strong>C++</strong><br />
ses Beispiel soll <strong>in</strong> der Folge das allseits bekannte Spiel Memory se<strong>in</strong>. Für<br />
Leser, die dieses Spiel nicht kennen, ist es e<strong>in</strong>fach erklärt:<br />
• Es gibt e<strong>in</strong> rechteckiges Gesamtspielfeld mit c Spalten und r Reihen, womit<br />
sich e<strong>in</strong>e schachbrettartige Aufteilung <strong>in</strong> c ∗ r E<strong>in</strong>zelfelder ergibt.<br />
• Auf jedem E<strong>in</strong>zelfeld liegt e<strong>in</strong>e Karte, die auf ihrer Vorderseite e<strong>in</strong> gewisses<br />
Symbol zeigt. Die Rückseiten aller Karten s<strong>in</strong>d gleich und machen e<strong>in</strong>e<br />
Unterscheidung der Karten unmöglich, wenn sie mit der Rückseite nach<br />
oben zu liegen kommen.<br />
• Die Symbole aller Karten zusammen, die auf dem Gesamtspielfeld zu liegen<br />
kommen, s<strong>in</strong>d so aufgeteilt, dass es jeweils genau zwei Karten mit<br />
demselben Symbol gibt.<br />
• Die Verteilung der Karten auf das Gesamtspielfeld ist zufällig, es können<br />
also die jeweils zwei zusammengehörigen Symbolkarten auf beliebigsten<br />
E<strong>in</strong>zelfeldern zu liegen kommen.<br />
• E<strong>in</strong> Spiel besteht nun aus folgenden Schritten:<br />
1. Genau so viele Karten wie es E<strong>in</strong>zelfelder gibt, werden zufällig mit der<br />
Rückseite nach oben auf das Gesamtspielfeld verteilt.<br />
2. Danach werden alle Karten am Gesamtspielfeld für e<strong>in</strong>en kurzen Zeitraum<br />
umgedreht, sodass die Symbole auf ihren Vorderseiten zu sehen<br />
s<strong>in</strong>d. Die Spieler<strong>in</strong> bzw. der Spieler hat <strong>in</strong> diesem kurzen Zeitraum<br />
Gelegenheit, sich die Symbole auf den e<strong>in</strong>zelnen Feldern e<strong>in</strong>zuprägen.<br />
3. Nach dieser kurzen E<strong>in</strong>prägephase werden alle Karten erneut umgedreht,<br />
sodass sie wieder ihre nicht unterscheidbaren Rückseiten zeigen.<br />
4. Die Spieler<strong>in</strong> bzw. der Spieler muss nun immer paarweise Karten aufdecken<br />
(=umdrehen), die das gleiche Symbol zeigen sollten.<br />
5. Zeigt e<strong>in</strong> aufgedecktes Paar auf beiden Karten das gleiche Symbol, so<br />
bleibt es aufgedeckt liegen.<br />
6. Zeigt e<strong>in</strong> aufgedecktes Paar zwei verschiedene Symbole, so werden beide<br />
Karten wieder umgedreht.<br />
• Ziel ist es, <strong>in</strong> so wenigen Schritten wie möglich, alle zusammengehörigen<br />
Paare gefunden zu haben und damit alle Karten umgedreht zu haben.<br />
Ich möchte nun an dieser Stelle ke<strong>in</strong>e ausführliche und e<strong>in</strong>gehende Analyse der<br />
gesamten Problemstellung machen, da dies hier eher stören als nützen würde.<br />
Jedoch sei darauf h<strong>in</strong>gewiesen, dass diese Analyse sehr wohl gemacht wurde<br />
und auch bei der Entwicklung gemacht werden muss, um auf e<strong>in</strong> vernünftiges<br />
Ergebnis zu kommen. Diese Gesamtanalyse folgt zur Wiederholung des<br />
Gelernten <strong>in</strong> Kapitel 10. Im Augenblick werden e<strong>in</strong>fach die e<strong>in</strong>zelnen Teile<br />
häppchenweise vorgestellt, die sich aus der Analyse ergeben haben. Ich<br />
möchte auch alle Leser bitten, die <strong>in</strong> der Folge vorgestellten Häppchen als<br />
noch nicht perfekt im S<strong>in</strong>ne von C ++ h<strong>in</strong>zunehmen. Der Grund dafür ist der,<br />
dass wirklich alle C ++ Mechanismen bekannt se<strong>in</strong> müssen, um alles so sauber<br />
wie möglich zu schreiben.
9.2 E<strong>in</strong>fache Klassen 173<br />
Zurück zum Thema: Betrachten wir als erste identifizierte Klasse e<strong>in</strong>e<br />
Karte. E<strong>in</strong>e Möglichkeit, diese <strong>in</strong> C ++ zu modellieren, mit der entsprechenden<br />
Deklaration des gefundenen Modells, f<strong>in</strong>det sich <strong>in</strong> folgendem Beispiel<br />
(memory_game_card.h):<br />
1 // memory game card . h − d e c l a r a t i o n o f the c l a s s MemoryGameCard<br />
2<br />
3 #ifndef memory game card h<br />
4 #def<strong>in</strong>e memory game card h<br />
5<br />
6 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
7 /∗<br />
8 ∗ MemoryGameCard<br />
9 ∗<br />
10 ∗ model of a card as used <strong>in</strong> the game ”memory”<br />
11 ∗<br />
12 ∗/<br />
13<br />
14 class MemoryGameCard<br />
15 {<br />
16 protected :<br />
17 char ∗ symbol ;<br />
18 public :<br />
19 MemoryGameCard( const char ∗ symbol ) ;<br />
20 ˜MemoryGameCard ( ) ;<br />
21<br />
22 const char ∗ getSymbol ( ) ;<br />
23 void changeSymbol ( const char ∗ symbol ) ;<br />
24 } ;<br />
25<br />
26<br />
27 #endif // memory game card h<br />
Ganz bewusst wurde hier gleich der richtige Weg e<strong>in</strong>geschlagen, e<strong>in</strong>e Klasse<br />
<strong>in</strong> e<strong>in</strong>em Header File zu deklarieren und die Def<strong>in</strong>ition <strong>in</strong> e<strong>in</strong> eigenes cpp<br />
File zu verlegen, anstatt alles geme<strong>in</strong>sam <strong>in</strong> e<strong>in</strong> File zu verpacken. Um e<strong>in</strong>e<br />
Klasse verwenden zu können, muss dann nur noch der entsprechende Header<br />
<strong>in</strong>kludiert werden, wie wir weiter unten sehen werden. Wenden wir uns aber<br />
lieber den wichtigeren D<strong>in</strong>gen zu, die hier zu f<strong>in</strong>den s<strong>in</strong>d.<br />
In den Zeilen 14–24 sieht man, wie man e<strong>in</strong>e Klasse deklariert. Dies<br />
geschieht gleich, wie die Deklaration e<strong>in</strong>er Structure, nur dass statt des Keywords<br />
struct das Keyword class verwendet wird. Wir deklarieren hier also<br />
e<strong>in</strong>e Klasse MemoryGameCard, die genau unter diesem Namen als Datentyp <strong>in</strong><br />
Programmen verwendet werden kann. Wie das genau passiert und was dabei<br />
abläuft, wird später noch näher beleuchtet werden.<br />
Dass man <strong>in</strong> e<strong>in</strong>er Klasse Member-Variablen und Methoden deklarieren<br />
kann und dass dies gleich passiert, wie wir es bei e<strong>in</strong>er struct bereits kennen<br />
gelernt haben, versteht sich von selbst. Viel <strong>in</strong>teressanter s<strong>in</strong>d da schon<br />
die Zeilen 16 und 18. In diesen Zeilen werden durch die besonderen Labels<br />
protected: bzw. public: die Zugriffsrechte auf die e<strong>in</strong>zelnen Members<br />
(Variablen wie auch Methoden) def<strong>in</strong>iert. Für diese besonderen Labels ist<br />
auch der Name Access Specifiers gebräuchlich. Folgende Access Specifiers<br />
mit entsprechender Bedeutung s<strong>in</strong>d <strong>in</strong> C ++ def<strong>in</strong>iert:
174 9. Klassen <strong>in</strong> <strong>C++</strong><br />
private: Es dürfen ausschließlich Methoden der eigenen Klasse und friend<br />
Funktionen bzw. Klassen, zu denen wir noch später kommen werden,<br />
zugreifen. Der Zugriff von außen, sowie der Zugriff aus von dieser Klasse<br />
abgeleiteten Klassen heraus, ist verboten.<br />
protected: Es dürfen Methoden der eigenen Klasse, friend Funktionen<br />
bzw. Klassen, sowie Methoden aus abgeleiteten Klassen zugreifen. Der<br />
Zugriff für außenstehende Methoden und Funktionen ist nicht gestattet.<br />
public: Es dürfen alle Funktionen und Methoden, egal <strong>in</strong> welchem Verhältnis<br />
sie zur Klasse stehen, zugreifen.<br />
Sollte ke<strong>in</strong> Access Specifier explizit angegeben se<strong>in</strong>, dann gilt alles bis zum<br />
Auftreten des ersten als private. Dies ist auch e<strong>in</strong>er der Unterschiede zwischen<br />
Classes und Structures: Bei Classes gilt alles grundsätzlich als private,<br />
ist also nicht von außen zugreifbar, bei Structures gilt alles grundsätzlich als<br />
public, ist also von außen une<strong>in</strong>geschränkt zugreifbar.<br />
Mir ist schon bewusst, dass bei der Beschreibung der Access Specifiers e<strong>in</strong>ige<br />
D<strong>in</strong>ge vorkommen, die bisher noch nicht bekannt s<strong>in</strong>d. Bitte ke<strong>in</strong>e Panik<br />
deswegen, sobald wir zu den entsprechenden Konstrukten kommen, wird e<strong>in</strong>e<br />
genauere Besprechung folgen. Ich wollte nur bereits hier e<strong>in</strong> vollständiges<br />
Bild der Möglichkeiten schaffen, um zu vermitteln, was es überhaupt gibt.<br />
Die Gültigkeitsbereiche von Access Specifiers s<strong>in</strong>d so def<strong>in</strong>iert, dass e<strong>in</strong><br />
Specifier vom Punkt, an dem er steht, bis zum nächsten auftretenden Access<br />
Specifier bestimmend ist. Es ist auch möglich, dieselben Access Specifiers<br />
mehrfach <strong>in</strong>nerhalb e<strong>in</strong>er Klasse zu verwenden. Z.B. kann man zuerst e<strong>in</strong>en<br />
private Block haben, danach e<strong>in</strong>en public Block, gefolgt von e<strong>in</strong>em erneuten<br />
private Block, etc. Der Grund für diese Regelung ist, dass man den<br />
Entwicklern Freiraum für die eigene Anordnung ihrer Deklarationen lässt und<br />
somit die Lesbarkeit erhöht.<br />
E<strong>in</strong>e E<strong>in</strong>schränkung möchte ich aus Gründen der Lesbarkeit allerd<strong>in</strong>gs<br />
unbed<strong>in</strong>gt zum Standard erheben: Member-Variablen werden immer oben<br />
und Methoden immer erst danach deklariert. E<strong>in</strong>e Mischung, <strong>in</strong> der zuerst<br />
e<strong>in</strong> paar Variablen, danach e<strong>in</strong> paar Methoden und dann wieder e<strong>in</strong> paar<br />
Variablen deklariert werden, ist absolut unleserlich! Als Entwickler will man<br />
<strong>in</strong> jedem Fall auf e<strong>in</strong>en Blick erfassen können, welche Variablen und Methoden<br />
es gibt, ohne gleich die gesamte Klassendeklaration vollständig durchsuchen<br />
zu müssen und vielleicht dabei etwas zu übersehen.<br />
Durch das Wissen um die Gültigkeitsbereiche der Access Specifiers ergibt<br />
sich also nun folgendes Bild:<br />
Die Member-Variable symbol_ ist nur <strong>in</strong>nerhalb der eigenen Klasse und<br />
für abgeleitete Klassen zugreifbar (und natürlich auch für Friends). Alle<br />
Methoden <strong>in</strong> den Zeilen 19, 20, 22 und 23 s<strong>in</strong>d für die Öffentlichkeit verfügbar.<br />
Sie def<strong>in</strong>ieren damit das sogenannte Public Interface der Klasse, also die<br />
Schnittstelle, über die man mit der Klasse regulär arbeiten kann.<br />
E<strong>in</strong> Blick auf die Zeilen 19 und 20 legt allerd<strong>in</strong>gs bei manchen Lesern die<br />
Vermutung nahe, dass bei mir im Laufe des Schreibens e<strong>in</strong> gewisser Zustand
9.2 E<strong>in</strong>fache Klassen 175<br />
geistiger Umnachtung e<strong>in</strong>gesetzt hat. Irgendwie sehen die beiden Konstrukte<br />
aus wie Deklarationen von Methoden, andererseits aber fehlt Entscheidendes,<br />
nämlich die Deklaration des return-Values. Vor allem hat sich <strong>in</strong> Zeile 20<br />
obendre<strong>in</strong> noch e<strong>in</strong> völlig unmotiviertes BIT-NOT vor dem Methodennamen<br />
e<strong>in</strong>geschlichen, das hier doch gar nicht erlaubt ist, oder???<br />
Zu me<strong>in</strong>er Verteidigung kann ich nur sagen, dass ich vollkommen unschuldig<br />
b<strong>in</strong>! Diese beiden Methoden, nämlich Konstruktor <strong>in</strong> Zeile 19 und<br />
Destruktor <strong>in</strong> Zeile 20 s<strong>in</strong>d tatsächlich genau so zu deklarieren. Per Def<strong>in</strong>ition<br />
wird der Konstruktor so deklariert (und def<strong>in</strong>iert), dass er den Klassennamen<br />
als Methodennamen hat und die entsprechende Parameterliste, die zum<br />
Konstruieren e<strong>in</strong>es Objekts dieses Typs notwendig ist. Der Destruktor hat<br />
den Klassennamen mit ~ als Präfix als Methodennamen und besitzt (s<strong>in</strong>nigerweise)<br />
e<strong>in</strong>e leere Parameterliste.<br />
Der Konstruktor wird automatisch aufgerufen, wenn e<strong>in</strong>e Instanz dieser<br />
Klasse erzeugt wird (also e<strong>in</strong>e Variable angelegt wird), der Destruktor, wenn<br />
diese Instanz wieder zerstört wird (also e<strong>in</strong>e Variable ihre Lifetime beendet<br />
hat). In Abschnitt 9.2.1 werden wir uns diesem Thema noch e<strong>in</strong> wenig<br />
e<strong>in</strong>gehender widmen, derzeit genügt dieses Wissen für unsere weiteren Betrachtungen.<br />
Von außen betrachtet bietet e<strong>in</strong> Objekt vom Typ MemoryGameCard also<br />
folgende Funktionalität:<br />
• Man kann e<strong>in</strong>e neue Karte erzeugen. Bei der Erzeugung muss man gleich<br />
e<strong>in</strong> entsprechendes Symbol mitgeben. Diese Eigenschaft ist gefordert, da<br />
es nur e<strong>in</strong>en Konstruktor mit e<strong>in</strong>em Symbol als Parameter gibt. E<strong>in</strong>e leere<br />
Karte ist also nicht erzeugbar.<br />
• Das Symbol, das zu e<strong>in</strong>er Karte gehört, ist <strong>in</strong> unserem Fall e<strong>in</strong>fach e<strong>in</strong><br />
Str<strong>in</strong>g. Zugegeben, dieses Verhalten ist nicht das tollste, aber für Demonstrationszwecke<br />
bleiben wir e<strong>in</strong>mal dabei.<br />
• Man kann e<strong>in</strong>e Karte nach dem Symbol fragen.<br />
• Man kann das Symbol e<strong>in</strong>er Karte ändern. Je nach Anwendungszweck<br />
kann man natürlich nun darüber streiten, ob es überhaupt sehr klug ist,<br />
dies zuzulassen. Aber das ist e<strong>in</strong>e andere Diskussion :-).<br />
Was explizit durch protected verboten ist, ist e<strong>in</strong> direkter Zugriff von außen<br />
auf die Member-Variable symbol_. Dadurch ist gewährleistet, dass symbol_<br />
immer nur mittels Zugriffsmethoden oder von abgeleiteten Klassen heraus<br />
direkt angegriffen werden darf, was eventuelle Inkonsistenzen verh<strong>in</strong>dert. Zugegeben,<br />
bei dieser Klasse ersche<strong>in</strong>t dies etwas p<strong>in</strong>gelig, aber wir werden noch<br />
genügend Fälle kennen lernen, <strong>in</strong> denen e<strong>in</strong> solcher Schutz lebensnotwendig<br />
se<strong>in</strong> kann.<br />
Die Implementation der e<strong>in</strong>zelnen Methoden, die <strong>in</strong> der Klasse deklariert<br />
wurden, erfolgt getrennt im File memory_game_card.cpp:
176 9. Klassen <strong>in</strong> <strong>C++</strong><br />
1 // memory game card . cpp − d e f i n i t i o n o f the methods o f MemoryGameCard<br />
2<br />
3 #<strong>in</strong>clude ”memory game card . h”<br />
4<br />
5 #<strong>in</strong>clude < c s t r i n g><br />
6<br />
7 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
8 /∗ constructor of MemoryGameCard<br />
9 ∗<br />
10 ∗ @param symbol the symbol represented by t h i s card<br />
11 ∗/<br />
12<br />
13 MemoryGameCard : : MemoryGameCard( const char ∗ symbol )<br />
14 {<br />
15 i f ( ! symbol )<br />
16 {<br />
17 symbol = 0;<br />
18 return ;<br />
19 }<br />
20 symbol = new char [ s t r l e n ( symbol ) + 1 ] ;<br />
21 strcpy ( symbol , symbol ) ;<br />
22 }<br />
23<br />
24 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
25 /∗ d e s t r u c t o r of MemoryGameCard<br />
26 ∗/<br />
27<br />
28 MemoryGameCard : : ˜ MemoryGameCard( )<br />
29 {<br />
30 i f ( symbol )<br />
31 delete [ ] symbol ;<br />
32 }<br />
33<br />
34 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
35 /∗<br />
36 ∗ @return the symbol that t h i s card r e p r e s e n t s<br />
37 ∗/<br />
38<br />
39 const char ∗MemoryGameCard : : getSymbol ( )<br />
40 {<br />
41 return ( symbol ) ;<br />
42 }<br />
43<br />
44<br />
45<br />
46 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
47 /∗<br />
48 ∗ @param symbol the symbol represented by t h i s card<br />
49 ∗/<br />
50<br />
51 void MemoryGameCard : : changeSymbol ( const char ∗ symbol )<br />
52 {<br />
53 i f ( symbol )<br />
54 delete [ ] symbol ;<br />
55 i f ( ! symbol )<br />
56 {<br />
57 symbol = 0;<br />
58 return ;<br />
59 }<br />
60 symbol = new char [ s t r l e n ( symbol ) + 1 ] ;<br />
61 strcpy ( symbol , symbol ) ;<br />
62 }
9.2 E<strong>in</strong>fache Klassen 177<br />
Im Pr<strong>in</strong>zip treffen wir <strong>in</strong> diesem File nur alte Bekannte, dementsprechend<br />
erübrigt sich e<strong>in</strong>e genaue Diskussion über die Interna. Wie man sieht, wird<br />
der Scope Operator bei Klassen genau gleich angewandt, wie wir ihn schon<br />
bei Structures kennen gelernt haben, um die Zugehörigkeit e<strong>in</strong>er Methode zu<br />
e<strong>in</strong>er Klassendef<strong>in</strong>ition zu signalisieren. Auch hier gelten dieselben Regeln für<br />
den default Scope der Methode, wie man z.B. <strong>in</strong> Zeile 17 sieht: Die Zuweisung<br />
auf symbol_ bezeichnet natürlich e<strong>in</strong>e Zuweisung auf den Member symbol_<br />
der entsprechenden Instanz der Klasse.<br />
Wie man jetzt mit unserer fertig deklarierten, def<strong>in</strong>ierten und implementierten<br />
Klasse arbeiten kann, zeigt das folgende kle<strong>in</strong>e Testprogrämmchen<br />
(memory_game_card_test.cpp):<br />
1 // memory game card test . cpp − simple t e s t program f o r MemoryGameCard<br />
2<br />
3 #<strong>in</strong>clude <br />
4 #<strong>in</strong>clude ”memory game card . h”<br />
5<br />
6 us<strong>in</strong>g std : : cout ;<br />
7 us<strong>in</strong>g std : : endl ;<br />
8<br />
9 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
10 {<br />
11 MemoryGameCard t e s t c a r d ( ”Symbol A” ) ;<br />
12<br />
13 cout
178 9. Klassen <strong>in</strong> <strong>C++</strong><br />
h<strong>in</strong>ter sich hat, also beim Verlassen des Blocks durch das return Statement<br />
<strong>in</strong> Zeile 20.<br />
Um das Testprogramm ordnungsgemäß übersetzen zu können, müssen<br />
die beiden C ++ Source Files compiliert und die resultierenden Object-Files<br />
zu e<strong>in</strong>em Executable gel<strong>in</strong>kt werden. Dass es nicht so toll ist, dies “zu Fuß”<br />
zu machen, versteht sich von selbst. Dementsprechend wird das Tool make<br />
mit dem folgenden Makefile verwendet (MemoryGameCardTestMakefile):<br />
1 OBJS = memory game card test . o \<br />
2 memory game card . o<br />
3 CC = g++<br />
4 LD = g++<br />
5 EXTRA CCINCLUDES =<br />
6 CC FLAGS = −c $ (EXTRA CCINCLUDES) −Wall<br />
7 EXTRA LIBDIRS =<br />
8 EXTRA LIBS =<br />
9 EXECUTABLE = memory game card test<br />
10 LD FLAGS = −o $ (EXECUTABLE) $ (EXTRA LIBDIRS) $ (EXTRA LIBS)<br />
11<br />
12 memory game card test : $ (OBJS)<br />
13 $ (LD) $ (LD FLAGS) $ (OBJS)<br />
14<br />
15 memory game card test . o : memory game card test . cpp \<br />
16 memory game card . h<br />
17 $ (CC) $ (CC FLAGS) memory game card test . cpp<br />
18<br />
19 memory game card . o : memory game card . cpp \<br />
20 memory game card . h<br />
21<br />
22 a l l : memory game card test<br />
23<br />
24 clean :<br />
25 rm $ (OBJS) $ (EXECUTABLE)<br />
Lesern, denen die Funktionsweise von make und das Erstellen von Makefiles<br />
nicht geläufig ist, möchte ich kurz die Lektüre von Kapitel 15 aus <strong>Softwareentwicklung</strong><br />
<strong>in</strong> C empfehlen. Dort werden alle notwendigen Schritte genau<br />
diskutiert.<br />
Wenn alles glatt gegangen ist und wir das Programm starten, dann<br />
beglückt es uns mit folgendem, unglaublich tollen Output:<br />
t e s t c a r d a f t e r construction : Symbol A<br />
t e s t c a r d a f t e r changeSymbol : Symbol B<br />
9.2.1 Konstruktor und Destruktor genauer beleuchtet<br />
Da die Aufrufe des Konstruktors und des Destruktors vom Compiler implizit<br />
e<strong>in</strong>gesetzt werden, möchte ich an dieser Stelle etwas Licht auf die Vorgänge<br />
h<strong>in</strong>ter den Kulissen werfen. Sehen wir uns dazu e<strong>in</strong>mal ganz kurz den Lebenszyklus<br />
e<strong>in</strong>er beliebigen Variable an:
9.2 E<strong>in</strong>fache Klassen 179<br />
1. Es wird Speicher für die Variable reserviert. Die Größe des Speichers<br />
ist bestimmt durch den Typ der Variable und durch gewisse andere system<strong>in</strong>terne<br />
Gegebenheiten. In jedem Fall wird garantiert, dass genug<br />
reserviert wird, um die Daten dem verwendeten Typ gemäß zu halten.<br />
2. Nach der Reservierung des Speichers folgt die Initialisierung der Variable.<br />
3. Mit der fertig <strong>in</strong>itialisierten Variable kann man ab jetzt arbeiten, wie es<br />
durch ihren Typ vorgegeben ist.<br />
4. Wenn e<strong>in</strong>e Variable ihre Lifetime beendet hat, wird sie “weggeräumt”.<br />
Der erste Schritt beim “Wegräumen” ist, dass eventuell notwendige explizite<br />
“Aufräumarbeiten” durchgeführt werden.<br />
5. Wenn alle notwendigen “Aufräumarbeiten” beendet s<strong>in</strong>d, wird der Speicher,<br />
der von der Variable belegt war, wieder freigegeben.<br />
Genau die Punkte 2 und 4 s<strong>in</strong>d es, die beim Arbeiten mit Klassen und<br />
Objekten für uns hier <strong>in</strong>teressant s<strong>in</strong>d. Es wird nämlich bei Punkt 2 vom<br />
Compiler der Aufruf des entsprechenden Konstruktors e<strong>in</strong>gesetzt, der der<br />
gewünschten Initialisierung entspricht. Bei Punkt 4 wird der Aufruf des<br />
Destruktors e<strong>in</strong>gesetzt. Haben wir es also mit e<strong>in</strong>fachen (=nicht abgeleiteten)<br />
Klassen zu tun, so sieht der Lifecycle e<strong>in</strong>es Objekts dieser Klasse genau<br />
folgendermaßen aus:<br />
1. Speicher reservieren.<br />
2. Entsprechenden Konstruktor aufrufen.<br />
3. Objekt lebt und mit ihm kann gearbeitet werden.<br />
4. Zu Ende der Lifetime Destruktor aufrufen.<br />
5. Speicher freigeben.<br />
Das Pr<strong>in</strong>zip des Overload<strong>in</strong>gs gilt natürlich nicht nur für Funktionen <strong>in</strong> C ++,<br />
sondern auch für Methoden. Da der Konstruktor e<strong>in</strong>e solche darstellt, ist<br />
selbstverständlich auch hier e<strong>in</strong> Overload<strong>in</strong>g möglich. Man kann also verschiedene<br />
Konstruktoren mit verschiedenen Parametersätzen zur Verfügung<br />
stellen, die entsprechend s<strong>in</strong>nvollen Initialisierungsbedürfnissen entsprechen.<br />
E<strong>in</strong>e gewisse Sonderstellung nimmt hierbei der sogenannte default Konstruktor<br />
e<strong>in</strong>, also der Konstruktor, der ke<strong>in</strong>en Parameter entgegennimmt. Im<br />
weiter unten folgenden Beispiel werden wir diesen noch kurz genauer kennen<br />
lernen.<br />
Vorgreifend möchte ich auch noch erwähnen, dass e<strong>in</strong>e ähnliche Sonderstellung<br />
vom sogenannten Copy Konstruktor e<strong>in</strong>genommen wird. Dieser wird<br />
gesondert <strong>in</strong> Abschnitt 9.2.2 behandelt um hier nicht für Verwirrung zu sorgen.<br />
Im Gegensatz zum Konstruktor ist e<strong>in</strong> Overload<strong>in</strong>g des Destruktors nicht<br />
möglich. Es kann ausschließlich immer nur genau e<strong>in</strong>en Destruktor geben,<br />
nämlich den, der ke<strong>in</strong>e Parameter entgegennimmt. Warum das so ist, ist<br />
auch leicht e<strong>in</strong>zusehen: Der Aufruf des Destruktors wird vom Compiler am<br />
Ende der Lifetime e<strong>in</strong>es Objekts e<strong>in</strong>gesetzt. Bei e<strong>in</strong>er auto-Variable also<br />
beim Verlassen des Blocks, <strong>in</strong> dem sie def<strong>in</strong>iert wurde. Wie soll nun der
180 9. Klassen <strong>in</strong> <strong>C++</strong><br />
Compiler wissen, welche von verschiedenen Möglichkeiten des Destruktors<br />
er dort e<strong>in</strong>setzen soll? Vor allem gibt es ja auch ke<strong>in</strong>e Möglichkeit, ihm<br />
mitzuteilen, welche Parameterwerte überhaupt e<strong>in</strong>gesetzt werden müssten,<br />
sollte e<strong>in</strong> Destruktor irgendwelche Parameter entgegennehmen können. Der<br />
Compiler bestimmt ja, wann die Lifetime e<strong>in</strong>er Variable zu Ende ist, dies wird<br />
(außer bei dynamischer Memory Verwaltung) niemals von den Entwicklern<br />
explizit angegeben.<br />
Wie im Rahmen des Life-Cycles e<strong>in</strong>es Objekts die Aufrufe von Konstruktoren<br />
und Destruktor passieren, lässt sich am e<strong>in</strong>fachsten an e<strong>in</strong>em Beispiel<br />
demonstrieren<br />
(simple_constr_destr_demo.cpp):<br />
1 // simple constr destr demo . cpp − constructor and d e s t r u c t o r demo<br />
2<br />
3 #<strong>in</strong>clude <br />
4<br />
5 us<strong>in</strong>g std : : cout ;<br />
6 us<strong>in</strong>g std : : endl ;<br />
7<br />
8 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
9 /∗<br />
10 ∗ SimpleConstrDestrDemo<br />
11 ∗<br />
12 ∗ Simple demo c l a s s f o r c o n s t r u c t o r s and d e s t r u c t o r<br />
13 ∗<br />
14 ∗/<br />
15<br />
16 class SimpleConstrDestrDemo<br />
17 {<br />
18 private :<br />
19 <strong>in</strong>t j u s t a n i n t v a l u e ;<br />
20 const char ∗ j u s t a s t r i n g v a l u e ;<br />
21 public :<br />
22 SimpleConstrDestrDemo ( ) ;<br />
23 SimpleConstrDestrDemo ( <strong>in</strong>t a param ) ;<br />
24 SimpleConstrDestrDemo ( const char ∗ a param ) ;<br />
25 ˜SimpleConstrDestrDemo ( ) ;<br />
26 } ;<br />
27<br />
28 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
29 /∗ d e f a u l t constructor<br />
30 ∗/<br />
31<br />
32 SimpleConstrDestrDemo : : SimpleConstrDestrDemo ( )<br />
33 {<br />
34 cout
49 }<br />
50<br />
9.2 E<strong>in</strong>fache Klassen 181<br />
51 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
52 /∗<br />
53 ∗ @param a param j u s t a s t r i n g parameter f o r demo purposes<br />
54 ∗/<br />
55<br />
56 SimpleConstrDestrDemo : : SimpleConstrDestrDemo ( const char ∗ a param )<br />
57 {<br />
58 cout
182 9. Klassen <strong>in</strong> <strong>C++</strong><br />
Objekt, das zuletzt angelegt wurde, als Erstes zerstört wird. Dies ist auch das<br />
normale Verhalten, das praktisch alle Compiler zeigen werden, denn die auto-<br />
Variablen liegen ja auf dem Stack und werden entsprechend beim Abbauen<br />
desselben zerstört.<br />
Noch e<strong>in</strong> kle<strong>in</strong>er Exkurs: Aus Gründen der Übersichtlichkeit habe ich<br />
hier die Deklaration der Klasse zusammen mit der Def<strong>in</strong>ition der Methoden<br />
geme<strong>in</strong>sam <strong>in</strong> e<strong>in</strong> Source File gesteckt. Dies ist im Entwickleralltag natürlich<br />
ke<strong>in</strong>esfalls nachzuahmen. Dort ist e<strong>in</strong>e saubere Aufteilung <strong>in</strong> .h (für die Klassendeklaration)<br />
und .cpp Files (für die Def<strong>in</strong>ition der Methoden) <strong>in</strong> jedem<br />
Fall vorzunehmen.<br />
Nach diesen beiden kle<strong>in</strong>en Exkursen zurück zum Thema: In den Zeilen<br />
22–24 des Programms f<strong>in</strong>det man die Deklarationen der verschiedenen<br />
möglichen Konstruktoren für die Klasse SimpleConstrDestrDemo. Man kann<br />
also Objekte auf e<strong>in</strong>e der dadurch festgelegten Arten anlegen:<br />
• Ohne Parameter, wobei der default Konstruktor aus Zeile 22 aufgerufen<br />
wird.<br />
• Mit e<strong>in</strong>em <strong>in</strong>t Parameter, wobei der Konstruktor aus Zeile 23 aufgerufen<br />
wird.<br />
• Mit e<strong>in</strong>em char * als Parameter, wobei der Konstruktor aus Zeile 24 aufgerufen<br />
wird.<br />
Die Konstruktoren werden gemäß den Regeln des Overload<strong>in</strong>gs, die wir bereits<br />
<strong>in</strong> Kapitel 5 kennen gelernt haben, vom Compiler ausgewählt. Sollte<br />
sich ke<strong>in</strong>e s<strong>in</strong>nvolle Übere<strong>in</strong>stimmung ergeben, dann führt dies natürlich zu<br />
e<strong>in</strong>em Fehler.<br />
Es ist aufgrund des zuvor skizzierten Lifecycles von Objekten garantiert,<br />
dass e<strong>in</strong> Konstruktor erst aufgerufen wird, wenn auf jeden Fall schon der<br />
Speicher für das Objekt reserviert wurde. Man hat es also <strong>in</strong>nerhalb des<br />
Konstruktors mit e<strong>in</strong>em angelegten, aber nicht <strong>in</strong>itialisierten Objekt zu tun.<br />
Es wurde bereits erwähnt, dass der default Konstruktor e<strong>in</strong>e gewisse Sonderstellung<br />
e<strong>in</strong>nimmt. In Zeile 78 sieht man auch, was es damit auf sich hat:<br />
Man legt e<strong>in</strong>fach e<strong>in</strong>e Variable der gewünschten Klasse an, ohne irgendwelche<br />
runden Klammern dah<strong>in</strong>ter zu schreiben. Im Gegensatz dazu gibt man<br />
bei Initialisierungen mittels anderer Konstruktoren die Initialisierungsparameter<br />
<strong>in</strong> runden Klammern h<strong>in</strong>ter dem Variablennamen an, wie man <strong>in</strong> den<br />
Zeilen 79 und 80 sieht.<br />
Vorsicht Falle: Die Sonderbehandlung des default Konstruktors, der ohne<br />
runde Klammern geschrieben wird, stellt e<strong>in</strong>e kle<strong>in</strong>e Inkonsistenz bezüglich<br />
der Intuitivität und der E<strong>in</strong>heitlichkeit von Code dar. Aus diesem Grund<br />
passiert es C ++ Neul<strong>in</strong>gen nur allzu leicht, dass sie Statements wie z.B. das<br />
folgende schreiben:<br />
SimpleConstrDestrDemo testvar1();
9.2 E<strong>in</strong>fache Klassen 183<br />
Leider wird dies aber vom Compiler nicht so <strong>in</strong>terpretiert, dass man e<strong>in</strong>e<br />
Variable testvar1 anlegen will und dazu den default Konstruktor verwenden<br />
will. Stattdessen <strong>in</strong>terpretiert dies der Compiler als Funktionsdeklaration und<br />
entsprechend sehen dann auch die Fehlermeldungen sowohl des Compilers<br />
als auch danach des L<strong>in</strong>kers aus. Diese Fehlermeldungen s<strong>in</strong>d auch zumeist<br />
kryptisch genug, dass man nicht sofort auf den tatsächlichen Fehler kommt,<br />
nämlich, dass die runden Klammern nicht angebracht waren.<br />
Der default Konstruktor hat noch e<strong>in</strong>e weitere Sonderstellung <strong>in</strong> C ++: Es<br />
gibt auch die Möglichkeit, für e<strong>in</strong>e Klasse ke<strong>in</strong>en e<strong>in</strong>zigen Konstruktor zu<br />
deklarieren und def<strong>in</strong>ieren. In diesem Fall wird vom Compiler implizit e<strong>in</strong><br />
default Konstruktor erzeugt, der folgende Eigenschaften hat:<br />
• Für alle Members, denen selbst benutzerdef<strong>in</strong>ierte Datentypen (=Klassen)<br />
zugrunde liegen (also ke<strong>in</strong>e primitiven Datentypen wie z.B. <strong>in</strong>t, etc.) wird<br />
die default Konstruktion veranlasst und damit kommt es zu e<strong>in</strong>er Initialisierung<br />
derselben.<br />
• Für alle Members, die primitive Datentypen s<strong>in</strong>d, wird ke<strong>in</strong>e implizite<br />
Initialisierung vorgenommen. Diese Members enthalten also damit<br />
re<strong>in</strong> zufällige Werte. Der Grund für dieses Verhalten ist die Angst vor<br />
unnötigem Runtime-Overhead durch Initialisierungsschritte. Me<strong>in</strong>er Me<strong>in</strong>ung<br />
nach allerd<strong>in</strong>gs ist der entstehende Overhead e<strong>in</strong> viel ger<strong>in</strong>geres Problem<br />
als die Gefahr, die man sich durch das Fehlen e<strong>in</strong>er Initialisierung<br />
e<strong>in</strong>handelt.<br />
Vorsicht Falle: Es wird als guter Programmierstil angesehen, jeder Klasse<br />
e<strong>in</strong>en default Konstruktor zu spendieren, der auf jeden Fall alle Members<br />
dieser Klasse korrekt <strong>in</strong>itialisiert. Verlässt man sich auf den vom Compiler<br />
e<strong>in</strong>gesetzten default Konstruktor, so kann es aus den zuvor genannten<br />
Gründen zu bösen Überraschungen kommen!<br />
Obwohl bereits implizit gesagt, möchte ich e<strong>in</strong>e Eigenschaft des vom Compiler<br />
e<strong>in</strong>gesetzten default Konstruktors hier noch e<strong>in</strong>mal explizit festhalten:<br />
Er wird ausschließlich dann erzeugt, wenn für e<strong>in</strong>e Klasse überhaupt ke<strong>in</strong><br />
Konstruktor deklariert wurde. Sollte auch nur e<strong>in</strong> e<strong>in</strong>ziger Konstruktor deklariert<br />
worden se<strong>in</strong>, egal, welche Parameter dieser nimmt, dann wird der<br />
default Konstruktor nicht erzeugt. Dadurch erreicht man, dass man als<br />
Designer von Klassen Entwickler zw<strong>in</strong>gen kann, e<strong>in</strong>e explizite Initialisierung<br />
vorzunehmen. Je nach Anwendungsfall kann dies auch ausgesprochen notwendig<br />
se<strong>in</strong>.<br />
Was für den default Konstruktor gilt, gilt auch für den Destruktor: Wird<br />
explizit ke<strong>in</strong> Destruktor deklariert und def<strong>in</strong>iert, so wird vom Compiler e<strong>in</strong>er<br />
e<strong>in</strong>gesetzt. Auch hier wird e<strong>in</strong>fach nur Code erzeugt, der dafür sorgt, dass<br />
Members benutzerdef<strong>in</strong>ierter Datentypen korrekt <strong>in</strong>klusive Destruktoraufrufen<br />
zerstört werden. Primitive Datentypen werden nicht angegriffen. Hat
184 9. Klassen <strong>in</strong> <strong>C++</strong><br />
man also e<strong>in</strong>en Member, der dynamisch allokierten Speicher hält, so wird<br />
dieser nicht automatisch freigegeben.<br />
Es wird garantiert, dass der Destruktor aufgerufen wird, solange für das<br />
Objekt noch vollständig Speicher reserviert ist. Das Freigeben des Speichers<br />
erfolgt erst danach. Damit kann man auf jeden Fall <strong>in</strong>nerhalb des Destruktors<br />
gefahrlos auf alle Members des Objekts zugreifen.<br />
Vorsicht Falle: Es wird als guter Programmierstil angesehen, jeder Klasse<br />
auf jeden Fall e<strong>in</strong>en Destruktor zu spendieren, auch wenn dieser im schlimmsten<br />
Fall leer ist. Durch e<strong>in</strong>en leeren Destruktor hat man zum<strong>in</strong>dest offiziell<br />
festgelegt, dass nichts Besonderes zu tun ist. Vergisst man z.B. das explizite<br />
Aufräumen von dynamisch allokiertem Speicher, dann bekommt man<br />
e<strong>in</strong> wachsendes Programm, was wiederum für e<strong>in</strong>ige schlaflose Nächte der<br />
Entwickler sorgt. Wenn man sich angewöhnt, immer e<strong>in</strong>en Destruktor zu<br />
deklarieren und def<strong>in</strong>ieren, dann er<strong>in</strong>nert e<strong>in</strong>en e<strong>in</strong> solcher bei e<strong>in</strong>er späteren<br />
Änderung des Klassencodes (z.B. E<strong>in</strong>führen e<strong>in</strong>er neuen dynamischen<br />
Variable) sofort beim Ansehen der Klassendeklaration daran, dass hier noch<br />
etwas zu tun wäre. Somit ist die Gefahr des Vergessens auf jeden Fall etwas<br />
ger<strong>in</strong>ger :-).<br />
Nur für Leser, die diese Falle im Zuge des Nachschlagens lesen, e<strong>in</strong> kurzer<br />
Vorgriff (alle anderen können diesen H<strong>in</strong>weis übergehen): Wir werden<br />
<strong>in</strong> späterer Folge noch e<strong>in</strong>e besondere Eigenschaft des Destruktors kennen<br />
lernen, nämlich, dass dieser unbed<strong>in</strong>gt virtual se<strong>in</strong> muss. Auch dies ist <strong>in</strong><br />
jedem Fall zu beachten, denn die re<strong>in</strong>e Existenz e<strong>in</strong>es Destruktors garantiert<br />
noch ke<strong>in</strong> vollständig korrektes und funktionsfähiges Programm!<br />
Der Vollständigkeit halber möchte ich hier noch erwähnen, dass man sowohl<br />
Konstruktor als auch Destruktor nicht unbed<strong>in</strong>gt public machen muss,<br />
sondern dass man sehr wohl auch andere Access Specifiers für diese verwenden<br />
kann. Wir werden später noch im Zuge von Beispielen mehrere sehr<br />
s<strong>in</strong>nvolle Anwendungen dafür kennen lernen. Derzeit allerd<strong>in</strong>gs würde e<strong>in</strong>e<br />
Diskussion darüber den Rahmen sprengen.<br />
9.2.2 Der Copy Konstruktor<br />
Um die Sonderstellung des sogenannten Copy Konstruktors zu erkennen, sehen<br />
wir uns e<strong>in</strong>mal folgenden Code an: (implicit_copy_constr_demo.cpp):<br />
1 // implicit copy constr demo . cpp − demo f o r an i m p l i c i t copy<br />
2 // constructor<br />
3<br />
4 #<strong>in</strong>clude <br />
5 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
6<br />
7 us<strong>in</strong>g std : : cout ;<br />
8 us<strong>in</strong>g std : : endl ;<br />
9<br />
10 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
11 /∗<br />
12 ∗ JustAClass<br />
13 ∗<br />
14 ∗ j u s t a dummy c l a s s f o r demo purposes<br />
15 ∗<br />
16 ∗/<br />
17<br />
18 class JustAClass<br />
19 {<br />
20 u<strong>in</strong>t32 a v a l u e ;<br />
21 public :<br />
22 JustAClass ( ) ;<br />
23 JustAClass ( u<strong>in</strong>t32 value ) ;<br />
24 ˜JustAClass ( ) ;<br />
25<br />
26 u<strong>in</strong>t32 getValue ( ) ;<br />
27 } ;<br />
28<br />
9.2 E<strong>in</strong>fache Klassen 185<br />
29 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
30 /∗ d e f a u l t constructor<br />
31 ∗/<br />
32 JustAClass : : JustAClass ( )<br />
33 {<br />
34 cout
186 9. Klassen <strong>in</strong> <strong>C++</strong><br />
77<br />
78 cout
25 ˜JustAClass ( ) ;<br />
26<br />
27 u<strong>in</strong>t32 getValue ( ) ;<br />
28 } ;<br />
9.2 E<strong>in</strong>fache Klassen 187<br />
Ich habe hier bewusst nicht noch e<strong>in</strong>mal das gesamte Programm abgedruckt,<br />
sondern möchte nur auf die Änderungen e<strong>in</strong>gehen. In Zeile 24 sieht man, wie<br />
e<strong>in</strong> Copy Konstruktor def<strong>in</strong>iert ist: Er nimmt als Parameter e<strong>in</strong>e Referenz auf<br />
e<strong>in</strong> Objekt vom “eigenen” Typ. Dass dieser Referenzparameter s<strong>in</strong>nigerweise<br />
e<strong>in</strong>e const Referenz se<strong>in</strong> muss, ist auch e<strong>in</strong>leuchtend, denn wie sollte sonst<br />
e<strong>in</strong>e Kopie e<strong>in</strong>es konstanten Objekts erstellt werden? Die Implementation<br />
des Copy Konstruktors sieht dann natürlich so aus:<br />
51 JustAClass : : JustAClass ( const JustAClass & s r c )<br />
52 {<br />
53 cout
188 9. Klassen <strong>in</strong> <strong>C++</strong><br />
Die Antwort, warum das passiert, f<strong>in</strong>det sich <strong>in</strong> der Tatsache, dass <strong>in</strong><br />
C ++ Aufrufe von Methoden ja mittels call-by-value stattf<strong>in</strong>den. Das bedeutet,<br />
dass bei der Übergabe des Objekts e<strong>in</strong>e Kopie erstellt wird! Hierbei<br />
wird natürlich mangels e<strong>in</strong>es expliziten Copy Konstruktors der implizite, vom<br />
Compiler generierte, e<strong>in</strong>gesetzt.<br />
Diese mörderische Gefahrenquelle lässt sich <strong>in</strong> ihren verschiedensten Ausprägungen<br />
e<strong>in</strong>zig und alle<strong>in</strong> durch konsequentes E<strong>in</strong>halten der folgenden Konvention<br />
<strong>in</strong> den Griff bekommen:<br />
• Jede Klasse muss e<strong>in</strong>en expliziten Copy Konstruktor besitzen!!!!!<br />
• Sollte es absolut nicht erwünscht se<strong>in</strong>, dass der Copy Konstruktor<br />
aufgerufen wird, so ist dieser private zu deklarieren. Ke<strong>in</strong>esfalls<br />
darf er aber weggelassen werden!<br />
Um nun bösen Kommentaren vorzubeugen, dass ich mich <strong>in</strong> diesem Buch<br />
selbst nicht an diese Konvention halten würde: Je nachdem, was e<strong>in</strong> Codebeispiel<br />
<strong>in</strong> diesem Buch demonstrieren soll, habe ich immer versucht, mich<br />
auf das Wesentliche zu beschränken, das der Demonstration dient. Hierbei<br />
wurde <strong>in</strong> e<strong>in</strong>igen Fällen auf gewissen Ballast verzichtet, um nicht unnötige<br />
Verwirrung zu stiften. Manchmal war auch der Copy Konstruktor e<strong>in</strong> solcher<br />
Ballast. In der Praxis allerd<strong>in</strong>gs b<strong>in</strong> ich bei solchen Konventionen tatsächlich<br />
immer konsequent :-).<br />
9.2.3 Initialisierung vs. Zuweisung<br />
Manche Leser mögen schon manchmal den Kopf darüber geschüttelt haben,<br />
dass ich so p<strong>in</strong>gelig auf dem Begriff der Initialisierung herumreite und betone,<br />
dass e<strong>in</strong>e Initialisierung nicht dasselbe ist, wie e<strong>in</strong>e Zuweisung. Am<br />
folgenden Beispiel lässt sich sehr e<strong>in</strong>fach erkennen, dass der Compiler me<strong>in</strong>e<br />
Me<strong>in</strong>ung teilt und ebenso p<strong>in</strong>gelig ist :-). Nehmen wir wieder unsere Klasse<br />
JustAClass von zuvor (die Variante <strong>in</strong>cl. Copy Konstruktor) und schreiben<br />
ma<strong>in</strong> folgendermaßen um (<strong>in</strong>itialization_demo.cpp):<br />
76 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
77 {<br />
78 JustAClass t e s t v a r 1 = 10;<br />
79 JustAClass t e s t v a r 2 = t e s t v a r 1 ;<br />
80<br />
81 cout
9.2 E<strong>in</strong>fache Klassen 189<br />
diese beiden Zeilen überhaupt so h<strong>in</strong>schreiben kann, verrät uns der Output<br />
des Programms:<br />
JustAClass : constructor with u<strong>in</strong>t32<br />
JustAClass : copy constructor<br />
The values stored <strong>in</strong> the v a r i a b l e s are :<br />
t e s t v a r 1 : 1 0<br />
t e s t v a r 2 : 1 0<br />
JustAClass : d e s t r u c t o r<br />
JustAClass : d e s t r u c t o r<br />
Zeile 78 resultiert <strong>in</strong> e<strong>in</strong>em Aufruf unseres speziellen Konstruktors mit dem<br />
u<strong>in</strong>t32 Parameter und Zeile 79 resultiert im Aufruf unseres Copy Konstruktors.<br />
Der Compiler sucht also def<strong>in</strong>itiv bei e<strong>in</strong>er Initialisierung nach e<strong>in</strong>em<br />
entsprechenden Konstruktor. Gibt es e<strong>in</strong>en solchen, so wird er e<strong>in</strong>gesetzt, gibt<br />
es ihn nicht, dann resultiert dies <strong>in</strong> e<strong>in</strong>em Compilerfehler! Wir können also<br />
bei Konstruktoren, die nur e<strong>in</strong>en Parameter nehmen, wahlweise die Klammer-<br />
Schreibweise oder die = Schreibweise verwenden. Das Resultat ist dasselbe.<br />
Im Rahmen des Operator Overload<strong>in</strong>gs wird der Unterschied zwischen Initialisierung<br />
und Zuweisung noch e<strong>in</strong>mal <strong>in</strong> e<strong>in</strong>em anderen Kontext aufgegriffen<br />
(siehe Abschnitt 12.1).<br />
9.2.4 Deklarieren von Konstruktoren als explicit<br />
Es gibt Fälle, <strong>in</strong> denen das implizite E<strong>in</strong>setzen e<strong>in</strong>es bestimmten Konstruktors<br />
im Rahmen e<strong>in</strong>er Initialisierung mittels = als zu gefährlich und fehlerträchtig<br />
ersche<strong>in</strong>t. Das folgende Beispiel demonstriert diesen Umstand<br />
(implicit_constructor_problem.cpp):<br />
1 // i m p l i c i t c o n s t r u c t o r p r o b l e m . cpp − demo f o r problems with<br />
2 // i m p l i c i t c o n s t r u c t o r s<br />
3<br />
4 #<strong>in</strong>clude <br />
5 #<strong>in</strong>clude < c s t r i n g><br />
6 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
7<br />
8 us<strong>in</strong>g std : : cout ;<br />
9 us<strong>in</strong>g std : : endl ;<br />
10<br />
11 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
12 /∗<br />
13 ∗ DummyStr<strong>in</strong>g<br />
14 ∗<br />
15 ∗ j u s t a dummy s t r i n g c l a s s f o r demo purposes<br />
16 ∗<br />
17 ∗/<br />
18<br />
19 class DummyStr<strong>in</strong>g<br />
20 {<br />
21 char ∗ s t r i n g ;<br />
22 u<strong>in</strong>t32 l e n g t h ;<br />
23 public :<br />
24 DummyStr<strong>in</strong>g( u<strong>in</strong>t32 length ) ;<br />
25 DummyStr<strong>in</strong>g( const char ∗ s t r ) ;<br />
26 DummyStr<strong>in</strong>g( const DummyStr<strong>in</strong>g & s r c ) ;<br />
27 ˜DummyStr<strong>in</strong>g ( ) ;<br />
28 } ;
190 9. Klassen <strong>in</strong> <strong>C++</strong><br />
29<br />
30 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
31 /∗ s p e c i a l constructor<br />
32 ∗/<br />
33 DummyStr<strong>in</strong>g : : DummyStr<strong>in</strong>g( u<strong>in</strong>t32 length )<br />
34 {<br />
35 cout
9.2 E<strong>in</strong>fache Klassen 191<br />
Str<strong>in</strong>g auch e<strong>in</strong>fach mit e<strong>in</strong>em char <strong>in</strong>itialisieren kann. Der Compiler hat<br />
sich auch <strong>in</strong> ke<strong>in</strong>ster Weise darüber beschwert, der Output des Programms<br />
zeigt jedoch, dass hier nicht wirklich das passiert, was man gerne hätte:<br />
DummyStr<strong>in</strong>g : constructor with s t r i n g<br />
DummyStr<strong>in</strong>g : copy constructor<br />
DummyStr<strong>in</strong>g : constructor with length<br />
DummyStr<strong>in</strong>g : d e s t r u c t o r<br />
DummyStr<strong>in</strong>g : d e s t r u c t o r<br />
DummyStr<strong>in</strong>g : d e s t r u c t o r<br />
Sieht man sich die dritte Zeile des Outputs an, so wird doch glatt hier der<br />
Konstruktor aufgerufen, der als Parameter die Länge des Str<strong>in</strong>gs enthält!<br />
Was aber wahrsche<strong>in</strong>lich beabsichtigt war, war die Initialisierung des Str<strong>in</strong>gs,<br />
so dass er e<strong>in</strong>fach e<strong>in</strong> x enthalten sollte. Der Compiler, der von der Absicht<br />
der Entwickler ke<strong>in</strong>e Ahnung hat, hat das allerd<strong>in</strong>gs ganz anders gesehen: Er<br />
hat e<strong>in</strong>en Konstruktor gesucht, der als Datentyp e<strong>in</strong>en char nimmt. Diesen<br />
gibt es nicht. Sehr wohl aber gibt es e<strong>in</strong>en Konstruktor, der dazu kompatibel<br />
ist, nämlich den Konstruktor, der die Länge als Parameter nimmt. E<strong>in</strong> char<br />
ist ja e<strong>in</strong>e Ganzzahl und der Konstruktor nimmt e<strong>in</strong>e Ganzzahl. Also ist mit<br />
e<strong>in</strong>er kle<strong>in</strong>en Typumwandlung alles bestens – oder, wie <strong>in</strong> unserem Fall hier,<br />
ganz und gar nichts mehr <strong>in</strong> Ordnung.<br />
Zum Glück gibt es e<strong>in</strong>en sauberen Weg, es gleich gar nicht zu e<strong>in</strong>er solchen<br />
Gefahr kommen zu lassen: Man kann dem Compiler mitteilen, dass<br />
bestimmte Konstruktoren niemals implizit e<strong>in</strong>gesetzt werden sollen, sondern<br />
nur explizit aufgerufen werden dürfen. Das Zauberwort dazu nennt sich <strong>in</strong>tuitiverweise<br />
explicit. Vorausschauende Entwickler würden also die Str<strong>in</strong>g<br />
Klasse folgendermaßen deklarieren (forc<strong>in</strong>g_explicit_constructor.cpp):<br />
19 class DummyStr<strong>in</strong>g<br />
20 {<br />
21 char ∗ s t r i n g ;<br />
22 u<strong>in</strong>t32 l e n g t h ;<br />
23 public :<br />
24 explicit DummyStr<strong>in</strong>g( u<strong>in</strong>t32 length ) ;<br />
25 DummyStr<strong>in</strong>g( const char ∗ s t r ) ;<br />
26 DummyStr<strong>in</strong>g( const DummyStr<strong>in</strong>g & s r c ) ;<br />
27 ˜DummyStr<strong>in</strong>g ( ) ;<br />
28 } ;<br />
In Zeile 24 wurde der gefährliche Konstruktor als explicit deklariert. Wie<br />
man an folgender Implementation von ma<strong>in</strong> sieht, ist hiermit die Fehlverwendung<br />
unterbunden, denn Zeile 81 würde <strong>in</strong> e<strong>in</strong>em Compilerfehler resultieren:<br />
76 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
77 {<br />
78 DummyStr<strong>in</strong>g s t r i n g 1 = ” otto ” ;<br />
79 DummyStr<strong>in</strong>g s t r i n g 2 = s t r i n g 1 ;<br />
80 // the f o l l o w i n g l i n e would r e s u l t <strong>in</strong> a compiler e r r o r<br />
81 // DummyStr<strong>in</strong>g s t r i n g 3 = ’ x ’ ;<br />
82<br />
83 // t h i s works , because of the e x p l i c i t c a l l<br />
84 DummyStr<strong>in</strong>g s t r i n g 4 ( 1 2 ) ;
192 9. Klassen <strong>in</strong> <strong>C++</strong><br />
85<br />
86 return ( 0 ) ;<br />
87 }<br />
Zeile 84 jedoch, die e<strong>in</strong>en expliziten Konstruktoraufruf enthält, funktioniert<br />
wie geplant. Es sollte also beim Deklarieren von Konstruktoren für e<strong>in</strong>e Klasse<br />
bei allen Lesern immer daran gedacht werden, unter welchen Umständen<br />
der Compiler für e<strong>in</strong> Objekt e<strong>in</strong>en impliziten Konstruktoraufruf e<strong>in</strong>setzt:<br />
• Bei Verwendung von = zur Initialisierung mit e<strong>in</strong>em Wert wird e<strong>in</strong> passender<br />
Konstruktor gesucht.<br />
• Bei Übergabe als Parameter an e<strong>in</strong>e Methode oder Funktion aufgrund des<br />
call-by-value Mechanismus wird der Copy Konstruktor aufgerufen.<br />
• Bei Verwendung <strong>in</strong> e<strong>in</strong>em return Statement wird der Copy Konstruktor<br />
aufgerufen, denn auch der return-Value wird ja by-value geliefert.<br />
• Bei Verwendung als Exception wird der Copy Konstruktor aufgerufen (dazu<br />
kommen wir noch <strong>in</strong> Kapitel 11).<br />
Sollte <strong>in</strong> irgende<strong>in</strong>em dieser Fälle der implizite Aufruf gefährlich se<strong>in</strong>, so muss<br />
e<strong>in</strong> Konstruktor als explicit deklariert werden.<br />
9.2.5 Object- und Class-Members<br />
In Abschnitt 8.3 wurde bereits erwähnt, dass es auch sogenannte Class-<br />
Members gibt. Dies s<strong>in</strong>d solche Members, die allen Instanzen e<strong>in</strong>er Klasse<br />
geme<strong>in</strong>sam s<strong>in</strong>d, anstatt für jedes Objekt extra zu existieren. Solche Members<br />
(Variablen oder auch Methoden) deklariert man dadurch, dass man sie<br />
static setzt, wie folgende kle<strong>in</strong>e Veränderung unserer Kartenklasse für das<br />
Memory Spiel zeigt. Diese protokolliert selbsttätig mit, wie viele Karten<br />
eigentlich im Programm <strong>in</strong>sgesamt existieren (memory_game_card_v2.h):<br />
1 // memory game card v2 . h − extension o f c l a s s MemoryGameCard<br />
2<br />
3 #ifndef memory game card v2 h<br />
4 #def<strong>in</strong>e memory game card v2 h<br />
5<br />
6 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
7 /∗<br />
8 ∗ MemoryGameCard<br />
9 ∗<br />
10 ∗ model of a card as used <strong>in</strong> the game ”memory”<br />
11 ∗<br />
12 ∗/<br />
13<br />
14 class MemoryGameCard<br />
15 {<br />
16 protected :<br />
17 static unsigned num <strong>in</strong>stances ;<br />
18 char ∗ symbol ;<br />
19 public :<br />
20 MemoryGameCard( const char ∗ symbol ) ;<br />
21 ˜MemoryGameCard ( ) ;
22<br />
23 const char ∗ getSymbol ( ) ;<br />
24 void changeSymbol ( const char ∗ symbol ) ;<br />
25 static unsigned getNumInstances ( ) ;<br />
26 } ;<br />
27<br />
28<br />
29 #endif // memory game card v2 h<br />
9.2 E<strong>in</strong>fache Klassen 193<br />
In Zeile 17 sieht man, wie man e<strong>in</strong>e Variable durch e<strong>in</strong>e static Deklaration<br />
zum Class-Member macht. Wie allerd<strong>in</strong>gs bereits aus dem Begriff Deklaration<br />
klar hervorgeht, dient Zeile 17 nur zur Beruhigung des Compilers. Man<br />
sagt ihm, dass es e<strong>in</strong>en Class-Member gibt und dass dieser schon irgendwo def<strong>in</strong>iert<br />
se<strong>in</strong> würde. Die tatsächliche Def<strong>in</strong>ition muss man natürlich an irgende<strong>in</strong>er<br />
Stelle im Programm auch noch h<strong>in</strong>schreiben. Dass dies natürlich nicht<br />
im Header geschehen darf, versteht sich von selbst. Ansonsten würde man<br />
ja Mehrfachdef<strong>in</strong>itionen provozieren, auf die der L<strong>in</strong>ker gar nicht so freundlich<br />
zu sprechen ist. Also wird die Def<strong>in</strong>ition s<strong>in</strong>nigerweise im zur Klasse<br />
gehörigen .cpp File stattf<strong>in</strong>den, wie wir <strong>in</strong> der Folge noch sehen werden.<br />
Auf num_<strong>in</strong>stances_ kann man aus allen Methoden heraus <strong>in</strong> altbekannter<br />
Manier e<strong>in</strong>fach zugreifen. Egal, aus welcher Instanz der Klasse heraus<br />
man zugreift, der Wert ist immer derselbe. Jedoch kann es passieren, dass<br />
man nicht immer e<strong>in</strong>e Instanz e<strong>in</strong>er Klasse zur Verfügung hat, um e<strong>in</strong>e Methode<br />
darauf aufzurufen. Vielleicht will man e<strong>in</strong>fach an irgende<strong>in</strong>er Stelle im<br />
Programm wissen, wie viele Karten existieren, obwohl man an dieser Stelle<br />
gar ke<strong>in</strong>e Instanz der Karte <strong>in</strong> der Hand hat. Zu diesem Zweck deklariert<br />
man dann e<strong>in</strong>e Class Member Methode. Diese ist, wie der Name schon sagt,<br />
auch nicht an e<strong>in</strong>e Instanz gebunden, sondern nur an die Klasse selbst. Intuitiverweise<br />
deklariert man e<strong>in</strong>e solche Methode gleich, wie es schon von<br />
der Variable her bekannt ist, als static. Dies sieht man <strong>in</strong> Zeile 25 unseres<br />
Programms.<br />
Was wir an unserer Implementation der Kartenklasse ändern müssen, um<br />
die Anzahl der Instanzen auch wirklich zu zählen und <strong>in</strong> Zugriff zu haben,<br />
sehen wir an den folgenden Ausschnitten von memory_game_card_v2.cpp:<br />
7 unsigned MemoryGameCard : : num <strong>in</strong>stances = 0;<br />
Wie bereits erwähnt, muss der static Member nicht nur deklariert, sondern<br />
auch def<strong>in</strong>iert werden. Dies geschieht, wie zu erwarten, <strong>in</strong>dem man e<strong>in</strong>fach<br />
die Variable mit dem entsprechenden Scope versieht und damit die Def<strong>in</strong>ition<br />
vornimmt. Hierbei wird allerd<strong>in</strong>gs das Keyword static nicht mehr geschrieben,<br />
denn der Compiler weiß bereits aus der Deklaration um die Natur des<br />
Members und static würde e<strong>in</strong>er weiteren Deklaration entsprechen. Wir<br />
wollen allerd<strong>in</strong>gs e<strong>in</strong>e Def<strong>in</strong>ition erreichen. Dass <strong>in</strong> diesem Statement e<strong>in</strong>e<br />
explizite Initialisierung auf 0 vorgenommen wird, soll nur demonstrieren, wie<br />
man e<strong>in</strong>e Initialisierung auf e<strong>in</strong>en speziellen Wert erreicht. In diesem Fall
194 9. Klassen <strong>in</strong> <strong>C++</strong><br />
ist sie nicht notwendig, da der Compiler bei Fehlen derselben implizit e<strong>in</strong>e<br />
Initialisierung auf 0 e<strong>in</strong>setzt, wie bereits aus Abschnitt 2.2 bekannt ist.<br />
Um nun tatsächlich die Anzahl der vorhandenen Instanzen zu zählen, werden<br />
Konstruktor und Destruktor entsprechend modifiziert. Wir wissen bereits,<br />
dass bei Erstellen e<strong>in</strong>er neuen Instanz gesichert der Konstruktor aufgerufen<br />
wird. Also kommt ihm die Aufgabe zu, den Wert von num_<strong>in</strong>stances_<br />
zu <strong>in</strong>krementieren, wie man <strong>in</strong> der Folge <strong>in</strong> Zeile 17 sieht:<br />
15 MemoryGameCard : : MemoryGameCard( const char ∗ symbol )<br />
16 {<br />
17 num <strong>in</strong>stances ++;<br />
18 i f ( ! symbol )<br />
19 {<br />
20 symbol = 0;<br />
21 return ;<br />
22 }<br />
23 symbol = new char [ s t r l e n ( symbol ) + 1 ] ;<br />
24 strcpy ( symbol , symbol ) ;<br />
25 }<br />
Wir wissen auch, dass beim Zerstören e<strong>in</strong>es Objekts der Destruktor gesichert<br />
aufgerufen wird. Dementsprechend kommt ihm nun die Aufgabe zu, den<br />
Instanzenzähler zu dekrementieren, wie man <strong>in</strong> der Folge <strong>in</strong> Zeile 35 sieht:<br />
31 MemoryGameCard : : ˜ MemoryGameCard( )<br />
32 {<br />
33 i f ( symbol )<br />
34 delete [ ] symbol ;<br />
35 num <strong>in</strong>stances −−;<br />
36 }<br />
Wie bereits besprochen, wollen wir auch auf die Anzahl der Instanzen zugreifen<br />
können, ohne dass wir direkt e<strong>in</strong> Objekt vom Typ MemoryGameCard<br />
<strong>in</strong> der Hand haben. Dazu haben wir e<strong>in</strong>e static Methode deklariert. Die<br />
Def<strong>in</strong>ition derselben im .cpp File sieht dann aus wie folgt:<br />
73 unsigned MemoryGameCard : : getNumInstances ( )<br />
74 {<br />
75 return ( num <strong>in</strong>stances ) ;<br />
76 }<br />
Man kann leicht erkennen, dass für static Methoden dasselbe gilt, wie für<br />
static Variablen: Das Keyword static wird bei der Def<strong>in</strong>ition nicht mehr<br />
geschrieben.<br />
Wie man unsere modifizierte Klasse verwenden kann, zeigt das folgende<br />
Demoprogramm (memory_game_card_v2_test.cpp):<br />
1 // memory game card v2 test . cpp − t e s t program f o r MemoryGameCard<br />
2<br />
3 #<strong>in</strong>clude
4 #<strong>in</strong>clude ”memory game card v2 . h”<br />
5<br />
6 us<strong>in</strong>g std : : cout ;<br />
7 us<strong>in</strong>g std : : endl ;<br />
8<br />
9 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
10 {<br />
11 MemoryGameCard t e s t c a r d ( ”Symbol A” ) ;<br />
12<br />
13 cout
196 9. Klassen <strong>in</strong> <strong>C++</strong><br />
number o f cards ( with : : operator ) : 1<br />
number o f cards ( c a l l e d on n e x t t e s t c a r d ) : 2<br />
number o f cards ( c a l l e d on t e s t c a r d ) : 2<br />
Der Vollständigkeit halber möchte ich noch erwähnen, dass das entsprechende<br />
Makefile zum Erstellen des Programms auf der beiliegenden CD-ROM<br />
mitgeliefert wird und MemoryGameCardTestV2Makefile heißt.<br />
Mit diesem Beispiel wurden nun e<strong>in</strong>mal <strong>in</strong> e<strong>in</strong>em ersten Durchgang die<br />
Grundmechanismen der Deklaration, Def<strong>in</strong>ition und Verwendung von e<strong>in</strong>fachen<br />
Klassen erklärt. Bevor wir zu weiteren Features, wie z.B. Operator<br />
Overload<strong>in</strong>g kommen, möchte ich das Bild im S<strong>in</strong>ne der Abhandlungen aus<br />
Kapitel 8 abrunden und das Pr<strong>in</strong>zip der Ableitung näher erläutern. Jedoch<br />
möchte ich für alle C ++ Neul<strong>in</strong>gen an dieser Stelle die Empfehlung aussprechen,<br />
das bisher Gelernte durch Ausprobieren an kle<strong>in</strong>en Beispielen wirklich<br />
zu ver<strong>in</strong>nerlichen. Ansonsten ist die Gefahr sehr groß, sich <strong>in</strong> der Folge <strong>in</strong><br />
Details zu verlieren und den Überblick nicht mehr bewahren zu können.<br />
Der Phantasie, was man ausprobieren könnte, s<strong>in</strong>d ke<strong>in</strong>e Grenzen gesetzt.<br />
Z.B. könnte man sich an e<strong>in</strong>er kle<strong>in</strong>en Klasse zum Speichern e<strong>in</strong>es Datums,<br />
e<strong>in</strong>er allgeme<strong>in</strong>en Array Klasse, e<strong>in</strong>er kle<strong>in</strong>en Str<strong>in</strong>g Klasse, die die so fehleranfällige<br />
direkte Verwendung des char * kapselt oder an mannigfaltigen<br />
andere kle<strong>in</strong>en Helferchen versuchen.<br />
Vorsicht Falle: Bei aller E<strong>in</strong>dr<strong>in</strong>glichkeit, mit der ich Leser zum Spielen und<br />
Probieren anregen möchte, soll e<strong>in</strong>e große Gefahr nicht unerwähnt bleiben:<br />
Es fehlt mit dem bisher vermittelten Wissen noch sehr viel und sehr wichtiges<br />
Handwerkszeug, das man für e<strong>in</strong>e “echte” und saubere OO Entwicklung <strong>in</strong><br />
C ++ braucht.<br />
Ke<strong>in</strong>esfalls also sollen sich Leser nun nach dem Motto “Ich weiß, wie<br />
das funktioniert” bereits jetzt an Utilities heranmachen, deren E<strong>in</strong>satz <strong>in</strong><br />
auslieferbaren Produkten geplant ist. Mit dem bisher Gelernten geht e<strong>in</strong><br />
solches Unterfangen unter Garantie schief!<br />
9.3 Abgeleitete Klassen<br />
Wie <strong>in</strong> Kapitel 8 besprochen, kann man Klassen von anderen Klassen ableiten<br />
und erbt damit ihre Eigenschaften. Dieser Mechanismus hilft, um<br />
entsprechend der Natur der <strong>in</strong> Software modellierten Welt Klassifikationsund<br />
Abstraktionsebenen e<strong>in</strong>zuziehen. Bleiben wir bei unserem begonnenen<br />
Memory Spiel und werfen e<strong>in</strong>en Blick auf die dortigen Gegebenheiten:<br />
• Karten werden nicht nur bei Memory verwendet, es gibt mannigfaltige<br />
Spiele, bei denen man es mit Karten zu tun hat.<br />
• Je nach Spiel haben die Karten verschiedene Eigenschaften. Grundsätzlich<br />
liegt allerd<strong>in</strong>gs e<strong>in</strong>es <strong>in</strong> der Natur von Karten: Sie haben e<strong>in</strong>e Vorder- und<br />
e<strong>in</strong>e Rückseite, wie diese auch immer beschaffen s<strong>in</strong>d.
9.3 Abgeleitete Klassen 197<br />
• Man kann naturgemäß Karten auf die Vorderseite oder auf die Rückseite<br />
legen und man kann sie auch umdrehen. Was sich hierbei verändert, ist<br />
e<strong>in</strong>fach die Seite, die sie zeigen. Aus Gründen der E<strong>in</strong>fachheit ignoriere<br />
ich hier e<strong>in</strong>mal, dass man auch Kartenhäuser bauen kann und damit beide<br />
Seiten sieht :-).<br />
• Im Fall unseres Memory Spiels wissen wir, dass wir e<strong>in</strong>e spezielle Art von<br />
Karten verwenden, nämlich solche, die auf der Vorderseite e<strong>in</strong> Symbol tragen<br />
und die alle gleich aussehen, wenn man die Rückseite betrachtet.<br />
Es wäre also nett, wenn man das folgende wirklichkeitsnahe Modell <strong>in</strong> Software<br />
gießen könnte:<br />
• Es gibt Karten.<br />
• Karten kann man h<strong>in</strong>legen und zwar sowohl auf die Vorder- als auch auf<br />
die Rückseite.<br />
• Was man von den Karten zu sehen bekommt, hängt von der Seite ab, auf<br />
der sie liegen.<br />
• Man kann Karten umdrehen.<br />
• Es gibt für das Memory Spiel spezielle Karten.<br />
• Die speziellen Karten haben alle Eigenschaften der allgeme<strong>in</strong>en Karten,<br />
wie sie zuerst beschrieben wurden.<br />
• Die speziellen Karten haben auf der Vorderseite e<strong>in</strong> Symbol.<br />
• Die speziellen Karten haben alle e<strong>in</strong> und dieselbe Rückseite (dass das mit<br />
derselben Rückseite besser von außen bestimmt wird, als zu e<strong>in</strong>er Klasseneigenschaft<br />
zu werden, ist e<strong>in</strong>e eigene Geschichte :-)).<br />
Nach den Pr<strong>in</strong>zipien aus Kapitel 8 sieht das Modell also folgendermaßen aus:<br />
• Es gibt e<strong>in</strong>e Klasse Karten mit den zuvor erwähnten Eigenschaften.<br />
• Es gibt e<strong>in</strong>e Klasse Memory Karten. Diese s<strong>in</strong>d Karten (=IS-A Relation)<br />
mit besonderen Zusatzeigenschaften.<br />
Was liegt also näher, als e<strong>in</strong>e allgeme<strong>in</strong>e Klasse Karte zu schreiben und danach<br />
e<strong>in</strong>e spezielle Klasse Memory Karte zu implementieren, die von dieser<br />
allgeme<strong>in</strong>en Klasse abgeleitet ist? In dieser speziellen Klasse f<strong>in</strong>det dann die<br />
Memory spezifische Funktionalität Platz, die zuvor erwähnt wurde. Sehen<br />
wir uns e<strong>in</strong>e solche Art der Implementation also e<strong>in</strong>fach e<strong>in</strong>mal <strong>in</strong> C ++ an,<br />
um herauszuf<strong>in</strong>den, wie diese Modellierung von der Sprache unterstützt wird.<br />
Unser Startpunkt ist die Deklaration der Basisklasse (game_card.h):<br />
1 // game card . h − d e c l a r a t i o n of a general card f o r games<br />
2<br />
3 #ifndef game card h<br />
4 #def<strong>in</strong>e game card h<br />
5<br />
6 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
7 /∗<br />
8 ∗ GameCard<br />
9 ∗
198 9. Klassen <strong>in</strong> <strong>C++</strong><br />
10 ∗ a general c l a s s f o r a card f o r games<br />
11 ∗<br />
12 ∗/<br />
13<br />
14 class GameCard<br />
15 {<br />
16 protected :<br />
17 unsigned v i s i b l e s i d e ;<br />
18 public :<br />
19 // constants f o r the v i s i b l e s i d e<br />
20 static const unsigned FRONT SIDE = 0 x01 ;<br />
21 static const unsigned BACK SIDE = 0x02 ;<br />
22<br />
23 GameCard(unsigned v i s i b l e s i d e ) ;<br />
24 ˜GameCard ( ) ;<br />
25<br />
26 void turnCard ( ) ;<br />
27 void putFrontSideUp ( ) ;<br />
28 void putBackSideUp ( ) ;<br />
29 unsigned g e t V i s i b l e S i d e ( ) ;<br />
30 const char ∗ getDisplayRep ( ) ;<br />
31 } ;<br />
32<br />
33<br />
34 #endif // game card h<br />
Die e<strong>in</strong>zige Neuigkeit, die hier zu f<strong>in</strong>den ist, ist die Deklaration von Konstanten<br />
<strong>in</strong> den Zeilen 20 und 21. Diese werden als static const Members deklariert.<br />
So weit wäre das alles ja noch nicht besonders verwunderlich. Warum<br />
aber wird ihnen hier gleich ihr Wert zugewiesen, wo es sich doch nur um e<strong>in</strong>e<br />
Deklaration handelt und nicht um e<strong>in</strong>e Def<strong>in</strong>ition? Diese Art der Initialisierung<br />
funktioniert, wenn es sich um static const Members, also praktisch<br />
um re<strong>in</strong>e Konstanten handelt, denn bei diesen ist klar, dass sich deren Wert<br />
ja niemals ändern darf. Damit hat der Compiler sogar die Möglichkeit, direkt<br />
die entsprechenden Werte <strong>in</strong> den Code e<strong>in</strong>zusetzen, anstatt immer über<br />
e<strong>in</strong>en Speicherzugriff zu arbeiten. Wie wir <strong>in</strong> der Folge sehen werden, darf<br />
aber trotzdem nicht auf die entsprechende Def<strong>in</strong>ition im zugehörigen .cpp<br />
File vergessen werden, sonst ist der L<strong>in</strong>ker gar nicht glücklich mit dem Programm,<br />
da er die entsprechenden Referenzen nicht auflösen kann. Diese direkte<br />
Wertvergabe im Header ist ausschließlich für static const Members<br />
zulässig. Sollte e<strong>in</strong> Member entweder nicht static oder nicht const oder<br />
beides nicht se<strong>in</strong>, dann führt e<strong>in</strong>e solche Initialisierung zu e<strong>in</strong>em Compilerfehler,<br />
weil dieser dann den Member nicht mehr als echte, unveränderbare<br />
Konstante betrachtet. In solchen Fällen muss die Initialisierung wie gewohnt<br />
im Rahmen der Def<strong>in</strong>ition stattf<strong>in</strong>den.<br />
Diese Art der Def<strong>in</strong>ition von Konstanten, die Members e<strong>in</strong>er Klasse s<strong>in</strong>d,<br />
ist die sauberste Möglichkeit, Namenskonflikte zwischen verschiedenen Konstanten<br />
<strong>in</strong> verschiedenen Klassen zu verh<strong>in</strong>dern. Ansprechbar s<strong>in</strong>d solche<br />
Konstanten dann wie zu erwarten über direkte Angabe der Klasse mittels<br />
Scope Operator, also z.B. als<br />
GameCard::FRONT_SIDE
9.3 Abgeleitete Klassen 199<br />
Die deklarierten Methoden der Klasse repräsentieren, was man mit e<strong>in</strong>er<br />
Karte machen kann. Man kann sie umdrehen, man kann die Vorderseite<br />
oder die Rückseite explizit nach oben drehen, man kann erfahren, welche die<br />
gerade sichtbare Seite ist (und bekommt e<strong>in</strong>e der vordef<strong>in</strong>ierten Konstanten<br />
geliefert) und man kann um e<strong>in</strong>en Str<strong>in</strong>g zur Darstellung bitten. Der Str<strong>in</strong>g<br />
zur Darstellung wird natürlich entweder die Repräsentation der Vorder- oder<br />
der Rückseite se<strong>in</strong>, je nachdem, welche gerade nach oben zeigt.<br />
Die Implementation von GameCard sieht dann folgendermaßen aus<br />
(game_card.cpp):<br />
1 // game card . cpp − implementation o f GameCard<br />
2<br />
3 #<strong>in</strong>clude ”game card . h”<br />
4<br />
5 // s t a t i c const member d e f i n i t i o n<br />
6 const unsigned GameCard : : FRONT SIDE;<br />
7 const unsigned GameCard : : BACK SIDE;<br />
8<br />
9 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
10 /∗ constructor<br />
11 ∗/<br />
12<br />
13 GameCard : : GameCard(unsigned v i s i b l e s i d e )<br />
14 {<br />
15 // no good p r a c t i c e to a s s i g n without check , but f o r demo . . .<br />
16 v i s i b l e s i d e = v i s i b l e s i d e ;<br />
17 }<br />
18<br />
19 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
20 /∗ d e s t r u c t o r<br />
21 ∗/<br />
22<br />
23 GameCard : : ˜ GameCard( )<br />
24 {<br />
25 // noth<strong>in</strong>g to be done here<br />
26 }<br />
27<br />
28 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
29 /∗ turns the card around<br />
30 ∗/<br />
31<br />
32 void GameCard : : turnCard ( )<br />
33 {<br />
34 v i s i b l e s i d e = ( v i s i b l e s i d e == FRONT SIDE) ?<br />
35 BACK SIDE : FRONT SIDE;<br />
36 }<br />
37<br />
38 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
39 /∗ l a y s the card down with the f r o n t s i d e up<br />
40 ∗/<br />
41<br />
42 void GameCard : : putFrontSideUp ( )<br />
43 {<br />
44 v i s i b l e s i d e = FRONT SIDE;<br />
45 }<br />
46<br />
47 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
48 /∗ l a y s the card down with the back s i d e up<br />
49 ∗/<br />
50<br />
51 void GameCard : : putBackSideUp ( )
200 9. Klassen <strong>in</strong> <strong>C++</strong><br />
52 {<br />
53 v i s i b l e s i d e = BACK SIDE;<br />
54 }<br />
55<br />
56 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
57 /∗ returns the v i s i b l e (=up ) s i d e o f the card<br />
58 ∗/<br />
59<br />
60 unsigned GameCard : : g e t V i s i b l e S i d e ( )<br />
61 {<br />
62 return ( v i s i b l e s i d e ) ;<br />
63 }<br />
64<br />
65 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
66 /∗<br />
67 ∗ @return the d i s p l a y r e p r e s e n t a t i o n o f the card<br />
68 ∗/<br />
69<br />
70 const char ∗GameCard : : getDisplayRep ( )<br />
71 {<br />
72 // very bad p r a c t i c e , but the way to handle t h i s c o r r e c t l y<br />
73 // i s not known yet !<br />
74 return ( 0 ) ;<br />
75 }<br />
In den Zeilen 6 und 7 sieht man, dass die static const Members der Klasse<br />
sehr wohl def<strong>in</strong>iert werden müssen, um den L<strong>in</strong>ker zu beruhigen, dass aber<br />
hierbei ke<strong>in</strong>e explizite Initialisierung auf bestimmte Werte mehr stattf<strong>in</strong>det.<br />
Die Initialwerte s<strong>in</strong>d ja bereits im Header vorgegeben. Würde man sie hier<br />
nochmals explizit h<strong>in</strong>schreiben, dann würde sich der Compiler mittels Fehlermeldung<br />
beschweren, dass man ihn doch bitte <strong>in</strong> Ruhe lassen soll, denn er<br />
weiß ja sowieso schon, welche Werte die Konstanten zu bekommen haben.<br />
Die Implementation der Methode getDisplayRep <strong>in</strong> den Zeilen 70–75<br />
retourniert e<strong>in</strong>en 0-Po<strong>in</strong>ter, denn die Basisklasse besitzt ke<strong>in</strong>e Darstellung<br />
und wüsste dementsprechend auch nicht, was sie darstellen sollte. Diese Art<br />
der Implementation ist absolut nicht für die Praxis geeignet und im Pr<strong>in</strong>zip<br />
e<strong>in</strong> böser Hack, jedoch ist bisher der Mechanismus noch nicht bekannt, durch<br />
den dies vermieden werden kann.<br />
Bisher haben wir nicht viel Neues kennen gelernt, vor allem noch immer<br />
nicht, wie man eigentlich e<strong>in</strong>e Ableitung von e<strong>in</strong>er Klasse <strong>in</strong> C ++ realisiert.<br />
Das ändert sich jetzt mit der speziellen Klasse von Karten für das Memory<br />
Spiel, die von der Basis GameCard abgeleitet ist. Deren Deklaration sieht<br />
folgendermaßen aus (memory_game_card_v3.h):<br />
1 // memory game card v3 . h − derived implementation o f MemoryGameCard<br />
2<br />
3 #ifndef memory game card v3 h<br />
4 #def<strong>in</strong>e memory game card v3 h<br />
5<br />
6 #<strong>in</strong>clude ”game card . h”<br />
7<br />
8 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
9 /∗<br />
10 ∗ MemoryGameCard<br />
11 ∗<br />
12 ∗ model of a card as used <strong>in</strong> the game ”memory”
13 ∗<br />
14 ∗/<br />
15<br />
16 class MemoryGameCard : public GameCard<br />
17 {<br />
18 protected :<br />
19 char ∗ front symbol ;<br />
20 char ∗ back symbol ;<br />
21 public :<br />
22 MemoryGameCard( const char ∗ front symbol ,<br />
23 const char ∗ back symbol = 0 ) ;<br />
24 ˜MemoryGameCard ( ) ;<br />
25<br />
9.3 Abgeleitete Klassen 201<br />
26 const char ∗ getDisplayRep ( ) ; // o v e r r i d e o f base c l a s s method<br />
27 } ;<br />
28<br />
29 #endif // memory game card v3 h<br />
In Zeile 16 zeigt sich des Pudels Kern, wie e<strong>in</strong>e Ableitung zu formulieren ist:<br />
Man schreibt e<strong>in</strong>fach e<strong>in</strong>en Doppelpunkt h<strong>in</strong>ter den Klassennamen der neu<br />
deklarierten Klasse und h<strong>in</strong>ter dem Doppelpunkt führt man die Basisklasse<br />
an, also <strong>in</strong> unserem Fall GameCard. Und wofür steht der Access Specifier<br />
public nun <strong>in</strong> diesem Kontext? Ganz e<strong>in</strong>fach: Wenn man ableitet, dann spezifiziert<br />
man auch, ob Entwickler, die die Klasse verwenden, die Basisklasse<br />
überhaupt explizit zu Gesicht bekommen oder nicht. Es wurde bereits diskutiert,<br />
dass Ableiten bedeutet, dass man alle Eigenschaften der Basisklasse<br />
erbt. Unsere Klasse MemoryGameCard erbt also <strong>in</strong> diesem Fall alle Methoden<br />
und Membervariablen und ist damit automatisch e<strong>in</strong>e vollständige GameCard.<br />
Wenn man, so wie hier, public ableitet, dann werden auch alle Methoden<br />
der Basisklasse mit ihren dort def<strong>in</strong>ierten Sichtbarkeiten nach außen weitergereicht.<br />
Es ist also z.B. möglich, die Methode turnCard auf e<strong>in</strong>em Objekt vom<br />
Typ MemoryGameCard von außen aufzurufen. Würde die Ableitung nun nicht<br />
public, sondern private erfolgen, dann g<strong>in</strong>ge dies nicht mehr. Denn damit<br />
ist es zwar <strong>in</strong>nerhalb der Klasse MemoryGameCard möglich, diese Methoden<br />
aufzurufen, mitnichten aber von außen. Natürlich ist nicht nur public und<br />
private möglich, man kann ebenso protected ableiten. Erwartungsgemäß<br />
bekommen dann die weiteren abgeleiteten Klassen von dieser Klasse den vollen<br />
Zugriff auf die Basis, aber niemand außerhalb der Ableitungshierarchie.<br />
Vorsicht Falle: Als default Wert für den Access der Basisklasse bei e<strong>in</strong>er<br />
Ableitung wird von C ++ private angenommen, falls nichts anderes explizit<br />
angegeben wurde. Oft unterläuft Neul<strong>in</strong>gen der Fehler, den Access Specifier<br />
zu vergessen. Dann beg<strong>in</strong>nt das Rätselraten, warum denn e<strong>in</strong>e geerbte Methode<br />
nicht aufgerufen werden kann, sondern dies vom Compiler bemängelt<br />
wird. In solchen Fällen hilft e<strong>in</strong> schneller Blick auf die Deklaration der Klasse,<br />
ob man auch mit dem richtigen Access Specifier abgeleitet hat.<br />
Wie bereits <strong>in</strong> Kapitel 8 erwähnt, leitet man ab, um die Natur von Objekten<br />
zu erben und kann dann besondere zusätzliche Features <strong>in</strong> der abgeleiteten<br />
Klasse dazu implementieren. In unserem Fall s<strong>in</strong>d das die Vorder- und
202 9. Klassen <strong>in</strong> <strong>C++</strong><br />
die Rückseite der Karte <strong>in</strong> den entsprechenden Members <strong>in</strong> den Zeilen 19–20.<br />
Damit aber nicht genug: Unsere besondere Karte implementiert auch e<strong>in</strong>en<br />
besonderen Konstruktor, der andere Parameter nimmt als der, der von der<br />
Basis vorgegeben ist. In unserem Fall wollen wir ja sowohl die Darstellung der<br />
Vorder- als auch die Rückseite der Karte von außen vorgegeben bekommen.<br />
Weil das noch nicht alles war, was wir erreichen wollen, def<strong>in</strong>ieren wir <strong>in</strong><br />
der abgeleiteten Klasse noch e<strong>in</strong>e Methode getDisplayRep. Diese Deklaration<br />
allerd<strong>in</strong>gs bewirkt etwas, was wir bisher noch nicht kennen gelernt haben:<br />
Wenn man sich die Deklaration von getDisplayRep <strong>in</strong> MemoryGameCard ansieht<br />
und mit der Deklaration <strong>in</strong> GameCard vergleicht, dann fällt auf, dass<br />
diese beiden Deklarationen tatsächlich vollkommen identisch s<strong>in</strong>d! Nun haben<br />
wir aber beim Overload<strong>in</strong>g gesagt, dass so etwas gar nicht se<strong>in</strong> darf, weil<br />
der Compiler dann nicht ause<strong>in</strong>ander halten kann, welche Implementation er<br />
nun e<strong>in</strong>setzen soll!<br />
Um die Verwirrung perfekt zu machen: Im Falle von Ableitungen darf<br />
es doch se<strong>in</strong> und es nennt sich Overrid<strong>in</strong>g. Dies bedeutet, dass e<strong>in</strong>e genau<br />
äquivalente Deklaration (und natürlich auch Def<strong>in</strong>ition) <strong>in</strong> e<strong>in</strong>er abgeleiteten<br />
Klasse die ursprüngliche Def<strong>in</strong>ition, die der Basisklasse zugrunde lag,<br />
versteckt. Ruft man also auf der Klasse MemoryGameCard die Methode<br />
getDisplayRep auf, so wird die Def<strong>in</strong>ition aus der abgeleiteten Klasse genommen<br />
und nicht die aus der Basisklasse GameCard, da diese durch die<br />
Neudeklaration und Def<strong>in</strong>ition zugedeckt wurde. Um e<strong>in</strong> Overrid<strong>in</strong>g zu erreichen,<br />
muss die Deklaration genau gleich aussehen, wie die ursprüngliche,<br />
ansonsten kommt es nur zu e<strong>in</strong>em Overload<strong>in</strong>g, es würden also beide Methoden<br />
friedlich nebene<strong>in</strong>ander existieren.<br />
Dasselbe, was für Methoden gilt, gilt natürlich auch für Membervariablen.<br />
Auch hier kann man e<strong>in</strong>e Variable <strong>in</strong> e<strong>in</strong>er abgeleiteten Klasse def<strong>in</strong>ieren, die<br />
denselben Namen hat wie die <strong>in</strong> e<strong>in</strong>er ihrer Basisklassen (direkt oder weiter<br />
oben <strong>in</strong> der Ableitungshierarchie). Damit wird, wie zu erwarten, die Variable<br />
der Basisklasse versteckt, existiert aber parallel zur hier def<strong>in</strong>ierten.<br />
Vorsicht Falle: In der Literatur werden leider nur allzu oft die Begriffe<br />
Overload<strong>in</strong>g und Overrid<strong>in</strong>g verwechselt oder sogar austauschbar verwendet.<br />
Dies ist aber absolut falsch und sorgt leider immer wieder für große<br />
Verwirrung und Missverständnisse.<br />
Overload<strong>in</strong>g bezeichnet das gleichzeitige Existieren mehrerer Funktionen<br />
oder Methoden nebene<strong>in</strong>ander, die verschiedene Parametersätze haben.<br />
Overrid<strong>in</strong>g bezeichnet das Verstecken e<strong>in</strong>er Methode bzw. Variable <strong>in</strong><br />
e<strong>in</strong>er Ableitungshierarchie durch e<strong>in</strong>e Methode bzw. Variable mit genau derselben<br />
Deklaration. Das bedeutet, dass e<strong>in</strong> Overrid<strong>in</strong>g E<strong>in</strong>fluss auf den Scope<br />
von Methoden und Variablen nimmt. Wie wir später noch sehen werden, ist<br />
trotz Overrid<strong>in</strong>g e<strong>in</strong> Zugriff auf die dadurch versteckten Def<strong>in</strong>itionen mittels<br />
Scope Operator möglich.
9.3 Abgeleitete Klassen 203<br />
Die Implementation der e<strong>in</strong>zelnen Methoden der Klasse MemoryGameCard<br />
sieht folgendermaßen aus (memory_game_card_v3.cpp):<br />
1 // memory game card v3 . cpp − Version 3 d e f i n i t i o n s o f MemoryGameCard<br />
2<br />
3 #<strong>in</strong>clude ”memory game card v3 . h”<br />
4<br />
5 #<strong>in</strong>clude < c s t r i n g><br />
6<br />
7 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
8 /∗ constructor of MemoryGameCard<br />
9 ∗<br />
10 ∗ @param front symbol the f r o n t symbol o f t h i s card<br />
11 ∗ @param back symbol the back symbol o f t h i s card<br />
12 ∗/<br />
13<br />
14 MemoryGameCard : : MemoryGameCard( const char ∗ front symbol ,<br />
15 const char ∗ back symbol ) :<br />
16 GameCard(BACK SIDE)<br />
17 {<br />
18 i f ( ! front symbol )<br />
19 front symbol = 0;<br />
20 else<br />
21 {<br />
22 front symbol = new char [ s t r l e n ( front symbol ) + 1 ] ;<br />
23 strcpy ( front symbol , front symbol ) ;<br />
24 }<br />
25<br />
26 i f ( ! back symbol )<br />
27 back symbol = 0;<br />
28 else<br />
29 {<br />
30 back symbol = new char [ s t r l e n ( back symbol ) + 1 ] ;<br />
31 strcpy ( back symbol , back symbol ) ;<br />
32 }<br />
33 }<br />
34<br />
35 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
36 /∗ d e s t r u c t o r of MemoryGameCard<br />
37 ∗/<br />
38<br />
39 MemoryGameCard : : ˜ MemoryGameCard( )<br />
40 {<br />
41 i f ( front symbol )<br />
42 delete [ ] front symbol ;<br />
43 i f ( back symbol )<br />
44 delete [ ] back symbol ;<br />
45 }<br />
46<br />
47 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
48 /∗<br />
49 ∗ @return the symbol that t h i s card r e p r e s e n t s<br />
50 ∗/<br />
51<br />
52 const char ∗MemoryGameCard : : getDisplayRep ( )<br />
53 {<br />
54 return ( ( v i s i b l e s i d e == FRONT SIDE) ?<br />
55 front symbol : back symbol ) ;<br />
56 }<br />
In den Zeilen 14–16 stoßen wir hier auf e<strong>in</strong> Konstrukt, das wir bisher noch<br />
nicht kennen gelernt haben: e<strong>in</strong> Doppelpunkt gefolgt von e<strong>in</strong>em sehr abstrusen<br />
Aufruf, den es eigentlich gar nicht geben kann – oder doch? Überlegen
204 9. Klassen <strong>in</strong> <strong>C++</strong><br />
wir kurz: Unsere abgeleitete Klasse hat e<strong>in</strong>en Konstruktor mit 2 Parametern,<br />
die Basisklasse hat nur e<strong>in</strong>en mit e<strong>in</strong>em Parameter. Wie also soll nun<br />
der Compiler wissen, wie er den Konstruktor der Basisklasse aufruft? Genau<br />
das ist es, was wir mit diesem Konstrukt erreichen, nämlich dass wir explizit<br />
den Aufruf e<strong>in</strong>es bestimmten Konstruktors mit e<strong>in</strong>em bestimmten von uns<br />
vorgegebenen Parameter veranlassen. In diesem Fall bewirken wir den Aufruf<br />
des Konstruktors auf die Art, dass die Rückseite der Karte nach dem<br />
Konstruieren sichtbar ist.<br />
Die Regel, die der Compiler beim impliziten E<strong>in</strong>setzen von Konstruktoraufrufen<br />
befolgt, ist e<strong>in</strong>fach: Er sucht <strong>in</strong> der Basisklasse immer nach e<strong>in</strong>em<br />
Konstruktor mit genau demselben Parametersatz, wie ihn der Konstruktor<br />
der abgeleiteten Klasse besitzt. Gibt es e<strong>in</strong>en solchen, kann der Compiler<br />
den impliziten Aufruf e<strong>in</strong>setzen, wenn nicht, dann führt dies zu e<strong>in</strong>em Fehler.<br />
Dann s<strong>in</strong>d wir also zum expliziten Aufruf gezwungen.<br />
Vorsicht Falle: Neul<strong>in</strong>ge und Entwickler, die aus der Java-Welt kommen,<br />
sitzen oft dem Irrtum auf, dass der Compiler implizit immer nur den default<br />
Konstruktor aufruft, falls e<strong>in</strong> solcher existiert. Dies ist falsch! E<strong>in</strong>e solche<br />
Fehlannahme kann natürlich zu ganz <strong>in</strong>teressanten Fehlern führen, die man<br />
lange sucht :-).<br />
Um hier nicht zu stark vom Thema abzukommen, möchte ich e<strong>in</strong>e genauere<br />
Diskussion über den Aufruf von Konstruktoren und Destruktoren auf<br />
später verlegen, ebenso wie e<strong>in</strong>e Abhandlung, was man nach diesem om<strong>in</strong>ösen<br />
Doppelpunkt noch so alles tun kann. Im Augenblick genügt es, zu wissen,<br />
dass man auf diese Art den Compiler zufrieden stellen kann und er den richtigen<br />
Konstruktor mit unseren gewünschten Parametern aufrufen wird.<br />
Vorsicht Falle: E<strong>in</strong> oftmaliger Fehler von Neul<strong>in</strong>gen <strong>in</strong> C ++ besteht dar<strong>in</strong>,<br />
dass der explizite H<strong>in</strong>weis auf den aufzurufenden Konstruktor fehlt, die<br />
Basisklasse aber ke<strong>in</strong>en entsprechenden Konstruktor zur Verfügung stellt,<br />
der implizit e<strong>in</strong>gesetzt werden könnte. Darüber beschwert sich natürlich der<br />
Compiler und will das Programm partout nicht fertig übersetzen. E<strong>in</strong> kurzer<br />
Blick auf die Implementation der Konstruktoren schafft hier Klarheit. Abhilfe<br />
<strong>in</strong> Form e<strong>in</strong>es expliziten H<strong>in</strong>weises an den Compiler lässt diesen dann <strong>in</strong><br />
puncto Fehlermeldungen weniger lautstark ans Werk gehen :-).<br />
Interessant ist auch noch das Testprogramm, denn dar<strong>in</strong> sieht man, wie<br />
man eigentlich überhaupt mit abgeleiteten Klassen umgeht<br />
(memory_game_card_v3_test.cpp):<br />
1 // memory game card v3 test . cpp − t e s t program f o r MemoryGameCard<br />
2<br />
3 #<strong>in</strong>clude <br />
4 #<strong>in</strong>clude ”memory game card v3 . h”<br />
5<br />
6 us<strong>in</strong>g std : : cout ;
7 us<strong>in</strong>g std : : endl ;<br />
8<br />
9 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
10 {<br />
11 MemoryGameCard t e s t c a r d ( ”Symbol A” , ”back” ) ;<br />
12<br />
9.3 Abgeleitete Klassen 205<br />
13 cout
206 9. Klassen <strong>in</strong> <strong>C++</strong><br />
Die s<strong>in</strong>nvolle und richtige OO Vorgangsweise wäre also, e<strong>in</strong>e Basisklasse<br />
Karte zu def<strong>in</strong>ieren, die alle Eigenschaften e<strong>in</strong>er Karte <strong>in</strong> sich vere<strong>in</strong>t.<br />
Außerdem def<strong>in</strong>iert man noch e<strong>in</strong>e Basisklasse anzeigbares Objekt, die alle<br />
Eigenschaften anzeigbarer Objekte <strong>in</strong> sich vere<strong>in</strong>t. Die Basis-Karte ist selbst<br />
noch nicht anzeigbar, denn man will ja nicht notwendigerweise voraussetzen,<br />
dass e<strong>in</strong>e Karte immer um jeden Preis angezeigt werden muss. Man denke<br />
sich nur, dass man z.B. e<strong>in</strong> Programm schreiben will, <strong>in</strong> dem zwei Computer<br />
gegene<strong>in</strong>ander e<strong>in</strong> Kartenspiel spielen. Dazu braucht man nicht notwendigerweise<br />
e<strong>in</strong>e Anzeige der Karten.<br />
Versuchen wir uns also an e<strong>in</strong>em Redesign der Karte für das Memory<br />
Spiel zusammen mit den dazugehörigen Basisklassen, um e<strong>in</strong>mal wirklich<br />
zu e<strong>in</strong>er sauberen Trennung zu kommen, wie sie auch <strong>in</strong> der Praxis<br />
geistreicherweise e<strong>in</strong>gesetzt werden soll. Nehmen wir uns zuerst e<strong>in</strong>e Klasse<br />
vor, die e<strong>in</strong>e primitive Version von anzeigbaren Objekten repräsentiert<br />
(displayable_object.h):<br />
1 // d i s p l a y a b l e o b j e c t . h − base c l a s s f o r o b j e c t s that can be<br />
2 // displayed .<br />
3<br />
4 #ifndef d i s p l a y a b l e o b j e c t h<br />
5 #def<strong>in</strong>e d i s p l a y a b l e o b j e c t h<br />
6<br />
7 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
8 /∗<br />
9 ∗ DisplayableObject<br />
10 ∗<br />
11 ∗ base f o r an o bject that i s d i s p l a y a b l e<br />
12 ∗<br />
13 ∗/<br />
14<br />
15 class DisplayableObject<br />
16 {<br />
17 public :<br />
18 const char ∗ getDisplayRep ( ) ;<br />
19 } ;<br />
20<br />
21<br />
22 #endif // d i s p l a y a b l e o b j e c t h<br />
Ich gebe schon zu, dass e<strong>in</strong>e Klasse mit nur e<strong>in</strong>er e<strong>in</strong>zigen Methode, ohne<br />
Daten und ohne besondere Funktionalität, nicht gerade berauschend ist.<br />
Im Normalfall würde diese Klasse auch nicht so aussehen, sondern würde<br />
ziemlich viel besondere Funktionalität enthalten, die e<strong>in</strong> darstellbares Objekt<br />
auszeichnet, z.B., ob e<strong>in</strong> Objekt nun sichtbar oder versteckt ist, die<br />
Bildschirmposition, Verschiebeoperationen, etc. Allerd<strong>in</strong>gs würde dies den<br />
Blick für das Wesentliche hier verschleiern und dementsprechend belassen<br />
wir es dabei. Ebenfalls nur als Gründen der Übersichtlichkeit wurde darauf<br />
verzichtet Konstruktor und Destruktor hier explizit zu implementieren.<br />
Genauso toll wie die Deklaration der Klasse liest sich auch die Def<strong>in</strong>ition<br />
der e<strong>in</strong>zigen Methode (displayable_object.cpp):
9.3 Abgeleitete Klassen 207<br />
1 // d i s p l a y a b l e o b j e c t . cpp − implementation o f DisplayableObject<br />
2<br />
3 #<strong>in</strong>clude ” d i s p l a y a b l e o b j e c t . h”<br />
4<br />
5 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
6 /∗<br />
7 ∗ @return the d i s p l a y rep of the o b j e c t<br />
8 ∗/<br />
9<br />
10 const char ∗ DisplayableObject : : getDisplayRep ( )<br />
11 {<br />
12 // t h i s i s not clean , but the mechanism how to implement<br />
13 // t h i s <strong>in</strong> a clean way i s not known yet .<br />
14 return ( 0 ) ;<br />
15 }<br />
Weil diese Implementation nun wirklich ke<strong>in</strong>e weiteren Kommentare benötigt,<br />
sehen wir uns auch gleich an, wozu unsere Basisklasse GameCard mutiert,<br />
um der hier vollzogenen Auftrennung der Eigenschaften gerecht zu werden<br />
(game_card_v2.h):<br />
1 // game card v2 . h − d e c l a r a t i o n o f a general card f o r games<br />
2<br />
3 #ifndef game card v2 h<br />
4 #def<strong>in</strong>e game card v2 h<br />
5<br />
6 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
7 /∗<br />
8 ∗ GameCard<br />
9 ∗<br />
10 ∗ a general c l a s s f o r a card f o r games<br />
11 ∗<br />
12 ∗/<br />
13<br />
14 class GameCard<br />
15 {<br />
16 protected :<br />
17 unsigned v i s i b l e s i d e ;<br />
18 public :<br />
19 // constants f o r the v i s i b l e s i d e<br />
20 static const unsigned FRONT SIDE = 0 x01 ;<br />
21 static const unsigned BACK SIDE = 0x02 ;<br />
22<br />
23 GameCard(unsigned v i s i b l e s i d e ) ;<br />
24 ˜GameCard ( ) ;<br />
25<br />
26 void turnCard ( ) ;<br />
27 void putFrontSideUp ( ) ;<br />
28 void putBackSideUp ( ) ;<br />
29 unsigned g e t V i s i b l e S i d e ( ) ;<br />
30 } ;<br />
31<br />
32<br />
33 #endif // game card v2 h<br />
Wie sich leicht erkennen lässt, ist die e<strong>in</strong>zige Veränderung gegenüber unserer<br />
ersten Version, dass die Methode getDisplayRep weggefallen ist. Aus diesem<br />
Grund erspare ich auch allen Lesern das E<strong>in</strong>b<strong>in</strong>den der dazugehörigen
208 9. Klassen <strong>in</strong> <strong>C++</strong><br />
Implementation (game_card_v2.cpp), denn diese ist bis auf das Fehlen der<br />
entsprechenden Def<strong>in</strong>ition von getDisplayRep identisch zur Erstversion.<br />
Unsere Klasse MemoryGameCard verändert sich durch das Auftrennen der<br />
beiden semantischen Aspekte <strong>in</strong> zwei Basisklassen zu folgender Deklaration<br />
(memory_game_card_v4.h):<br />
1 // memory game card v4 . h − multiple i n h e r i t a n c e f o r MemoryGameCard<br />
2<br />
3 #ifndef memory game card v4 h<br />
4 #def<strong>in</strong>e memory game card v4 h<br />
5<br />
6 #<strong>in</strong>clude ”game card v2 . h”<br />
7 #<strong>in</strong>clude ” d i s p l a y a b l e o b j e c t . h”<br />
8<br />
9 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
10 /∗<br />
11 ∗ MemoryGameCard<br />
12 ∗<br />
13 ∗ model of a card as used <strong>in</strong> the game ”memory”<br />
14 ∗<br />
15 ∗/<br />
16<br />
17 class MemoryGameCard : public GameCard ,<br />
18 public DisplayableObject<br />
19 {<br />
20 protected :<br />
21 char ∗ front symbol ;<br />
22 char ∗ back symbol ;<br />
23 public :<br />
24 MemoryGameCard( const char ∗ front symbol ,<br />
25 const char ∗ back symbol = 0 ) ;<br />
26 ˜MemoryGameCard ( ) ;<br />
27<br />
28 const char ∗ getDisplayRep ( ) ; // o v e r r i d e o f base c l a s s method<br />
29 } ;<br />
30<br />
31 #endif // memory game card v4 h<br />
Wie sich unschwer erkennen lässt, ist die e<strong>in</strong>zige Veränderung hier, dass<br />
MemoryGameCard nun von zwei Klassen zugleich abgeleitet ist. Dies geschieht<br />
<strong>in</strong>tuitiverweise dadurch, dass man die Ableitungen der beiden Klassen durch<br />
Beistrich getrennt angibt. Die Zeilen 17–18 demonstrieren dies. Man sieht<br />
auch, dass die Access Specifiers für jede e<strong>in</strong>zelne der Ableitungen getrennt<br />
angegeben werden. Dadurch kann man bestimmte Basisklassen nach außen<br />
zugänglich machen und andere nicht.<br />
Durch diese Deklaration ist nun unsere Klasse e<strong>in</strong>e besondere Karte, die<br />
auch noch gleichzeitig e<strong>in</strong> anzeigbares Objekt darstellt. Genau das ist auch<br />
die semantische Bedeutung der Ableitung von mehreren Klassen gleichzeitig:<br />
E<strong>in</strong>e Klasse, die Gebrauch von Mehrfachvererbung macht, ist alles<br />
zusammen, was die e<strong>in</strong>zelnen Basisklassen für sich alle<strong>in</strong> s<strong>in</strong>d. Die abgeleitete<br />
Klasse erbt also alle Members, Methoden wie auch Variablen, die <strong>in</strong><br />
jeder e<strong>in</strong>zelnen der Basisklassen deklariert (und def<strong>in</strong>iert) s<strong>in</strong>d. Die Methode<br />
getDisplayRep ist also <strong>in</strong> unserem Fall e<strong>in</strong> Override der gleichnamigen Me-
9.3 Abgeleitete Klassen 209<br />
thode aus DisplayableObject und nicht, wie <strong>in</strong> unserer ersten Version aus<br />
GameCard.<br />
Die Implementation dieser Version (memory_game_card_v4.cpp) ist im<br />
Pr<strong>in</strong>zip vollkommen äquivalent zur vorherigen Implementation. Die e<strong>in</strong>zige<br />
Ausnahme ist das Inkludieren von memory_game_card_v4.h. Aus diesem<br />
Grund erspare ich mir hier das Abdrucken des Source Codes. Auch das Testprogramm<br />
(memory_game_card_v4_test.cpp) hat sich bis auf e<strong>in</strong> geändertes<br />
Inkludieren nicht verändert. Ebenso wurde auch das dazugehörige Makefile<br />
(MemoryGameCardTestV4Makefile) nur entsprechend adaptiert, um auch<br />
displayable_object.cpp korrekt zu compilieren. Also möchte ich auch mit<br />
diesen Files nicht s<strong>in</strong>nlos Seiten im Buch füllen.<br />
Wie leicht e<strong>in</strong>zusehen ist, ist Mehrfachvererbung e<strong>in</strong>e sehr saubere<br />
OO Möglichkeit, um verschiedene, vone<strong>in</strong>ander unabhängige Eigenschaften<br />
<strong>in</strong> verschiedenen Ableitungshierarchien getrennt vone<strong>in</strong>ander zu modellieren.<br />
Sollte e<strong>in</strong>e Klasse mehrere Eigenschaften <strong>in</strong> sich vere<strong>in</strong>en, dann kann dies<br />
durch multiple Inheritance sehr elegant gelöst werden. E<strong>in</strong>e solchermaßen<br />
abgeleitete Klasse erbt alle Eigenschaften aller verschiedenen Basisklassen.<br />
Jedoch kann es dabei auch zu e<strong>in</strong> paar technischen Problemen kommen,<br />
wenn man nicht aufpasst! Wenden wir uns e<strong>in</strong>mal e<strong>in</strong>em leicht zu lösenden<br />
Problem zu, bevor wir <strong>in</strong> Abschnitt 9.4 zu den harten Brocken kommen: Was<br />
passiert, wenn beide Basisklassen e<strong>in</strong>- und dieselbe Methode implementieren?<br />
Der Begriff e<strong>in</strong>- und dieselbe bezieht sich natürlich auf die vollständige Signatur,<br />
also auch den Parametersatz. Würde der Parametersatz verschieden<br />
se<strong>in</strong>, so würde sich ja e<strong>in</strong>fach e<strong>in</strong>e problemlos aufzulösende Overload<strong>in</strong>g Situation<br />
ergeben. Haben wir also wirklich komplette Gleichheit, welche der<br />
Implementationen wird nun bei e<strong>in</strong>em Aufruf vom Compiler e<strong>in</strong>gesetzt? Wie<br />
sich unschwer erraten lässt, ke<strong>in</strong>e der beiden! Der Compiler wird sich e<strong>in</strong>fach<br />
beschweren, dass er e<strong>in</strong>e Ambiguität im Aufruf entdeckt hat und damit<br />
das weitere Übersetzen verweigern. Das bedeutet also, dass die Entwickler<br />
die Ambiguität per Hand auflösen müssen, um den Compiler zu beruhigen.<br />
Wie dies passiert, sehen wir uns am besten an e<strong>in</strong>em kle<strong>in</strong>en Beispiel an<br />
(method_ambiguity_problem.cpp). Die erste Betrachtung gilt zwei Klassendeklarationen<br />
<strong>in</strong> diesem Progrämmchen:<br />
11 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
12 /∗<br />
13 ∗ DisplayableObject<br />
14 ∗<br />
15 ∗ A simple d i s p l a y a b l e o bject<br />
16 ∗<br />
17 ∗/<br />
18<br />
19 class DisplayableObject<br />
20 {<br />
21 protected :<br />
22 char ∗ s t r i n g r e p ;<br />
23 DisplayableObject ( const char ∗ s t r i n g r e p ) ;<br />
24 public :<br />
25 ˜ DisplayableObject ( ) ;<br />
26 const char ∗ getStr<strong>in</strong>gRep ( ) ;
210 9. Klassen <strong>in</strong> <strong>C++</strong><br />
27 } ;<br />
28<br />
29 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
30 /∗<br />
31 ∗ Pr<strong>in</strong>tableObject<br />
32 ∗<br />
33 ∗ A simple p r i n t a b l e o bject<br />
34 ∗<br />
35 ∗/<br />
36<br />
37 class Pr<strong>in</strong>tableObject<br />
38 {<br />
39 protected :<br />
40 char ∗ s t r i n g r e p ;<br />
41 Pr<strong>in</strong>tableObject ( const char ∗ s t r i n g r e p ) ;<br />
42 public :<br />
43 ˜ Pr<strong>in</strong>tableObject ( ) ;<br />
44 const char ∗ getStr<strong>in</strong>gRep ( ) ;<br />
45 } ;<br />
Abgesehen davon, dass <strong>in</strong> Realität natürlich für anzeigbare sowie für druckbare<br />
Objekte bei weitem mehr vonnöten ist als e<strong>in</strong>e e<strong>in</strong>fache Methode<br />
getStr<strong>in</strong>gRep, ist die Intention h<strong>in</strong>ter diesen beiden Klassen im Pr<strong>in</strong>zip recht<br />
nett: Sie speichern die Repräsentation und liefern sie direkt auf Anfrage.<br />
Klassen, die von e<strong>in</strong>er der beiden ableiten, müssen also nur im Konstruktor<br />
die anfängliche Repräsentation mitgeben und danach bei Bedarf ändern<br />
(über die Member Variable str<strong>in</strong>g_rep_), sofern sich der anzeigbare Inhalt<br />
geändert hat. Der Rest wird von der Basis übernommen.<br />
Jetzt aber wollen wir e<strong>in</strong>e Klasse schreiben, die sowohl anzeigbar als auch<br />
druckbar ist. S<strong>in</strong>nigerweise leiten wir diese Klasse von beiden ab und alles<br />
funktioniert wunderbar... oder auch nicht! Sagen wir, e<strong>in</strong>e Klasse MyObject<br />
würde e<strong>in</strong>fach folgendermaßen aussehen:<br />
1 class MyObject : public DisplayableObject ,<br />
2 public Pr<strong>in</strong>tableObject<br />
3 {<br />
4 public :<br />
5 MyObject ( ) ;<br />
6 ˜MyObject ( ) ;<br />
7 } ;<br />
Abgesehen davon, dass diese Klasse nicht viel darstellt, aber das tut hier<br />
nichts zur Sache, was würde passieren, wenn man irgendwo im Programm<br />
z.B. die folgenden beiden Zeilen stehen hat?<br />
MyObject my_object;<br />
my_object.getStr<strong>in</strong>gRep();<br />
Richtig erkannt! Die Ambiguität ist nicht auflösbar, denn woher soll der<br />
Compiler denn wissen, ob die Implementation aus DisplayableObject oder<br />
die aus Pr<strong>in</strong>tableObject nun die gewünschte ist. Damit endet der Versuch<br />
der Übersetzung mit e<strong>in</strong>em Fehler. Irgendwie muss man also dafür sorgen,<br />
dass der Compiler wieder beruhigt wird und weiß, was er tun soll. Die e<strong>in</strong>zige<br />
Möglichkeit, das zu erreichen, besteht dar<strong>in</strong>, dass es <strong>in</strong> der Klasse MyObject
9.3 Abgeleitete Klassen 211<br />
selbst e<strong>in</strong>e Deklaration ebendieser Methode getStr<strong>in</strong>gRep gibt, die die entsprechenden<br />
Maßnahmen setzt. E<strong>in</strong>e mögliche Deklaration dieser Klasse sieht<br />
dann folgendermaßen aus:<br />
47 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
48 /∗<br />
49 ∗ MyObject<br />
50 ∗<br />
51 ∗ A simple c l a s s that i s d i s p l a y a b l e and p r i n t a b l e<br />
52 ∗<br />
53 ∗/<br />
54<br />
55 class MyObject : public DisplayableObject ,<br />
56 public Pr<strong>in</strong>tableObject<br />
57 {<br />
58 public :<br />
59 MyObject ( ) ;<br />
60 ˜MyObject ( ) ;<br />
61 const char ∗ getStr<strong>in</strong>gRep ( ) ;<br />
62 } ;<br />
Wenn man nun getStr<strong>in</strong>gRep auf e<strong>in</strong>er Variable vom Typ MyObject aufruft,<br />
dann wird auf jeden Fall die hier deklarierte genommen, denn diese ist<br />
nach dem Pr<strong>in</strong>zip des Overrid<strong>in</strong>gs die für den Compiler sichtbare. Dadurch<br />
ist der Konflikt e<strong>in</strong>mal für den Compiler behoben und die Verantwortung<br />
übergeben. Nun braucht man nur noch getStr<strong>in</strong>gRep <strong>in</strong> MyObject korrekt<br />
implementieren und alles funktioniert wieder wie geplant – nun ja, zum<strong>in</strong>dest<br />
fast wie geplant, aber dazu kommen wir weiter unten noch. Sehen wir uns<br />
e<strong>in</strong>mal die Implementation der Klasse DisplayableObject an:<br />
64 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
65 /∗ constructor<br />
66 ∗<br />
67 ∗ @param s t r i n g r e p the s t r i n g r e p r e s e n t a t i o n o f the o b j e c t<br />
68 ∗/<br />
69<br />
70 DisplayableObject : : DisplayableObject ( const char ∗ s t r i n g r e p )<br />
71 {<br />
72 i f ( ! s t r i n g r e p )<br />
73 {<br />
74 s t r i n g r e p = 0;<br />
75 return ;<br />
76 }<br />
77 s t r i n g r e p = new char [ s t r l e n ( s t r i n g r e p ) + 1 ] ;<br />
78 strcpy ( s t r i n g r e p , s t r i n g r e p ) ;<br />
79 }<br />
80<br />
81 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
82 /∗ d e s t r u c t o r<br />
83 ∗/<br />
84<br />
85 DisplayableObject : : ˜ DisplayableObject ( )<br />
86 {<br />
87 i f ( s t r i n g r e p )<br />
88 delete [ ] s t r i n g r e p ;<br />
89 }<br />
90<br />
91 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
92 /∗
212 9. Klassen <strong>in</strong> <strong>C++</strong><br />
93 ∗ @return the s t r i n g to be displayed<br />
94 ∗/<br />
95<br />
96 const char ∗ DisplayableObject : : getStr<strong>in</strong>gRep ( )<br />
97 {<br />
98 return ( s t r i n g r e p ) ;<br />
99 }<br />
Die Implementation der Klasse Pr<strong>in</strong>tableObject ist im Pr<strong>in</strong>zip äquivalent<br />
dazu:<br />
107 Pr<strong>in</strong>tableObject : : Pr<strong>in</strong>tableObject ( const char ∗ s t r i n g r e p )<br />
108 {<br />
109 i f ( ! s t r i n g r e p )<br />
110 {<br />
111 s t r i n g r e p = 0;<br />
112 return ;<br />
113 }<br />
114 s t r i n g r e p = new char [ s t r l e n ( s t r i n g r e p ) + 1 ] ;<br />
115 strcpy ( s t r i n g r e p , s t r i n g r e p ) ;<br />
116 }<br />
117<br />
118 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
119 /∗ d e s t r u c t o r<br />
120 ∗/<br />
121<br />
122 Pr<strong>in</strong>tableObject : : ˜ Pr<strong>in</strong>tableObject ( )<br />
123 {<br />
124 i f ( s t r i n g r e p )<br />
125 delete [ ] s t r i n g r e p ;<br />
126 }<br />
127<br />
128 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
129 /∗<br />
130 ∗ @return the s t r i n g to be displayed<br />
131 ∗/<br />
132<br />
133 const char ∗ Pr<strong>in</strong>tableObject : : getStr<strong>in</strong>gRep ( )<br />
134 {<br />
135 return ( s t r i n g r e p ) ;<br />
136 }<br />
Zu guter Letzt sieht die Klasse MyObject nach unseren Betrachtungen von<br />
oben folgendermaßen aus:<br />
138 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
139 /∗ constructor<br />
140 ∗<br />
141 ∗ @param s t r i n g r e p the s t r i n g r e p r e s e n t a t i o n o f the o b j e c t<br />
142 ∗/<br />
143<br />
144 MyObject : : MyObject ( ) : DisplayableObject ( ” ( disp ) j u s t a s t r i n g rep ” ) ,<br />
145 Pr<strong>in</strong>tableObject ( ” ( p r i n t ) j u s t a s t r i n g rep ” )<br />
146<br />
147 {<br />
148 }<br />
149<br />
150 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
151 /∗ d e s t r u c t o r<br />
152 ∗/<br />
153
154 MyObject : : ˜ MyObject ( )<br />
155 {<br />
156 }<br />
157<br />
9.3 Abgeleitete Klassen 213<br />
158 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
159 /∗<br />
160 ∗ @return the s t r i n g to be displayed<br />
161 ∗/<br />
162<br />
163 const char ∗ MyObject : : getStr<strong>in</strong>gRep ( )<br />
164 {<br />
165 return ( DisplayableObject : : getStr<strong>in</strong>gRep ( ) ) ;<br />
166 }<br />
Und damit das Programm auch e<strong>in</strong>en Output liefert, gibt’s natürlich noch<br />
e<strong>in</strong> ma<strong>in</strong> dazu:<br />
168 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
169 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
170 {<br />
171 MyObject t e s t o b j e c t ;<br />
172<br />
173 cout
214 9. Klassen <strong>in</strong> <strong>C++</strong><br />
Bei der Implementation von MyObject wurde entschieden, dass<br />
DisplayableObject entscheidend für die Speicherung und das Liefern der<br />
Str<strong>in</strong>g Repräsentation ist. Was passiert nun allerd<strong>in</strong>gs, wenn e<strong>in</strong> Objekt der<br />
Klasse Pr<strong>in</strong>tableObject <strong>in</strong>tern mit se<strong>in</strong>er eigenen str<strong>in</strong>g_rep_ mehr macht,<br />
als sie nur zu speichern und auf Anfrage herzugeben? In unserem Fall wird<br />
dann alles <strong>in</strong>konsistent, denn die Implementation von MyObject ignoriert das<br />
Pr<strong>in</strong>tableObject vollkommen, mit Ausnahme des Konstruktoraufrufs, zu<br />
dem es gezwungen ist. Neben diesen möglichen Inkonsistenzen gibt es noch<br />
e<strong>in</strong>en zweiten Aspekt, der zu sehr ungewollten Resultaten führen kann: Es<br />
ist möglich, dass von außen tatsächlich die Methode getStr<strong>in</strong>gRep aus der<br />
Basisklasse Pr<strong>in</strong>tableObject aufgerufen wird, anstatt die Implementation<br />
aus MyObject heranzuziehen. Wie das absichtlich (oder auch ungewollt!)<br />
passieren kann, wird noch später <strong>in</strong> Abschnitt 9.4 besprochen. Im Augenblick<br />
ist es ausreichend, zu wissen, dass es vorkommen kann. Das bedeutet,<br />
dass man bei der Auflösung von Ambiguitäten niemals e<strong>in</strong>fach e<strong>in</strong>e der Basisklassen<br />
ignorieren darf, sondern sich immer darum kümmern muss, dass<br />
alle Basisklassen gleichermaßen konsistent behandelt werden.<br />
Der Designfehler bei unseren Klassen liegt dar<strong>in</strong> versteckt, dass beide Basisklassen<br />
den Entwicklern Arbeit abnehmen wollen, <strong>in</strong>dem sie die Str<strong>in</strong>g Repräsentation<br />
speichern und auf Abruf den gespeicherten Str<strong>in</strong>g liefern. Nun<br />
wissen diese beiden Klassen aber naturgemäß nicht, was im Str<strong>in</strong>g enthalten<br />
se<strong>in</strong> soll, denn dies ist Aufgabe der abgeleiteten Klasse. Würde das Design<br />
so aussehen, dass e<strong>in</strong>fach die Methode getStr<strong>in</strong>gRep <strong>in</strong> den Basisklassen<br />
existiert und mittels Override von der abgeleiteten Klasse implementiert werden<br />
muss, sodass die abgeleitete Klasse selbst die Repräsentation speichert<br />
und liefert, dann kommt es erst gar nicht zu diesem Problem. Denn damit<br />
ist dann wieder e<strong>in</strong>e e<strong>in</strong>zige speichernde Stelle def<strong>in</strong>iert und Inkonsistenzen<br />
werden verh<strong>in</strong>dert. Bevor nun jemand die Klassen entsprechend umschreibt,<br />
möchte ich noch kurz darauf h<strong>in</strong>weisen, dass <strong>in</strong> Abschnitt 9.4 e<strong>in</strong> paar zusätzliche<br />
Aspekte diskutiert werden, die bei e<strong>in</strong>er solchen Implementation sehr<br />
hilfreich und sogar z.T. unabd<strong>in</strong>gbar s<strong>in</strong>d.<br />
Vorsicht Falle: Nicht nur von Neul<strong>in</strong>gen wird sehr oft der Fehler begangen,<br />
dass Klassen “zu viel wissen”, obwohl dieses Wissen nicht <strong>in</strong> ihrer ursächlichen<br />
Natur liegt. Man will den Entwicklern Arbeit abnehmen, <strong>in</strong>dem man<br />
etwas besonders schön implementiert und erreicht leider damit das Gegenteil,<br />
nämlich dass man ihnen das Leben besonders schwer macht. Um nicht<br />
<strong>in</strong> diese Falle zu tappen, die im vorangegangenen Beispiel skizziert wurde,<br />
muss man sich unbed<strong>in</strong>gt an das folgende Designpr<strong>in</strong>zip halten:<br />
E<strong>in</strong> Objekt darf niemals “weil es gerade so praktisch ist” Daten<br />
speichern, die es nicht selbst unter Kontrolle hat! Wenn solche Daten<br />
benötigt werden, dann müssen sie ausnahmslos über Methodenaufrufe von<br />
den tatsächlichen speichernden Objekten angefordert werden. Wie man das<br />
genau erreicht, wird <strong>in</strong> Abschnitt 9.4 besprochen.
9.3.2 Konstruktoren und Destruktoren<br />
9.3 Abgeleitete Klassen 215<br />
Bei den e<strong>in</strong>fachen Klassen wurde bereits besprochen, dass beim Erzeugen e<strong>in</strong>es<br />
Objekts der Konstruktor aufgerufen wird und bei dessen Zerstörung der<br />
Destruktor. Bei abgeleiteten Klassen, egal ob mittels E<strong>in</strong>fach- oder Mehrfachvererbung,<br />
s<strong>in</strong>d nun zwangsweise mehrere Konstruktoren und Destruktoren<br />
im Spiel, denn alle Klassen <strong>in</strong> der Ableitungshierarchie besitzen solche.<br />
Wie diese Konstruktoren und Destruktoren nun <strong>in</strong>tern behandelt und aufgerufen<br />
werden, betrachten wir am besten gleich an e<strong>in</strong>em simplen Beispiel<br />
(derived_constr_destr_demo.cpp):<br />
1 // derived constr destr demo . cpp − demo program to show what<br />
2 // happens with c o n s t r u c t o r s and d e s t r u c t o r s <strong>in</strong> derived c l a s s e s<br />
3<br />
4 #<strong>in</strong>clude <br />
5<br />
6 us<strong>in</strong>g std : : cout ;<br />
7 us<strong>in</strong>g std : : endl ;<br />
8<br />
9 class RootClass<br />
10 {<br />
11 public :<br />
12 RootClass ( ) ;<br />
13 ˜RootClass ( ) ;<br />
14 } ;<br />
15<br />
16 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
17 class DerivedClassLevelOne : public RootClass<br />
18 {<br />
19 public :<br />
20 DerivedClassLevelOne ( ) ;<br />
21 ˜ DerivedClassLevelOne ( ) ;<br />
22 } ;<br />
23<br />
24 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
25 class DerivedClassLevelTwo : public DerivedClassLevelOne<br />
26 {<br />
27 public :<br />
28 DerivedClassLevelTwo ( ) ;<br />
29 ˜DerivedClassLevelTwo ( ) ;<br />
30 } ;<br />
31<br />
32 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
33 RootClass : : RootClass ( )<br />
34 {<br />
35 cout
216 9. Klassen <strong>in</strong> <strong>C++</strong><br />
51 DerivedClassLevelOne : : ˜ DerivedClassLevelOne ( )<br />
52 {<br />
53 cout
9.3 Abgeleitete Klassen 217<br />
Um nun nicht <strong>in</strong> begriffliche Probleme für unsere weiteren Betrachtungen<br />
h<strong>in</strong>e<strong>in</strong>zulaufen, möchte ich folgende Konvention festlegen, wie sie <strong>in</strong> der<br />
OO <strong>Softwareentwicklung</strong> üblich ist:<br />
In e<strong>in</strong>er Ableitungshierarchie steht die Basisklasse oben und abgeleitete<br />
Klassen stehen entsprechend unter ihr. Die Aufrufe der Konstruktoren<br />
erfolgen also von oben nach unten <strong>in</strong> der Hierarchie und die Aufrufe der<br />
Destruktoren erfolgen entsprechend umgekehrt von unten nach oben.<br />
Was bedeutet dieses Verhalten nun für Softwareentwickler? Ganz e<strong>in</strong>fach:<br />
Man kann sich immer darauf verlassen, dass e<strong>in</strong> Konstruktor erst dann aufgerufen<br />
wird, wenn die darüber liegenden Klassen <strong>in</strong> der Ableitungshierarchie<br />
bereits vollständig konstruiert wurden. Dementsprechend kann man bereits<br />
im Konstruktor auf die Funktionalität und die Daten der darüber liegenden<br />
Klassen zugreifen, ohne befürchten zu müssen, dass man auf noch un<strong>in</strong>itialisierte<br />
Werte stößt. Ok, ich geb’s ja zu – natürlich stimmt das nur dann,<br />
wenn die Konstruktoren der darüber liegenden Klassen korrekt implementiert<br />
wurden, aber davon gehen wir hier e<strong>in</strong>mal aus.<br />
Im Falle e<strong>in</strong>er e<strong>in</strong>fachen Vererbungshierarchie ist also alles klar. Was<br />
passiert nun bei Mehrfachvererbung? Auch das ist wieder am leichtesten an<br />
e<strong>in</strong>em Beispiel demonstriert (multiple_<strong>in</strong>her_constr_destr_demo.cpp):<br />
10 class RootClassA<br />
11 {<br />
12 public :<br />
13 RootClassA ( ) ;<br />
14 ˜RootClassA ( ) ;<br />
15 } ;<br />
16<br />
17 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
18 class RootClassB<br />
19 {<br />
20 public :<br />
21 RootClassB ( ) ;<br />
22 ˜RootClassB ( ) ;<br />
23 } ;<br />
24<br />
25 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
26 class DerivedClassLevelOneA : public RootClassA<br />
27 {<br />
28 public :<br />
29 DerivedClassLevelOneA ( ) ;<br />
30 ˜DerivedClassLevelOneA ( ) ;<br />
31 } ;<br />
32<br />
33 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
34 class DerivedClassLevelOneB : public RootClassB<br />
35 {<br />
36 public :<br />
37 DerivedClassLevelOneB ( ) ;<br />
38 ˜DerivedClassLevelOneB ( ) ;<br />
39 } ;<br />
40<br />
41 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
42 class DerivedClassLevelTwo : public DerivedClassLevelOneA ,<br />
43 public DerivedClassLevelOneB<br />
44 {<br />
45 public :
218 9. Klassen <strong>in</strong> <strong>C++</strong><br />
46 DerivedClassLevelTwo ( ) ;<br />
47 ˜DerivedClassLevelTwo ( ) ;<br />
48 } ;<br />
Ich habe bewusst nur den Ausschnitt des Programms mit der Deklaration der<br />
entsprechenden Klassen hier abgedruckt, die Implementation ist im Pr<strong>in</strong>zip<br />
äquivalent mit dem vorherigen Beispiel: Konstruktoren und Destruktoren<br />
der e<strong>in</strong>zelnen Klassen geben bei Aufruf e<strong>in</strong>e Meldung aus.<br />
Die <strong>in</strong> diesem Programm modellierte Ableitungshierarchie hat zwei Wurzeln,<br />
von denen jeweils e<strong>in</strong>e Klasse abgeleitet ist. Die Klasse<br />
DerivedClassLevelTwo schließlich ist von diesen beiden Klassen geme<strong>in</strong>sam<br />
abgeleitet. In Abbildung 9.1 ist die Ableitungshierarchie dieses Beispiels visualisiert.<br />
RootClassA RootClassB<br />
DerivedClassLevelOneA DerivedClassLevelOneB<br />
DerivedClassLevelTwo<br />
Abbildung 9.1: Ableitungshierarchie des Mehrfachvererbungs-Beispiels<br />
Die Konstruktoraufrufe beim Erzeugen und die Destruktoraufrufe beim<br />
Zerstören dieser Klasse erfolgen dann so, wie man es <strong>in</strong> folgendem Output<br />
sieht:<br />
constructor of RootClassA<br />
constructor of DerivedClassLevelOneA<br />
constructor of RootClassB<br />
constructor of DerivedClassLevelOneB<br />
constructor of DerivedClassLevelTwo<br />
∗∗∗∗∗ now the o bject i s a l i v e<br />
d e s t r u c t o r of DerivedClassLevelTwo<br />
d e s t r u c t o r of DerivedClassLevelOneB<br />
d e s t r u c t o r of RootClassB<br />
d e s t r u c t o r of DerivedClassLevelOneA<br />
d e s t r u c t o r of RootClassA<br />
Zuerst wird also der l<strong>in</strong>ke Teil der Ableitungshierarchie (siehe Abbildung 9.1)<br />
konstruiert, danach der rechte Teil und zu guter Letzt das endgültige Objekt<br />
der Klasse DerivedClassLevelTwo, die von beiden Teilen abgeleitet ist.<br />
Die Reihenfolge der Konstruktion der Basisklassen, also zuerst der l<strong>in</strong>ke und<br />
dann der rechte Teil, ist durch die Reihenfolge der Angabe bei der Ableitung
9.4 Weitere wichtige technische Aspekte 219<br />
abhängig. Würde <strong>in</strong> den Zeilen 42–43 DerivedClassLevelOneB vorher angegeben<br />
se<strong>in</strong>, dann würde sich damit auch die Reihenfolge der Konstruktoraufrufe<br />
umdrehen. Es ist nicht möglich, durch explizite Angabe der Konstruktoren<br />
die Reihenfolge zu bee<strong>in</strong>flussen. Hätte man z.B. folgenden Konstruktor<br />
für DerivedClassLevelTwo:<br />
DerivedClassLevelTwo::DerivedClassLevelTwo() :<br />
DerivedClassLevelOneB(), DerivedClassLevelOneA()<br />
{<br />
//...<br />
}<br />
dann würde dies bestenfalls zu e<strong>in</strong>er Warnung des Compilers führen, dass er<br />
die “verkehrte” Aufrufreihenfolge der Konstruktoren wieder umgedreht und<br />
damit korrigiert hat.<br />
Sehen wir uns die Reihenfolge der Aufrufe der Destruktoren an, so ist<br />
leicht zu erkennen, dass dieselbe Regel gilt wie bei e<strong>in</strong>facher Vererbung: Die<br />
Destruktoren werden garantiert genau <strong>in</strong> umgekehrter Reihenfolge zu den<br />
Konstruktoren aufgerufen. Es wird also zuerst die “Hauptklasse”, danach<br />
der rechte Zweig der Ableitungshierarchie von unten nach oben und zum<br />
Schluss der l<strong>in</strong>ke Zweig der Ableitungshierarchie destruiert.<br />
9.4 Weitere wichtige technische Aspekte<br />
Bisher ist bekannt, wie man mit Klassen sowie e<strong>in</strong>facher und mehrfacher<br />
Vererbung arbeitet. Allerd<strong>in</strong>gs ist das noch nicht die ganze Wahrheit, denn<br />
es gibt e<strong>in</strong>ige sehr wichtige technische Aspekte, die die Funktionalität von<br />
Objekten erheblich bee<strong>in</strong>flussen! Genau um diese Aspekte geht es <strong>in</strong> der<br />
Folge.<br />
9.4.1 Static und Dynamic B<strong>in</strong>d<strong>in</strong>g<br />
Zu e<strong>in</strong>er sehr wichtigen oder vielleicht sogar zur wichtigsten Eigenschaft von<br />
Objekten überhaupt, die saubere OO Programme erst wirklich ermöglicht,<br />
habe ich mich bisher noch überhaupt nicht ausgelassen: Hat man es mit<br />
e<strong>in</strong>em Objekt zu tun, dem als Datentyp e<strong>in</strong>e abgeleitete Klasse zugrunde liegt,<br />
dann kann man dieses Objekt auch e<strong>in</strong>fach so behandeln, als ob es e<strong>in</strong>fach<br />
den Typ der Basisklasse hätte.<br />
Was das nun wieder bedeutet, lässt sich leicht überlegen: Er<strong>in</strong>nern wir<br />
uns daran, dass <strong>in</strong> unserer bisher letzten Version 4 der Karte für das Memory<br />
Spiel diese Karte sowohl von e<strong>in</strong>er allgeme<strong>in</strong>en Kartenklasse als auch<br />
von e<strong>in</strong>er allgeme<strong>in</strong>en “anzeigbaren” Klasse abgeleitet war. Nehmen wir<br />
nun an, es gibt e<strong>in</strong> allgeme<strong>in</strong>es Anzeigemodul <strong>in</strong> e<strong>in</strong>em Programm, das so<br />
def<strong>in</strong>iert ist, dass es immer nur mit “anzeigbaren” Objekten umgeht, egal<br />
welche genaue Natur diese jetzt haben. Wozu sollte dieses Modul sich
220 9. Klassen <strong>in</strong> <strong>C++</strong><br />
auch darum kümmern, ob es jetzt mit e<strong>in</strong>er Karte oder e<strong>in</strong>er Schachfigur<br />
oder e<strong>in</strong>fach nur mit irgende<strong>in</strong>em Text arbeitet. Solange sie “anzeigbar”<br />
ist, soll es der Klasse recht se<strong>in</strong>. Unsere MemoryGameCard im Beispiel war<br />
(auch) abgeleitet von DisplayableObject. Da e<strong>in</strong>e Ableitung ja e<strong>in</strong>e IS-<br />
A Relation darstellt, bedeutet das, dass MemoryGameCard tatsächlich e<strong>in</strong><br />
DisplayableObject ist, also e<strong>in</strong>fach als solches behandelt werden kann, weil<br />
die Typenkompatibilität gegeben ist. Sehen wir uns das e<strong>in</strong>mal am Beispiel<br />
an (virtual_method_demo.cpp):<br />
1 // virtual method demo . cpp − demo f o r the use o f v i r t u a l methods<br />
2<br />
3 #<strong>in</strong>clude <br />
4<br />
5 us<strong>in</strong>g std : : cout ;<br />
6 us<strong>in</strong>g std : : endl ;<br />
7<br />
8 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
9 /∗<br />
10 ∗ Displayable<br />
11 ∗<br />
12 ∗ base c l a s s f o r a l l d i s p l a y a b l e o b j e c t s<br />
13 ∗<br />
14 ∗/<br />
15<br />
16 class Displayable<br />
17 {<br />
18 public :<br />
19 virtual const char ∗ getDisplayRep ( ) const ;<br />
20 } ;<br />
21<br />
22 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
23 /∗<br />
24 ∗ OneClass<br />
25 ∗<br />
26 ∗ j u s t a d i s p l a y a b l e c l a s s<br />
27 ∗<br />
28 ∗/<br />
29<br />
30 class OneClass : public Displayable<br />
31 {<br />
32 public :<br />
33 virtual const char ∗ getDisplayRep ( ) const ;<br />
34 } ;<br />
35<br />
36 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
37 /∗<br />
38 ∗ OneClass<br />
39 ∗<br />
40 ∗ j u s t another d i s p l a y a b l e c l a s s<br />
41 ∗<br />
42 ∗/<br />
43<br />
44 class AnotherClass : public Displayable<br />
45 {<br />
46 public :<br />
47 virtual const char ∗ getDisplayRep ( ) const ;<br />
48 } ;<br />
49<br />
50 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
51 /∗<br />
52 ∗ DisplayHandler<br />
53 ∗
9.4 Weitere wichtige technische Aspekte 221<br />
54 ∗ the handler that i s r e s p o n s i b l e f o r d i s p l a y i n g o b j e c t s<br />
55 ∗<br />
56 ∗/<br />
57<br />
58 class DisplayHandler<br />
59 {<br />
60 public :<br />
61 static void displayObject ( const Displayable & o b j e c t ) ;<br />
62 } ;<br />
63<br />
64 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
65 /∗<br />
66 ∗ @return the d i s p l a y rep of the o b j e c t<br />
67 ∗/<br />
68<br />
69 const char ∗ Displayable : : getDisplayRep ( ) const<br />
70 {<br />
71 return ( ” d i s p l a y rep of Displayable ” ) ;<br />
72 }<br />
73<br />
74 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
75 /∗<br />
76 ∗ @return the d i s p l a y rep of the o b j e c t<br />
77 ∗/<br />
78<br />
79 const char ∗ OneClass : : getDisplayRep ( ) const<br />
80 {<br />
81 return ( ” d i s p l a y rep of OneClass” ) ;<br />
82 }<br />
83<br />
84 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
85 /∗<br />
86 ∗ @return the d i s p l a y rep of the o b j e c t<br />
87 ∗/<br />
88<br />
89 const char ∗ AnotherClass : : getDisplayRep ( ) const<br />
90 {<br />
91 return ( ” d i s p l a y rep of AnotherClass” ) ;<br />
92 }<br />
93<br />
94 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
95 /∗<br />
96 ∗ @param o bject the o bject to be displayed<br />
97 ∗ @return<br />
98 ∗/<br />
99<br />
100 void DisplayHandler : : displayObject ( const Displayable & o b j e c t )<br />
101 {<br />
102 cout
222 9. Klassen <strong>in</strong> <strong>C++</strong><br />
Der Output dieses Progrämmchens zeigt, dass wir auf jeden Fall e<strong>in</strong>mal erreicht<br />
haben, was wir wollten: Egal welches Objekt wir anzeigen wollen, es<br />
wird korrekt angezeigt, ohne dass der genaue Typ bekannt se<strong>in</strong> müsste. Es<br />
genügt, dass e<strong>in</strong> Objekt Displayable ist.<br />
d i s p l a y i n g object . . .<br />
d i s p l a y rep o f OneClass<br />
d i s p l a y i n g object . . .<br />
d i s p l a y rep o f AnotherClass<br />
E<strong>in</strong> Blick auf den Code, z.B. auf Zeile 19 eröffnet Erklärungsbedarf: Was<br />
bedeutet virtual und wozu steht h<strong>in</strong>ter der Methode das Keyword const?<br />
Ich möchte zuerst die zweite Frage beantworten, denn diese Antwort ist<br />
e<strong>in</strong>facher zu verdauen: Das Keyword const h<strong>in</strong>ter e<strong>in</strong>er Methode bedeutet,<br />
dass es sich hierbei um e<strong>in</strong>e sogenannte konstante Methode handelt, also um<br />
e<strong>in</strong>e, die bei Aufruf nichts am Inhalt e<strong>in</strong>es Objekts ändert. Sollte man <strong>in</strong>nerhalb<br />
e<strong>in</strong>er solchen Methode versuchen, irgende<strong>in</strong>e Member Variable e<strong>in</strong>es<br />
Objekts zu verändern, dann regt sich der Compiler darüber auf. Und damit<br />
haben wir endlich auch die letzte Antwort, wozu e<strong>in</strong> const_cast noch gebraucht<br />
werden kann: Gesetzt den Fall, e<strong>in</strong> Objekt würde sich nach außen<br />
h<strong>in</strong> durch den Aufruf e<strong>in</strong>er Methode nicht verändern, allerd<strong>in</strong>gs <strong>in</strong>tern quasi<br />
versteckt und damit für niemanden bemerkbar trotzdem e<strong>in</strong>e Statusänderung<br />
vornehmen, dann wird e<strong>in</strong>e solche Methode als const deklariert und<br />
die zu ändernden Variablen <strong>in</strong> der Methode durch e<strong>in</strong>en const_cast von<br />
ihrem Schreibschutz befreit. Dieses Verhalten braucht man z.B. manchmal,<br />
wenn man e<strong>in</strong>en <strong>in</strong>ternen Cache implementiert. Alternativ zum const_cast<br />
kann man auch mit dem schöneren Konstrukt, nämlich den mutable Members<br />
arbeiten (siehe Abschnitt 15.1).<br />
E<strong>in</strong>en weiteren Aspekt von const gibt es noch, der <strong>in</strong> unserem Programm<br />
<strong>in</strong> der Methode displayObject <strong>in</strong> den Zeilen 100–104 zum Tragen kommt:<br />
Die Methode nimmt als Parameter e<strong>in</strong>e const Reference. Auf dieser wird<br />
dann <strong>in</strong> Zeile 103 die Methode getDisplayRep aufgerufen. Wäre die Methode<br />
nicht selbst schon als const deklariert worden, so würde sich hier der<br />
Compiler beschweren, denn man riefe damit e<strong>in</strong>e Methode, die etwas an e<strong>in</strong>em<br />
Objekt verändern kann, auf e<strong>in</strong>em konstanten Objekt auf. Das darf<br />
natürlich nicht se<strong>in</strong>! Durch die Deklaration der Methode als const ist der<br />
Compiler beruhigt.<br />
Nun zur ersten Frage: Was bedeutet virtual <strong>in</strong> Zeile 19? Um dies zu<br />
beantworten, muss ich etwas weiter ausholen und mich kurz über technische<br />
Details von Members im Allgeme<strong>in</strong>en auslassen. Wie bereits bekannt ist,<br />
werden beim Compilieren und endgültig beim darauf folgenden L<strong>in</strong>ken alle<br />
Namen durch Adressen ersetzt, mit denen der Computer arbeiten kann. E<strong>in</strong>e<br />
Variable ist über ihre Adresse ansprechbar und dasselbe gilt auch für e<strong>in</strong>e<br />
Methode (deswegen gibt es ja auch Funktionspo<strong>in</strong>ter). Schränken wir unsere<br />
Betrachtungen nun auf die Methoden e<strong>in</strong>, denn diese s<strong>in</strong>d hier <strong>in</strong>teressant:<br />
Was passiert <strong>in</strong>tern, wenn man e<strong>in</strong>e “normale” (also nicht virtual) Methode<br />
auf e<strong>in</strong>em Objekt aufruft? Ganz e<strong>in</strong>fach, es wird genau die Methode aufgeru-
9.4 Weitere wichtige technische Aspekte 223<br />
fen, die für die Klasse des Objekts implementiert wurde, denn der Compiler<br />
hat beim Übersetzen die entsprechende Adresse e<strong>in</strong>gesetzt. Dieses E<strong>in</strong>setzen<br />
e<strong>in</strong>er Adresse zur Compilezeit nennt man static B<strong>in</strong>d<strong>in</strong>g. Hierbei wird jeder<br />
Aufruf gleich beim Compilieren und L<strong>in</strong>ken <strong>in</strong> e<strong>in</strong>e direkte Adressreferenz<br />
umgesetzt.<br />
Allerd<strong>in</strong>gs ist dieses Verhalten nicht unbed<strong>in</strong>gt immer erwünscht. Sehen<br />
wir e<strong>in</strong>fach nur unser Beispiel an: Der Methode displayObject aus<br />
DisplayHandler wird immer nur e<strong>in</strong> Objekt vom Typ Displayable übergeben.<br />
Hätten wir es mit static B<strong>in</strong>d<strong>in</strong>g zu tun, dann würde der Aufruf<br />
getDisplayRep <strong>in</strong> Zeile 103 immer direkt diese Methode aus der Klasse<br />
Displayable aufrufen, ohne sich darum zu kümmern, dass ja eigentlich<br />
e<strong>in</strong> Overrid<strong>in</strong>g <strong>in</strong> e<strong>in</strong>er Ableitung stattgefunden hat. Was wir wollen,<br />
ist aber etwas ganz Anderes: In unserem Beispiel soll immer die Methode<br />
getDisplayRep aus der abgeleiteten Klasse aufgerufen werden. Nur diese<br />
weiß ja, wie das tatsächliche Objekt s<strong>in</strong>nvoll dargestellt wird. Dazu brauchen<br />
wir offensichtlich e<strong>in</strong>en anderen Mechanismus, denn zur Compilezeit ist nicht<br />
bekannt, um welches Objekt es sich bei e<strong>in</strong>em Aufruf handelt, also kann auch<br />
ke<strong>in</strong>e endgültige Adresse für die Methode e<strong>in</strong>gesetzt werden. Zum Beispiel<br />
soll <strong>in</strong> Zeile 112 e<strong>in</strong> Objekt vom Typ OneClass und <strong>in</strong> Zeile 113 e<strong>in</strong> Objekt<br />
vom Typ AnotherClass angezeigt werden. Für den Aufruf getDisplayRep<br />
<strong>in</strong> Zeile 103 kann also erst zur Laufzeit e<strong>in</strong>e Entscheidung getroffen werden,<br />
welche Implementation von getDisplayRep nun aufgrund des Objekttyps<br />
zum Tragen kommt. Dieses Verhalten des E<strong>in</strong>setzens des richtigen Aufrufs<br />
zur Laufzeit nennt man dynamic B<strong>in</strong>d<strong>in</strong>g. Um dem Compiler mitzuteilen,<br />
dass für e<strong>in</strong>e bestimmte Methode dynamic B<strong>in</strong>d<strong>in</strong>g vorgenommen werden<br />
soll, verwendet man das om<strong>in</strong>öse Keyword virtual.<br />
Diese Eigenschaft, dass e<strong>in</strong>e Klasse sich verschiedenartig verhalten kann,<br />
je nachdem, ob man es mit e<strong>in</strong>er Ableitung zu tun hat oder auch, mit welcher<br />
Ableitung man es zu tun hat, wird auch als Polymorphismus bezeichnet.<br />
Alle Methoden, die als virtual deklariert werden, werden folgendermaßen<br />
behandelt:<br />
• Objekte haben zur Laufzeit e<strong>in</strong>e sogenannte virtual Table zur Verfügung,<br />
<strong>in</strong> der alle virtual Methoden mit ihren aktuellen Adressen gemäß der<br />
Ableitungshierarchie vermerkt s<strong>in</strong>d.<br />
• Bei e<strong>in</strong>em Aufruf e<strong>in</strong>er virtual Methode wird ihr E<strong>in</strong>trag <strong>in</strong> der virtual<br />
Table nachgeschlagen und die korrekte Implementation, die dem tatsächlichen<br />
Objekt entspricht, aufgerufen.<br />
Kompliziert? Ke<strong>in</strong>e Sorge, es kl<strong>in</strong>gt schlimmer, als es <strong>in</strong> Wirklichkeit ist. Im<br />
folgenden Beispiel ist der Unterschied zwischen static und dynamic B<strong>in</strong>d<strong>in</strong>g<br />
schnell zu sehen (static_dynamic_b<strong>in</strong>d<strong>in</strong>g_demo.cpp):
224 9. Klassen <strong>in</strong> <strong>C++</strong><br />
1 // static dynamic b<strong>in</strong>d<strong>in</strong>g demo . cpp − demo f o r the d i f f e r e n c e<br />
2 // between s t a t i c and dynamic b<strong>in</strong>d<strong>in</strong>g o f methods<br />
3<br />
4 #<strong>in</strong>clude <br />
5<br />
6 us<strong>in</strong>g std : : cout ;<br />
7 us<strong>in</strong>g std : : endl ;<br />
8<br />
9 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
10 /∗<br />
11 ∗ Base<br />
12 ∗<br />
13 ∗ j u s t a base c l a s s<br />
14 ∗<br />
15 ∗/<br />
16<br />
17 class Base<br />
18 {<br />
19 public :<br />
20 void staticallyBoundMethod ( ) const ;<br />
21 virtual void dynamicallyBoundMethod ( ) const ;<br />
22 } ;<br />
23<br />
24 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
25 /∗<br />
26 ∗ JustAClass<br />
27 ∗<br />
28 ∗ j u s t a demo c l a s s<br />
29 ∗<br />
30 ∗/<br />
31<br />
32 class JustAClass : public Base<br />
33 {<br />
34 public :<br />
35 void staticallyBoundMethod ( ) const ;<br />
36 virtual void dynamicallyBoundMethod ( ) const ;<br />
37 } ;<br />
38<br />
39 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
40 void Base : : staticallyBoundMethod ( ) const<br />
41 {<br />
42 cout
66 o b j ect . staticallyBoundMethod ( ) ;<br />
67 o b j ect . dynamicallyBoundMethod ( ) ;<br />
68 }<br />
69<br />
9.4 Weitere wichtige technische Aspekte 225<br />
70 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
71 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
72 {<br />
73 JustAClass t e s t o b j e c t ;<br />
74<br />
75 cout
226 9. Klassen <strong>in</strong> <strong>C++</strong><br />
tatsächlich die Implementation aus JustAClass für den Aufruf herangezogen,<br />
denn diese ist <strong>in</strong> der virtual Table als endgültiges Overrid<strong>in</strong>g <strong>in</strong> der<br />
Hierarchie e<strong>in</strong>getragen. Es ist also <strong>in</strong> diesem Fall egal, ob e<strong>in</strong>e virtual<br />
Methode auf der Basisklasse oder auf der endgültigen Klasse aufgerufen<br />
wird, das Ergebnis ist immer dasselbe.<br />
Vorsicht Falle: Oft wird von Neul<strong>in</strong>gen der Fehler begangen, e<strong>in</strong>e Methode<br />
nicht bei ihrem ersten Auftreten <strong>in</strong> der Basisklasse als virtual zu deklarieren,<br />
sondern dies erst weiter unten <strong>in</strong> der Ableitungshierarchie zu versuchen. Das<br />
funktioniert natürlich nicht und führt unweigerlich zu e<strong>in</strong>em Compilerfehler,<br />
denn der Compiler kann nicht zuerst statisches B<strong>in</strong>d<strong>in</strong>g durchführen und<br />
danach für e<strong>in</strong> und dieselbe Methode plötzlich den Rest dynamisch b<strong>in</strong>den.<br />
Sobald e<strong>in</strong>e Methode <strong>in</strong> der Basisklasse als virtual deklariert ist, muss<br />
im Pr<strong>in</strong>zip <strong>in</strong> abgeleiteten Klassen e<strong>in</strong> Overrid<strong>in</strong>g nicht mehr explizit als<br />
virtual deklariert werden. Der Compiler weiß durch die erste Deklaration<br />
<strong>in</strong> der Basis Bescheid und behandelt alle Overrid<strong>in</strong>gs korrekt. Trotzdem wird<br />
es als sauberer Programmierstil angesehen, das Keyword virtual bei allen<br />
Overrid<strong>in</strong>gs <strong>in</strong> der Ableitungshierarchie zum Signalisieren dieser Eigenschaft<br />
h<strong>in</strong>zuschreiben.<br />
Ganz wichtig im Zusammenhang mit dynamic B<strong>in</strong>d<strong>in</strong>g ist es, zu wissen,<br />
wie genau die virtual Table zur Laufzeit gebaut wird. In diesem Wissen<br />
nämlich liegen die Antworten auf viele Fragen und das Werkzeug zur Behebung<br />
schwerer Fehler, die leider nicht nur von Neul<strong>in</strong>gen gemacht werden.<br />
Greifen wir am besten also gleich <strong>in</strong> die Tasten und erstellen e<strong>in</strong> kle<strong>in</strong>es Testprogramm,<br />
das sich folgendermaßen liest (virtual_table_build_demo.cpp):<br />
1 // v i r t u a l t a b l e b u i l d d e m o . cpp − demo program to show how<br />
2 // the v i r t u a l t a b l e i s b u i l t dur<strong>in</strong>g runtime<br />
3<br />
4 #<strong>in</strong>clude <br />
5<br />
6 us<strong>in</strong>g std : : cout ;<br />
7 us<strong>in</strong>g std : : endl ;<br />
8<br />
9 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
10 /∗<br />
11 ∗ BaseA<br />
12 ∗<br />
13 ∗ j u s t a base c l a s s<br />
14 ∗<br />
15 ∗/<br />
16<br />
17 class BaseA<br />
18 {<br />
19 public :<br />
20 BaseA ( ) ;<br />
21 virtual ˜ BaseA ( ) ;<br />
22 virtual void dynamicallyBoundMethod ( ) ;<br />
23 } ;<br />
24<br />
25 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
26 /∗
27 ∗ BaseB<br />
28 ∗<br />
29 ∗ j u s t another base c l a s s<br />
30 ∗<br />
31 ∗/<br />
32<br />
33 class BaseB<br />
34 {<br />
35 public :<br />
36 BaseB ( ) ;<br />
37 virtual ˜ BaseB ( ) ;<br />
38 virtual void dynamicallyBoundMethod ( ) ;<br />
39 } ;<br />
40<br />
9.4 Weitere wichtige technische Aspekte 227<br />
41 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
42 /∗<br />
43 ∗ DerivedA<br />
44 ∗<br />
45 ∗ j u s t a demo c l a s s<br />
46 ∗<br />
47 ∗/<br />
48<br />
49 class DerivedA : public BaseA<br />
50 {<br />
51 public :<br />
52 DerivedA ( ) ;<br />
53 virtual ˜ DerivedA ( ) ;<br />
54 virtual void dynamicallyBoundMethod ( ) ;<br />
55 } ;<br />
56<br />
57 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
58 /∗<br />
59 ∗ DerivedB<br />
60 ∗<br />
61 ∗ j u s t a demo c l a s s<br />
62 ∗<br />
63 ∗/<br />
64<br />
65 class DerivedB : public BaseB<br />
66 {<br />
67 public :<br />
68 DerivedB ( ) ;<br />
69 virtual ˜ DerivedB ( ) ;<br />
70 virtual void dynamicallyBoundMethod ( ) ;<br />
71 } ;<br />
72<br />
73 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
74 /∗<br />
75 ∗ DerivedC<br />
76 ∗<br />
77 ∗ j u s t a demo c l a s s<br />
78 ∗<br />
79 ∗/<br />
80<br />
81 class DerivedC : public DerivedA ,<br />
82 public DerivedB<br />
83 {<br />
84 public :<br />
85 DerivedC ( ) ;<br />
86 virtual ˜ DerivedC ( ) ;<br />
87 virtual void dynamicallyBoundMethod ( ) ;<br />
88 } ;<br />
89<br />
90 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
91 BaseA : : BaseA ( )<br />
92 {
228 9. Klassen <strong>in</strong> <strong>C++</strong><br />
93 cout
159 {<br />
160 cout
230 9. Klassen <strong>in</strong> <strong>C++</strong><br />
BaseB : : dynamicallyBoundMethod c a l l e d<br />
constructor of DerivedB<br />
DerivedB : : dynamicallyBoundMethod c a l l e d<br />
constructor of DerivedC<br />
DerivedC : : dynamicallyBoundMethod c a l l e d<br />
−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
∗∗∗∗∗ new t e s t o b j p t r ∗∗∗∗∗<br />
constructor of BaseA<br />
BaseA : : dynamicallyBoundMethod c a l l e d<br />
constructor of DerivedA<br />
DerivedA : : dynamicallyBoundMethod c a l l e d<br />
constructor of BaseB<br />
BaseB : : dynamicallyBoundMethod c a l l e d<br />
constructor of DerivedB<br />
DerivedB : : dynamicallyBoundMethod c a l l e d<br />
constructor of DerivedC<br />
DerivedC : : dynamicallyBoundMethod c a l l e d<br />
−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
∗∗∗∗∗ c a l l i n g method on t e s t o b j p t r ∗∗∗∗∗<br />
DerivedC : : dynamicallyBoundMethod c a l l e d<br />
−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
∗∗∗∗∗ d e l e t e t e s t o b j p t r ∗∗∗∗∗<br />
d e s t r u c t o r of DerivedC<br />
DerivedC : : dynamicallyBoundMethod c a l l e d<br />
d e s t r u c t o r of DerivedB<br />
DerivedB : : dynamicallyBoundMethod c a l l e d<br />
d e s t r u c t o r of BaseB<br />
BaseB : : dynamicallyBoundMethod c a l l e d<br />
d e s t r u c t o r of DerivedA<br />
DerivedA : : dynamicallyBoundMethod c a l l e d<br />
d e s t r u c t o r of BaseA<br />
BaseA : : dynamicallyBoundMethod c a l l e d<br />
−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
∗∗∗∗∗ now t e s t o b j e c t ’ s l i f e t i m e ends ∗∗∗∗∗<br />
d e s t r u c t o r of DerivedC<br />
DerivedC : : dynamicallyBoundMethod c a l l e d<br />
d e s t r u c t o r of DerivedB<br />
DerivedB : : dynamicallyBoundMethod c a l l e d<br />
d e s t r u c t o r of BaseB<br />
BaseB : : dynamicallyBoundMethod c a l l e d<br />
d e s t r u c t o r of DerivedA<br />
DerivedA : : dynamicallyBoundMethod c a l l e d<br />
d e s t r u c t o r of BaseA<br />
BaseA : : dynamicallyBoundMethod c a l l e d<br />
Die erste Auffälligkeit <strong>in</strong> unserem Testprogrämmchen zeigt sich <strong>in</strong> Zeile 21:<br />
Der Destruktor von BaseA ist virtual! Ebenso s<strong>in</strong>d alle Destruktoren aller<br />
anderen Klassen als virtual deklariert. Dies hat noch nichts mit dem Bauen<br />
der virtual Table an sich zu tun, sondern hat e<strong>in</strong>en ganz anderen aber sehr<br />
wichtigen Grund. Dazu werfen wir e<strong>in</strong>mal e<strong>in</strong>en Blick auf die Zeilen 200–<br />
206. In diesen Zeilen wird e<strong>in</strong> Objekt vom Typ DerivedC dynamisch erzeugt,<br />
allerd<strong>in</strong>gs wird dieser Po<strong>in</strong>ter <strong>in</strong> e<strong>in</strong>er Variable vom Typ BaseA * gehalten.<br />
Dies ist, wie bereits bekannt ist, absolut zulässig und gängige Praxis, z.B. bei<br />
Parameterübergaben, wie wir sie schon kennen gelernt haben. In Zeile 204<br />
sieht man wieder, was sowieso schon bekannt ist: Beim Call e<strong>in</strong>er virtual<br />
Methode wird zur Laufzeit entschieden, welche der Implementationen <strong>in</strong> der<br />
Ableitungshierarchie nun tatsächlich aufgerufen wird.<br />
Zeile 206 allerd<strong>in</strong>gs ist nun des Pudels Kern, was die om<strong>in</strong>ösen virtuellen<br />
Destruktoren anbelangt: Stellen wir uns e<strong>in</strong>fach vor, die Destruktoren
9.4 Weitere wichtige technische Aspekte 231<br />
wären statisch und nicht dynamisch gebunden. Dadurch würde Furchtbares<br />
passieren, nämlich dass ausschließlich der Destruktor von BaseA aufgerufen<br />
wird. Der Compiler hat ja statisch diesen Aufruf e<strong>in</strong>gefügt. Was allerd<strong>in</strong>gs<br />
passieren muss, damit das Programm korrekt funktioniert, ist, dass alle Destruktoren<br />
der e<strong>in</strong>zelnen Klassen <strong>in</strong> der Ableitungshierarchie korrekt aufgerufen<br />
werden. Genau das erreicht man nur, wenn man mit dynamic B<strong>in</strong>d<strong>in</strong>g<br />
arbeitet, also den Destruktor virtual deklariert. Damit wird zur Laufzeit<br />
entschieden, welcher der “tiefste” Destruktor des vorliegenden Objekts ist<br />
und von diesem ausgehend werden die anderen Destruktoren, die höher <strong>in</strong><br />
der Ableitungshierarchie liegen, korrekt aufgerufen.<br />
Vorsicht Falle: Die schweißtreibendsten Debug Sessions, an denen schon<br />
sehr viele Entwickler verzweifelt s<strong>in</strong>d, ergeben sich bei der Suche nach völlig<br />
unerklärlichen Effekten, die durch e<strong>in</strong>en statisch gebundenen Destruktor<br />
trotz dynamischer Verwendung von Objekten zustande kommen. Aus diesem<br />
Grund lautet die Grundregel Nummer e<strong>in</strong>s beim Entwickeln von C ++<br />
Programmen:<br />
Jede Klasse muss unbed<strong>in</strong>gt e<strong>in</strong>en virtual Destruktor besitzen!<br />
Auch wenn dies <strong>in</strong> manchen Fällen aufgrund der Verwendung als nicht<br />
notwendig ersche<strong>in</strong>t, so ist man auf jeden Fall bei E<strong>in</strong>halten dieser Regel<br />
immer auf der sicheren Seite. Vor allem passiert es oft genug, dass nach Programmänderungen<br />
Objekte polymorph verwendet werden, obwohl das zuvor<br />
beim Grunddesign der Klasse noch gar nicht der Fall und leider auch nicht<br />
mitgeplant war. Und genau dann kommt es garantiert zur Katastrophe. Zum<br />
Glück warnen moderne Compiler, wenn man <strong>in</strong> e<strong>in</strong>er Klasse virtual Methoden<br />
hat, aber den Destruktor nicht virtual deklariert.<br />
Nun zum eigentlichen Thema unseres Testprogramms: Es galt, herauszuf<strong>in</strong>den,<br />
wie die virtual Table aufgebaut (und auch wieder abgebaut) wird.<br />
Um festzustellen, welche Implementation der dynamisch gebundenen Methode<br />
gerade die “letzte aktive” <strong>in</strong> der virtual Table ist, wurden <strong>in</strong> die e<strong>in</strong>zelnen<br />
Konstruktoren und <strong>in</strong> die e<strong>in</strong>zelnen Destruktoren Outputs e<strong>in</strong>gebaut. Betrachtet<br />
man den Output, der beim Erzeugen von test_object entsteht, so<br />
kann man Folgendes beobachten:<br />
• Im Konstruktor von BaseA wird dynamicallyBoundMethod aufgerufen.<br />
Der tatsächlich ausgeführte Call ist die Implementation, die durch BaseA<br />
gegeben ist.<br />
• Im Konstruktor der davon abgeleiteten Klasse DerivedA erfolgt, wie man<br />
sieht, e<strong>in</strong> Aufruf der Implementation, die durch DerivedA vorgegeben ist.<br />
• Selbiges passiert <strong>in</strong> allen anderen Konstruktoren, die der Reihe nach bis<br />
zum fertigen Objekt aufgerufen werden.<br />
Was sagt uns das <strong>in</strong> Bezug auf die virtual Table? Nun, ganz e<strong>in</strong>fach: Die<br />
virtual Table wird sukzessive geme<strong>in</strong>sam mit dem Aufruf der Konstruktoren
232 9. Klassen <strong>in</strong> <strong>C++</strong><br />
aufgebaut. Sobald e<strong>in</strong> Objekt konstruiert wird, wird e<strong>in</strong> Update der virtual<br />
Table vorgenommen, das die neuen Overrid<strong>in</strong>gs reflektiert. Es kann also<br />
niemals passieren, dass für e<strong>in</strong>en Methodenaufruf <strong>in</strong> e<strong>in</strong>em Konstruktor aufgrund<br />
von dynamic B<strong>in</strong>d<strong>in</strong>g e<strong>in</strong>e Methode e<strong>in</strong>gesetzt wird, die aus e<strong>in</strong>em Teil<br />
der Ableitungshierarchie kommt, der noch gar nicht konstruiert ist. Dies ist<br />
auch sehr gut so, denn stellen wir uns vor, was passieren würde, wenn die<br />
virtual Table vor dem Aufruf der e<strong>in</strong>zelnen Konstruktoren bereits fertig erstellt<br />
worden wäre: Dann wird z.B. <strong>in</strong> BaseA unsere dynamisch gebundene<br />
Methode aufgerufen und es würde tatsächlich der Call der Implementation<br />
aus DerivedC e<strong>in</strong>gesetzt. Dieser ist ja bei fertig konstruiertem Objekt das<br />
endgültig letzte Overrid<strong>in</strong>g. Aber damit würde dann e<strong>in</strong>e Methode aus e<strong>in</strong>em<br />
Objekt aufgerufen, das noch nicht e<strong>in</strong>mal fertig konstruiert ist, denn der<br />
Konstruktoraufruf von DerivedC erfolgt ja erst viel später!<br />
Zugegeben, es gibt Situationen, <strong>in</strong> denen dieses Verhalten sogar recht gut<br />
e<strong>in</strong>setzbar wäre. E<strong>in</strong>e solche Situation wäre z.B. die Implementation e<strong>in</strong>es<br />
Callbacks, über das man Daten aus abgeleiteten Klassen bekommt, die man<br />
im Konstruktor e<strong>in</strong>er der Basisklassen benötigt. Allerd<strong>in</strong>gs lässt sich dieses<br />
Problem auch anders lösen und die Gefahr dieses Verhaltens ist bei weitem<br />
größer als der Nutzen.<br />
Sp<strong>in</strong>nt man den Gedanken zu Ende, dass e<strong>in</strong>e Methode nur auf e<strong>in</strong>em<br />
“lebendigen” Objekt aufgerufen werden darf, um nicht <strong>in</strong>s offene Messer zu<br />
laufen, dann kann man sich auch sofort vorstellen, wie die virtual Table beim<br />
Aufruf der e<strong>in</strong>zelnen Destruktoren wieder abgebaut wird. Sobald e<strong>in</strong> Destruktor<br />
ausgeführt wurde, wird e<strong>in</strong> Update der virtual Table vorgenommen,<br />
<strong>in</strong> dem die Overrid<strong>in</strong>gs des nun zerstörten Objekts wieder entfernt werden.<br />
Auch das dient wieder der Sicherheit, denn wenn e<strong>in</strong> Objekt e<strong>in</strong>mal destruiert<br />
wurde, dann darf natürlich ke<strong>in</strong>e Methode mehr darauf aufgerufen werden.<br />
Dieses Verhalten lässt sich auch im Output unseres Programms leicht erkennen.<br />
Die Methodenaufrufe <strong>in</strong> den e<strong>in</strong>zelnen Destruktoren beziehen sich<br />
immer auf das “letzte noch lebendige” Objekt <strong>in</strong> der Hierarchie.<br />
9.4.2 Abstrakte Methoden und Klassen<br />
Bisher haben wir mehrfach kennen gelernt, dass man <strong>in</strong> e<strong>in</strong>er Basisklasse verlangen<br />
will, dass e<strong>in</strong>e abgeleitete Klasse e<strong>in</strong>e Methode overrided, weil man <strong>in</strong><br />
der Basisklasse gar ke<strong>in</strong>e vernünftige Implementation e<strong>in</strong>er Methode schreiben<br />
kann. Nehmen wir nur das Beispiel mit dem Displayable Objekt her:<br />
Die Eigenschaft, Displayable zu se<strong>in</strong>, bedeutet, dass e<strong>in</strong>e Klasse, die davon<br />
abgeleitet ist, e<strong>in</strong>e Methode getDisplayRep zur Verfügung stellt. Die Basisklasse<br />
Displayable selbst weiß nichts über die von ihr abgeleiteten Klassen<br />
und kann dementsprechend gar ke<strong>in</strong>e vernünftige Implementation liefern.<br />
Was wir also haben wollen, ist e<strong>in</strong>e Möglichkeit, <strong>in</strong> e<strong>in</strong>er Basisklasse zu<br />
sagen: Die Methode getDisplayRep existiert und ist folgendermaßen aufrufbar.<br />
Sie muss allerd<strong>in</strong>gs immer <strong>in</strong> der abgeleiteten Klasse implementiert<br />
werden.
9.4 Weitere wichtige technische Aspekte 233<br />
E<strong>in</strong>e solche Vorschrift nennt man e<strong>in</strong>e abstrakte Methode. Von ihr ist<br />
<strong>in</strong> der Basis nur die Signatur (=das Interface) bekannt, aber es gibt dort<br />
noch ke<strong>in</strong>e Implementation davon. Klassen, die solche abstrakten Methoden<br />
enthalten, nennt man abstrakte Klassen. Im Extremfall kann es sogar se<strong>in</strong>,<br />
dass e<strong>in</strong>e Klasse ausschließlich abstrakte Methoden enthält. Dann spricht<br />
man von re<strong>in</strong> abstrakten Klassen bzw. auch <strong>in</strong> Anlehnung an Java von re<strong>in</strong>en<br />
Interfaces.<br />
Die Realisierung unseres Wunsches nach e<strong>in</strong>em solchen Konstrukt ist e<strong>in</strong>fach,<br />
wie man an folgendem Beispiel sieht (abstract_displayable.h):<br />
1 // a b s t r a c t d i s p l a y a b l e . h − abstract d i s p l a y a b l e base c l a s s<br />
2<br />
3 #ifndef a b s t r a c t d i s p l a y a b l e h<br />
4 #def<strong>in</strong>e a b s t r a c t d i s p l a y a b l e h<br />
5<br />
6 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
7 /∗<br />
8 ∗ Displayable<br />
9 ∗<br />
10 ∗ a bstract base c l a s s f o r d i s p l a y a b l e o b j e c t s<br />
11 ∗<br />
12 ∗/<br />
13<br />
14 class Displayable<br />
15 {<br />
16 public :<br />
17 virtual ˜ Displayable ( ) { }<br />
18<br />
19 virtual const char ∗ getDisplayRep ( ) const = 0;<br />
20 } ;<br />
21<br />
22<br />
23 #endif // a b s t r a c t d i s p l a y a b l e h<br />
In dieser Klassendeklaration s<strong>in</strong>d zwei kle<strong>in</strong>e Neuigkeiten versteckt:<br />
• Zeile 19 zeigt, wie man e<strong>in</strong>e abstrakte Methode deklariert: Man schreibt<br />
e<strong>in</strong>fach = 0 h<strong>in</strong>ter die Signatur. Wieso das so geschrieben wird, möchte ich<br />
hier e<strong>in</strong>fach e<strong>in</strong>mal salopp erklären: E<strong>in</strong>e Methode ist natürlich, wie auch<br />
e<strong>in</strong>e Funktion, nach der Übersetzung durch den Compiler und Auflösung<br />
durch den L<strong>in</strong>ker e<strong>in</strong>fach e<strong>in</strong> Po<strong>in</strong>ter, der die E<strong>in</strong>sprungadresse repräsentiert.<br />
Durch die Schreibweise = 0 teilt man dem Compiler e<strong>in</strong>fach mit, dass<br />
diese Methode auf Adresse 0 zu liegen kommt, was bedeutet, dass sie nicht<br />
aufgerufen werden kann. Damit kann man sich natürlich auch die Implementation<br />
dazu sparen. Ich möchte noch dazu sagen, dass diese saloppe<br />
Erklärung nicht zu 100% den Tatsachen entspricht, da noch andere D<strong>in</strong>ge<br />
hier mit <strong>in</strong>s Spiel kommen, aber als Gedankenmodell ist sie <strong>in</strong> Ordnung.<br />
• In Zeile 17 sieht man, dass es nicht nur <strong>in</strong>l<strong>in</strong>e Funktionen, sondern logischerweise<br />
auch <strong>in</strong>l<strong>in</strong>e Methoden gibt. Bei diesen kann das Keyword<br />
<strong>in</strong>l<strong>in</strong>e, wie hier geschehen, auch weggelassen werden, denn das Vorkommen<br />
e<strong>in</strong>er Implementation (hier e<strong>in</strong>er leeren Implementation) <strong>in</strong>nerhalb e<strong>in</strong>er<br />
Klassendeklaration ist automatisch schon e<strong>in</strong> Kennzeichen für <strong>in</strong>l<strong>in</strong>e.
234 9. Klassen <strong>in</strong> <strong>C++</strong><br />
Das br<strong>in</strong>gt uns auch gleich auf den Punkt, wie man e<strong>in</strong>e <strong>in</strong>l<strong>in</strong>e Methode<br />
deklariert und gleichzeitig def<strong>in</strong>iert: Man schreibt die Implementation<br />
direkt h<strong>in</strong>ter die Deklaration. Den Strichpunkt h<strong>in</strong>ter der schließenden<br />
Klammer der Implementation kann man sich natürlich sparen.<br />
Zu den abstrakten Methoden wäre noch zu erwähnen, dass klarerweise ausschließlich<br />
virtual Methoden abstrakt se<strong>in</strong> können. Ansonsten müsste ja<br />
static B<strong>in</strong>d<strong>in</strong>g mit e<strong>in</strong>er nicht existenten Methode stattf<strong>in</strong>den. Dass das der<br />
Compiler nicht besonders toll f<strong>in</strong>det und entsprechende Kommentare von sich<br />
gibt, lässt sich leicht vorstellen. Aus der Eigenschaft abstrakter Methoden,<br />
dass sie auf jeden Fall virtual se<strong>in</strong> müssen, resultiert auch noch e<strong>in</strong> anderer<br />
sehr gebräuchlicher Name für sie: Man nennt sie auch pure virtual Methods.<br />
Verwendet wird e<strong>in</strong>e abstrakte Basisklasse erwartungsgemäß gleich, wie<br />
jede andere Klasse auch. Sehen wir uns diese Verwendung e<strong>in</strong>mal an<br />
(abstract_displayable_demo.cpp):<br />
1 // abstract displayable demo . cpp − small demo program f o r the<br />
2 // a b s t r a c t Displayable c l a s s<br />
3<br />
4 #<strong>in</strong>clude ” a b s t r a c t d i s p l a y a b l e . h”<br />
5 #<strong>in</strong>clude < c s t r i n g><br />
6 #<strong>in</strong>clude <br />
7<br />
8 us<strong>in</strong>g std : : cout ;<br />
9 us<strong>in</strong>g std : : endl ;<br />
10<br />
11 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
12 /∗<br />
13 ∗ TextObject<br />
14 ∗<br />
15 ∗ j u s t a text o bject f o r demo purposes<br />
16 ∗<br />
17 ∗/<br />
18<br />
19 class TextObject : public Displayable<br />
20 {<br />
21 protected :<br />
22 char ∗ t e x t ;<br />
23 public :<br />
24 TextObject ( const char ∗ text ) ;<br />
25 virtual ˜ TextObject ( ) ;<br />
26<br />
27 virtual const char ∗ getDisplayRep ( ) const<br />
28 {<br />
29 return ( t e x t ) ;<br />
30 }<br />
31 } ;<br />
32<br />
33 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
34 /∗ constructor<br />
35 ∗ @param text the text held <strong>in</strong> t h i s o b j e c t<br />
36 ∗/<br />
37<br />
38 TextObject : : TextObject ( const char ∗ text )<br />
39 {<br />
40 i f ( ! text )<br />
41 {<br />
42 t e x t = 0;<br />
43 return ;
44 }<br />
45 t e x t = new char [ s t r l e n ( text ) + 1 ] ;<br />
46 strcpy ( t e x t , text ) ;<br />
47 }<br />
48<br />
9.4 Weitere wichtige technische Aspekte 235<br />
49 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
50 /∗ d e s t r u c t o r<br />
51 ∗/<br />
52<br />
53 TextObject : : ˜ TextObject ( )<br />
54 {<br />
55 i f ( t e x t )<br />
56 delete [ ] t e x t ;<br />
57 }<br />
58<br />
59 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
60 void displayObject ( const Displayable & o b j e c t )<br />
61 {<br />
62 cout
236 9. Klassen <strong>in</strong> <strong>C++</strong><br />
anderen Aspekt von dynamic B<strong>in</strong>d<strong>in</strong>g, der beim Design von Klassenhierarchien<br />
sehr wichtig ist: virtuelle Ableitungen. Nehmen wir an, wir wollten<br />
e<strong>in</strong>e Klassenhierarchie, wie <strong>in</strong> Abbildung 9.2 dargestellt, modellieren.<br />
Displayable<br />
TextuallyDisplayable GraphicallyDisplayable<br />
Pr<strong>in</strong>table<br />
GameCard<br />
Abbildung 9.2: E<strong>in</strong>e etwas kniffligere Klassenhierarchie<br />
Die Klassenhierarchie <strong>in</strong> Abbildung 9.2 beruht auf folgendem Design:<br />
• Es gibt e<strong>in</strong>e Klasse Displayable, die die Basis für alle anzeigbaren Objekte<br />
verkörpert, egal auf welchem Device oder wie diese dargestellt werden.<br />
• Von Displayable abgeleitet ist e<strong>in</strong>e Klasse TextuallyDisplayable, die<br />
die Basis für alle Klassen verkörpert, die textuell dargestellt werden<br />
können.<br />
• Von Displayable abgeleitet ist e<strong>in</strong>e Klasse GraphicallyDisplayable, die<br />
die Basis für alle Klassen verkörpert, die graphisch dargestellt werden<br />
können.<br />
• Von Displayable abgeleitet ist e<strong>in</strong>e Klasse Pr<strong>in</strong>table, die die Basis für<br />
alle Klassen verkörpert, die ausgedruckt werden können (= am Drucker<br />
dargestellt werden können).<br />
• E<strong>in</strong>e Spielkarte ist nun so def<strong>in</strong>iert, dass sie sowohl textuell als auch graphisch<br />
dargestellt und auch ausgedruckt werden kann. Damit ist GameCard<br />
von allen drei entsprechenden Klassen abgeleitet.<br />
Neben anderen D<strong>in</strong>gen sieht man an diesem Design auch, dass oft viele Wege<br />
nach Rom führen. Früher hatten wir es mit e<strong>in</strong>em Design zu tun, <strong>in</strong><br />
dem Pr<strong>in</strong>table und Displayable zwei vone<strong>in</strong>ander unabhängige Basisklassen<br />
waren. Nun haben wir es mit der Interpretation zu tun, dass etwas,<br />
was druckbar ist, natürlich darstellbar ist, und zwar am Drucker. Selbiges<br />
gilt für die Textausgabe, denn sie ist semantisch <strong>in</strong> unserem Design etwas,<br />
was darstellbar ist, und zwar auf e<strong>in</strong>em Textbildschirm. Analog dazu ist die<br />
Interpretation für etwas graphisch Darstellbares.<br />
Genau diese verschiedenen Interpretationsmöglichkeiten e<strong>in</strong>- und desselben<br />
Sachverhalts s<strong>in</strong>d es auch, die Erfahrung und e<strong>in</strong>e ausführliche Designphase<br />
bei der OO <strong>Softwareentwicklung</strong> so unabd<strong>in</strong>gbar machen. Erfahrene<br />
und gute Softwareentwickler werden e<strong>in</strong>erseits schon von Beg<strong>in</strong>n an viele
9.4 Weitere wichtige technische Aspekte 237<br />
Designaspekte im H<strong>in</strong>terkopf haben, die sich positiv auf e<strong>in</strong> klares Design<br />
auswirken. Weiters werden solche Entwickler immer wieder überlegen, wie<br />
man e<strong>in</strong>- und denselben Sachverhalt vielleicht auch anders und damit schlüssiger<br />
und e<strong>in</strong>facher darstellen könnte. Auf diese Art f<strong>in</strong>den sie das (hoffentlich)<br />
s<strong>in</strong>nvollste aus mehreren verschiedenen möglichen Modellen für e<strong>in</strong>e Problemstellung.<br />
Unerfahrenere Entwickler (und auch schlimme Hacker) greifen gerne zum<br />
ersten Modell, das ihnen logisch ersche<strong>in</strong>t und dieses wird dann ohne Rücksicht<br />
auf Verluste bis zum bitteren Ende durchgezogen. Dadurch werden allerd<strong>in</strong>gs<br />
Programme zumeist unglaublich kompliziert und das Resultat lässt<br />
jedes logische semantische Modell vermissen. Somit ist der böse Hack perfekt,<br />
den sich niemand mehr zu ändern traut, denn jede Änderung an e<strong>in</strong>em<br />
Teil der Software wirkt sich mörderisch <strong>in</strong> anderen Teilen derselben aus.<br />
Ich weiß schon, dass hier wieder e<strong>in</strong>mal e<strong>in</strong> bisschen der Missionar <strong>in</strong><br />
puncto sauberes Design mit mir durchgegangen ist, aber die Gelegenheit war<br />
e<strong>in</strong>fach zu gut :-). Also zurück zum Thema: Nehmen wir e<strong>in</strong>fach e<strong>in</strong>mal<br />
h<strong>in</strong>, dass die <strong>in</strong> Abbildung 9.2 skizzierte Klassenhierarchie für unser Problem<br />
das schlüssigste Modell darstellt. Nach dem bisherigen Wissensstand lässt<br />
es sich allerd<strong>in</strong>gs nicht <strong>in</strong> C ++ Code umsetzen, ohne dass sich der Compiler<br />
fürchterlich beschwert und die Übersetzung verweigert! Den Grund für diese<br />
Beschwerde f<strong>in</strong>den wir im Speicherabbild, das der Compiler aus unseren<br />
e<strong>in</strong>zelnen Klassen <strong>in</strong> der Ableitungshierarchie erzeugt:<br />
• Wir wissen, dass jede Klasse beim Ableiten alle Eigenschaften der Basis<br />
vollständig erbt.<br />
• Das bedeutet, dass beim Erzeugen der Instanz e<strong>in</strong>es Objekts für alle e<strong>in</strong>zelnen<br />
Teil-Objekte, die aus den Klassen <strong>in</strong> der Hierarchie resultieren, Speicher<br />
reserviert wird. Die Speicherbereiche bei Ableitungshierarchien kommen<br />
h<strong>in</strong>tere<strong>in</strong>ander zu liegen.<br />
• Nehmen wir nur die obersten beiden Ebenen der <strong>in</strong> Abbildung 9.2 skizzierten<br />
Hierarchie, so ergibt sich folgendes Bild:<br />
– E<strong>in</strong> TextuallyDisplayable Objekt besteht aus zwei Teilobjekten. Es<br />
wird also Speicher für Displayable und weiters Speicher für alles Weitere<br />
reserviert, das TextuallyDisplayable noch an Members dazudef<strong>in</strong>iert.<br />
– Für GraphicallyDisplayable und auch für Pr<strong>in</strong>table gilt dasselbe.<br />
• Beziehen wir nun auch noch die dritte Ebene der Hierarchie <strong>in</strong> unsere Betrachtungen<br />
mit e<strong>in</strong>, dann vervollständigt sich das Bild dah<strong>in</strong>gehend, dass<br />
für e<strong>in</strong>e GameCard folgende Sub-Objekte ihren Speicherblock bekommen:<br />
– TextuallyDisplayable<br />
– GraphicallyDisplayable<br />
– Pr<strong>in</strong>table<br />
Und genau hier liegt das Problem: Zieht man <strong>in</strong> Betracht, dass jedes dieser<br />
drei Sub-Objekte selbst wieder Displayable als Sub-Objekt besitzt, dann
238 9. Klassen <strong>in</strong> <strong>C++</strong><br />
bedeutet das ja, dass Displayable gleich drei Mal <strong>in</strong> e<strong>in</strong>- und demselben<br />
Objekt vorkommt! Dass das nicht im S<strong>in</strong>ne des Erf<strong>in</strong>ders se<strong>in</strong> kann, ist klar,<br />
denn eigentlich darf es nur e<strong>in</strong>mal vorkommen, sonst ist ke<strong>in</strong>e Konsistenz<br />
mehr erzielbar!<br />
Abgesehen vom Speicheraspekt, den man auch mit besonderer Berücksichtigung<br />
beim static B<strong>in</strong>d<strong>in</strong>g <strong>in</strong> den Griff bekommen könnte, würden mit e<strong>in</strong>er<br />
solchen Konstruktion noch weitere Probleme auftauchen, die nur noch<br />
durch dynamic B<strong>in</strong>d<strong>in</strong>g zu lösen s<strong>in</strong>d, z.B., dass natürlich der Konstruktor<br />
der Basisklasse nur e<strong>in</strong>mal und nicht drei Mal aufzurufen ist.<br />
Es ist also <strong>in</strong> e<strong>in</strong>em solchen Fall notwendig, dass die Basisklasse nur e<strong>in</strong>mal im<br />
Speicherabbild existiert und dass sich Member-Zugriffe und alle Methodenaufrufe<br />
<strong>in</strong>cl. Konstruktor und Destruktor konsistent auf diese Basis beziehen.<br />
Mittels dynamic B<strong>in</strong>d<strong>in</strong>g ist dies möglich, da <strong>in</strong> diesem Fall erst zur Laufzeit<br />
die dann bekannten und korrekten Adressen e<strong>in</strong>gesetzt werden. Die dafür<br />
notwendige virtuelle Ableitung erreicht man, <strong>in</strong>dem man bei der Deklaration<br />
e<strong>in</strong>er abgeleiteten Klasse das Keyword virtual zur Angabe der Basisklasse<br />
h<strong>in</strong>zufügt. Am besten, wir sehen uns e<strong>in</strong>e mögliche m<strong>in</strong>imale Implementation<br />
unserer <strong>in</strong> Abbildung 9.2 skizzierten Hierarchie an. Die folgende Implementation<br />
wird uns auch gleich Auskunft über e<strong>in</strong>e Besonderheit beim Konstruktoraufruf<br />
geben. Da das Programm virtual_derivation_demo.cpp etwas<br />
lang ist, werden <strong>in</strong> der Folge nur die e<strong>in</strong>zelnen Teile hier <strong>in</strong>kludiert und besprochen,<br />
die für unsere Betrachtungen Relevanz haben. Zuallererst sehen<br />
wir uns e<strong>in</strong>mal die Deklaration von Displayable an:<br />
37 class Displayable<br />
38 {<br />
39 public :<br />
40 Displayable ( ) ;<br />
41 Displayable ( <strong>in</strong>t whatever ) ;<br />
42 virtual ˜ Displayable ( ) ;<br />
43<br />
44 virtual const DisplayRep &getDisplayRep ( ) const = 0;<br />
45 } ;<br />
Nur zu Demonstrationszwecken zum Thema Besonderheit beim Konstruktoraufruf<br />
wurden <strong>in</strong> den Zeilen 40–41 zwei verschiedene Konstruktoren deklariert.<br />
In Zeile 44 wird e<strong>in</strong>e const Referenz auf e<strong>in</strong> Objekt vom Typ<br />
DisplayRep retourniert. Die Klasse DisplayRep entspricht e<strong>in</strong>fach e<strong>in</strong>er allgeme<strong>in</strong><br />
darstellbaren Repräsentation, von der dann wiederum die besonderen<br />
textuellen, graphischen, etc. Repräsentationen abgeleitet werden sollen. In<br />
unserem Testprogramm ist dies nicht implementiert, hier ist e<strong>in</strong>fach e<strong>in</strong>e<br />
Billig-Implementation verewigt, da e<strong>in</strong>e s<strong>in</strong>nvolle Implementation nichts zu<br />
den hier im Programm demonstrierten Aspekten beiträgt.<br />
Stellvertretend für die verschiedenen Arten von anzeigbaren Klassen werfen<br />
wir e<strong>in</strong>en Blick auf TextuallyDisplayable:
9.4 Weitere wichtige technische Aspekte 239<br />
55 class TextuallyDisplayable : public virtual Displayable<br />
56 {<br />
57 public :<br />
58 TextuallyDisplayable ( ) ;<br />
59 virtual ˜ TextuallyDisplayable ( ) ;<br />
60 } ;<br />
In Zeile 55 erkennt man, wie man e<strong>in</strong>e virtuelle Ableitung <strong>in</strong> C ++ formuliert.<br />
Ansonsten s<strong>in</strong>d hier ke<strong>in</strong>e erwähnenswerten Besonderheiten zu f<strong>in</strong>den.<br />
Die anderen direkt abgeleiteten Klassen, also GraphicallyDisplayable und<br />
Pr<strong>in</strong>table s<strong>in</strong>d analog deklariert. Die Klasse GameCard, die schließlich von<br />
den drei verschiedenen darstellbaren Klassen abgeleitet ist, liest sich folgendermaßen:<br />
100 class GameCard : public TextuallyDisplayable ,<br />
101 public GraphicallyDisplayable ,<br />
102 public Pr<strong>in</strong>table<br />
103 {<br />
104 protected :<br />
105 DisplayRep c u r r e n t d i s p l a y r e p ;<br />
106 public :<br />
107 GameCard ( ) ;<br />
108 virtual ˜GameCard ( ) ;<br />
109<br />
110 virtual const DisplayRep &getDisplayRep ( ) const ;<br />
111 } ;<br />
In den Implementationen der verschiedenen Konstruktoren und Destruktoren<br />
f<strong>in</strong>den wir wieder den schon altbekannten Output, der uns anzeigt, wer nun<br />
wann aufgerufen wird. Stellvertretend für diese werfen wir e<strong>in</strong>fach e<strong>in</strong>en Blick<br />
auf Konstruktor und Destruktor von TextuallyDisplayable:<br />
140 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
141 /∗ constructor<br />
142 ∗/<br />
143<br />
144 TextuallyDisplayable : : TextuallyDisplayable ( )<br />
145 {<br />
146 cout
240 9. Klassen <strong>in</strong> <strong>C++</strong><br />
198 GameCard : : GameCard ( ) : Displayable (17)<br />
199 {<br />
200 cout
9.4 Weitere wichtige technische Aspekte 241<br />
schon den zweifelhaften Luxus solcher schweren Designfehler geleistet und<br />
als Konsequenz zahlreiche aufwändige Workarounds verursacht (z.B. durch<br />
e<strong>in</strong>en solchen Fehler <strong>in</strong> e<strong>in</strong>er GUI-Klassenbibliothek). Als Grundregel zur<br />
Verwendung von virtual bei e<strong>in</strong>er Ableitung gilt:<br />
Leitet man getrennt vone<strong>in</strong>ander mehrere Klassen von e<strong>in</strong>- und<br />
derselben Basis ab und gibt es e<strong>in</strong>e s<strong>in</strong>nvolle Möglichkeit, dass<br />
Mehrfachvererbung mit diesen Klassen als Basis direkt oder <strong>in</strong>direkt<br />
stattf<strong>in</strong>den könnte, dann ist unbed<strong>in</strong>gt virtuell abzuleiten.<br />
9.4.4 Downcasts von Klassen<br />
Es wurde bereits mehrfach erwähnt, dass abgeleitete Klassen vollständig kompatibel<br />
zu ihrer bzw. ihren Basisklassen s<strong>in</strong>d und auch so angesprochen<br />
werden können. Aus diesem Grund gibt es ja auch den Mechanismus des<br />
dynamic B<strong>in</strong>d<strong>in</strong>gs von Methoden, um sicherzustellen, dass immer die “richtige”<br />
Methode, also die der letzten Ableitung, die e<strong>in</strong> Overrid<strong>in</strong>g vornimmt,<br />
aufgerufen wird. Diese impliziten oder expliziten Casts von Klassen auf e<strong>in</strong>e<br />
ihrer Basisklassen werden auch als Upcasts bezeichnet. Der Name kommt<br />
daher, dass Basisklassen, wie bereits bekannt, als darüber liegend <strong>in</strong> Bezug<br />
auf die Ableitungshierarchie betrachtet werden.<br />
Sp<strong>in</strong>nt man diesen Gedanken weiter, dann kommt man zur Erkenntnis,<br />
dass es ja auch das Gegenteil geben muss, nämlich e<strong>in</strong>en Downcast. Wurde<br />
z.B. e<strong>in</strong>e Referenz auf e<strong>in</strong> Objekt als Parameter bei e<strong>in</strong>em Methodenaufruf<br />
übergeben, wobei der Typ der Referenz e<strong>in</strong>er der Basisklassen entspricht,<br />
dann muss es doch möglich se<strong>in</strong>, über e<strong>in</strong>en Downcast wieder auf den “richtigen”<br />
Typ zuzugreifen. Man müsste ja nur dem Compiler erklären, dass er<br />
es <strong>in</strong> Wirklichkeit sowieso nicht mit der Basis, sondern mit e<strong>in</strong>em abgeleiteten<br />
Typ zu tun hat. Ich formuliere es e<strong>in</strong>mal vorsichtig: Downcasts s<strong>in</strong>d<br />
natürlich möglich und auch <strong>in</strong> verschiedenen Fällen sehr s<strong>in</strong>nvoll, aber nur,<br />
wenn man genau weiß, was man tut! Bevor ich mich aber jetzt über die<br />
Gefahren von Downcasts auslasse, die vor allem dann ganz besonders lauern,<br />
wenn man e<strong>in</strong> Fehldesign auf diese Art zu kaschieren versucht, sehen<br />
wir uns e<strong>in</strong>fach e<strong>in</strong>mal an e<strong>in</strong>em Beispielchen an, wie dieser Mechanismus<br />
funktioniert (downcast_demo.cpp):<br />
1 // downcast demo . cpp − small example to demonstrate downcasts<br />
2<br />
3 #<strong>in</strong>clude <br />
4 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
5<br />
6 us<strong>in</strong>g std : : cout ;<br />
7 us<strong>in</strong>g std : : endl ;<br />
8<br />
9 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
10 /∗<br />
11 ∗ Event<br />
12 ∗<br />
13 ∗ base c l a s s f o r events
242 9. Klassen <strong>in</strong> <strong>C++</strong><br />
14 ∗<br />
15 ∗/<br />
16<br />
17 class Event<br />
18 {<br />
19 protected :<br />
20 <strong>in</strong>t32 event type ;<br />
21 public :<br />
22 static const <strong>in</strong>t32 KEY EVENT = 0x1 ;<br />
23 static const <strong>in</strong>t32 MOUSE EVENT = 0x2 ;<br />
24<br />
25 Event ( <strong>in</strong>t event type ) { event type = event type ; }<br />
26 virtual ˜ Event ( ) { }<br />
27<br />
28 <strong>in</strong>t32 getType ( ) const { return ( event type ) ; }<br />
29 } ;<br />
30<br />
31 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
32 /∗<br />
33 ∗ KeyEvent<br />
34 ∗<br />
35 ∗ c l a s s f o r Keyboard Events<br />
36 ∗<br />
37 ∗/<br />
38<br />
39 class KeyEvent : public Event<br />
40 {<br />
41 protected :<br />
42 char key ;<br />
43 public :<br />
44 KeyEvent ( char key ) ;<br />
45<br />
46 char getKey ( ) const { return ( key ) ; }<br />
47 } ;<br />
48<br />
49 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
50 /∗<br />
51 ∗ MouseEvent<br />
52 ∗<br />
53 ∗ c l a s s f o r Mouse Events<br />
54 ∗<br />
55 ∗/<br />
56<br />
57 class MouseEvent : public Event<br />
58 {<br />
59 protected :<br />
60 <strong>in</strong>t32 x pos ;<br />
61 <strong>in</strong>t32 y pos ;<br />
62 <strong>in</strong>t32 button mask ;<br />
63 public :<br />
64 static const <strong>in</strong>t32 RIGHT BUTTON = 0x1 ;<br />
65 static const <strong>in</strong>t32 MIDDLE BUTTON = 0x2 ;<br />
66 static const <strong>in</strong>t32 LEFT BUTTON = 0x4 ;<br />
67<br />
68 MouseEvent ( <strong>in</strong>t32 x pos , <strong>in</strong>t32 y pos , <strong>in</strong>t32 button mask ) ;<br />
69<br />
70 <strong>in</strong>t32 getXPos ( ) const { return ( x pos ) ; }<br />
71 <strong>in</strong>t32 getYPos ( ) const { return ( y pos ) ; }<br />
72 bool isLeftButtonPressed ( ) const<br />
73 {<br />
74 return ( button mask & LEFT BUTTON) ;<br />
75 }<br />
76 } ;<br />
77<br />
78 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
79 /∗
80 ∗ EventHandler<br />
81 ∗<br />
82 ∗ c l a s s f o r Event Handlers<br />
83 ∗<br />
84 ∗/<br />
85<br />
86 class EventHandler<br />
87 {<br />
88 public :<br />
89 virtual ˜ EventHandler ( ) { } ;<br />
90<br />
91 virtual void handleEvent ( const Event &event ) ;<br />
92 } ;<br />
93<br />
94<br />
95 const <strong>in</strong>t32 Event : :KEY EVENT;<br />
96 const <strong>in</strong>t32 Event : :MOUSE EVENT;<br />
97<br />
98 const <strong>in</strong>t32 MouseEvent : :RIGHT BUTTON;<br />
99 const <strong>in</strong>t32 MouseEvent : :MIDDLE BUTTON;<br />
100 const <strong>in</strong>t32 MouseEvent : :LEFT BUTTON;<br />
101<br />
9.4 Weitere wichtige technische Aspekte 243<br />
102 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
103 /∗<br />
104 ∗ @param key the key that was pressed<br />
105 ∗/<br />
106<br />
107 KeyEvent : : KeyEvent ( char key ) : Event (KEY EVENT)<br />
108 {<br />
109 key = key ;<br />
110 }<br />
111<br />
112 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
113 /∗<br />
114 ∗ @param x pos the x p o s i t i o n of the mouse po<strong>in</strong>ter<br />
115 ∗ @param y pos the y p o s i t i o n of the mouse po<strong>in</strong>ter<br />
116 ∗ @param button mask the buttons that were pressed<br />
117 ∗/<br />
118<br />
119 MouseEvent : : MouseEvent ( <strong>in</strong>t32 x pos , <strong>in</strong>t32 y pos ,<br />
120 <strong>in</strong>t32 button mask ) : Event (MOUSE EVENT)<br />
121 {<br />
122 x pos = x pos ;<br />
123 y pos = y pos ;<br />
124 button mask = button mask ;<br />
125 }<br />
126<br />
127 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
128 /∗<br />
129 ∗ @param the event to be handled the event to be handled<br />
130 ∗<br />
131 ∗/<br />
132<br />
133 void EventHandler : : handleEvent ( const Event &event )<br />
134 {<br />
135 switch ( event . getType ( ) )<br />
136 {<br />
137 case Event : :KEY EVENT:<br />
138 {<br />
139 const KeyEvent &key event =<br />
140 dynamic cast(event ) ;<br />
141 cout
244 9. Klassen <strong>in</strong> <strong>C++</strong><br />
146 {<br />
147 const MouseEvent &mouse event =<br />
148 dynamic cast(event ) ;<br />
149 cout
9.4 Weitere wichtige technische Aspekte 245<br />
Nun ist es aber so, dass der übergebene Parameter event vom Typ Event<br />
ist. Dort s<strong>in</strong>d also die speziellen Methoden der davon abgeleiteten Klassen<br />
KeyEvent und MouseEvent noch nicht bekannt, denn diese werden ja erst<br />
<strong>in</strong> den abgeleiteten Klassen deklariert. Also können sie direkt auf event<br />
natürlich auch nicht aufgerufen werden. Der Compiler würde ja z.B. bei<br />
e<strong>in</strong>em Aufruf<br />
event.getKey();<br />
gar nicht sehr freundlich reagieren und anmerken, dass man sich gefälligst e<strong>in</strong>mal<br />
die Deklaration der Klasse Event ansehen soll und nur D<strong>in</strong>ge verwenden<br />
soll, die tatsächlich dort deklariert s<strong>in</strong>d.<br />
Jedoch ist aufgrund des Event-Typs, der <strong>in</strong> Zeile 135 abgefragt wird, sehr<br />
wohl bekannt, dass es sich z.B. wirklich um e<strong>in</strong>en KeyEvent handelt, auch<br />
wenn dieser nur als Reference auf e<strong>in</strong>en Event übergeben wurde. Also überzeugt<br />
man den Compiler, dass man schon weiß, was man tut und setzt e<strong>in</strong>en<br />
expliziten Downcast auf KeyEvent e<strong>in</strong>, wie <strong>in</strong> den Zeilen 139–140 zu sehen ist.<br />
Und genau hier sehen wir zum ersten Mal die wahre Natur von dynamic_cast:<br />
Bei e<strong>in</strong>em dynamic_cast wird zur Laufzeit e<strong>in</strong>e Überprüfung vorgenommen,<br />
ob diese Umwandlung überhaupt zulässig ist. Wenn ja, dann<br />
wird die Umwandlung durchgeführt, wenn ne<strong>in</strong>, dann endet das Programm<br />
<strong>in</strong> e<strong>in</strong>em Fehler – nun ja, muss nicht se<strong>in</strong>, wenn man weiß, wie man das Ende<br />
des Programms verh<strong>in</strong>dert. In Wahrheit wird e<strong>in</strong>e Exception geworfen und<br />
zwar e<strong>in</strong>e bad_cast Exception. Wie man mit solchen Exceptions umgeht, ist<br />
Thema von Kapitel 11. Was es ganz genau mit dieser besonderen Exception<br />
auf sich hat, wird <strong>in</strong> Abschnitt 15.6 noch näher besprochen. Zurück zum<br />
Thema: Nach e<strong>in</strong>em zulässigen dynamic_cast, der nicht <strong>in</strong> e<strong>in</strong>er Exception<br />
endet, haben wir also sichergestellt, dass wir es tatsächlich mit e<strong>in</strong>em Objekt<br />
vom Typ KeyEvent zu tun haben und können damit auch getKey darauf<br />
aufrufen.<br />
Zu Beg<strong>in</strong>n der Diskussion über Downcasts habe ich schon erwähnt, dass<br />
man wirklich genau wissen muss, was man tut. Leider gibt es nicht allzu<br />
wenige Entwickler, bei denen dieses Wissen eher rudimentär vorhanden ist,<br />
die aber trotzdem Datentypen mittels Downcasts munter durch die Gegend<br />
wandeln und so die schönsten Zeitbomben <strong>in</strong> ihre Software e<strong>in</strong>bauen. In<br />
altbekannter Manier möchte ich daher die häufigsten Fehlerquellen <strong>in</strong> der<br />
Folge aufzeigen.<br />
Vorsicht Falle: Downcasts sollten pr<strong>in</strong>zipiell nur dann verwendet werden,<br />
wenn sie unbed<strong>in</strong>gt notwendig s<strong>in</strong>d. Oft passiert es, dass bereits e<strong>in</strong> Fehldesign<br />
vorliegt, das auf die Schnelle durch e<strong>in</strong>en Downcast versteckt werden<br />
kann (z.B. unnötig falscher Parameter <strong>in</strong> Methodenaufruf, etc.). Weil aber<br />
Downcasts pr<strong>in</strong>zipiell e<strong>in</strong>e Gefahr darstellen, sollte man darüber nachdenken,<br />
ob man vielleicht auch e<strong>in</strong>e saubere Lösung ohne Downcasts f<strong>in</strong>den kann. Das<br />
bedeutet natürlich nicht, dass man Downcasts auf Kosten der Modularität,<br />
Erweiterbarkeit und Allgeme<strong>in</strong>heit e<strong>in</strong>es Programms vermeiden muss, aber
246 9. Klassen <strong>in</strong> <strong>C++</strong><br />
e<strong>in</strong>en Gedanken ist es schon wert, ob man nicht e<strong>in</strong>e genauso schöne Lösung<br />
ohne Downcasts f<strong>in</strong>det.<br />
Vorsicht Falle: Nur allzu oft werden abstruseste Annahmen getroffen, dass<br />
“sowieso nur e<strong>in</strong> Objekt von genau diesem Typ” hier vorkommen kann. Solange<br />
dies allerd<strong>in</strong>gs nicht durch irgende<strong>in</strong>e Maßnahme (z.B. <strong>in</strong> unserem Beispiel<br />
das <strong>in</strong>terne Speichern des Typs) sichergestellt ist, können durch Programmfehler<br />
oder nach Programmänderungen die tollsten Effekte e<strong>in</strong>treten.<br />
Und e<strong>in</strong> Programm nach Downcast-Fehlern zu durchsuchen, hat bei Entwicklern<br />
üblicherweise akute Magenschmerzen gepaart mit übertriebenem Haarausfall<br />
durch äußere E<strong>in</strong>wirkung von stark zupackenden und an den Haaren<br />
ziehenden Händen zur Folge.<br />
Vorsicht Falle: Auch wenn e<strong>in</strong>e Maßnahme die korrekte Typwandlung<br />
quasi sicherstellt, ist man noch lange nicht vor “echten” Programmfehlern<br />
geschützt. Wer sagt, dass nicht aufgrund e<strong>in</strong>es Fehlers e<strong>in</strong>mal e<strong>in</strong> falscher Typ<br />
<strong>in</strong> der Variable vermerkt wird (typische Copy-and-Paste Folgeersche<strong>in</strong>ung)?<br />
Solche Fehler können nur durch ausführliches und s<strong>in</strong>nvolles Testen gefunden<br />
und behoben werden. Trotzdem muss e<strong>in</strong> entsprechender Fehlerbehandlungscode<br />
immer im Programm erhalten bleiben, denn auch ausführliches Testen<br />
schützt noch nicht zu 100%!<br />
Vorsicht Falle: Die Funktionsweise der verschiedenen Cast-Operatoren<br />
wird leider sehr oft nicht vollständig durchschaut, bzw. deren Existenz überhaupt<br />
ignoriert und mit dem “guten alten” C-Style Cast gearbeitet. Es gibt<br />
aber nicht von ungefähr die verschiedenen Cast-Operatoren. Durch den E<strong>in</strong>satz<br />
der “falschen” Operatoren lassen sich herrliche Zeitbomben basteln. In<br />
der Folge wird der Unterschied zwischen den e<strong>in</strong>zelnen Operatoren noch e<strong>in</strong>mal<br />
genau umrissen. Als Grundregel für Downcasts möchte ich den Lesern<br />
Folgendes auf den Weg geben:<br />
Pr<strong>in</strong>zipiell ist bei Downcasts e<strong>in</strong> dynamic_cast zu verwenden.<br />
Um das Bild abzurunden, hier noch e<strong>in</strong>mal e<strong>in</strong>e Zusammenfassung, was<br />
die e<strong>in</strong>zelnen Cast-Operatoren genau tun. Ich lasse <strong>in</strong> dieser Zusammenfassung<br />
bewusst den const_cast und den C-Style Cast aus, denn diese s<strong>in</strong>d<br />
für unsere Betrachtungen hier belanglos. Leser, die sich mittlerweile nicht<br />
mehr so sicher s<strong>in</strong>d, was diese beiden tun, möchte ich auf Abschnitt 3.6.5<br />
und Abschnitt 3.6.6 verweisen.<br />
dynamic_cast: Dieser Cast überprüft zur Laufzeit des Programms, ob e<strong>in</strong>e<br />
Umwandlung zum gewünschten Typ tatsächlich möglich ist. Wenn<br />
ne<strong>in</strong>, dann bewirkt bereits die Anweisung mit der Anwendung des Casts<br />
e<strong>in</strong>en Laufzeitfehler.
9.4 Weitere wichtige technische Aspekte 247<br />
static_cast: Dieser Cast überprüft zur Compilezeit, ob e<strong>in</strong>e Umwandlung<br />
zu e<strong>in</strong>em gewünschten Typ theoretisch möglich se<strong>in</strong> könnte.<br />
Wenn ja, wird der entsprechende Code e<strong>in</strong>gesetzt. Die Anwendung des<br />
Casts alle<strong>in</strong> führt zur Laufzeit noch zu ke<strong>in</strong>em Fehler. Erst falscher Zugriff<br />
auf Members (Variablen oder Methoden) e<strong>in</strong>es illegal gewandelten<br />
Typs führen dann direkt zu e<strong>in</strong>er Segmentation Violation.<br />
re<strong>in</strong>terpret_cast: Dieser Cast nimmt überhaupt ke<strong>in</strong>e Überprüfung<br />
vor, ob e<strong>in</strong>e Umwandlung überhaupt möglich se<strong>in</strong> könnte. Entsprechende<br />
Fehl<strong>in</strong>terpretationen fallen zur Compilezeit sicherlich nicht auf und zur<br />
Laufzeit machen sie sich <strong>in</strong> gewohnter Manier durch e<strong>in</strong>e Segmentation<br />
Violation bemerkbar.<br />
Genau <strong>in</strong> diesem Verhalten der Cast-Operatoren ist bed<strong>in</strong>gt, dass ich zuerst<br />
die starke Empfehlung gegeben habe, bei Downcasts <strong>in</strong> jedem Fall e<strong>in</strong>en<br />
dynamic_cast zu verwenden. Sollte es hier zum Problem kommen, dann<br />
passiert der Fehler genau dort, wo die Umwandlung stattf<strong>in</strong>det. Würde man<br />
stattdessen e<strong>in</strong>en static_cast nehmen, dann überprüft der Compiler kurz<br />
die Ableitungshierarchie auf entsprechende Möglichkeiten. Sollte die Wandlung<br />
theoretisch möglich se<strong>in</strong>, so wird der entsprechende Code e<strong>in</strong>gesetzt. Zur<br />
Laufzeit allerd<strong>in</strong>gs wird beim Umwandeln ke<strong>in</strong>e Überprüfung mehr vorgenommen<br />
und das Programm fällt dann unmotiviert bei irgende<strong>in</strong>er sche<strong>in</strong>bar<br />
korrekten Anweisung auf die Nase. Das bedeutet also, dass man bei e<strong>in</strong>em<br />
fehlgeschlagenen dynamic_cast Ursache und Auswirkung mite<strong>in</strong>ander gekoppelt<br />
hat und solche Fehler daher leicht f<strong>in</strong>det. Bei e<strong>in</strong>em static_cast s<strong>in</strong>d<br />
Ursache und Auswirkung entkoppelt und die Fehlersuche wird zum nächtlichen<br />
Vergnügen.<br />
9.4.5 Friends von Klassen<br />
Manchmal (wirklich nur manchmal!!!) gibt es Fälle, <strong>in</strong> denen trotz e<strong>in</strong>es<br />
sauberen Designs die <strong>in</strong> C ++ verfügbaren Access-Specifiers nicht ausreichen,<br />
um e<strong>in</strong>e vernünftige Kapselung des Zugriffs vorzunehmen. Solche Fälle treten<br />
dann auf, wenn z.B. e<strong>in</strong>e besondere Funktion (nicht Methode!) oder<br />
alle Methoden e<strong>in</strong>er bestimmten Klasse Zugriff auf Interna e<strong>in</strong>er bestimmten<br />
Klasse erhalten sollen. Mittels public würde man den Zugriff für alle Außenstehenden<br />
freigeben, das will man nicht. Man will den Zugriff nur ganz<br />
gezielt erlauben. Hierzu gibt es <strong>in</strong> C ++ die sogenannten friend Deklarationen.<br />
Wie der Name schon sagt, erklärt man damit bestimmte Außenstehende<br />
zu freundlich Ges<strong>in</strong>nten, die schon nichts Böses anrichten werden und denen<br />
deshalb der Zugriff auch auf die privatesten Bereiche gestattet wird.<br />
Vorsicht Falle: Dass man mit der Annahme der freundlichen Ges<strong>in</strong>nung<br />
vorsichtig se<strong>in</strong> muss, versteht sich von selbst. Es gibt <strong>in</strong> der <strong>Softwareentwicklung</strong>,<br />
so wie auch im realen Leben, auch immer wieder falsche Freunde.<br />
Nichts lässt bei Entwicklern mehr Freude aufkommen, als wenn sie Fehler
248 9. Klassen <strong>in</strong> <strong>C++</strong><br />
suchen müssen, die von Außenstehenden verursacht werden, denen Zugriffe<br />
gestattet wurden, vor denen man sich üblicherweise aus gutem Grund<br />
schützt.<br />
Um für die Fälle, bei denen es wirklich unumgänglich ist, zu wissen, wie<br />
man den Zugriff für Außenstehende gestattet, werfen wir e<strong>in</strong>en Blick auf das<br />
folgende Beispiel (friend_demo.cpp):<br />
1 // friend demo . cpp − demo f o r the use o f the f r i e n d d e c l .<br />
2<br />
3 #<strong>in</strong>clude <br />
4<br />
5 us<strong>in</strong>g std : : cout ;<br />
6 us<strong>in</strong>g std : : endl ;<br />
7<br />
8 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
9 /∗<br />
10 ∗ MyClass<br />
11 ∗<br />
12 ∗ j u s t a demo c l a s s<br />
13 ∗<br />
14 ∗/<br />
15<br />
16 class MyClass<br />
17 {<br />
18 friend class MyGoodFriend ;<br />
19 friend void myGoodFriendFunction ( MyClass &obj ) ;<br />
20 private :<br />
21 <strong>in</strong>t my private member ;<br />
22 <strong>in</strong>t myPrivateMethod ( ) { return ( my private member ) ; }<br />
23 public :<br />
24 MyClass ( ) { my private member = 2 0; }<br />
25 } ;<br />
26<br />
27 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
28 /∗<br />
29 ∗ MyDerivedClass<br />
30 ∗<br />
31 ∗ j u s t another demo c l a s s<br />
32 ∗<br />
33 ∗/<br />
34<br />
35 class MyDerivedClass : public MyClass<br />
36 {<br />
37 private :<br />
38 <strong>in</strong>t really private member ;<br />
39 <strong>in</strong>t reallyPrivateMethod ( ) { return ( really private member ) ; }<br />
40 public :<br />
41 MyDerivedClass ( ) { really private member = 7 0; }<br />
42 } ;<br />
43<br />
44 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
45 /∗<br />
46 ∗ MyGoodFriend<br />
47 ∗<br />
48 ∗ j u s t a demo f o r a f r i e n d c l a s s f o r MyClass<br />
49 ∗<br />
50 ∗/<br />
51<br />
52 class MyGoodFriend<br />
53 {<br />
54 public :<br />
55 void showAccessToMyClass ( MyClass &obj )
9.4 Weitere wichtige technische Aspekte 249<br />
56 {<br />
57 cout
250 9. Klassen <strong>in</strong> <strong>C++</strong><br />
Betrachtet man die Zeilen 55–60 und die Zeilen 88–93, dann sieht man<br />
gleich, dass es für gute Freunde möglich ist, auch auf private Bereiche e<strong>in</strong>er<br />
Klasse zuzugreifen.<br />
E<strong>in</strong>e sehr wichtige E<strong>in</strong>schränkung gilt allerd<strong>in</strong>gs für friend Deklarationen:<br />
Die Eigenschaft, e<strong>in</strong> friend zu se<strong>in</strong>, wird nicht an abgeleitete Klassen<br />
vererbt, wie man an der Klasse NotAGoodFriend <strong>in</strong> den Zeilen 71–82 sehen<br />
kann. Die dort auskommentierten Statements würden <strong>in</strong> e<strong>in</strong>em Compilerfehler<br />
enden.<br />
Andersrum gesehen bleibt der Zugriff auf die Basis erhalten, auch wenn<br />
e<strong>in</strong>e abgeleitete Klasse gewisse Freunde nicht mehr hat, wie man <strong>in</strong> den Zeilen<br />
107–109 sieht. Natürlich s<strong>in</strong>d hierbei die Zugriffe nur auf die Bereiche der<br />
Basisklasse erlaubt, nicht auf private Bereiche der Ableitung. Dazu müsste<br />
die abgeleitete Klasse selbst bestimmte friend Deklarationen enthalten.<br />
9.4.6 Overload<strong>in</strong>g von const und non-const Methoden<br />
Es wurde bereits besprochen, dass const Methoden e<strong>in</strong>en Sonderstatus e<strong>in</strong>nehmen,<br />
da bei Aufruf derselben das Objekt, auf dem sie aufgerufen werden,<br />
nicht verändert werden darf. Der Sonderstatus schlägt sich auch <strong>in</strong> der Signatur<br />
der Methoden nieder, denn const ist e<strong>in</strong> Teil derselben. Wenn man diesen<br />
Gedanken weitersp<strong>in</strong>nt, dann kann man leicht erkennen, dass e<strong>in</strong> Overload<strong>in</strong>g<br />
e<strong>in</strong>er const Methode durch e<strong>in</strong>e non-const Methode mit genau demselben<br />
Parametersatz möglich ist. Der Compiler geht mit dieser Situation folgendermaßen<br />
um:<br />
• Wird e<strong>in</strong>e Methode auf e<strong>in</strong>em Objekt aufgerufen, das nicht durch const<br />
vor schreibendem Zugriff geschützt ist, so wird versucht, e<strong>in</strong>e non-const<br />
Methode zu f<strong>in</strong>den, die der Signatur des Aufrufs entspricht.<br />
• Wird ke<strong>in</strong>e solche non-const Methode gefunden, so wird nach e<strong>in</strong>er entsprechenden<br />
const Methode gesucht. Dieses Verhalten ist auch vollkommen<br />
logisch, denn e<strong>in</strong>e const Methode auf e<strong>in</strong>em nicht konstanten Objekt<br />
aufzurufen ist natürlich ke<strong>in</strong> Problem.<br />
• Wird e<strong>in</strong>e Methode auf e<strong>in</strong>em const Objekt aufgerufen, dann wird natürlich<br />
nur e<strong>in</strong>e entsprechende const Methode akzeptiert.<br />
Es ist also möglich, Methoden <strong>in</strong> zwei verschiedenen Ausprägungen zu schreiben,<br />
die, je nachdem, ob e<strong>in</strong> Objekt selbst nun const ist, oder nicht, aufgerufen<br />
werden. Auf diese Art kann man <strong>in</strong> speziellen Fällen Optimierungsmaßnahmen<br />
implementieren. Im folgenden Beispiel sieht man, wie sich e<strong>in</strong><br />
solches Overload<strong>in</strong>g auswirkt (const_non_const_overload<strong>in</strong>g_demo.cpp):<br />
1 // const non const overload<strong>in</strong>g demo . cpp − demo f o r overload<strong>in</strong>g<br />
2 // o f const and non−const methods<br />
3<br />
4 #<strong>in</strong>clude <br />
5 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
6
7 us<strong>in</strong>g std : : cout ;<br />
8 us<strong>in</strong>g std : : endl ;<br />
9<br />
9.4 Weitere wichtige technische Aspekte 251<br />
10 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
11 /∗<br />
12 ∗ DummyClass<br />
13 ∗<br />
14 ∗ j u s t a dummy f o r demo purposes<br />
15 ∗<br />
16 ∗/<br />
17<br />
18 class DummyClass<br />
19 {<br />
20 public :<br />
21 virtual void writeOutput ( ) const<br />
22 {<br />
23 cout
252 9. Klassen <strong>in</strong> <strong>C++</strong><br />
7 ∗<br />
8 ∗ j u s t a c l a s s with only a non−d e f a u l t constructor<br />
9 ∗<br />
10 ∗/<br />
11<br />
12 class OneClass<br />
13 {<br />
14 public :<br />
15 OneClass ( <strong>in</strong>t just for demo ) {}<br />
16 } ;<br />
17<br />
18 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
19 /∗<br />
20 ∗ AnotherClass<br />
21 ∗<br />
22 ∗ j u s t a c l a s s with only a non−d e f a u l t constructor<br />
23 ∗<br />
24 ∗/<br />
25<br />
26 class AnotherClass<br />
27 {<br />
28 OneClass just a member ;<br />
29 public :<br />
30 AnotherClass ( ) : just a member (17) {}<br />
31 } ;<br />
32<br />
33 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
34 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
35 {<br />
36 AnotherClass j u s t a v a r i a b l e ;<br />
37 return ( 0 ) ;<br />
38 }<br />
Man sieht, dass OneClass <strong>in</strong> den Zeilen 12–16 so deklariert ist, dass sie<br />
ke<strong>in</strong>en default Konstruktor besitzt. Die Klasse AnotherClass <strong>in</strong> den Zeilen<br />
26–31 wiederum hat e<strong>in</strong>en Member vom Typ OneClass. Jetzt stellt sich<br />
natürlich die Frage, welchen Code der Compiler beim Erzeugen e<strong>in</strong>er Instanz<br />
dieses Members e<strong>in</strong>setzen soll, denn diese Instanz des Members wird ja automatisch<br />
beim Erstellen e<strong>in</strong>er Instanz von AnotherClass erstellt. Irgendwo<br />
müssen wir dem Compiler also mitteilen, welchen Konstruktoraufruf er für<br />
just_a_member_ zu tätigen hat. Das geschieht nach altbekannter Manier<br />
im Konstruktor von AnotherClass, angegeben h<strong>in</strong>ter dem obligatorischen<br />
Doppelpunkt, wie man <strong>in</strong> Zeile 30 sieht. Der Unterschied zum expliziten<br />
Konstruktoraufruf e<strong>in</strong>er Basisklasse ist der, dass hier natürlich nicht der<br />
Klassenname der zu konstruierenden Klasse geschrieben wird, sondern der<br />
entsprechende Variablenname des zu <strong>in</strong>itialisierenden Members. In unserem<br />
Fall also wird just_a_member_ mit dem Wert 17 als Parameter <strong>in</strong>itialisiert.<br />
Sollte es e<strong>in</strong>en default Konstruktor geben, dann hätte man theoretisch<br />
die Wahl zwischen dieser hier gezeigten Art, e<strong>in</strong>en besonderen Konstruktor<br />
aufzurufen und der Möglichkeit, es beim default Konstruktor zu belassen und<br />
danach e<strong>in</strong>e explizite Zuweisung im eigenen Konstruktor vorzunehmen. Es<br />
ist allerd<strong>in</strong>gs guter Programmierstil, die hier gezeigte Konstruktor Aufrufschreibweise<br />
zu verwenden, da dadurch explizit klar ist, dass es sich um e<strong>in</strong>e<br />
Initialisierung handelt und nicht um e<strong>in</strong>e “e<strong>in</strong>fache Zuweisung”.
9.4 Weitere wichtige technische Aspekte 253<br />
Vorsicht Falle: Bei Neul<strong>in</strong>gen gibt es öfters großes Staunen, warum sich der<br />
Compiler beschwert, dass er e<strong>in</strong>en Member e<strong>in</strong>er Klasse nicht <strong>in</strong>itialisieren<br />
kann. Sehr oft liegt der Grund dar<strong>in</strong>, dass vergessen wurde, im Konstruktor<br />
der Klasse die notwendige explizite Initialisierung bei nicht-Vorhandense<strong>in</strong><br />
e<strong>in</strong>es default Konstruktors dieses Members anzugeben. E<strong>in</strong> Blick auf die<br />
Deklaration der Klasse des problematischen Members lohnt sich zumeist, um<br />
hier Klarheit zu schaffen.<br />
An dieser Stelle muss noch erwähnt werden, dass die spezielle Schreibweise<br />
zur Initialisierung nicht nur für benutzerdef<strong>in</strong>ierte Objekte funktioniert,<br />
sondern auch auf primitive Datentypen anwendbar ist. Aus Gründen der<br />
E<strong>in</strong>heitlichkeit und aus anderen, noch viel wichtigeren Gründen, die <strong>in</strong> Kapitel<br />
11 noch genauer beleuchtet werden, wird dr<strong>in</strong>gend empfohlen, diese<br />
Art der Initialisierung der e<strong>in</strong>fachen Zuweisung <strong>in</strong>nerhalb e<strong>in</strong>es Konstruktors<br />
vorzuziehen.<br />
9.4.8 Temporäre Objekte<br />
Temporäre Objekte s<strong>in</strong>d Objekte, die nicht über e<strong>in</strong>en Variablennamen ansprechbar<br />
s<strong>in</strong>d, sondern nur als Speicher für Zwischenergebnisse bei der Abarbeitung<br />
von Expressions dienen. Sobald die entsprechende Expression ausgewertet<br />
ist, werden diese temporären Objekte wieder verworfen. Wenn sie<br />
schon ke<strong>in</strong>e echten Variablen s<strong>in</strong>d und auch nicht über e<strong>in</strong>en Variablennamen<br />
angesprochen werden können, wie entstehen und verschw<strong>in</strong>den sie dann?<br />
Ganz e<strong>in</strong>fach: Dafür ist e<strong>in</strong>zig und alle<strong>in</strong> der Compiler verantwortlich. Am<br />
leichtesten ist dies anhand der Auswertung von mathematischen Ausdrücken<br />
zu verstehen. Analysieren wir e<strong>in</strong>fach e<strong>in</strong>mal die schrittweise Abarbeitung<br />
der Expression<br />
result = var1 + var2 + (var3 + var4) * var5;<br />
Es ist belanglos, welchen Datentyp unsere e<strong>in</strong>zelnen Variablen und das Ergebnis<br />
haben, der Compiler wertet den Ausdruck pr<strong>in</strong>zipiell so aus:<br />
1. Es wird die Addition von var1 und var2 durchgeführt. Das Ergebnis<br />
dieser Addition wird <strong>in</strong> e<strong>in</strong>em temporären Objekt abgelegt, nennen wir<br />
es e<strong>in</strong>fach e<strong>in</strong>mal temp1. Was sollte der Compiler auch sonst mit dem<br />
Ergebnis tun? Er darf weder var1 noch var2 verändern, also muss er<br />
das Ergebnis irgendwo anders zwischenspeichern.<br />
2. Im nächsten Schritt wird die Addition von var3 und var4 vorgenommen.<br />
Das Resultat landet zwangsweise wieder <strong>in</strong> e<strong>in</strong>em temporären Objekt, das<br />
wir hier als temp2 bezeichnen wollen.<br />
3. Das temporäre Objekt temp2 wird mit var5 multipliziert, was erneut e<strong>in</strong><br />
temporäres Objekt ergibt. Nennen wir dieses temp3.<br />
4. Nun werden temp1 und temp3 addiert, was erneut e<strong>in</strong> temporäres Objekt<br />
ergibt, das wir mit temp4 bezeichnen wollen.
254 9. Klassen <strong>in</strong> <strong>C++</strong><br />
5. Im letzten Schritt der Auswertung der Expression wird nun temp4 an<br />
result zugewiesen.<br />
6. Da jetzt die gesamte Expression fertig abgearbeitet ist, sorgt der Compiler<br />
dafür, dass die Geister, die er rief, nämlich die temporären Objekte,<br />
auch wieder aufgeräumt werden.<br />
Ich möchte hier noch anmerken, dass ich hier ganz bewusst den naiven Weg<br />
beschrieben habe und ke<strong>in</strong>erlei Rücksicht auf eventuelle Optimierungsmaßnahmen<br />
des Compilers genommen habe.<br />
Soweit es also Expressions wie die gerade analysierte betrifft, haben Entwickler<br />
mit dem Erzeugen und dem Zerstören von temporären Objekten nicht<br />
wirklich sehr viel zu tun. Nun ja, be<strong>in</strong>ahe – Wir werden dieses Thema <strong>in</strong><br />
Kapitel 12 noch e<strong>in</strong>mal kurz anreißen, und aus e<strong>in</strong>em anderen Blickw<strong>in</strong>kel<br />
betrachten. Es gibt jedoch auch Fälle, <strong>in</strong> denen wir zwar dem Compiler die<br />
Aufsicht über die Lifetime e<strong>in</strong>es temporären Objekts überlassen wollen, jedoch<br />
das temporäre Objekt selbst per Hand erzeugen wollen. E<strong>in</strong>e solche<br />
Situation ist an folgendem Beispiel zu sehen (temp_object_demo.cpp):<br />
1 // temp object demo . cpp − demo how to u t i l i z e temporary o b j e c t s<br />
2<br />
3 #<strong>in</strong>clude <br />
4 #<strong>in</strong>clude < c s t r i n g><br />
5<br />
6 us<strong>in</strong>g std : : cout ;<br />
7 us<strong>in</strong>g std : : endl ;<br />
8<br />
9 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
10 /∗<br />
11 ∗ Message<br />
12 ∗<br />
13 ∗ j u s t a dummy f o r demo purposes<br />
14 ∗<br />
15 ∗/<br />
16<br />
17 class Message<br />
18 {<br />
19 protected :<br />
20 char ∗ message ;<br />
21 public :<br />
22 Message ( const char ∗ message ) ;<br />
23 Message ( const Message & s r c ) ;<br />
24 virtual ˜ Message ( ) ;<br />
25<br />
26 virtual char ∗ g e t S t r i n g ( )<br />
27 {<br />
28 return ( message ) ;<br />
29 }<br />
30 } ;<br />
31<br />
32 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
33 Message : : Message ( const char ∗ message )<br />
34 {<br />
35 cout
41<br />
9.4 Weitere wichtige technische Aspekte 255<br />
42 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
43 Message : : Message ( const Message & s r c )<br />
44 {<br />
45 cout
256 9. Klassen <strong>in</strong> <strong>C++</strong><br />
3. Im Zuge der Abarbeitung dieses Aufrufs wird das temporäre Message<br />
Objekt erzeugt. Dies sieht man am Output des entsprechenden Konstruktors.<br />
4. Auf diesem Objekt wird die Methode getStr<strong>in</strong>g aufgerufen, die den<br />
entsprechenden Str<strong>in</strong>g liefert.<br />
5. Der gelieferte Str<strong>in</strong>g wird ausgegeben.<br />
6. Direkt auf den Str<strong>in</strong>g folgend wird auch noch der Zeilenumbruch ausgegeben.<br />
7. Damit ist die Expression fertig abgearbeitet und das temporäre Objekt<br />
wird plangemäß <strong>in</strong> die ewigen Jagdgründe geschickt, wie man am Output<br />
des Destruktors erkennt.
10. Memory – e<strong>in</strong> kle<strong>in</strong>es Beispiel<br />
Wie versprochen, möchte ich hier nach dem Kennenlernen der notwendigen<br />
Fakten zu Klassen <strong>in</strong> C ++ das Beispiel des Spiels Memory noch e<strong>in</strong>mal aufgreifen<br />
und vollständig von Anfang an implementieren. Am konkreten Beispiel<br />
ist es e<strong>in</strong>facher, e<strong>in</strong>mal alle Gedanken zu ordnen, die vielleicht zwischendurch,<br />
bei der Lektüre so e<strong>in</strong>iger technischer Details, durche<strong>in</strong>ander gekommen s<strong>in</strong>d.<br />
Zu e<strong>in</strong>er sauberen Entwicklung gehört natürlich e<strong>in</strong> sauberes Konzept. Dieses<br />
wird <strong>in</strong> der Praxis dadurch erreicht, dass man zuerst alle e<strong>in</strong>zelnen Use-Cases<br />
Stück für Stück ablaufmäßig erfasst und aus diesen die User-Requirements<br />
ableitet. Diese landen dann im URD (=User Requirements Document). Um<br />
nun niemanden zu sehr zu langweilen, erspare ich mir an dieser Stelle die<br />
Abhandlung, wie die Abläufe des Drückens auf e<strong>in</strong>e Taste, des Umdrehens<br />
aller Karten, des Umdrehens e<strong>in</strong>er e<strong>in</strong>zelnen Karte, etc. aus Sicht des Endbenutzers<br />
aussehen. Auch das SRD (=Software Requirements Document) ist an<br />
dieser Stelle nicht besonders spannend. An dieser Stelle bedeutet <strong>in</strong> diesem<br />
Kontext hier im Buch. Im Rahmen e<strong>in</strong>er Entwicklung (und ich me<strong>in</strong>e jeder<br />
Entwicklung!) s<strong>in</strong>d diese Dokumente sehr wohl spannend, um nicht zu sagen<br />
lebensnotwendig!<br />
Für die Beschreibung hier im Buch wird es beim ADD (=Architectural<br />
Design Document) wichtig. Dieses entsteht als Resultat aus URD und SRD<br />
und das ist auch genau der Punkt, an dem wir hier <strong>in</strong> die Entwicklung e<strong>in</strong>steigen.<br />
10.1 Das ADD<br />
Das ADD beschreibt im Pr<strong>in</strong>zip die <strong>in</strong>terne Architektur der zu erstellenden<br />
Software. Der wichtigste Teil der Arbeit bei der Erstellung e<strong>in</strong>er tragfähigen<br />
Softwarearchitektur besteht dar<strong>in</strong>, e<strong>in</strong> sauberes Modell zu f<strong>in</strong>den, das<br />
modular und damit durchschaubar und beherrschbar ist. Mit durchschaubar<br />
und beherrschbar me<strong>in</strong>e ich hier natürlich auch, dass diese Eigenschaften<br />
nicht nur bis zur ersten Version gegeben s<strong>in</strong>d, sondern, dass sie über alle Stufen<br />
späterer Weiterentwicklung erhalten bleiben. Je nachdem, wie weit man<br />
vorausgedacht hat, s<strong>in</strong>d bei späteren Anpassungen der Software auch Änderungen<br />
an der Architektur notwendig. Die <strong>in</strong> der Folge sehr kurz dargestellten<br />
Designentscheidungen entsprechen e<strong>in</strong>er von vielen möglichen sauberen
258 10. Memory – e<strong>in</strong> kle<strong>in</strong>es Beispiel<br />
Lösungen für e<strong>in</strong>e Architektur für Memory. Es mögen aber bitte alle Leser im<br />
H<strong>in</strong>terkopf behalten, dass wir hier noch nicht die ultimative Memory-Version<br />
entwickeln. Dazu würden noch e<strong>in</strong>ige Aspekte h<strong>in</strong>zukommen, die bisher nicht<br />
bekannt s<strong>in</strong>d.<br />
Ich möchte nochmals betonen, dass e<strong>in</strong> ADD niemals <strong>in</strong> e<strong>in</strong>em Stück von<br />
Anfang bis Ende geschrieben wird und dann e<strong>in</strong>fach passt. Im Normalfall<br />
s<strong>in</strong>d etliche Iterationsschritte vonnöten, um kle<strong>in</strong>ere oder größere Probleme<br />
befriedigend <strong>in</strong> den Griff zu bekommen. Je größer und komplexer die zu entwickelnde<br />
Software ist, desto mehr solche Schritte müssen bis zum Erreichen<br />
des Ziels durchgeführt werden. Es sollten sich die Leser des Buchs also nicht<br />
erwarten, dass sie zu e<strong>in</strong>er geforderten Software e<strong>in</strong>fach e<strong>in</strong> ADD aus dem<br />
Ärmel schütteln und damit gleich alles bestens erfüllt haben.<br />
10.1.1 Identifikation der Grobmodule<br />
E<strong>in</strong> wenig Überlegung über die <strong>in</strong>ternen Gegebenheiten und das Zusammenspiel<br />
verschiedener Faktoren im Programm ergibt e<strong>in</strong>mal e<strong>in</strong>e Identifikation<br />
funktionaler Module, die sich im Design wiederf<strong>in</strong>det:<br />
Input Handl<strong>in</strong>g: Das Input Handl<strong>in</strong>g besteht s<strong>in</strong>nigerweise daraus, dass Keyboard<br />
E<strong>in</strong>gaben der Benutzer entgegengenommen werden. Diese werden<br />
dann zuerst auf ihre S<strong>in</strong>nhaftigkeit untersucht. Im Fall, dass sie uns<strong>in</strong>nig<br />
s<strong>in</strong>d, werden sie mit e<strong>in</strong>em entsprechenden Fehler quittiert, ansonsten<br />
werden Aktionen getriggert. Das Triggern von Aktionen erfolgt über das<br />
Modul Spielsteuerung.<br />
Spielsteuerung: Die Spielsteuerung übernimmt die gesamte Steuerung des<br />
Ablaufs e<strong>in</strong>er Partie Memory. Sie triggert das Initialisieren des Spielfelds,<br />
das Auf- und Zudecken der Karten und alle Aktionen, die aufgrund von<br />
Benutzere<strong>in</strong>gaben stattf<strong>in</strong>den.<br />
Output Handl<strong>in</strong>g: Das Output Handl<strong>in</strong>g übernimmt die Aufgabe, das Spielfeld<br />
bei Aufruf im aktuellen Zustand anzuzeigen.<br />
Memory Spielfeld: Das Spielfeld übernimmt die Aufgabe, die e<strong>in</strong>zelnen Karten<br />
zu halten, die jedem E<strong>in</strong>zelfeld zugeordnet s<strong>in</strong>d.<br />
Memory Karte: Es gibt im Spiel e<strong>in</strong>e gewisse Anzahl von Memory Karten,<br />
die auf das Spielfeld verteilt werden.<br />
Commandl<strong>in</strong>e Handl<strong>in</strong>g: Dieses Modul ergibt sich aus e<strong>in</strong>em Requirement,<br />
das bisher noch verschwiegen wurde. Die Größe des Spielfelds wird mittels<br />
zweier Parameter auf der Commandl<strong>in</strong>e beim Starten des Programms<br />
bestimmt. Der erste Parameter repräsentiert die Anzahl der Spalten<br />
(=Zellen pro Reihe), der zweite Parameter die Anzahl der Reihen.<br />
Man könnte (und sollte sogar) noch viel ausführlicher die Aufgaben der Grobmodule<br />
beschreiben, aber ich glaube, die Idee h<strong>in</strong>ter dieser Aufteilung ist e<strong>in</strong>igermaßen<br />
klar. Deshalb ist es s<strong>in</strong>nvoller, <strong>in</strong> der Folge die e<strong>in</strong>zelnen Module<br />
Stück für Stück unter die Lupe zu nehmen und sie weiter zu zerlegen, um
10.1 Das ADD 259<br />
unserem eigentlichen Ziel, der Implementation des Spiels, schneller näher zu<br />
kommen.<br />
10.1.2 Weitere Zerlegung der e<strong>in</strong>zelnen Grobmodule<br />
Betrachten wir nun die e<strong>in</strong>zelnen Module getrennt vone<strong>in</strong>ander, um ihre<br />
Schnittstellen, sowie ihre <strong>in</strong>terne Aufteilung identifizieren zu können. Es ist<br />
<strong>in</strong> der Folge leicht erkennbar, dass durch die genauere Beschreibung gewisser<br />
Abläufe und Gegebenheiten leicht e<strong>in</strong>e Liste von Anforderungen der Module<br />
untere<strong>in</strong>ander identifiziert werden kann, durch die die Schnittstellen def<strong>in</strong>iert<br />
bzw. auf ihre S<strong>in</strong>nhaftigkeit überprüft werden können.<br />
Input Handl<strong>in</strong>g. Um e<strong>in</strong>e vernünftige Analyse des Input Handl<strong>in</strong>gs machen<br />
zu können, muss ich hier etwas nachholen, was <strong>in</strong> der ursprünglichen Angabe<br />
zum Spiel verschwiegen wurde: Wie bedient man das Spiel eigentlich? Hierzu<br />
nehmen wir die e<strong>in</strong>fachst mögliche Variante:<br />
• Das Programm wartet immer auf die E<strong>in</strong>gabe e<strong>in</strong>es Zahlenpaares, wobei<br />
die erste Zahl die Reihe und die zweite Zahl die Spalte darstellt.<br />
• Nachdem immer zwei zusammengehörige Karten aufgedeckt werden sollen,<br />
ist die weitere Logik folgendermaßen:<br />
– Das erste Zahlenpaar wird an die Spielsteuerung weitergegeben. Diese<br />
hat dann die Aufgabe, die entsprechende Karte e<strong>in</strong>mal aufzudecken und<br />
auf die Übermittlung des zweiten Zahlenpaars zu warten.<br />
– Das zweite Zahlenpaar wird an die Spielsteuerung weitergegeben. Dadurch<br />
wird die zweite Karte aufgedeckt. Sollten die beiden Karten dasselbe<br />
Symbol zeigen, dann werden beide aufgedeckt liegen gelassen, ansonsten<br />
werden sie beide wieder umgedreht.<br />
Wenn man weiter überlegt, dann sieht man, dass die Aufgabe des Input<br />
Handl<strong>in</strong>gs nicht wirklich viel mit dem Spiel direkt zu tun hat, denn dieses<br />
Modul soll ja allgeme<strong>in</strong> verwendbar se<strong>in</strong>. Dasselbe Input Handl<strong>in</strong>g wie hier<br />
kann ja auch z.B. <strong>in</strong> e<strong>in</strong>em Texteditor Verwendung f<strong>in</strong>den. Dementsprechend<br />
beschränken wir die Aufgabe des Input Handl<strong>in</strong>gs darauf, E<strong>in</strong>gabeworte entgegenzunehmen<br />
und als Events an angemeldete Handlers weiterzureichen.<br />
Der bzw. die Handlers stellen die Schnittstelle zur Spielsteuerung dar.<br />
Spielsteuerung. Die Spielsteuerung wird über die e<strong>in</strong>gehenden Keyboard<br />
Events <strong>in</strong>formiert und triggert die entsprechenden Aktionen am Spielfeld.<br />
Das bedeutet, dass die gesamte Logik des Spiels, also, was zu welchem Zeitpunkt<br />
zu passieren hat, <strong>in</strong> diesem Modul steckt. Dies betrifft natürlich auch<br />
die Initialisierungsphase des Spiels.<br />
Dadurch, dass nur der Spielsteuerung die Abläufe bekannt s<strong>in</strong>d, ist sie<br />
auch dafür verantwortlich, die Ausgabe des Spielfeldes durch das Output<br />
Handl<strong>in</strong>g zu veranlassen.
260 10. Memory – e<strong>in</strong> kle<strong>in</strong>es Beispiel<br />
Output Handl<strong>in</strong>g. Das Output Handl<strong>in</strong>g ist dafür verantwortlich, die Ausgabe<br />
des gesamten Spielfeldes, respektive der e<strong>in</strong>zelnen Karten auf ihm, zu<br />
steuern und dafür zu sorgen, dass diese am Bildschirm ersche<strong>in</strong>en. Weil<br />
auch dieses natürlich e<strong>in</strong> allgeme<strong>in</strong>es Modul se<strong>in</strong> soll, wird hier das bereits<br />
mehrfach besprochene Pr<strong>in</strong>zip mittels e<strong>in</strong>er abstrakten darstellbaren Klasse<br />
Anwendung f<strong>in</strong>den.<br />
Da die Randbed<strong>in</strong>gungen hierfür bereits mehrfach aus verschiedenen Gesichtspunkten<br />
besprochen wurden, möchte ich den Lesern hier e<strong>in</strong>e nochmalige<br />
genaue Beschreibung ersparen.<br />
Memory Spielfeld. Das Memory Spielfeld ist im Pr<strong>in</strong>zip e<strong>in</strong>e Matrix von<br />
E<strong>in</strong>zelfeldern, wobei jedes dieser E<strong>in</strong>zelfelder genau e<strong>in</strong>e Karte halten kann.<br />
Im Pr<strong>in</strong>zip gibt es hierfür e<strong>in</strong>e besonders s<strong>in</strong>nvolle Konstruktion: E<strong>in</strong>e Matrix<br />
ist im Pr<strong>in</strong>zip nichts anderes als e<strong>in</strong> Vektor von Vektoren. Die dadurch entstehenden<br />
Zellen halten jeweils e<strong>in</strong>e Karte. Dazu lässt sich leicht e<strong>in</strong> besonders<br />
sauberes Design mittels allgeme<strong>in</strong>er Vektoren und Spezialisierung derselben<br />
auf bestimmte Daten beschreiben. Nur leider fehlen uns derzeit noch e<strong>in</strong><br />
paar wichtige Konstrukte, die e<strong>in</strong>e elegante Umsetzung dieses Designs auf<br />
C ++ Sprachebene ermöglichen. Aus diesem Grund bleiben wir zwar beim<br />
allgeme<strong>in</strong>en Design, die Implementation desselben sollte aber bitte nicht als<br />
Vorlage für e<strong>in</strong> professionelles Vektor-Design dienen.<br />
Memory Karte. Zu den Aspekten des Designs e<strong>in</strong>er Memory Karte möchte<br />
ich mich hier auch nicht weiter auslassen, denn diese wurden bereits mehrfach<br />
aus verschiedenen Blickw<strong>in</strong>keln besprochen. E<strong>in</strong>e Wiederholung an dieser<br />
Stelle wäre dementsprechend langweilig.<br />
Commandl<strong>in</strong>e Handl<strong>in</strong>g. Das Commandl<strong>in</strong>e Handl<strong>in</strong>g bekommt die dem<br />
Programm übergebenen Parameter überreicht und sorgt dafür, dass das entsprechende<br />
Bootstrapp<strong>in</strong>g des Systems stattf<strong>in</strong>det. Im Bootstrapp<strong>in</strong>g werden<br />
die entsprechenden notwendigen Instanzen der Klassen erzeugt und zwar <strong>in</strong><br />
der Form, wie sie den e<strong>in</strong>gegebenen Parametern (Zeilen, Spalten) entspricht.<br />
10.2 Das DDD<br />
Viele Entwickler empf<strong>in</strong>den die Erstellung e<strong>in</strong>es DDD (=Detailed Design Document)<br />
als lästig und zeitraubend und deshalb wird diese Aufgabe oft nur<br />
sehr halbherzig gemacht oder e<strong>in</strong>fach vollständig übersprungen. In Summe<br />
erspart man sich durch diese Herangehensweise aber ke<strong>in</strong>e Zeit, ganz im<br />
Gegenteil! Durch das Fehlen des DDD fallen viele Probleme erst beim Implementieren<br />
der Software auf und führen zu dementsprechend vielen kle<strong>in</strong>eren<br />
und größeren Änderungen derselben <strong>in</strong> dieser Phase. Das Schlimmste bei<br />
diesen Änderungen ist, dass man es <strong>in</strong> Bezug auf die Schnittstellen mit e<strong>in</strong>em<br />
mov<strong>in</strong>g Target zu tun hat. Dadurch werden mit wachsender Größe der<br />
Software die Fehler immer häufiger und schwerer zu lokalisieren, denn man
10.2 Das DDD 261<br />
kann sich sicher nicht immer ganz genau er<strong>in</strong>nern, wer jetzt e<strong>in</strong>e Klasse wie<br />
verwendet hat.<br />
Allen Lesern, die sich bisher immer gesträubt haben, e<strong>in</strong> DDD zu erstellen,<br />
bevor sie zu implementieren begonnen haben, möchte ich <strong>in</strong> der Folge e<strong>in</strong>e<br />
Kompromisslösung vorstellen, wie sie <strong>in</strong> kle<strong>in</strong>en Projekten recht brauchbar<br />
e<strong>in</strong>gesetzt werden kann. Sie ist nicht vollkommen gleichwertig zu e<strong>in</strong>em echten<br />
DDD, aber sie stellt zum<strong>in</strong>dest e<strong>in</strong>e deutliche Verbesserung im Vergleich<br />
zur Vorgehensweise dar, gar ke<strong>in</strong> DDD zu schreiben. Außerdem bekommt<br />
man durch diese Methode e<strong>in</strong>igermaßen dokumentierten Source Code, ohne<br />
h<strong>in</strong>terher mühsam dokumentieren zu müssen.<br />
10.2.1 Klassendiagramm<br />
Im ersten Schritt nimmt man das ADD zur Hand. Aus der dar<strong>in</strong> umrissenen<br />
Architektur ergibt sich <strong>in</strong> der Folge durch mehrere Analyse- und Verfe<strong>in</strong>erungsschritte<br />
das grobe Klassendiagramm, wie es <strong>in</strong> Abbildung 10.1 dargestellt<br />
ist. Die Ableitungspfeile s<strong>in</strong>d ja schon bekannt, die strichlierten Pfeile<br />
<strong>in</strong> diesem Diagramm stehen für entsprechende Referenzen der Klassen untere<strong>in</strong>ander<br />
(=HAS-A Relationen).<br />
10.2.2 Klassendeklarationen<br />
Mit Hilfe des Klassendiagramms erstellt man die vollständigen Deklarationen<br />
für die e<strong>in</strong>zelnen Klassen. Hierbei werden gleich die entsprechenden Headers<br />
geschrieben, die auch <strong>in</strong> der fertigen Implementation verwendet werden. Der<br />
Trick bei diesem Schritt ist, dass alle Variablen, Methoden und Funktionen<br />
gleich im Header genau beschrieben werden. Dadurch hat man erstens die<br />
Möglichkeit, zu prüfen, ob das Design funktionieren kann oder ob man vielleicht<br />
etwas vergessen hat. Auch fallen Fehldesigns sehr schnell auf, weil<br />
diese schon <strong>in</strong> der Beschreibung recht umständlich werden. Dadurch aber,<br />
dass man noch ke<strong>in</strong>en Code geschrieben hat, s<strong>in</strong>d Änderungen noch relativ<br />
problemlos möglich. Weil die <strong>in</strong> der Designphase geschriebenen Headers<br />
auch wirklich <strong>in</strong> der Implementation verwendet werden, s<strong>in</strong>d <strong>in</strong> der hier abgedruckten<br />
Version auch e<strong>in</strong> paar Inl<strong>in</strong>e Methoden zu f<strong>in</strong>den, die nicht aus<br />
der Designphase, sondern aus der Implementation kommen.<br />
E<strong>in</strong> ganz großer Vorteil bei dieser Vorgehensweise ist auch, dass man die<br />
Headers an andere Entwickler zur Implementation weitergeben kann. Durch<br />
die genaue Beschreibung müssen diese dann nicht selbst alles noch e<strong>in</strong>mal<br />
von vorn durchdenken (und dabei vielleicht das Design verletzen), sondern<br />
können nach genauer Vorgabe zur Implementation schreiten. Schreiten wir<br />
also zur Tat...<br />
E<strong>in</strong> ke<strong>in</strong>er Exkurs: Im Anschluss an die dokumentierten Klassendeklarationen<br />
f<strong>in</strong>den sich noch ausgewählte Teile des Source Codes. Diese sollen
262 10. Memory – e<strong>in</strong> kle<strong>in</strong>es Beispiel<br />
SimpleOutputHandl<strong>in</strong>g OutputContext<br />
Displayable GameCard<br />
TextOutputContext<br />
MemoryGameboard MemoryGameCard<br />
SimpleInputHandl<strong>in</strong>g<br />
EventHandler<br />
MemoryGameControl<br />
Event<br />
WordEvent<br />
MemoryCardSymbolGenerator<br />
MemoryCardpair<br />
Commandl<strong>in</strong>eHandl<strong>in</strong>g ArgumentHandler<br />
MemoryCommandl<strong>in</strong>eArgumentHandler<br />
Abbildung 10.1: grobes Klassendiagramm<br />
Vector<br />
used by many classes<br />
e<strong>in</strong>erseits die <strong>in</strong>ternen Abläufe im Programm verdeutlichen, andererseits werden<br />
e<strong>in</strong> paar wichtige Details besprochen, über die man sonst im Programmieralltag<br />
stolpern könnte. Ich möchte deshalb wirklich allen Lesern nahe<br />
legen, die folgenden Teile aufmerksam zu lesen und zu versuchen, e<strong>in</strong> tieferes<br />
Verständnis für das Programm zu bekommen. Die Methode, e<strong>in</strong>fach unaufmerksam<br />
quer zu lesen, führt mit ziemlicher Sicherheit zur Verwirrung und<br />
das geht dann garantiert am S<strong>in</strong>n dieses Kapitels vorbei.<br />
Obwohl konzentriertes Lesen bereits sehr viel br<strong>in</strong>gt, möchte ich zusätzlich<br />
noch allen Lesern ans Herz legen, mit dem Programm e<strong>in</strong> wenig zu spielen
10.2 Das DDD 263<br />
und eigene Änderungen und Erweiterungen zu versuchen. Auf diese Art lernt<br />
man unter Garantie am meisten, auch wenn man sich vielleicht den e<strong>in</strong>en oder<br />
anderen Absturz e<strong>in</strong>handelt :-).<br />
10.2.3 Vector<br />
Beg<strong>in</strong>nen wir unser detailed Design mit der Klasse, die durch ihre Natur als<br />
Utility an vielen Stellen gebraucht wird, selbst aber wenig Direktes mit dem<br />
Rest des Programms zu tun hat: mit unserer Klasse Vector. Der Header für<br />
diesen hat hier bezeichnenderweise den Namen simple_vector.h bekommen,<br />
weil er zu Demonstrationszwecken so e<strong>in</strong>fach wie möglich gehalten ist.<br />
E<strong>in</strong>e wichtige Überlegung spielt <strong>in</strong> das Design dieses Vektors h<strong>in</strong>e<strong>in</strong>: Auf<br />
welche Art soll man Elemente im Vektor speichern? Im S<strong>in</strong>ne der Allgeme<strong>in</strong>heit<br />
soll es ke<strong>in</strong>e Rolle spielen, ob man nun Elemente e<strong>in</strong>es primitiven Datentyps<br />
oder benutzerdef<strong>in</strong>ierte Objekte speichert. Im Augenblick fehlt uns<br />
noch das Wissen um Templates, deshalb ist der allgeme<strong>in</strong>st mögliche Ansatz,<br />
der uns zur Verfügung steht, das Speichern der Elemente über void *.<br />
Dadurch, dass nicht die Elemente selbst, sondern nur Po<strong>in</strong>ter auf die Elemente<br />
im Vektor gehalten werden, stellt sich gleich e<strong>in</strong>e ganz wichtige Frage:<br />
Was soll man mit diesen im Destruktor tun? Soll man delete auf die Elemente<br />
aufrufen oder nicht? Leider ist ke<strong>in</strong>e der beiden Methoden im S<strong>in</strong>ne<br />
der Allgeme<strong>in</strong>heit befriedigend. Ruft man im Destruktor ke<strong>in</strong> delete für die<br />
e<strong>in</strong>zelnen Elemente auf, dann hat man die Verantwortung für die Freigabe<br />
delegiert. Das jedoch kann, je nach Verwendung, zu s<strong>in</strong>nlosen Mehrfachspeicherungen<br />
und wüsten Seiteneffekten führen. Ruft man jedoch im Destruktor<br />
immer e<strong>in</strong> delete für die Elemente auf, dann hat man dadurch erfolgreich<br />
verh<strong>in</strong>dert, dass im Vektor auch Elemente gespeichert werden können, die<br />
nicht dynamisch allokiert wurden.<br />
Der sauberste Ausweg aus diesem Dilemma ist, den Benutzern des Vektors<br />
die Entscheidung zu überlassen und sie wählen zu lassen, welche der beiden<br />
Methoden sie nun bevorzugen. Diese Entscheidung führt dann zu folgendem<br />
Design des Vektors:<br />
1 // s i m p l e v e c t o r . h − a simple vector c l a s s<br />
2<br />
3 #ifndef s i m p l e v e c t o r h<br />
4 #def<strong>in</strong>e s i m p l e v e c t o r h<br />
5<br />
6 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
7 #<strong>in</strong>clude ” o b j e c t d e l e t o r . h”<br />
8<br />
9 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
10 /∗ A very simple vector c l a s s that i s able to s t o r e a number o f<br />
11 ∗ elements as given <strong>in</strong> the constructor . The number o f elements<br />
12 ∗ cannot be changed dur<strong>in</strong>g runtime .<br />
13 ∗ When construct<strong>in</strong>g a vector i t has to be chosen , whether<br />
14 ∗ the elements stored <strong>in</strong> i t are deleted on d e s t r u c t i o n .<br />
15 ∗/<br />
16
264 10. Memory – e<strong>in</strong> kle<strong>in</strong>es Beispiel<br />
17 class Vector<br />
18 {<br />
19 private :<br />
20<br />
21 /∗ Copy−construction i s not allowed<br />
22 ∗/<br />
23 Vector ( const Vector & s r c ) ;<br />
24<br />
25 protected :<br />
26<br />
27 /∗ The array of p o i n t e r s that hold the elements . I f the<br />
28 ∗ number of elements f o r t h i s vector i s 0 the po<strong>in</strong>ter<br />
29 ∗ i s a l s o 0 .<br />
30 ∗/<br />
31 void ∗∗ elements ;<br />
32<br />
33 /∗ The number of elements that can be held . The array o f<br />
34 ∗ p o i n t e r s hold<strong>in</strong>g the elements i s a l l o c a t e d to t h i s s i z e .<br />
35 ∗/<br />
36 u<strong>in</strong>t32 num elements ;<br />
37<br />
38 /∗ The d e l e t o r f o r elements . I t i s s e t as a parameter<br />
39 ∗ <strong>in</strong> the constructor i f d e l e t i o n o f o b j e c t s i s d e s i r e d when<br />
40 ∗ e i t h e r the vector i s destructed or the element i s overwritten .<br />
41 ∗ I f i t i s not s e t , no d e l e t i o n o f elements w i l l be done . This<br />
42 ∗ o bject i t s e l f w i l l not be deleted by the d e s t r u c t o r .<br />
43 ∗/<br />
44 ObjectDeletor & d e l e t o r ;<br />
45<br />
46 public :<br />
47<br />
48 /∗ Standard constructor<br />
49 ∗ A l l o c a t e s elements , s e t s num elements and<br />
50 ∗ d e l e l e m e n t s i f n e c e s s a r y accord<strong>in</strong>gly .<br />
51 ∗ @param num elements The number o f elements that the vector<br />
52 ∗ can hold .<br />
53 ∗ @param d e l e t o r The d e l e t o r i s c a l l e d whenever an element i s<br />
54 ∗ e i t h e r overwritten or the d e s t r u c t o r i s c a l l e d . I f<br />
55 ∗ d e l e t i o n of elements i s not d e s i r e d there i s a l s o<br />
56 ∗ a s p e c i a l DontDelete d e l e t o r ( sounds strange , eh ? :−))<br />
57 ∗ that does noth<strong>in</strong>g<br />
58 ∗/<br />
59 Vector ( u<strong>in</strong>t32 num elements , ObjectDeletor & d e l e t o r ) ;<br />
60<br />
61 /∗ Destructor<br />
62 ∗ d e l e t e s elements and i f d e l e l e m e n t s i f n e c e s s a r y i s s e t<br />
63 ∗ i t a l s o d e l e t e s every s i n g l e element stored <strong>in</strong> the vector<br />
64 ∗/<br />
65 virtual ˜ Vector ( ) ;<br />
66<br />
67 /∗ Sets the given element at the given <strong>in</strong>dex p o s i t i o n . I s a l s o<br />
68 ∗ r e s p o n s i b l e to perform a check to determ<strong>in</strong>e i f <strong>in</strong>dex i s<br />
69 ∗ i n s i d e the allowed range . I f the <strong>in</strong>dex i s out o f range the<br />
70 ∗ c a l l i s ignored ( only because exceptions are not known yet )<br />
71 ∗ I f there i s an element at the given <strong>in</strong>dex and i f the<br />
72 ∗ d e l e l e m e n t s i f n e c e s s a r y f l a g i s s e t t h i s element i s<br />
73 ∗ deleted before i t s po<strong>in</strong>ter i s overwritten by the new one .<br />
74 ∗ @param <strong>in</strong>dex The <strong>in</strong>dex f o r the element ( s t a r t i n g at 0 ) .<br />
75 ∗ @param element A po<strong>in</strong>ter to the element that has to be s e t .<br />
76 ∗/<br />
77 virtual void setElementAt ( u<strong>in</strong>t32 <strong>in</strong>dex , void ∗ element ) ;<br />
78<br />
79 /∗ Returns the element at the given <strong>in</strong>dex p o s i t i o n . I s a l s o<br />
80 ∗ r e s p o n s i g l e to perform a range−check o f <strong>in</strong>dex . I f the <strong>in</strong>dex<br />
81 ∗ i s outside the allowed range i t returns 0 ( only because<br />
82 ∗ exceptions are not known yet ) .
10.2 Das DDD 265<br />
83 ∗ @param <strong>in</strong>dex The <strong>in</strong>dex of the d e s i r e d element ( s t a r t i n g at 0 ) .<br />
84 ∗ @return The stored po<strong>in</strong>ter to the element or 0<br />
85 ∗ i f outside range .<br />
86 ∗/<br />
87 virtual void ∗ getElementAt ( u<strong>in</strong>t32 <strong>in</strong>dex ) const ;<br />
88<br />
89 /∗ Returns the element at the given <strong>in</strong>dex p o s i t i o n and removes<br />
90 ∗ i t from the vector without c a l l i n g the d e l e t o r . I s a l s o<br />
91 ∗ r e s p o n s i g l e to perform a range−check o f <strong>in</strong>dex . I f the <strong>in</strong>dex<br />
92 ∗ i s outside the allowed range i t returns 0 ( only because<br />
93 ∗ exceptions are not known yet ) .<br />
94 ∗ @param <strong>in</strong>dex The <strong>in</strong>dex of the d e s i r e d element ( s t a r t i n g at 0 ) .<br />
95 ∗ @return The stored po<strong>in</strong>ter to the element or 0<br />
96 ∗ i f outside range .<br />
97 ∗/<br />
98 virtual void ∗ getAndRemoveElementAt ( u<strong>in</strong>t32 <strong>in</strong>dex ) const ;<br />
99<br />
100 /∗ Returns the number of elements that the vector can hold as<br />
101 ∗ stored <strong>in</strong> num elements .<br />
102 ∗ @return The number of elements that the vector can hold .<br />
103 ∗/<br />
104 virtual u<strong>in</strong>t32 getMaxNumElements ( ) const<br />
105 {<br />
106 return ( num elements ) ;<br />
107 }<br />
108 } ;<br />
109<br />
110<br />
111 #endif // s i m p l e v e c t o r h<br />
In Zeile 44 sieht man, welche Konsequenz die Entscheidung hat, das Freigeben<br />
von Elementen den Benutzern des Vektors zu überlassen: Der Vektor<br />
kann nicht e<strong>in</strong>fach selbst e<strong>in</strong> delete für e<strong>in</strong> Element aufrufen, denn die Elemente<br />
s<strong>in</strong>d ja als void * gespeichert und delete ist nicht auf e<strong>in</strong>em void *<br />
aufrufbar. Man kann sich auch sehr schnell überlegen, warum das so ist:<br />
Es ist delete ja dafür verantwortlich, eventuell existente Destruktoren aufzurufen.<br />
Das geht aber nicht, wenn man ihm nicht mitteilt, um welchen<br />
Typ es sich handelt. Aus diesem Grund wurde e<strong>in</strong>e spezielle ObjectDeletor<br />
Klasse e<strong>in</strong>geführt, von der die konkreten Implementationen für die verschiedenen<br />
Elementtypen abgeleitet werden. Diese Klasse wurde aus Gründen der<br />
Übersichtlichkeit nicht <strong>in</strong> das Klassendiagramm aufgenommen.<br />
Mittels Templates, die <strong>in</strong> Kapitel 13 besprochen werden, kann man dieses<br />
Problem bei weitem sauberer, durchsichtiger, e<strong>in</strong>facher und effizienter <strong>in</strong><br />
den Griff bekommen. Deshalb möchte ich es hier dabei belassen und ke<strong>in</strong>e<br />
besonderen Abhandlungen zum Thema Deletor schreiben. Leser, die, von C<br />
kommend, <strong>in</strong> Funktionspo<strong>in</strong>ter verliebt s<strong>in</strong>d, können sich natürlich auch e<strong>in</strong>e<br />
solche Lösung durch den Kopf gehen lassen. Zu den Funktionspo<strong>in</strong>tern werde<br />
ich noch <strong>in</strong> Abschnitt 15.3 e<strong>in</strong> paar Worte verlieren.<br />
Vorsicht Falle: E<strong>in</strong>e weitere Konsequenz hat die Entscheidung, den Vektor<br />
mit Hilfe des Deletors Objekte löschen zu lassen: Wird e<strong>in</strong> Element im<br />
Vektor überschrieben, so wird das zuvor an dieser Stelle bef<strong>in</strong>dliche Objekt<br />
gelöscht. Ist auch klar, sonst würde ja e<strong>in</strong> Speicherloch entstehen, wenn man<br />
den Po<strong>in</strong>ter durch Überschreiben verliert. Was passiert allerd<strong>in</strong>gs, wenn man
266 10. Memory – e<strong>in</strong> kle<strong>in</strong>es Beispiel<br />
z.B. zwei Elemente im Vektor vertauschen will? Man ruft getElementAt<br />
für das erste Element auf und merkt sich den retournierten Po<strong>in</strong>ter. Dann<br />
ruft man getElementAt für das zweite Element auf, um es gleich wieder mit<br />
setElementAt an die Stelle des ersten Elements zu schreiben. Dann schreibt<br />
man das zwischengespeicherte erste Element an die Stelle, an der vorher das<br />
zweite Element stand. Und dann sucht man beim nächsten Zugriff auf e<strong>in</strong>es<br />
der beiden Elemente ziemlich lange nach e<strong>in</strong>er völlig unerklärbaren Segmentation<br />
Violation...<br />
In diesem Beispiel wird nämlich bei den beiden setElementAt Operationen<br />
von der Vektor Klasse das jeweils vorher dort gespeicherte Element<br />
durch e<strong>in</strong>en entsprechenden Deletor Aufruf über den Jordan geschickt! Woher<br />
sollte der Vektor denn auch ahnen, dass wir die Elemente auch wirklich<br />
nicht “verlieren”? Um dieses Problem <strong>in</strong> den Griff zu bekommen, wurde die<br />
Methode getAndRemoveElementAt e<strong>in</strong>geführt, die das gelieferte Objekt aus<br />
dem Vektor entfernt, <strong>in</strong>dem an diese Stelle e<strong>in</strong> 0 Po<strong>in</strong>ter gesetzt wird.<br />
10.2.4 ObjectDeletor<br />
Das allgeme<strong>in</strong>e Interface für e<strong>in</strong>en ObjectDeletor ist denkbar e<strong>in</strong>fach, wie<br />
man <strong>in</strong> der Folge sieht:<br />
1 // o b j e c t d e l e t o r . h − the i n t e r f a c e f o r an o b j e c t d e l e t o r<br />
2 // as i t i s used by the simple vector c l a s s<br />
3<br />
4 #ifndef o b j e c t d e l e t o r h<br />
5 #def<strong>in</strong>e o b j e c t d e l e t o r h<br />
6<br />
7 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
8 /∗ The i n t e r f a c e f o r an o bject d e l e t o r<br />
9 ∗/<br />
10<br />
11 class ObjectDeletor<br />
12 {<br />
13 public :<br />
14 /∗ Destructor<br />
15 ∗ Just to make sure that a v i r t u a l d e s t r u c t o r e x i s t s<br />
16 ∗/<br />
17 virtual ˜ ObjectDeletor ( ) { }<br />
18<br />
19 /∗ I s c a l l e d with a po<strong>in</strong>ter to the o b j e c t and has to d e l e t e<br />
20 ∗ i t . Please note that i t can a l s o happen that a 0 po<strong>in</strong>ter<br />
21 ∗ i s given as a parameter<br />
22 ∗ @param obj The po<strong>in</strong>ter to the o b j e c t that has to be deleted<br />
23 ∗/<br />
24 virtual void deleteObject ( void ∗ obj ) = 0 ;<br />
25 } ;<br />
26<br />
27 #endif // o b j e c t d e l e t o r h
10.2.5 Konkrete Deletors<br />
10.2 Das DDD 267<br />
Für die verschiedenen Elementtypen, die <strong>in</strong> Vektoren gespeichert werden,<br />
müssen natürlich die entsprechenden konkreten Deletors implementiert werden.<br />
Diese wurden gleich kurzerhand alle geme<strong>in</strong>sam <strong>in</strong> e<strong>in</strong> File verpackt.<br />
Der e<strong>in</strong>zig <strong>in</strong>teressante Aspekt bei diesen konkreten Implementationen ist,<br />
dass es sich hier um e<strong>in</strong>e leichte Abwandlung des S<strong>in</strong>gleton Design Patterns<br />
handelt: Es gibt von jedem dieser konkreten Deletors im gesamten System<br />
nur jeweils e<strong>in</strong>e e<strong>in</strong>zige Instanz. Dies ist deshalb s<strong>in</strong>nvoll, weil diese Deletors<br />
ke<strong>in</strong>e Status<strong>in</strong>formation speichern. Und man will ja nicht für jeden e<strong>in</strong>zelnen<br />
Vektor immer wieder unnötig e<strong>in</strong>en neuen Deletor anlegen, den man eigentlich<br />
sowieso schon hat. Um nun nicht mit irgendwelchen Merkervariablen<br />
Abhängigkeiten und Seiteneffekte zu erzeugen, gibt es bei unseren konkreten<br />
Deletors immer e<strong>in</strong>e static Methode getInstance, über die man e<strong>in</strong>e<br />
Referenz auf die e<strong>in</strong>zige existente Instanz des Deletors erhält.<br />
Der erste Deletor <strong>in</strong> den Zeilen 17–54 ist auch gleich e<strong>in</strong> nettes Paradoxon:<br />
Wie der Name DontDelete schon sagt, ist er e<strong>in</strong> Deletor, der gar nicht daran<br />
denkt, etwas zu löschen :-). Der Grund für dessen Existenz ist e<strong>in</strong>fach: Aus<br />
Sicherheitsgründen wollte ich nicht mit Po<strong>in</strong>ters auf Deletors arbeiten, denn<br />
dabei kann man groben Unfug treiben (z.B. die e<strong>in</strong>zige Instanz löschen, etc.).<br />
Daher wird im Programm immer nur mit Referenzen auf Deletors gearbeitet,<br />
so auch im Vektor selbst. Mit Referenzen hat man allerd<strong>in</strong>gs im Vektor die<br />
Möglichkeit nicht, e<strong>in</strong>fach e<strong>in</strong>en 0 Po<strong>in</strong>ter als Signal für “nichts löschen” zu<br />
verwenden. Deshalb ruft der Vektor immer den ihm übergebenen Deletor auf<br />
und im Fall, dass man nichts löschen will, übergibt man eben die Instanz des<br />
DontDelete Deletors. Durch diese Maßnahme wird das Programm auch bei<br />
späteren Änderungen robuster gegen schwer zu f<strong>in</strong>dende Po<strong>in</strong>terfehler. Es<br />
ist nichts leichter, als im Vektor bei e<strong>in</strong>er Änderung e<strong>in</strong>e 0-Abfrage auf den<br />
Deletor Po<strong>in</strong>ter zu vergessen...<br />
1 // c o n c r e t e o b j e c t d e l e t o r s . h − a c o l l e c t i o n o f the concrete<br />
2 // o bject d e l e t o r s that are used f o r the v e c t o r s <strong>in</strong> the<br />
3 // memory game<br />
4<br />
5 #ifndef c o n c r e t e o b j e c t d e l e t o r s h<br />
6 #def<strong>in</strong>e c o n c r e t e o b j e c t d e l e t o r s h<br />
7<br />
8 #<strong>in</strong>clude ” o b j e c t d e l e t o r . h”<br />
9 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
10 #<strong>in</strong>clude ” s i m p l e v e c t o r . h”<br />
11 #<strong>in</strong>clude ” s i m p l e d i s p l a y a b l e . h”<br />
12<br />
13 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
14 /∗ A concrete d e l e t o r that does not d e l e t e anyth<strong>in</strong>g :−)<br />
15 ∗/<br />
16<br />
17 class DontDelete : public ObjectDeletor<br />
18 {<br />
19 private :<br />
20 /∗ Copy construction i s not allowed<br />
21 ∗/<br />
22 DontDelete ( const DontDelete& s r c ) {}
268 10. Memory – e<strong>in</strong> kle<strong>in</strong>es Beispiel<br />
23 protected :<br />
24<br />
25 /∗ This v a r i a b l e holds the s i n g l e i n stance o f t h i s d e l e t o r .<br />
26 ∗ To obta<strong>in</strong> t h i s <strong>in</strong>stance , getInstance has to be c a l l e d<br />
27 ∗/<br />
28 static DontDelete d e l e t o r ;<br />
29<br />
30 /∗ This c l a s s i s implemented as a s i n g l e t o n , so the constructor<br />
31 ∗ must not be public .<br />
32 ∗/<br />
33 DontDelete ( ) { }<br />
34<br />
35 /∗ This c l a s s i s implemented as a s i n g l e t o n , so the d e s t r u c t o r<br />
36 ∗ must not be public .<br />
37 ∗/<br />
38 virtual ˜ DontDelete ( ) { }<br />
39 public :<br />
40<br />
41 /∗ This method i s used to obta<strong>in</strong> an i n s t a n c e o f the d e l e t o r<br />
42 ∗/<br />
43 static DontDelete & getInstance ( )<br />
44 {<br />
45 return ( d e l e t o r ) ;<br />
46 }<br />
47<br />
48 /∗ This i s the dummy c a l l b a c k implementation that does not<br />
49 ∗ d e l e t e anyth<strong>in</strong>g . . .<br />
50 ∗ @param obj The po<strong>in</strong>ter to the o b j e c t to be deleted<br />
51 ∗/<br />
52 virtual void deleteObject ( void ∗ obj ) {}<br />
53<br />
54 } ;<br />
55<br />
56<br />
57 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
58 /∗ A concrete d e l e t o r f o r char ∗ elements<br />
59 ∗/<br />
60<br />
61 class CharDeletor : public ObjectDeletor<br />
62 {<br />
63 private :<br />
64 /∗ Copy construction i s not allowed<br />
65 ∗/<br />
66 CharDeletor ( const CharDeletor& s r c ) {}<br />
67 protected :<br />
68<br />
69 /∗ This v a r i a b l e holds the s i n g l e i n s t a n c e o f t h i s d e l e t o r .<br />
70 ∗ To obta<strong>in</strong> t h i s <strong>in</strong>stance , getInstance has to be c a l l e d<br />
71 ∗/<br />
72 static CharDeletor d e l e t o r ;<br />
73<br />
74 /∗ This c l a s s i s implemented as a s i n g l e t o n , so the constructor<br />
75 ∗ must not be public .<br />
76 ∗/<br />
77 CharDeletor ( ) { }<br />
78<br />
79 /∗ This c l a s s i s implemented as a s i n g l e t o n , so the d e s t r u c t o r<br />
80 ∗ must not be public .<br />
81 ∗/<br />
82 virtual ˜ CharDeletor ( ) { }<br />
83 public :<br />
84<br />
85 /∗ This method i s used to obta<strong>in</strong> an i n stance o f the d e l e t o r<br />
86 ∗/<br />
87 static CharDeletor & getInstance ( )<br />
88 {
89 return ( d e l e t o r ) ;<br />
90 }<br />
91<br />
92 /∗ This i s the c a l l b a c k implementation<br />
93 ∗ @param obj The po<strong>in</strong>ter to the o b j e c t to be deleted<br />
94 ∗/<br />
95 virtual void deleteObject ( void ∗ obj )<br />
96 {<br />
97 delete static cast(obj ) ;<br />
98 }<br />
99 } ;<br />
100<br />
10.2 Das DDD 269<br />
101 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
102 /∗ A concrete d e l e t o r f o r u<strong>in</strong>t32 ∗ elements<br />
103 ∗/<br />
104<br />
105 class U<strong>in</strong>t32Deletor : public ObjectDeletor<br />
106 {<br />
107 private :<br />
108 /∗ Copy construction i s not allowed<br />
109 ∗/<br />
110 U<strong>in</strong>t32Deletor ( const U<strong>in</strong>t32Deletor& s r c ) {}<br />
111 protected :<br />
112<br />
113 /∗ This v a r i a b l e holds the s i n g l e i n stance o f t h i s d e l e t o r .<br />
114 ∗ To obta<strong>in</strong> t h i s <strong>in</strong>stance , getInstance has to be c a l l e d<br />
115 ∗/<br />
116 static U<strong>in</strong>t32Deletor d e l e t o r ;<br />
117<br />
118 /∗ This c l a s s i s implemented as a s i n g l e t o n , so the constructor<br />
119 ∗ must not be public .<br />
120 ∗/<br />
121 U<strong>in</strong>t32Deletor ( ) { }<br />
122<br />
123 /∗ This c l a s s i s implemented as a s i n g l e t o n , so the d e s t r u c t o r<br />
124 ∗ must not be public .<br />
125 ∗/<br />
126 virtual ˜ U<strong>in</strong>t32Deletor ( ) { }<br />
127 public :<br />
128<br />
129 /∗ This method i s used to obta<strong>in</strong> an i n s t a n c e o f the d e l e t o r<br />
130 ∗/<br />
131 static U<strong>in</strong>t32Deletor & getInstance ( )<br />
132 {<br />
133 return ( d e l e t o r ) ;<br />
134 }<br />
135<br />
136 /∗ This i s the c a l l b a c k implementation<br />
137 ∗ @param obj The po<strong>in</strong>ter to the o b j e c t to be deleted<br />
138 ∗/<br />
139 virtual void deleteObject ( void ∗ obj )<br />
140 {<br />
141 delete static cast(obj ) ;<br />
142 }<br />
143 } ;<br />
144<br />
145 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
146 /∗ A concrete d e l e t o r f o r Vector ∗ elements<br />
147 ∗/<br />
148<br />
149 class VectorDeletor : public ObjectDeletor<br />
150 {<br />
151 private :<br />
152 /∗ Copy construction i s not allowed<br />
153 ∗/<br />
154 VectorDeletor ( const VectorDeletor& s r c ) {}
270 10. Memory – e<strong>in</strong> kle<strong>in</strong>es Beispiel<br />
155 protected :<br />
156<br />
157 /∗ This v a r i a b l e holds the s i n g l e i n stance o f t h i s d e l e t o r .<br />
158 ∗ To obta<strong>in</strong> t h i s <strong>in</strong>stance , getInstance has to be c a l l e d<br />
159 ∗/<br />
160 static VectorDeletor d e l e t o r ;<br />
161<br />
162 /∗ This c l a s s i s implemented as a s i n g l e t o n , so the constructor<br />
163 ∗ must not be public .<br />
164 ∗/<br />
165 VectorDeletor ( ) { }<br />
166<br />
167 /∗ This c l a s s i s implemented as a s i n g l e t o n , so the d e s t r u c t o r<br />
168 ∗ must not be public .<br />
169 ∗/<br />
170 virtual ˜ VectorDeletor ( ) { }<br />
171 public :<br />
172<br />
173 /∗ This method i s used to obta<strong>in</strong> an i n s t a n c e o f the d e l e t o r<br />
174 ∗/<br />
175 static VectorDeletor & getInstance ( )<br />
176 {<br />
177 return ( d e l e t o r ) ;<br />
178 }<br />
179<br />
180 /∗ This i s the c a l l b a c k implementation<br />
181 ∗ @param obj The po<strong>in</strong>ter to the o b j e c t to be deleted<br />
182 ∗/<br />
183 virtual void deleteObject ( void ∗ obj )<br />
184 {<br />
185 delete static cast(obj ) ;<br />
186 }<br />
187 } ;<br />
188<br />
189 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
190 /∗ A concrete d e l e t o r f o r Displayable ∗ elements<br />
191 ∗/<br />
192<br />
193 class DisplayableDeletor : public ObjectDeletor<br />
194 {<br />
195 private :<br />
196 /∗ Copy construction i s not allowed<br />
197 ∗/<br />
198 DisplayableDeletor ( const DisplayableDeletor & s r c ) {}<br />
199 protected :<br />
200<br />
201 /∗ This v a r i a b l e holds the s i n g l e i n stance o f t h i s d e l e t o r .<br />
202 ∗ To obta<strong>in</strong> t h i s <strong>in</strong>stance , getInstance has to be c a l l e d<br />
203 ∗/<br />
204 static DisplayableDeletor d e l e t o r ;<br />
205<br />
206 /∗ This c l a s s i s implemented as a s i n g l e t o n , so the constructor<br />
207 ∗ must not be public .<br />
208 ∗/<br />
209 DisplayableDeletor ( ) { }<br />
210<br />
211 /∗ This c l a s s i s implemented as a s i n g l e t o n , so the d e s t r u c t o r<br />
212 ∗ must not be public .<br />
213 ∗/<br />
214 virtual ˜ DisplayableDeletor ( ) { }<br />
215 public :<br />
216<br />
217 /∗ This method i s used to obta<strong>in</strong> an i n s t a n c e o f the d e l e t o r<br />
218 ∗/<br />
219 static DisplayableDeletor & getInstance ( )<br />
220 {
221 return ( d e l e t o r ) ;<br />
222 }<br />
223<br />
224 /∗ This i s the c a l l b a c k implementation<br />
225 ∗ @param obj The po<strong>in</strong>ter to the o b j e c t to be deleted<br />
226 ∗/<br />
227 virtual void deleteObject ( void ∗ obj )<br />
228 {<br />
229 delete static cast(obj ) ;<br />
230 }<br />
231 } ;<br />
232<br />
233<br />
234 #endif // c o n c r e t e o b j e c t d e l e t o r s h<br />
10.2.6 ArgumentHandler<br />
10.2 Das DDD 271<br />
Neben dem Vector ist auch die Basisklasse ArgumentHandler und alles, was<br />
sich rund um diese Klasse herum abspielt, e<strong>in</strong> relativ abgeschlossenes Thema<br />
für sich. Also wenden wir uns ihr gleich e<strong>in</strong>mal zu. Der ArgumentHandler ist<br />
ganz e<strong>in</strong>fach nur e<strong>in</strong>e abstrakte Basis, über die das Commandl<strong>in</strong>eHandl<strong>in</strong>g die<br />
e<strong>in</strong>zelnen Parameter zur Auswertung weitergeben kann. Dementsprechend<br />
e<strong>in</strong>fach ist auch dieses Interface:<br />
1 // simple argument handler . h − i n t e r f a c e f o r an argument handler<br />
2<br />
3 #ifndef simple argument handler h<br />
4 #def<strong>in</strong>e simple argument handler h<br />
5<br />
6 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
7 /∗ An abstract base c l a s s of a very simple argument handler .<br />
8 ∗ A concrete implementation of a handler has to be derived from<br />
9 ∗ t h i s base and obta<strong>in</strong>s n o t i f i c a t i o n s f o r every s i n g l e argument .<br />
10 ∗/<br />
11<br />
12 class ArgumentHandler<br />
13 {<br />
14 public :<br />
15<br />
16 /∗ Destructor<br />
17 ∗ The d e s t r u c t o r i s j u s t implemented to make sure that a v i r t u a l<br />
18 ∗ d e s t r u c t o r e x i s t s<br />
19 ∗/<br />
20 virtual ˜ ArgumentHandler ( ) { }<br />
21<br />
22 /∗ This method i s a c a l l b a c k used to n o t i f y the handler o f an<br />
23 ∗ argument that was obta<strong>in</strong>ed .<br />
24 ∗ @param <strong>in</strong>dex The <strong>in</strong>dex of the argument . All arguments are<br />
25 ∗ numbered <strong>in</strong> a consecutive order s t a r t i n g at 0 .<br />
26 ∗ @param arg A po<strong>in</strong>ter to the argument that was obta<strong>in</strong>ed . This<br />
27 ∗ parameter i s declared as a void ∗ , because argument<br />
28 ∗ n o t i f i c a t i o n s are performed <strong>in</strong> a typed manner .<br />
29 ∗ Depend<strong>in</strong>g on the expected argument type the t r i g g e r<br />
30 ∗ c l a s s of a n o t i f i c a t i o n has to perform the accord<strong>in</strong>g<br />
31 ∗ conversion . I f e . g . an i n t argument i s expected<br />
32 ∗ the n o t i f i c a t i o n i s done with an i n t ∗ .<br />
33 ∗/<br />
34 virtual void argumentNotification ( u<strong>in</strong>t32 <strong>in</strong>dex , const void ∗ arg ) = 0 ;<br />
35 } ;
272 10. Memory – e<strong>in</strong> kle<strong>in</strong>es Beispiel<br />
36<br />
37<br />
38 #e n d i f // simple argument handler h<br />
10.2.7 MemoryCommandl<strong>in</strong>eArgumentHandler<br />
Da wir gerade das entsprechende Interface kennen gelernt haben, sehen wir<br />
uns auch gleich die entsprechende Implementation an, die <strong>in</strong> unserem Spiel<br />
Anwendung f<strong>in</strong>den wird.<br />
1 // memory commandl<strong>in</strong>e arg handler . h − s p e c i a l argument handler<br />
2 // f o r the memory game<br />
3<br />
4 #ifndef memory commandl<strong>in</strong>e arg handler h<br />
5 #def<strong>in</strong>e memory commandl<strong>in</strong>e arg handler h<br />
6<br />
7 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
8 /∗ The commandl<strong>in</strong>e argument handler f o r the memory game<br />
9 ∗ I t i s implemented as a simple conta<strong>in</strong>er that s t o r e s the number<br />
10 ∗ o f rows and columns that are s e t via c a l l b a c k s from the<br />
11 ∗ Commandl<strong>in</strong>eHandl<strong>in</strong>g or a compatible c l a s s .<br />
12 ∗/<br />
13<br />
14 class MemoryCommandl<strong>in</strong>eArgumentHandler : public ArgumentHandler<br />
15 {<br />
16 protected :<br />
17<br />
18 /∗ The i n d i c e s of the number of rows and columns accord<strong>in</strong>g to<br />
19 ∗ the commandl<strong>in</strong>e s p e c i f i c a t i o n o f the game ( argv [ 0 ] = progname ,<br />
20 ∗ argv [ 1 ] = rows , argv [ 2 ] = c o l s ) .<br />
21 ∗/<br />
22 static const u<strong>in</strong>t32 ROW DEF INDEX = 1;<br />
23 static const u<strong>in</strong>t32 COL DEF INDEX = 2;<br />
24<br />
25 /∗ The number of rows of the gameboard<br />
26 ∗/<br />
27 u<strong>in</strong>t32 num rows ;<br />
28<br />
29 /∗ The number of columns of the gameboard<br />
30 ∗/<br />
31 u<strong>in</strong>t32 num cols ;<br />
32<br />
33 public :<br />
34<br />
35 /∗ Default constructor<br />
36 ∗ I n i t i a l i z e s a l l v a r i a b l e s to 0<br />
37 ∗/<br />
38 MemoryCommandl<strong>in</strong>eArgumentHandler ( ) :<br />
39 num rows ( 0 ) , num cols ( 0) { }<br />
40<br />
41 /∗ Destructor<br />
42 ∗ Just to make sure that there i s a v i r t u a l d e s t r u c t o r<br />
43 ∗/<br />
44 virtual ˜ MemoryCommandl<strong>in</strong>eArgumentHandler ( ) { }<br />
45<br />
46 /∗ Callback f o r a n o t i f i c a t i o n accord<strong>in</strong>g to the base i n t e r f a c e<br />
47 ∗ I f everyth<strong>in</strong>g i s ok t h i s c a l l b a c k w i l l be c a l l e d f o r two<br />
48 ∗ arguments : The number of rows and the number o f columns .<br />
49 ∗ Accord<strong>in</strong>g to the convention both arguments w i l l be passed<br />
50 ∗ as p o i n t e r s to u<strong>in</strong>t32 . See a l s o c l a s s Commandl<strong>in</strong>eHandl<strong>in</strong>g<br />
51 ∗ @param <strong>in</strong>dex The <strong>in</strong>dex of the argument <strong>in</strong> the commandl<strong>in</strong>e .
10.2 Das DDD 273<br />
52 ∗ As i s the s p e c i f i c a t i o n the program name has <strong>in</strong>dex 0<br />
53 ∗ and the arguments s t a r t with <strong>in</strong>dex 1 .<br />
54 ∗ @param arg The argument s t r i n g as given <strong>in</strong> the commandl<strong>in</strong>e<br />
55 ∗/<br />
56 virtual void argumentNotification ( u<strong>in</strong>t32 <strong>in</strong>dex , const void ∗ arg )<br />
57 {<br />
58 switch ( <strong>in</strong>dex )<br />
59 {<br />
60 case ROW DEF INDEX:<br />
61 num rows = ∗( static cast(arg ) ) ;<br />
62 break ;<br />
63 case COL DEF INDEX:<br />
64 num cols = ∗( static cast(arg ) ) ;<br />
65 break ;<br />
66 default :<br />
67 break ;<br />
68 }<br />
69 }<br />
70<br />
71 /∗ Returns the number of rows as s e t from the commandl<strong>in</strong>e<br />
72 ∗ @return The value of the rows−parameter<br />
73 ∗/<br />
74 virtual u<strong>in</strong>t32 getRows ( )<br />
75 {<br />
76 return ( num rows ) ;<br />
77 }<br />
78<br />
79 /∗ Returns the number of columns as s e t from the commandl<strong>in</strong>e<br />
80 ∗ @return The value of the c o l s−parameter<br />
81 ∗/<br />
82 virtual u<strong>in</strong>t32 getCols ( )<br />
83 {<br />
84 return ( num cols ) ;<br />
85 }<br />
86 } ;<br />
87<br />
88<br />
89 #endif // memory commandl<strong>in</strong>e arg handler h<br />
10.2.8 Commandl<strong>in</strong>eHandl<strong>in</strong>g<br />
Um das Kapitel des Argument Handl<strong>in</strong>gs abzurunden, werfen wir e<strong>in</strong>en Blick<br />
auf die Klasse Commandl<strong>in</strong>eHandl<strong>in</strong>g. Diese Klasse ist dafür verantwortlich,<br />
die Argumente aus der Commandl<strong>in</strong>e entgegenzunehmen und an die entsprechende<br />
Instanz e<strong>in</strong>es Argument Handlers weiterzuleiten. Um das Handl<strong>in</strong>g<br />
der Commandl<strong>in</strong>e möglichst offen und wiederverwendbar zu gestalten, implementiert<br />
die hier def<strong>in</strong>ierte Klasse Commandl<strong>in</strong>eHandl<strong>in</strong>g folgendes Schema:<br />
• Jedes e<strong>in</strong>zelne Argument, das e<strong>in</strong> Programm erwartet, wird explizit deklariert.<br />
• E<strong>in</strong>e solche Deklaration enthält den Index des erwarteten Arguments, den<br />
Typ, der erwartet wird und den Handler, der für das Argument schlussendlich<br />
verantwortlich ist.<br />
• Nachdem alle erwarteten Argumente deklariert wurden, wird die Commandl<strong>in</strong>e<br />
an das Handl<strong>in</strong>g übergeben und entsprechend ausgewertet. Für<br />
alle zuvor deklarierten Argumente, die auf der Commandl<strong>in</strong>e spezifiziert<br />
wurden, wird der entsprechend registrierte Handler aufgerufen.
274 10. Memory – e<strong>in</strong> kle<strong>in</strong>es Beispiel<br />
Ich möchte hier gleich betonen, dass diese Art des Handl<strong>in</strong>gs e<strong>in</strong>er Commandl<strong>in</strong>e<br />
wirklich nur für sehr e<strong>in</strong>fache Programme s<strong>in</strong>nvoll ist. Jedoch geht<br />
die Implementation e<strong>in</strong>es wirklich <strong>in</strong> der Praxis e<strong>in</strong>setzbaren Commandl<strong>in</strong>e<br />
Handl<strong>in</strong>gs hier bei weitem am S<strong>in</strong>n des kle<strong>in</strong>en Beispiels vorbei. Sehen wir<br />
uns also an, wie diese Klasse unter den gegebenen Voraussetzungen aussieht:<br />
1 // simple commandl<strong>in</strong>e handl<strong>in</strong>g . h − a simple c l a s s f o r handl<strong>in</strong>g<br />
2 // commandl<strong>in</strong>e arguments<br />
3<br />
4 #ifndef simple commandl<strong>in</strong>e handl<strong>in</strong>g h<br />
5 #def<strong>in</strong>e simple commandl<strong>in</strong>e handl<strong>in</strong>g h<br />
6<br />
7 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
8 #<strong>in</strong>clude ” s i m p l e v e c t o r . h”<br />
9 #<strong>in</strong>clude ” simple argument handler . h”<br />
10 #<strong>in</strong>clude ” c o n c r e t e o b j e c t d e l e t o r s . h”<br />
11<br />
12 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
13 /∗ Every s i n g l e argument that has to be handled has to be<br />
14 ∗ declared with a type and an accord<strong>in</strong>g handler here . I t i s<br />
15 ∗ not mandatory that a l l arguments are declared , there may<br />
16 ∗ a l s o be undeclared ones that are simply skipped .<br />
17 ∗/<br />
18<br />
19 class Commandl<strong>in</strong>eHandl<strong>in</strong>g<br />
20 {<br />
21 protected :<br />
22<br />
23 /∗ The types of a l l declared arguments . I f an argument i s<br />
24 ∗ declared f o r a c e r t a i n <strong>in</strong>dex , the type i s r e g i s t e r e d here .<br />
25 ∗ I f there i s no argument declared f o r a c e r t a i n <strong>in</strong>dex then<br />
26 ∗ t h i s element i s a 0 po<strong>in</strong>ter . This i s i n t e r p r e t e d as ” ignore<br />
27 ∗ the argument ” and the c l a s s has to r e a c t accord<strong>in</strong>gly . An<br />
28 ∗ undeclared argument i s d e f i n i t e l y a s p e c i f i e d s i t u a t i o n .<br />
29 ∗/<br />
30 Vector types ;<br />
31<br />
32 /∗ The handlers f o r a l l declared arguments . The same r u l e s<br />
33 ∗ f o r ” miss<strong>in</strong>g ” p o s i t i o n s are v a l i d as f o r the types f i e l d .<br />
34 ∗ The only d i f f e r e n c e i s that handlers f o r the ” miss<strong>in</strong>g ”<br />
35 ∗ p o s i t i o n s are 0 p o i n t e r s . The s i n g l e handlers w i l l not<br />
36 ∗ be deleted on d e s t r u c t i o n .<br />
37 ∗/<br />
38 Vector handlers ;<br />
39<br />
40 /∗ The maximum number of d e c l a r a b l e arguments . I f more arguments<br />
41 ∗ are passed on from the commandl<strong>in</strong>e they are simply ignored .<br />
42 ∗/<br />
43 <strong>in</strong>t32 max num args ;<br />
44<br />
45 public :<br />
46<br />
47 /∗ The constants f o r a l l the argument types . Add a d d i t i o n a l<br />
48 ∗ known types here .<br />
49 ∗/<br />
50 static const u<strong>in</strong>t32 UINT32 ARG = 0 x01 ;<br />
51<br />
52 protected :<br />
53<br />
54 /∗ This constant holds the <strong>in</strong>dex f o r the highest declared<br />
55 ∗ argument type constant . I f constants f o r new types are<br />
56 ∗ added i t has to be adopted accord<strong>in</strong>gly . All argument<br />
57 ∗ types with values higher than t h i s one are considered<br />
58 ∗ e r r o r s and ignored accord<strong>in</strong>gly ( j u s t because exceptions
59 ∗ are not known yet )<br />
60 ∗/<br />
61 static const u<strong>in</strong>t32 HIGHEST ARG TYPE = UINT32 ARG;<br />
62<br />
63 private :<br />
64<br />
65 /∗ Copy construction i s not allowed<br />
66 ∗/<br />
67 Commandl<strong>in</strong>eHandl<strong>in</strong>g ( const Commandl<strong>in</strong>eHandl<strong>in</strong>g & s r c ) :<br />
68 types ( 0 , DontDelete : : getInstance ( ) ) ,<br />
69 handlers ( 0 , DontDelete : : getInstance ( ) ) { }<br />
70<br />
71 public :<br />
72<br />
10.2 Das DDD 275<br />
73 /∗ Standard Constructor<br />
74 ∗ @param max num args S p e c i f i e s the maximum number o f arguments<br />
75 ∗ that may be declared f o r t h i s i n s t a n c e o f the handler<br />
76 ∗/<br />
77 explicit Commandl<strong>in</strong>eHandl<strong>in</strong>g ( <strong>in</strong>t32 max num args ) :<br />
78 types ( max num args , U<strong>in</strong>t32Deletor : : getInstance ( ) ) ,<br />
79 handlers ( max num args , DontDelete : : getInstance ( ) ) ,<br />
80 max num args ( max num args ) {}<br />
81<br />
82 /∗ Destructor<br />
83 ∗ Just to make sure that a v i r t u a l d e s t r u c t o r e x i s t s<br />
84 ∗/<br />
85 virtual ˜ Commandl<strong>in</strong>eHandl<strong>in</strong>g ( ) { }<br />
86<br />
87 /∗ This method i s used to d e c l a r e an argument that has to be<br />
88 ∗ handled .<br />
89 ∗ @param <strong>in</strong>dex The <strong>in</strong>dex of the argument to be handled . I f<br />
90 ∗ the <strong>in</strong>dex i s out of range i t i s ignored ( j u s t because<br />
91 ∗ exceptions are not known yet ) .<br />
92 ∗ @param type The type of the argument . Valid types are the<br />
93 ∗ ones that e x i s t as constants above .<br />
94 ∗ @param handler The handler that has to be c a l l e d f o r the<br />
95 ∗ declared argument . I f t h i s handler i s a 0 po<strong>in</strong>ter<br />
96 ∗ then the operation i s ignored ( j u s t because<br />
97 ∗ exceptions are not known yet ) .<br />
98 ∗/<br />
99 virtual void declareArgument ( u<strong>in</strong>t32 <strong>in</strong>dex , u<strong>in</strong>t32 type ,<br />
100 ArgumentHandler ∗ handler ) ;<br />
101<br />
102 /∗ This method i s c a l l e d a f t e r the i n i t i a l i z a t i o n phase to<br />
103 ∗ t r i g g e r handl<strong>in</strong>g of the commandl<strong>in</strong>e . I t takes the arguments<br />
104 ∗ one by one , converts them to the types declared f o r them<br />
105 ∗ and c a l l s the appropriate handlers .<br />
106 ∗ @param num args The number of arguments passed on<br />
107 ∗ @param args The arguments as obta<strong>in</strong>ed from the commandl<strong>in</strong>e<br />
108 ∗/<br />
109 virtual void handleCommandl<strong>in</strong>e ( <strong>in</strong>t32 num args ,<br />
110 char ∗ args [ ] ) ;<br />
111 } ;<br />
112<br />
113<br />
114 #endif // simple commandl<strong>in</strong>e handl<strong>in</strong>g h<br />
10.2.9 SimpleOutputHandl<strong>in</strong>g<br />
Bevor wir die Klasse SimpleOutputHandl<strong>in</strong>g näher betrachten, möchte ich<br />
noch anmerken, dass diese deshalb <strong>in</strong> der hier angeführten Form geschrieben<br />
wurde, da der Umgang mit allgeme<strong>in</strong>en Output Streams derzeit noch nicht
276 10. Memory – e<strong>in</strong> kle<strong>in</strong>es Beispiel<br />
bekannt ist. Ansonsten würden diese sich im Design dieser Klasse und der<br />
von ihr verwalteten Displayable Klassen niederschlagen.<br />
Die Idee h<strong>in</strong>ter der Klasse SimpleOutputHandl<strong>in</strong>g ist nun, dass alle Teile<br />
des Spiels, die etwas zur Ausgabe beizutragen haben, von Displayable<br />
abgeleitet s<strong>in</strong>d und beim Handler explizit angemeldet werden. Wenn e<strong>in</strong><br />
Output generiert werden soll, so werden die angemeldeten darstellbaren Objekte<br />
genau <strong>in</strong> der Reihenfolge ihrer Anmeldung zum Schreiben desselben<br />
aufgefordert.<br />
Im Gegensatz zu den Beispielen, die <strong>in</strong> Kapitel 9 besprochen wurden, haben<br />
wir es hier mit e<strong>in</strong>er kle<strong>in</strong>en Abwandlung der Idee mit den darstellbaren<br />
Objekten zu tun: Es wird nicht mehr e<strong>in</strong>e Display Repräsentation verlangt,<br />
die dann vom Handler auf den Output geschrieben wird. Dies ist vor allem <strong>in</strong><br />
Bezug auf Conta<strong>in</strong>er, die selbst darstellbare Objekte halten, nicht besonders<br />
effizient und verleitet auch <strong>in</strong> diesen Fällen zur Implementation von Seiteneffekten.<br />
Hier wird e<strong>in</strong> anderer Weg gegangen: Das Callback, das aufgerufen<br />
wird, enthält e<strong>in</strong>e Referenz auf e<strong>in</strong>en OutputContext. Dieser enthält die<br />
notwendigen Methoden, die es e<strong>in</strong>em darstellbaren Objekt erlauben, se<strong>in</strong>en<br />
Output zu schreiben. Woh<strong>in</strong> dieser Output nun geschrieben wird, ist durch<br />
die spezielle Ausprägung des Contexts bestimmt. In unserem Fall ist dies e<strong>in</strong><br />
simpler textueller Context, der die Ausgabe auf cout weiterreicht.<br />
Noch e<strong>in</strong>e wichtige Entscheidung wurde getroffen, die sich im Displayable<br />
niederschlägt: Wenn e<strong>in</strong> Displayable beim Output Handl<strong>in</strong>g angemeldet<br />
wird, so wird es durch Aufruf e<strong>in</strong>es Callbacks von dieser Anmeldung unterrichtet.<br />
Dadurch können eventuelle Initialisierungen zum richtigen Zeitpunkt<br />
stattf<strong>in</strong>den.<br />
Diese Ideen sehen nun <strong>in</strong> e<strong>in</strong>e Klasse gegossen so aus:<br />
1 // simple output handl<strong>in</strong>g . h − simple v e r s i o n o f output handl<strong>in</strong>g<br />
2<br />
3 #ifndef s i m p l e o u t p u t h a n d l i n g h<br />
4 #def<strong>in</strong>e s i m p l e o u t p u t h a n d l i n g h<br />
5<br />
6 #<strong>in</strong>clude ” s i m p l e d i s p l a y a b l e . h”<br />
7 #<strong>in</strong>clude ” s i m p l e v e c t o r . h”<br />
8 #<strong>in</strong>clude ” c o n c r e t e o b j e c t d e l e t o r s . h”<br />
9<br />
10 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
11 /∗<br />
12 ∗ A simple output handl<strong>in</strong>g c l a s s f o r text based output . Elements<br />
13 ∗ o f c l a s s Displayable are added one by one and when an output<br />
14 ∗ operation i s t r i g g e r e d they are asked <strong>in</strong> exactly the same order<br />
15 ∗ as they have been r e g i s t e r e d to d e l i v e r t h e i r d i s p l a y<br />
16 ∗ r e p r e s e n t a t i o n . This r e p r e s e n t a t i o n i s then written to cout .<br />
17 ∗/<br />
18<br />
19 class SimpleOutputHandl<strong>in</strong>g<br />
20 {<br />
21 protected :<br />
22<br />
23 /∗ This vector has space f o r the maximum number o f d i s p l a y a b l e s<br />
24 ∗ as given <strong>in</strong> the constructor . The order <strong>in</strong> which d i s p l a y a b l e s<br />
25 ∗ are stored <strong>in</strong> t h i s vector i s the order <strong>in</strong> which they w i l l<br />
26 ∗ be c a l l e d when output i s written .
27 ∗/<br />
28 Vector d i s p l a y a b l e s ;<br />
29<br />
10.2 Das DDD 277<br />
30 /∗ The number of d i s p l a y a b l e s c u r r e n t l y stored <strong>in</strong> the vector<br />
31 ∗/<br />
32 u<strong>in</strong>t32 num displayables ;<br />
33<br />
34 /∗ The output context which d i s p l a y a b l e s obta<strong>in</strong> through the<br />
35 ∗ c a l l b a c k to write t h e i r output . This context i s s e t by<br />
36 ∗ an appropriate parameter <strong>in</strong> the constructor .<br />
37 ∗/<br />
38 OutputContext & output context ;<br />
39<br />
40 private :<br />
41<br />
42 /∗ Copy construction i s not allowed<br />
43 ∗/<br />
44 SimpleOutputHandl<strong>in</strong>g ( const SimpleOutputHandl<strong>in</strong>g & s r c ) :<br />
45 d i s p l a y a b l e s ( 0 , DontDelete : : getInstance ( ) ) ,<br />
46 output context ( s r c . output context ) {}<br />
47<br />
48 public :<br />
49<br />
50 /∗ Standard constructor<br />
51 ∗ @param max num displayables The maximum number o f d i s p l a y a b l e s<br />
52 ∗ that s h a l l be handled by an i n stance o f t h i s c l a s s<br />
53 ∗ @param output context The output context that the d i s p l a y a b l e s<br />
54 ∗ have to use .<br />
55 ∗/<br />
56 SimpleOutputHandl<strong>in</strong>g ( u<strong>in</strong>t32 max num displayables ,<br />
57 OutputContext &output context ) :<br />
58 d i s p l a y a b l e s ( max num displayables , DontDelete : : getInstance ( ) ) ,<br />
59 num displayables ( 0 ) ,<br />
60 output context ( output context ) {}<br />
61<br />
62 /∗ Destructor<br />
63 ∗ Just to make sure that a v i r t u a l d e s t r u c t o r e x i s t s<br />
64 ∗/<br />
65 virtual ˜ SimpleOutputHandl<strong>in</strong>g ( ) { }<br />
66<br />
67 /∗ Adds a d i s p l a y a b l e f o r handl<strong>in</strong>g . I f the maximum number o f<br />
68 ∗ d i s p l a y a b l e s i s reached the d i s p l a y a b l e i s simply ignored<br />
69 ∗ ( j u s t because exceptions are not known yet ) . The r e g i s t e r e d<br />
70 ∗ d i s p l a y a b l e i s a l s o n o t i f i e d through a c a l l to the method<br />
71 ∗ d i s p l a y a b l e R e g i s t e r e d of the c l a s s Displayable .<br />
72 ∗ @param d i s p l a y a b l e A r e f e r e n c e to the d i s p l a y a b l e which i s<br />
73 ∗ added f o r handl<strong>in</strong>g . Although i t should be c l e a r<br />
74 ∗ because of the r e f e r e n c e : d i s p l a y a b l e s which are<br />
75 ∗ r e g i s t e r e d are NOT deleted when the handler i s<br />
76 ∗ destructed !<br />
77 ∗/<br />
78 virtual void addDisplayable ( Displayable & d i s p l a y a b l e ) ;<br />
79<br />
80 /∗ This method i s c a l l e d to t r i g g e r writ<strong>in</strong>g o f the output . I t<br />
81 ∗ c a l l s the c a l l b a c k methods of the r e g i s t e r e d d i s p l a y a b l e s<br />
82 ∗ one by one <strong>in</strong> exactly the same order <strong>in</strong> which they were<br />
83 ∗ r e g i s t e r e d . This order i s r e f l e c t e d by the order o f the<br />
84 ∗ d i s p l a y a b l e s <strong>in</strong> the vector .<br />
85 ∗/<br />
86 virtual void writeOutput ( ) ;<br />
87<br />
88 /∗ Returns a clone of the current output context . Please note<br />
89 ∗ that the c a l l e r i s r e s p o n s i b l e to d e l e t e i t i f no longer<br />
90 ∗ needed .<br />
91 ∗ @return A po<strong>in</strong>ter to a dynamically a l l o c a t e d clone o f t h i s<br />
92 ∗ <strong>in</strong>stance
278 10. Memory – e<strong>in</strong> kle<strong>in</strong>es Beispiel<br />
93 ∗/<br />
94 virtual OutputContext ∗ getOutputContextClone ( )<br />
95 {<br />
96 return ( output context . getClone ( ) ) ;<br />
97 }<br />
98 } ;<br />
99<br />
100<br />
101 #endif // s i m p l e o u t p u t h a n d l i n g h<br />
10.2.10 Displayable<br />
Die Klasse SimpleOutputHandl<strong>in</strong>g arbeitet ausschließlich mit Elementen der<br />
Klasse Displayable. Diese ist e<strong>in</strong>e abstrakte Basisklasse, von der man darstellbare<br />
Klassen entsprechend ableiten muss. Das Interface, das solche Klassen<br />
dann implementieren müssen, ist sehr e<strong>in</strong>fach:<br />
1 // s i m p l e d i s p l a y a b l e . h − a simple a b s t r a c t base f o r a<br />
2 // d i s p l a y a b l e o b j e c t<br />
3<br />
4 #ifndef s i m p l e d i s p l a y a b l e h<br />
5 #def<strong>in</strong>e s i m p l e d i s p l a y a b l e h<br />
6<br />
7 #<strong>in</strong>clude ” simple output context . h”<br />
8 class SimpleOutputHandl<strong>in</strong>g ;<br />
9<br />
10 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
11 /∗ The abstract base c l a s s f o r t e x t u a l l y d i s p l a y a b l e o b j e c t s ,<br />
12 ∗ as i t i s used by SimpleOutputHandl<strong>in</strong>g<br />
13 ∗/<br />
14<br />
15 class Displayable<br />
16 {<br />
17 public :<br />
18<br />
19 /∗ Destructor<br />
20 ∗ Just to make sure that a v i r t u a l d e s t r u c t o r e x i s t s<br />
21 ∗/<br />
22 virtual ˜ Displayable ( ) { }<br />
23<br />
24 /∗ This c a l l b a c k i s used to n o t i f y a d i s p l a y a b l e that i t<br />
25 ∗ has j u s t been r e g i s t e r e d with an output handler . I t can<br />
26 ∗ perform a l l necessary operations <strong>in</strong> t h i s method to<br />
27 ∗ be prepared to generate output .<br />
28 ∗ @param handler The handler that t h i s d i s p l a y a b l e was<br />
29 ∗ r e g i s t e r e d with .<br />
30 ∗/<br />
31 virtual void d i s p l a y a b l e R e g i s t e r e d (<br />
32 SimpleOutputHandl<strong>in</strong>g &handler ) = 0 ;<br />
33<br />
34 /∗ The c a l l b a c k that i s c a l l e d by SimpleOutputHandl<strong>in</strong>g when<br />
35 ∗ outputt<strong>in</strong>g the d i s p l a y a b l e s i s t r i g g e r e d .<br />
36 ∗ @param context The output context to write to .<br />
37 ∗/<br />
38 virtual void writeDisplayRep ( OutputContext &context ) = 0 ;<br />
39<br />
40 } ;<br />
41<br />
42 #endif // s i m p l e d i s p l a y a b l e h
10.2.11 OutputContext<br />
10.2 Das DDD 279<br />
Abhängig davon, ob es sich nun um e<strong>in</strong>en Text-, Graphik- oder anderen<br />
Kontext handelt, stehen verschiedene Methoden <strong>in</strong> diesem Kontext zur<br />
Verfügung. Diese können nicht wirklich <strong>in</strong> e<strong>in</strong>er Basisklasse deklariert werden,<br />
denn sie s<strong>in</strong>d e<strong>in</strong>fach zu unterschiedlich und nicht vorhersehbar. Aus<br />
diesem Grund ist die abstrakte Basisklasse OutputContext auch dementsprechend<br />
primitiv gestaltet:<br />
1<br />
2 #ifndef s i m p l e o u t p u t c o n t e x t h<br />
3 #def<strong>in</strong>e s i m p l e o u t p u t c o n t e x t h<br />
4<br />
5 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
6 /∗ The base c l a s s f o r a l l d i f f e r e n t output contexts . I t i s<br />
7 ∗ used <strong>in</strong> context with the Displayable i n t e r f a c e .<br />
8 ∗/<br />
9<br />
10 class OutputContext<br />
11 {<br />
12 public :<br />
13<br />
14 /∗ Destructor<br />
15 ∗ Just to make sure that a v i r t u a l d e s t r u c t o r e x i s t s<br />
16 ∗/<br />
17 virtual ˜ OutputContext ( ) { }<br />
18<br />
19 /∗ Returns a clone of the output context . Has to be overridden<br />
20 ∗ by a l l derived c l a s s e s to d e l i v e r a clone o f the c o r r e c t<br />
21 ∗ type . This clone has to be dynamically a l l o c a t e d via new<br />
22 ∗ and the c a l l e r i s r e s p o n s i b l e to d e l e t e i t i f no longer<br />
23 ∗ needed .<br />
24 ∗ @return A po<strong>in</strong>ter to the dynamically generated clone o f t h i s<br />
25 ∗ OutputContext . This clone has to have exactly the<br />
26 ∗ type of the derived c l a s s .<br />
27 ∗/<br />
28 virtual OutputContext ∗ getClone ( ) = 0 ;<br />
29 } ;<br />
30<br />
31<br />
32 #endif // s i m p l e o u t p u t c o n t e x t h<br />
10.2.12 TextOutputContext<br />
Die Ausprägung des Output Contexts, die im Spiel Verwendung f<strong>in</strong>det, ist e<strong>in</strong><br />
ganz primitiver TextOutputContext, der gerade eben e<strong>in</strong>mal e<strong>in</strong> paar Methoden<br />
besitzt, über die man Daten ausgeben kann. Diese werden e<strong>in</strong>fach auf<br />
cout geschrieben. Mit dem Wissen um Streams, die noch <strong>in</strong> Abschnitt 16.6<br />
kurz umrissen werden, könnte man diesen Kontext natürlich sauberer gestalten.<br />
1 // s i m p l e t e x t o u t p u t c o n t e x t . h − a simple text output context<br />
2<br />
3 #ifndef s i m p l e t e x t o u t p u t c o n t e x t h<br />
4 #def<strong>in</strong>e s i m p l e t e x t o u t p u t c o n t e x t h
280 10. Memory – e<strong>in</strong> kle<strong>in</strong>es Beispiel<br />
5<br />
6 #<strong>in</strong>clude <br />
7<br />
8 #<strong>in</strong>clude ” simple output context . h”<br />
9<br />
10 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
11 /∗ A very simple text output context<br />
12 ∗/<br />
13<br />
14 class TextOutputContext : public OutputContext<br />
15 {<br />
16 public :<br />
17<br />
18 /∗ Default constructor<br />
19 ∗ Noth<strong>in</strong>g has to be done <strong>in</strong> i t<br />
20 ∗/<br />
21 TextOutputContext ( ) { }<br />
22<br />
23 /∗ Copy constructor<br />
24 ∗ Just because i t has to be made e x p l i c i t<br />
25 ∗/<br />
26 TextOutputContext ( const TextOutputContext & s r c ) {}<br />
27<br />
28 /∗ Destructor<br />
29 ∗ Just because of the convention . . .<br />
30 ∗/<br />
31 virtual ˜ TextOutputContext ( ) { }<br />
32<br />
33 /∗ This method i s used to write text to the output .<br />
34 ∗/<br />
35 virtual void write ( const char ∗ text )<br />
36 {<br />
37 std : : cout
10.2.13 GameCard<br />
10.2 Das DDD 281<br />
Die Basisklasse für unsere Spielkarte ist e<strong>in</strong>e weitere kle<strong>in</strong>e Variation der im<br />
letzten Kapitel besprochenen Karten, die folgendermaßen aussieht:<br />
1 // game card v3 . h − d e c l a r a t i o n o f a general card f o r games<br />
2<br />
3 #ifndef game card v3 h<br />
4 #def<strong>in</strong>e game card v3 h<br />
5<br />
6 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
7<br />
8 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
9 /∗ A general c l a s s of a card f o r games . I t has two s i d e s and can<br />
10 ∗ be turned around . One way of turn<strong>in</strong>g i t i s to d e l i b e r a t e l y put<br />
11 ∗ the f r o n t or the back s i d e up . The other way i s to f l i p the<br />
12 ∗ card . This c l a s s does not handle any other <strong>in</strong>formation than<br />
13 ∗ the v i s i b l e s i d e . I t has noth<strong>in</strong>g to do with d i s p l a y<br />
14 ∗ r e p r e s e n t a t i o n s and other s t u f f .<br />
15 ∗/<br />
16<br />
17 class GameCard<br />
18 {<br />
19 protected :<br />
20<br />
21 /∗ Stores the s i d e of the card that i s v i s i b l e at the moment .<br />
22 ∗/<br />
23 u<strong>in</strong>t8 v i s i b l e s i d e ;<br />
24<br />
25 public :<br />
26<br />
27 /∗ The f o l l o w i n g two constants d e f i n e the p o s s i b l e values f o r<br />
28 ∗ the v i s i b l e s i d e of the card .<br />
29 ∗/<br />
30 static const u<strong>in</strong>t8 FRONT SIDE = 0 x01 ;<br />
31 static const u<strong>in</strong>t8 BACK SIDE = 0x02 ;<br />
32<br />
33 /∗ Standard Constructor<br />
34 ∗ @param v i s i b l e s i d e Has to be e i t h e r o f the two constants<br />
35 ∗ def<strong>in</strong>ed above . The parameter i s checked f o r<br />
36 ∗ v a l i d i t y and i f i t i s i n v a l i d the i n i t i a l l y<br />
37 ∗ v i s i b l e s i d e i s the f r o n t s i d e .<br />
38 ∗/<br />
39 explicit GameCard( u<strong>in</strong>t8 v i s i b l e s i d e ) :<br />
40 v i s i b l e s i d e ( ( ( v i s i b l e s i d e == FRONT SIDE ) | |<br />
41 ( v i s i b l e s i d e == BACK SIDE) ) ?<br />
42 v i s i b l e s i d e : FRONT SIDE) {}<br />
43<br />
44 /∗ Copy Constructor<br />
45 ∗ Implemented e x p l i c i t l y to make sure that i t cannot be<br />
46 ∗ f o r g o t t e n <strong>in</strong> f uture changes .<br />
47 ∗/<br />
48 GameCard( const GameCard & s r c )<br />
49 {<br />
50 v i s i b l e s i d e = s r c . v i s i b l e s i d e ;<br />
51 }<br />
52<br />
53 /∗ Destructor<br />
54 ∗ Just to make sure that a v i r t u a l d e s t r u c t o r e x i s t s<br />
55 ∗/<br />
56 virtual ˜GameCard( ) { }<br />
57<br />
58 /∗ Turns the card to the other s i d e . No checks f o r the<br />
59 ∗ v a l i d i t y of the stored s i d e are necessary because i t s<br />
60 ∗ v a l i d i t y i s f u l l y guaranteed by the constructor and
282 10. Memory – e<strong>in</strong> kle<strong>in</strong>es Beispiel<br />
61 ∗ the other methods .<br />
62 ∗/<br />
63 virtual void turnCard ( )<br />
64 {<br />
65 v i s i b l e s i d e = ( v i s i b l e s i d e == FRONT SIDE) ?<br />
66 BACK SIDE : FRONT SIDE;<br />
67 }<br />
68<br />
69 /∗ Puts the f r o n t s i d e of the card up ( i . e . makes i t v i s i b l e ) ,<br />
70 ∗ r e g a r d l e s s of which s i d e i s v i s i b l e at the moment .<br />
71 ∗/<br />
72 virtual void putFrontSideUp ( )<br />
73 {<br />
74 v i s i b l e s i d e = FRONT SIDE;<br />
75 }<br />
76<br />
77 /∗ Puts the back s i d e of the card up ( i . e . makes i t v i s i b l e ) ,<br />
78 ∗ r e g a r d l e s s of which s i d e i s v i s i b l e at the moment .<br />
79 ∗/<br />
80 virtual void putBackSideUp ( )<br />
81 {<br />
82 v i s i b l e s i d e = BACK SIDE;<br />
83 }<br />
84<br />
85 /∗ Returns the v i s i b l e s i d e of the card .<br />
86 ∗ @return The v i s i b l e s i d e of the card <strong>in</strong> the form o f one o f<br />
87 ∗ the two constants FRONT SIDE or BACK SIDE.<br />
88 ∗/<br />
89 virtual u<strong>in</strong>t8 g e t V i s i b l e S i d e ( )<br />
90 {<br />
91 return ( v i s i b l e s i d e ) ;<br />
92 }<br />
93 } ;<br />
94<br />
95<br />
96 #endif // game card v3 h<br />
10.2.14 MemoryGameCard<br />
Die Klasse MemoryGameCard ist ebenfalls e<strong>in</strong>e weitere kle<strong>in</strong>e Variation der im<br />
letzten Kapitel besprochenen Memory Spielkarten. Sie ist e<strong>in</strong>e e<strong>in</strong>fache, darstellbare<br />
Spielkarte. Als solche ist sie von Displayable und von GameCard<br />
abgeleitet und speichert e<strong>in</strong> Symbol für die Vorderseite und e<strong>in</strong> Symbol für<br />
die Rückseite. Nachdem die Karte ke<strong>in</strong>e Kristallkugel befragen kann, welches<br />
Symbol sie für die Vorder- und welches für die Rückseite speichern soll,<br />
müssen diese beiden von außen gesetzt werden. Dies muss natürlich im Konstruktor<br />
geschehen, denn es muss ja verh<strong>in</strong>dert werden, dass jemand schummelt,<br />
<strong>in</strong>dem e<strong>in</strong>fach das Symbol umgesetzt wird :-). Dieses Meisterwerk hat<br />
dann die folgende Form:<br />
1 // memory game card v5 . h − memory game card as used <strong>in</strong> the example<br />
2<br />
3 #ifndef memory game card v5 h<br />
4 #def<strong>in</strong>e memory game card v5 h<br />
5<br />
6 #<strong>in</strong>clude ”game card v3 . h”<br />
7 #<strong>in</strong>clude ” s i m p l e d i s p l a y a b l e . h”
8 #<strong>in</strong>clude ” s i m p l e t e x t o u t p u t c o n t e x t . h”<br />
9<br />
10.2 Das DDD 283<br />
10 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
11 /∗ A game card f o r the memory game . I t i s able to s t o r e symbols<br />
12 ∗ f o r both s i d e s . Both are stored as a r b i t r a r y char p o i n t e r s .<br />
13 ∗ The card i s compatible to the mechanism def<strong>in</strong>ed by the<br />
14 ∗ SimpleOutputHandl<strong>in</strong>g c l a s s because i t i s derived from<br />
15 ∗ Displayable . I t i s a l s o a standard GameCard .<br />
16 ∗/<br />
17 class MemoryGameCard : public GameCard ,<br />
18 public Displayable<br />
19 {<br />
20 protected :<br />
21<br />
22 /∗ The s t r i n g s t o r i n g the f r o n t s i d e symbol . I t i s a copy o f<br />
23 ∗ the f r o n t s i d e symbol that i s passed on as a parameter o f<br />
24 ∗ the constructor and has to be deleted <strong>in</strong> the d e s t r u c t o r .<br />
25 ∗/<br />
26 char ∗ front symbol ;<br />
27<br />
28 /∗ The s t r i n g s t o r i n g the back s i d e symbol . I t i s a copy o f<br />
29 ∗ the back s i d e symbol that i s passed on as a parameter o f<br />
30 ∗ the constructorand has to be deleted <strong>in</strong> the d e s t r u c t o r .<br />
31 ∗/<br />
32 char ∗ back symbol ;<br />
33<br />
34 private :<br />
35<br />
36 /∗ Copy construction i s forbidden<br />
37 ∗/<br />
38 MemoryGameCard( const MemoryGameCard & s r c ) :<br />
39 GameCard(BACK SIDE) {}<br />
40<br />
41 public :<br />
42<br />
43 /∗ Standard constructor<br />
44 ∗ I t has to c a l l the constructor f o r the base c l a s s e s . The<br />
45 ∗ constructor f o r GameCard i s c a l l e d so that the back s i d e i s<br />
46 ∗ i n i t i a l l y the v i s i b l e s i d e .<br />
47 ∗ The s t r i n g s passed on f o r the symbols have to be copied to the<br />
48 ∗ appropriate members . I f 0 p o i n t e r s are passed then an empty<br />
49 ∗ s t r i n g i s generated f o r the i n t e r n a l r e p r e s e n t a t i o n ( j u s t<br />
50 ∗ because exceptions are not known yet ) .<br />
51 ∗/<br />
52 MemoryGameCard( const char ∗ front symbol ,<br />
53 const char ∗ back symbol ) ;<br />
54<br />
55 /∗ Destructor<br />
56 ∗ Has to d e l e t e the copied s t r i n g s f o r the f r o n t and the<br />
57 ∗ back symbols<br />
58 ∗/<br />
59 virtual ˜MemoryGameCard ( ) ;<br />
60<br />
61 /∗ Noth<strong>in</strong>g s p e c i a l to be done when a card i s r e g i s t e r e d<br />
62 ∗ @param handler The handler that t h i s d i s p l a y a b l e was<br />
63 ∗ r e g i s t e r e d with .<br />
64 ∗/<br />
65 virtual void d i s p l a y a b l e R e g i s t e r e d (<br />
66 SimpleOutputHandl<strong>in</strong>g &handler ) {}<br />
67<br />
68 /∗ Writes e i t h e r the f r o n t or the back symbol accord<strong>in</strong>g to the<br />
69 ∗ s i d e that i s v i s i b l e at the moment .<br />
70 ∗/<br />
71 virtual void writeDisplayRep ( OutputContext &context )<br />
72 {<br />
73 dynamic cast(context ) .
284 10. Memory – e<strong>in</strong> kle<strong>in</strong>es Beispiel<br />
74 write ( ( v i s i b l e s i d e == FRONT SIDE) ?<br />
75 front symbol : back symbol ) ;<br />
76 }<br />
77<br />
78 /∗ Returns the f r o n t symbol of the card<br />
79 ∗ @return The f r o n t symbol<br />
80 ∗/<br />
81 virtual const char ∗ getFrontSymbol ( )<br />
82 {<br />
83 return ( front symbol ) ;<br />
84 }<br />
85<br />
86 /∗ Returns the back symbol of the card<br />
87 ∗ @return The back symbol<br />
88 ∗/<br />
89 virtual const char ∗ getBackSymbol ( )<br />
90 {<br />
91 return ( front symbol ) ;<br />
92 }<br />
93 } ;<br />
94<br />
95 #endif // memory game card v5 h<br />
10.2.15 MemoryGameboard<br />
Die Klasse MemoryGameboard ist für alles verantwortlich, was mit der Anzeige<br />
des Spielbretts und der darauf liegenden Karten zu tun hat. Aus diesem<br />
Grund ist sie auch die Drehscheibe für alles, was mit dem Auflegen und<br />
Umdrehen von Karten zu tun hat. Um der Spielsteuerung zu ermöglichen,<br />
herauszuf<strong>in</strong>den, wie viele Karten bereits umgedreht wurden, hat diese Klasse<br />
auch die entsprechenden Abfragemethoden zu implementieren. Diese Methoden<br />
dürfen vor allem nicht naiv implementiert werden, denn je nach Größe des<br />
Spielfelds kann e<strong>in</strong> oftmaliges Abfragen jeder e<strong>in</strong>zelnen Karte zu erheblichen<br />
Performanceproblemen führen.<br />
Das Memory Spielbrett ist nicht dafür verantwortlich, die Karten, die<br />
auf ihm liegen, zu generieren. Es ist nur dafür verantwortlich, Karten, die<br />
aufgelegt werden, auch entsprechend zu speichern.<br />
Auch bei der Darstellung des Spielfelds ist noch e<strong>in</strong>e Kle<strong>in</strong>igkeit zu beachten:<br />
Es ist nicht genug, e<strong>in</strong>fach nur die auf dem Feld liegenden Karten bei<br />
der verantwortlichen Instanz von SimpleOutputHandl<strong>in</strong>g anzumelden. Um<br />
s<strong>in</strong>nvoll spielen zu können, müssen auch die Reihen und Spalten des Spielfelds<br />
mit entsprechenden Köpfen versehen werden, die die Koord<strong>in</strong>aten anzeigen.<br />
Um nun ke<strong>in</strong>e Seiteneffekte und künstliche Abhängigkeiten <strong>in</strong> das Programm<br />
e<strong>in</strong>zubauen, ist der e<strong>in</strong>zig s<strong>in</strong>nvolle Weg, auch diese Köpfe über entsprechende<br />
darstellbare Objekte zu realisieren, die von Displayable abgeleitet s<strong>in</strong>d.<br />
Diese s<strong>in</strong>d im überblicksartigen Klassendiagramm noch nicht enthalten,<br />
werden aber nun als notwendige Hilfsklassen e<strong>in</strong>geführt (siehe Abschnitt<br />
10.2.16 und Abschnitt 10.2.17).<br />
E<strong>in</strong> kle<strong>in</strong>er Exkurs: Manche Leser werden es schon bemerkt haben, dass<br />
ich hier e<strong>in</strong>e Kle<strong>in</strong>igkeit demonstrieren möchte. Der Schritt, e<strong>in</strong>fach so e<strong>in</strong>e
10.2 Das DDD 285<br />
kle<strong>in</strong>e Hilfsklasse e<strong>in</strong>zuführen, die im Klassendiagramm noch nicht enthalten<br />
ist, ist schlimm genug. In der Praxis muss nun unbed<strong>in</strong>gt e<strong>in</strong> Schritt zurück<br />
gemacht werden und das Klassendiagramm ergänzt werden.<br />
Das ist jedoch noch nicht alles: Im Normalfall wäre jetzt sogar der<br />
Zeitpunkt gekommen, zu dem man allgeme<strong>in</strong>gültig über simple darstellbare<br />
Objekte und über Conta<strong>in</strong>er von darstellbaren Objekten nachdenken muss.<br />
Das Spielbrett ist nämlich von se<strong>in</strong>er Logik her genau e<strong>in</strong> solcher Conta<strong>in</strong>er.<br />
Das würde dann e<strong>in</strong>e weitere Änderung im Klassendiagramm bedeuten:<br />
E<strong>in</strong> darstellbarer Conta<strong>in</strong>er ist abgeleitet von Displayable und das<br />
MemoryGameboard ist dann von diesem abgeleitet. Um hier nicht zur absoluten<br />
Verwirrung beizutragen, führe ich diesen allgeme<strong>in</strong>en Conta<strong>in</strong>er nicht<br />
e<strong>in</strong>. Stattdessen wird das MemoryGameboard dergestalt entworfen, dass es die<br />
Conta<strong>in</strong>erfunktionalität besitzt.<br />
Alle Leser mögen sich aber nun unbed<strong>in</strong>gt e<strong>in</strong> paar Gedanken machen,<br />
was diese Vorgangsweise, die leider auch <strong>in</strong> der Praxis sehr oft praktiziert<br />
wird, bedeutet! Hier bahnt sich bereits e<strong>in</strong> böser Hack an, denn es ist zu<br />
erwarten, dass im Lauf der Zeit das Spiel mehrere Änderungen erfährt und<br />
dass wahrsche<strong>in</strong>lich auch dann an mehreren Stellen e<strong>in</strong> allgeme<strong>in</strong>er Conta<strong>in</strong>er<br />
gebraucht würde. In jedem Fall muss dann e<strong>in</strong>e erhebliche Menge an<br />
Code umgeschrieben werden, wenn man ihn erst verspätet e<strong>in</strong>führt. Oder,<br />
noch schlimmer, alle Klassen, die ihn brauchen, implementieren die Funktionalität<br />
selbst. Dass das dann auf Kosten der Übersichtlichkeit, Wartbarkeit,<br />
Änderbarkeit und Intuitivität geht, lässt sich leicht nachvollziehen.<br />
Nach diesem Exkurs, der etwas zum Nachdenken anregen sollte, sieht das mit<br />
Vorsicht zu genießende Resultat für unser Spielbrett folgendermaßen aus:<br />
1 // memory gameboard . h − a simple gameboard f o r memory<br />
2<br />
3 #ifndef memory gameboard h<br />
4 #def<strong>in</strong>e memory gameboard h<br />
5<br />
6 #<strong>in</strong>clude ” s i m p l e d i s p l a y a b l e . h”<br />
7 #<strong>in</strong>clude ” simple output handl<strong>in</strong>g . h”<br />
8 #<strong>in</strong>clude ” s i m p l e v e c t o r . h”<br />
9 #<strong>in</strong>clude ”memory game card v5 . h”<br />
10 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
11<br />
12 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
13 /∗ The c l a s s f o r the gameboard . This c l a s s i s r e s p o n s i b l e f o r<br />
14 ∗ the management of the cards and has to take care f o r<br />
15 ∗ r e g i s t e r i n g them at the output handler . I t a l s o has to keep<br />
16 ∗ track of the number of cards on the board and o f the number<br />
17 ∗ o f cards with f r o n t and back s i d e s up . Turn<strong>in</strong>g cards i s<br />
18 ∗ performed via a c a l l to t h i s c l a s s .<br />
19 ∗/<br />
20<br />
21 class MemoryGameboard : public Displayable<br />
22 {<br />
23 protected :<br />
24<br />
25 /∗ Stores the number of rows of the board accord<strong>in</strong>g to the<br />
26 ∗ parameter passed to the constructor . In p r i n c i p l e t h i s
286 10. Memory – e<strong>in</strong> kle<strong>in</strong>es Beispiel<br />
27 ∗ i s a reduncancy , because the vector o f v e c t o r s used to<br />
28 ∗ s t o r e the cards a l s o keeps track o f i t , but i t ’ s much<br />
29 ∗ more readable t h i s way .<br />
30 ∗/<br />
31 u<strong>in</strong>t32 num rows ;<br />
32<br />
33 /∗ Stores the number of columns o f the board accord<strong>in</strong>g to the<br />
34 ∗ parameter passed to the constructor . This i s the same<br />
35 ∗ reduncancy as s t o r i n g the number o f rows , but i t ’ s a l s o<br />
36 ∗ done f o r r e a d a b i l i t y reasons .<br />
37 ∗/<br />
38 u<strong>in</strong>t32 num cols ;<br />
39<br />
40 /∗ The number of cards on the board that are l y i n g f r o n t<br />
41 ∗ s i d e up . This member i s updated on every s i n g l e operation<br />
42 ∗ that i s performed on a card on the board .<br />
43 ∗/<br />
44 u<strong>in</strong>t32 num cards front side up ;<br />
45<br />
46 /∗ The row vector that s t o r e s a l l the column v e c t o r s used<br />
47 ∗ to hold the cards . This vector i s i n i t i a l i z e d to d e l e t e<br />
48 ∗ the cards <strong>in</strong> the d e s t r u c t o r .<br />
49 ∗/<br />
50 Vector ∗ row vector ;<br />
51<br />
52 /∗ The vector s t o r i n g a l l the d i s p l a y a b l e s that are r e s p o n s i b l e<br />
53 ∗ f o r the row head<strong>in</strong>gs . I t i s i n i t i a l i z e d to d e l e t e the<br />
54 ∗ d i s p l a y a b l e s <strong>in</strong> the d e s t r u c t o r .<br />
55 ∗/<br />
56 Vector ∗ r o w h e a d i n g d i s p l a y a b l e s ;<br />
57<br />
58 /∗ The vector s t o r i n g a l l the d i s p l a y a b l e s that are r e s p o n s i b l e<br />
59 ∗ f o r the column head<strong>in</strong>gs . I t i s i n i t i a l i z e d to d e l e t e the<br />
60 ∗ d i s p l a y a b l e s <strong>in</strong> the d e s t r u c t o r .<br />
61 ∗/<br />
62 Vector ∗ c o l h e a d i n g d i s p l a y a b l e s ;<br />
63<br />
64 /∗ The vector s t o r i n g a l l the d i s p l a y a b l e s that are r e s p o n s i b l e<br />
65 ∗ f o r the end of l i n e a f t e r each column i n c l u d i n g the column<br />
66 ∗ head<strong>in</strong>g l i n e . I t i s i n i t i a l i z e d to d e l e t e the d i s p l a y a b l e s<br />
67 ∗ <strong>in</strong> the d e s t r u c t o r .<br />
68 ∗/<br />
69 Vector ∗ e o l d i s p l a y a b l e s ;<br />
70<br />
71 /∗ This <strong>in</strong>stance of the output handler i s needed to implement<br />
72 ∗ the conta<strong>in</strong>er ’ s d i s p l a y f u n c t i o n a l i t y . Here a l l the cards ,<br />
73 ∗ row and column head<strong>in</strong>gs are r e g i s t e r e d . On a c a l l to<br />
74 ∗/<br />
75 SimpleOutputHandl<strong>in</strong>g ∗ conta<strong>in</strong>er output handl<strong>in</strong>g ;<br />
76<br />
77 /∗ This holds the clone of the output context to be able to<br />
78 ∗ d e l e t e i t <strong>in</strong> the d e s t r u c t o r .<br />
79 ∗/<br />
80 OutputContext ∗ c o n t a i n e r o u t p u t c o n t e x t ;<br />
81<br />
82 private :<br />
83<br />
84 /∗ Copy construction i s not allowed<br />
85 ∗/<br />
86 MemoryGameboard( const MemoryGameboard& s r c ) {}<br />
87<br />
88 public :<br />
89<br />
90 /∗ Standard constructor<br />
91 ∗ I n i t i a l i z e s the v e c t o r s f o r the cards , the row and column<br />
92 ∗ head<strong>in</strong>gs and s e t s the output handler to 0 . The output
10.2 Das DDD 287<br />
93 ∗ handler i s then s e t when the gameboard i t s e l f i s r e g i s t e r e d<br />
94 ∗ with the ” master ” output handler .<br />
95 ∗ The number of rows and the number o f columns are checked <strong>in</strong><br />
96 ∗ a way that they both have to be non−zero and that the number<br />
97 ∗ o f f i e l d s o v e r a l l i s even ( there i s always an even number o f<br />
98 ∗ cards ! ) . I f someth<strong>in</strong>g i s wrong the values have to be c o r r e c t e d<br />
99 ∗ accord<strong>in</strong>gly ( j u s t because exceptions are not known yet ) .<br />
100 ∗ @param num rows The number of rows f o r the f i e l d<br />
101 ∗ @param num cols The number of columns f o r the f i e l d<br />
102 ∗/<br />
103 MemoryGameboard( u<strong>in</strong>t32 num rows , u<strong>in</strong>t32 num cols ) ;<br />
104<br />
105 /∗ Destructor<br />
106 ∗ Has to d e l e t e a l l v e c t o r s and the output handler f o r the<br />
107 ∗ conta<strong>in</strong>er .<br />
108 ∗/<br />
109 virtual ˜MemoryGameboard ( ) ;<br />
110<br />
111 /∗ Because the gameboard does not produce any s p e c i a l i z e d<br />
112 ∗ output i t s e l f ( a l l text f i e l d s are r e g i s t e r e d as text<br />
113 ∗ d i s p l a y a b l e s ) i t only t r i g g e r s output o f the conta<strong>in</strong>ed<br />
114 ∗ d i s p l a y a b l e elements .<br />
115 ∗ @param context The output context to write to<br />
116 ∗/<br />
117 virtual void writeDisplayRep ( OutputContext &context )<br />
118 {<br />
119 i f ( conta<strong>in</strong>er output handl<strong>in</strong>g )<br />
120 conta<strong>in</strong>er output handl<strong>in</strong>g −>writeOutput ( ) ;<br />
121 }<br />
122<br />
123 /∗ When t h i s method i s c a l l e d a l l necessary text d i s p l a y a b l e s<br />
124 ∗ have to be generated and stored <strong>in</strong> the row and column head<strong>in</strong>g<br />
125 ∗ v e c t o r s . Additionally the conta<strong>in</strong>er output handler has to<br />
126 ∗ be i n s t a n t i a t e d and these generated d i s p l a y a b l e s have to<br />
127 ∗ be r e g i s t e r e d there together with a l l the cards .<br />
128 ∗ ATTENTION: This behaviour means that a l l cards already<br />
129 ∗ have to be on the gameboard before r e g i s t e r i n g i t with the<br />
130 ∗ d i s p l a y handler ! ! ! ! Otherwise t h i s method ’ s tasks are not<br />
131 ∗ f u l f i l l a b l e c o r r e c t l y ! ! !<br />
132 ∗ ATTENTION: I f t h i s board i s r e g i s t e r e d with more than one<br />
133 ∗ d i s p l a y handler the i n i t i a l i z i n g s t e p s must not be done<br />
134 ∗ aga<strong>in</strong> , because everyth<strong>in</strong>g i s already setup c o r r e c t l y .<br />
135 ∗ Therefore t h i s method has to check t h i s b e f o r e .<br />
136 ∗ @param handler The handler f o r which t h i s d i s p l a y a b l e was<br />
137 ∗ r e g i s t e r e d .<br />
138 ∗/<br />
139 virtual void d i s p l a y a b l e R e g i s t e r e d (<br />
140 SimpleOutputHandl<strong>in</strong>g &handler ) ;<br />
141<br />
142 /∗ Turns a l l the cards so that they show the f r o n t s i d e . This<br />
143 ∗ method does not t r i g g e r any output , so f o r the r e s u l t o f<br />
144 ∗ the operation to be shown the output has to be t r i g g e r e d<br />
145 ∗ e x p l i c i t l y by an accord<strong>in</strong>g c a l l to the d i s p l a y handler .<br />
146 ∗ This method a l s o updates the i n t e r n a l cache f o r the number<br />
147 ∗ o f cards that show t h e i r f r o n t s i d e at the moment ( t h i s<br />
148 ∗ i s the v a r i a b l e num cards front side up ) .<br />
149 ∗/<br />
150 virtual void putAllCardsFrontSideUp ( ) ;<br />
151<br />
152 /∗ Turns a l l the cards so that they show the back s i d e . This<br />
153 ∗ method does not t r i g g e r any output , so f o r the r e s u l t o f<br />
154 ∗ the operation to be shown the output has to be t r i g g e r e d<br />
155 ∗ e x p l i c i t l y by an accord<strong>in</strong>g c a l l to the d i s p l a y handler .<br />
156 ∗ This method a l s o updates the i n t e r n a l cache f o r the number<br />
157 ∗ o f cards that show t h e i r f r o n t s i d e at the moment ( t h i s<br />
158 ∗ i s the v a r i a b l e num cards front side up ) .
288 10. Memory – e<strong>in</strong> kle<strong>in</strong>es Beispiel<br />
159 ∗/<br />
160 virtual void putAllCardsBackSideUp ( ) ;<br />
161<br />
162 /∗ Turns the card given by the coord<strong>in</strong>ates , so that i t shows<br />
163 ∗ the f r o n t s i d e . I f necessary ( i . e . the card showed i t s<br />
164 ∗ back s i d e before ) the v a r i a b l e num cards front side up<br />
165 ∗ has to be updated .<br />
166 ∗ @param row The row of the card to be turned s t a r t i n g<br />
167 ∗ with 0 f o r the f i r s t row .<br />
168 ∗ @param c o l The column of the card to be turned s t a r t i n g<br />
169 ∗ with 0 f o r the f i r s t column .<br />
170 ∗/<br />
171 virtual void putCardFrontSideUp ( u<strong>in</strong>t32 row , u<strong>in</strong>t32 c o l ) ;<br />
172<br />
173 /∗ Turns the card given by the coord<strong>in</strong>ates , so that i t shows<br />
174 ∗ the back s i d e . I f necessary ( i . e . the card showed i t s<br />
175 ∗ f r o n t s i d e before ) the v a r i a b l e num cards front side up<br />
176 ∗ has to be updated .<br />
177 ∗ @param row The row of the card to be turned s t a r t i n g<br />
178 ∗ with 0 f o r the f i r s t row .<br />
179 ∗ @param c o l The column of the card to be turned s t a r t i n g<br />
180 ∗ with 0 f o r the f i r s t column .<br />
181 ∗/<br />
182 virtual void putCardBackSideUp ( u<strong>in</strong>t32 row , u<strong>in</strong>t32 c o l ) ;<br />
183<br />
184 /∗ Returns a po<strong>in</strong>ter to the card at the given p o s i t i o n .<br />
185 ∗ I f there i s no card at t h i s p o s i t i o n a 0 po<strong>in</strong>ter i s<br />
186 ∗ returned ( t h i s happens i m p l i c i t l y because o f the k<strong>in</strong>d o f<br />
187 ∗ storage of the cards <strong>in</strong> the vector ) .<br />
188 ∗ @param row The row of the d e s i r e d card s t a r t i n g with 0<br />
189 ∗ f o r the f i r s t row .<br />
190 ∗ @param c o l The column of the d e s i r e d card s t a r t i n g<br />
191 ∗ with 0 f o r the f i r s t column .<br />
192 ∗/<br />
193 virtual MemoryGameCard ∗ getCard ( u<strong>in</strong>t32 row , u<strong>in</strong>t32 c o l ) const ;<br />
194<br />
195 /∗ Returns the number of rows . This may be a number that i s<br />
196 ∗ d i f f e r e n t to the one given <strong>in</strong> the constructor , because i t<br />
197 ∗ could have happened that i t was c o r r e c t e d i n t e r n a l l y ( see<br />
198 ∗ a l s o : d e s c r i p t i o n of the constructor )<br />
199 ∗ @return The actual number of rows<br />
200 ∗/<br />
201 virtual u<strong>in</strong>t32 getNumRows ( ) const<br />
202 {<br />
203 return ( num rows ) ;<br />
204 }<br />
205<br />
206 /∗ Returns the number of columns . This may be a number that i s<br />
207 ∗ d i f f e r e n t to the one given <strong>in</strong> the constructor , because i t<br />
208 ∗ could have happened that i t was c o r r e c t e d i n t e r n a l l y ( see<br />
209 ∗ a l s o : d e s c r i p t i o n of the constructor )<br />
210 ∗ @return The actual number of columns<br />
211 ∗/<br />
212 virtual u<strong>in</strong>t32 getNumCols ( ) const<br />
213 {<br />
214 return ( num cols ) ;<br />
215 }<br />
216<br />
217 /∗ Returns the number of cards that c u r r e n t l y are l y i n g on the<br />
218 ∗ board with t h e i r f r o n t s i d e s up .<br />
219 ∗ @return The actual number of cards that show t h e i r f r o n t s i d e s<br />
220 ∗/<br />
221 virtual u<strong>in</strong>t32 getNumCardsFrontSideUp ( ) const<br />
222 {<br />
223 return ( num cards front side up ) ;<br />
224 }
225<br />
10.2 Das DDD 289<br />
226 /∗ Returns the number of cards that c u r r e n t l y are l y i n g on the<br />
227 ∗ board with t h e i r back s i d e s up .<br />
228 ∗ @return The actual number of cards that show t h e i r back s i d e s<br />
229 ∗/<br />
230 virtual u<strong>in</strong>t32 getNumCardsBackSideUp ( ) const<br />
231 {<br />
232 return ( ( num rows ∗ num cols ) − num cards front side up ) ;<br />
233 }<br />
234<br />
235 /∗ This method i s c a l l e d to put a card on the board . I f a card<br />
236 ∗ already occupies the d e s i r e d place t h i s c a l l i s ignored .<br />
237 ∗ Please note that the card has to be deleted then , because<br />
238 ∗ the c a l l e r s r e l y on t h i s f a c t ! ( j u s t because exceptions are<br />
239 ∗ not known yet )<br />
240 ∗ A range−check has to be performed on the coord<strong>in</strong>ates . I f they<br />
241 ∗ are out of range the c a l l i s ignored and the card i s deleted ,<br />
242 ∗ because c a l l e r s r e l y on t h i s f a c t ! ( j u s t because exceptions<br />
243 ∗ are not known yet )<br />
244 ∗ @param card A po<strong>in</strong>ter to the card that has to be put on the<br />
245 ∗ board . The card w i l l be deleted <strong>in</strong> the d e s t r u c t o r !<br />
246 ∗ @param row The row , where the card has to be put .<br />
247 ∗ @param c o l The column , where the card has to be put .<br />
248 ∗/<br />
249 virtual void putCardOnBoard (MemoryGameCard ∗ card ,<br />
250 u<strong>in</strong>t32 row , u<strong>in</strong>t32 c o l ) ;<br />
251 protected :<br />
252 /∗ An i n t e r n a l convenience method used to obta<strong>in</strong> a card . I t<br />
253 ∗ does not perform any checks because i t assumes that they<br />
254 ∗ have already been performed before .<br />
255 ∗ @param row The row of the d e s i r e d card .<br />
256 ∗ @param c o l The column of the d e s i r e d card .<br />
257 ∗/<br />
258 MemoryGameCard ∗ <strong>in</strong>ternalGetCard ( u<strong>in</strong>t32 row , u<strong>in</strong>t32 c o l ) const<br />
259 {<br />
260 return ( static cast(<br />
261 static cast(<br />
262 row vector −>getElementAt ( row))−>getElementAt ( c o l ) ) ) ;<br />
263 }<br />
264 } ;<br />
265<br />
266<br />
267 #endif // memory gameboard h<br />
Dass es e<strong>in</strong>en Vektor für die e<strong>in</strong>zelnen Displayables der Reihenköpfe (Zeile<br />
56) und Spaltenköpfe (Zeile 62) gibt, ist ja zu erwarten. Was aber soll im<br />
Vektor <strong>in</strong> Zeile 69 bloß gehalten werden? Ganz e<strong>in</strong>fach: Die Displayables, die<br />
nach jeder Spielfeldzeile den Zeilenumbruch bewirken! Ansonsten wäre das<br />
Spielfeld ja ziemlich lang und flach :-).<br />
10.2.16 IntDisplayable<br />
Die Klasse IntDisplayable, die hier vorgestellt wird, braucht eigentlich ke<strong>in</strong>e<br />
besondere Erklärung:<br />
1 // s i m p l e i n t d i s p l a y a b l e . h − a d i s p l a y a b l e f o r i n t e g r a l numbers<br />
2<br />
3 #ifndef s i m p l e i n t d i s p l a y a b l e h<br />
4 #def<strong>in</strong>e s i m p l e i n t d i s p l a y a b l e h
290 10. Memory – e<strong>in</strong> kle<strong>in</strong>es Beispiel<br />
5<br />
6 #<strong>in</strong>clude ” s i m p l e d i s p l a y a b l e . h”<br />
7 #<strong>in</strong>clude ” s i m p l e t e x t o u t p u t c o n t e x t . h”<br />
8<br />
9 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
10 /∗ A simple d i s p l a y a b l e f o r signed i n t e g e r s o f 6 4 Bits maximum length .<br />
11 ∗ I t implements the Displayable i n t e r f a c e and i s constructed with an<br />
12 ∗ appropriate number . When the c a l l b a c k to generate output i s c a l l e d ,<br />
13 ∗ the i n t e g e r i s written .<br />
14 ∗/<br />
15 class IntDisplayable : public Displayable<br />
16 {<br />
17 protected :<br />
18<br />
19 /∗ The i n t e g e r that s h a l l be displayed as passed on <strong>in</strong> the constructor .<br />
20 ∗/<br />
21 <strong>in</strong>t64 num ;<br />
22<br />
23 private :<br />
24<br />
25 /∗ Copy construction i s forbidden<br />
26 ∗/<br />
27 IntDisplayable ( const IntDisplayable & s r c ) {}<br />
28<br />
29 public :<br />
30<br />
31 /∗ Standard constructor<br />
32 ∗ I t has to c a l l the constructor f o r the base c l a s s and s t o r e the<br />
33 ∗ given i n t e g e r<br />
34 ∗/<br />
35 explicit IntDisplayable ( <strong>in</strong>t64 num ) :<br />
36 Displayable ( ) , num (num) {}<br />
37<br />
38 /∗ Destructor<br />
39 ∗ Just to meet the convention<br />
40 ∗/<br />
41 virtual ˜ IntDisplayable ( ) { }<br />
42<br />
43 /∗ Noth<strong>in</strong>g s p e c i a l has to be done upon r e g i s t r a t i o n<br />
44 ∗ @param handler The handler that t h i s d i s p l a y a b l e was<br />
45 ∗ r e g i s t e r e d with .<br />
46 ∗/<br />
47 virtual void d i s p l a y a b l e R e g i s t e r e d (<br />
48 SimpleOutputHandl<strong>in</strong>g &handler ) {}<br />
49<br />
50 /∗ Writes the number to the context . This method can only work<br />
51 ∗ with a text context .<br />
52 ∗ @param context The output context to write to .<br />
53 ∗/<br />
54 virtual void writeDisplayRep ( OutputContext &context )<br />
55 {<br />
56 dynamic cast(context ) . write (num ) ;<br />
57 }<br />
58<br />
59 } ;<br />
60<br />
61 #endif // s i m p l e i n t d i s p l a y a b l e h<br />
10.2.17 TextDisplayable<br />
Analog zur Darstellung von Ganzzahlen mittels e<strong>in</strong>es besonderen Displayables<br />
gibt es auch e<strong>in</strong>e entsprechende Klasse TextDisplayable, die sich für<br />
Str<strong>in</strong>gs verantwortlich fühlt:
1 // s i m p l e t e x t d i s p l a y a b l e . h − a d i s p l a y a b l e f o r text<br />
2<br />
3 #ifndef s i m p l e t e x t d i s p l a y a b l e h<br />
4 #def<strong>in</strong>e s i m p l e t e x t d i s p l a y a b l e h<br />
5<br />
6 #<strong>in</strong>clude < c s t r i n g><br />
7 #<strong>in</strong>clude ” s i m p l e d i s p l a y a b l e . h”<br />
8 #<strong>in</strong>clude ” s i m p l e t e x t o u t p u t c o n t e x t . h”<br />
9<br />
10.2 Das DDD 291<br />
10 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
11 /∗ A simple d i s p l a y a b l e f o r text<br />
12 ∗ I t implements the Displayable i n t e r f a c e and i s constructed with an<br />
13 ∗ appropriate s t r i n g . When the c a l l b a c k to<br />
14 ∗ obta<strong>in</strong> the d i s p l a y r e p r e s e n t a t i o n i s c a l l e d t h i s s t r i n g i s written .<br />
15 ∗ The s t r i n g may be any s t a t i c a l l y or dynamically a l l o c a t e d char ∗ ,<br />
16 ∗ because i t i s copied .<br />
17 ∗/<br />
18 class TextDisplayable : public Displayable<br />
19 {<br />
20 protected :<br />
21<br />
22 /∗ The s t r i n g s t o r i n g the text to be displayed . I t i s a copy o f the<br />
23 ∗ s t r i n g that i s passed on as a parameter o f the constructor .<br />
24 ∗ Therefore i t has to be deleted <strong>in</strong> the d e s t r u c t o r .<br />
25 ∗/<br />
26 char ∗ t e x t ;<br />
27<br />
28 private :<br />
29<br />
30 /∗ Copy construction i s forbidden<br />
31 ∗/<br />
32 TextDisplayable ( const TextDisplayable & s r c ) {}<br />
33<br />
34 public :<br />
35<br />
36 /∗ Standard constructor<br />
37 ∗ I t has to c a l l the constructor f o r the base c l a s s .<br />
38 ∗ The text passed on i s copied to the t e x t member . I f a 0 po<strong>in</strong>ter<br />
39 ∗ i s given as a parameter t e x t i s i n i t i a l i z e d to an empty<br />
40 ∗ s t r i n g ( j u s t because exceptions are not known yet ) .<br />
41 ∗/<br />
42 explicit TextDisplayable ( const char ∗ text ) :<br />
43 Displayable ( ) , t e x t (0)<br />
44 {<br />
45 i f ( text )<br />
46 {<br />
47 t e x t = new char [ s t r l e n ( text ) + 1 ] ;<br />
48 strcpy ( t e x t , text ) ;<br />
49 }<br />
50 }<br />
51<br />
52 /∗ Destructor<br />
53 ∗ Just to meet the convention<br />
54 ∗/<br />
55 virtual ˜ TextDisplayable ( )<br />
56 {<br />
57 delete t e x t ;<br />
58 }<br />
59<br />
60 /∗ Noth<strong>in</strong>g s p e c i a l has to be done upon r e g i s t r a t i o n<br />
61 ∗ @param handler The handler that t h i s d i s p l a y a b l e was<br />
62 ∗ r e g i s t e r e d with .<br />
63 ∗/<br />
64 virtual void d i s p l a y a b l e R e g i s t e r e d (<br />
65 SimpleOutputHandl<strong>in</strong>g &handler ) {}
292 10. Memory – e<strong>in</strong> kle<strong>in</strong>es Beispiel<br />
66<br />
67 /∗ Writes the number to the context . This method can only work<br />
68 ∗ with a text context .<br />
69 ∗ @param context The output context to write to .<br />
70 ∗/<br />
71 virtual void writeDisplayRep ( OutputContext &context )<br />
72 {<br />
73 dynamic cast(context ) . write ( t e x t ) ;<br />
74 }<br />
75<br />
76 } ;<br />
77<br />
78 #endif // s i m p l e t e x t d i s p l a y a b l e h<br />
10.2.18 Event<br />
Da die Entscheidung beim Reagieren auf den User-Input zugunsten e<strong>in</strong>es<br />
sauberen Event Modells gefallen ist, brauchen wir e<strong>in</strong>mal e<strong>in</strong>e Basisklasse<br />
Event. Diese benötigt eigentlich ke<strong>in</strong>e besondere, tiefergehende Erklärung:<br />
1 // simple event . h − simple c l a s s f o r events<br />
2<br />
3 #ifndef s i m p l e e v e n t h<br />
4 #def<strong>in</strong>e s i m p l e e v e n t h<br />
5<br />
6 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
7<br />
8 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
9 /∗ A very simple base c l a s s f o r events . All events have to be<br />
10 ∗ derived from i t and f o r each event type a correspond<strong>in</strong>g<br />
11 ∗ constant has to be placed here . This base does noth<strong>in</strong>g but<br />
12 ∗ s t o r e the type of the event so that i t can be asked f o r i t .<br />
13 ∗ All f u n c t i o n a l i t y f o r s p e c i a l events has to be implemented<br />
14 ∗ <strong>in</strong> the appropriate derived c l a s s e s .<br />
15 ∗/<br />
16<br />
17 class Event<br />
18 {<br />
19 protected :<br />
20<br />
21 /∗ The type of the event . Must be one o f the constants below<br />
22 ∗/<br />
23 <strong>in</strong>t32 event type ;<br />
24<br />
25 public :<br />
26<br />
27 /∗ Constants f o r p o s s i b l e event types . I f a new type i s<br />
28 ∗ <strong>in</strong>troduced an accord<strong>in</strong>g constant has to be placed here .<br />
29 ∗/<br />
30 static const <strong>in</strong>t32 WORDEVENT = 0x1 ;<br />
31<br />
32 /∗ Standard constructor<br />
33 ∗ Has to s t o r e the type i n t e r n a l l y . No checks f o r v a l i d types<br />
34 ∗ are performed because events are u s u a l l y generated extremely<br />
35 ∗ often and t h e r e f o r e performance i s an i s s u e .<br />
36 ∗ @param event type The type of the event . Has to be one o f<br />
37 ∗ the constants above<br />
38 ∗/<br />
39 explicit Event ( <strong>in</strong>t event type )<br />
40 {<br />
41 event type = event type ;
42 }<br />
43<br />
44 /∗ Copy constructor<br />
45 ∗/<br />
46 Event ( const Event & s r c )<br />
47 {<br />
48 event type = s r c . event type ;<br />
49 }<br />
50<br />
51 /∗ Destructor<br />
52 ∗ Just to make sure that a v i r t u a l d e s t r u c t o r e x i s t s<br />
53 ∗/<br />
54 virtual ˜ Event ( ) { }<br />
55<br />
10.2 Das DDD 293<br />
56 /∗ Returns the type of the event<br />
57 ∗ @return The type of the event as given <strong>in</strong> the constructor<br />
58 ∗/<br />
59 <strong>in</strong>t32 getType ( ) const<br />
60 {<br />
61 return ( event type ) ;<br />
62 }<br />
63 } ;<br />
64<br />
65<br />
66 #endif // s i m p l e e v e n t h<br />
10.2.19 WordEvent<br />
Der hier vorgestellte WordEvent stellt e<strong>in</strong>e Spezialisierung des allgeme<strong>in</strong>en<br />
Events von zuvor dar. Er wird dazu verwendet, den erhaltenen User Input<br />
Wort für Wort an die Applikation weiterzureichen:<br />
1 // simple word event . h − a word event c l a s s<br />
2<br />
3 #ifndef simple word event h<br />
4 #def<strong>in</strong>e simple word event h<br />
5<br />
6 #<strong>in</strong>clude < c s t r i n g><br />
7<br />
8 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
9 /∗ A c l a s s f o r a s p e c i f i c word event . I s used f o r words that<br />
10 ∗ are <strong>in</strong>put by u sers and have to be passed on .<br />
11 ∗/<br />
12<br />
13 class WordEvent : public Event<br />
14 {<br />
15 protected :<br />
16<br />
17 /∗ The word that a r r i v e d as a user <strong>in</strong>put and has to be<br />
18 ∗ passed on . This i s a copy and has to be deleted <strong>in</strong><br />
19 ∗ the d e s t r u c t o r . Also a 0 po<strong>in</strong>ter i s p o s s i b l e .<br />
20 ∗/<br />
21 char ∗ word ;<br />
22<br />
23 public :<br />
24<br />
25 /∗ Standard constructor<br />
26 ∗ c o p i e s the word that i s passed on to i t<br />
27 ∗/<br />
28 explicit WordEvent( const char ∗ word ) :<br />
29 Event (WORDEVENT) ,
294 10. Memory – e<strong>in</strong> kle<strong>in</strong>es Beispiel<br />
30 word (0)<br />
31 {<br />
32 i f ( ! word )<br />
33 return ;<br />
34 word = new char [ s t r l e n ( word ) + 1 ] ;<br />
35 strcpy ( word , word ) ;<br />
36 }<br />
37<br />
38 /∗ Copy constructor<br />
39 ∗ c o p i e s the word from the o r i g i n a l<br />
40 ∗/<br />
41 WordEvent( const WordEvent & s r c ) :<br />
42 Event (WORDEVENT) ,<br />
43 word (0)<br />
44 {<br />
45 i f ( ! s r c . word )<br />
46 return ;<br />
47 word = new char [ s t r l e n ( s r c . word ) + 1 ] ;<br />
48 strcpy ( word , s r c . word ) ;<br />
49 }<br />
50<br />
51 /∗ Destructor<br />
52 ∗ d e l e t e s the copy of the word<br />
53 ∗/<br />
54 virtual ˜WordEvent ( )<br />
55 {<br />
56 delete [ ] word ;<br />
57 }<br />
58<br />
59 /∗ Returns the word that i s stored <strong>in</strong> the event<br />
60 ∗ @return The word stored <strong>in</strong> the event<br />
61 ∗/<br />
62 virtual const char ∗ getWord ( ) const<br />
63 {<br />
64 return ( word ) ;<br />
65 }<br />
66 } ;<br />
67<br />
68<br />
69 #endif // simple word event h<br />
10.2.20 SimpleInputHandl<strong>in</strong>g<br />
Im Pr<strong>in</strong>zip implementiert die Klasse SimpleInputHandl<strong>in</strong>g e<strong>in</strong>en e<strong>in</strong>fachen<br />
Dispatcher Loop, der auch nur e<strong>in</strong>en e<strong>in</strong>zigen Event Handler kennt. Diesen<br />
muss man explizit registrieren und danach kann man den Loop über die entsprechende<br />
Methode starten. Es wurde hier ke<strong>in</strong>erlei Multithread<strong>in</strong>g implementiert,<br />
deshalb kehrt die Methode runDispatcher auch nicht zurück, bis<br />
man den Loop gestoppt hat. Es ist allerd<strong>in</strong>gs garantiert, dass der Loop ohne<br />
e<strong>in</strong>en zuvor gesetzten Handler sich nicht erhängt oder endlos läuft. Falls ke<strong>in</strong><br />
Handler gesetzt wurde, kehrt diese Methode sofort zurück. Diese grandiose<br />
Klasse hat dann die folgende Form:<br />
1 // simple <strong>in</strong>put handl<strong>in</strong>g . h − a simple <strong>in</strong>put handl<strong>in</strong>g c l a s s<br />
2<br />
3 #ifndef s i m p l e i n p u t h a n d l i n g h<br />
4 #def<strong>in</strong>e s i m p l e i n p u t h a n d l i n g h<br />
5
6 #<strong>in</strong>clude ” simple event handler . h”<br />
7<br />
10.2 Das DDD 295<br />
8 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
9 /∗ This c l a s s reads the user <strong>in</strong>put from s t d i n and passes i t on<br />
10 ∗ to the r e g i s t e r e d handler word by word <strong>in</strong> the form o f<br />
11 ∗ WordEvent i n s t a n c e s .<br />
12 ∗/<br />
13<br />
14 class SimpleInputHandl<strong>in</strong>g<br />
15 {<br />
16 protected :<br />
17<br />
18 /∗ The maximum length of accepted <strong>in</strong>put words . I f an <strong>in</strong>put word<br />
19 ∗ i s longer i t w i l l be s p l i t up i n t o two <strong>in</strong>put words .<br />
20 ∗/<br />
21 static const u<strong>in</strong>t32 MAXWORDLENGTH = 256;<br />
22<br />
23 private :<br />
24<br />
25 /∗ This i s a pure s t a t i c method c o l l e c t i o n . Therefore f o r b i d<br />
26 ∗ c o nstruct<strong>in</strong>g an <strong>in</strong>stance .<br />
27 ∗/<br />
28 SimpleInputHandl<strong>in</strong>g ( ) { } ;<br />
29<br />
30 /∗ I f construction i s not p o s s i b l e , a l s o d e s t r u c t i o n does<br />
31 ∗ not make any sense at a l l .<br />
32 ∗/<br />
33 virtual ˜ SimpleInputHandl<strong>in</strong>g ( ) { } ;<br />
34<br />
35 protected :<br />
36<br />
37 /∗ This v a r i a b l e i s used i n t e r n a l l y to stop the dispatcher<br />
38 ∗ loop . I f i t i s s e t the loop w i l l stop a f t e r the next<br />
39 ∗ word that has been obta<strong>in</strong>ed from the user <strong>in</strong>put .<br />
40 ∗/<br />
41 static bool s t o p d i s p a t c h e r l o o p ;<br />
42<br />
43 /∗ This i s the event handler that obta<strong>in</strong>s the n o t i f i c a t i o n s<br />
44 ∗ about the user <strong>in</strong>put words .<br />
45 ∗/<br />
46 static EventHandler ∗ event handler ;<br />
47<br />
48 public :<br />
49<br />
50 /∗ This method i s used to s e t the r e s p o n s i b l e event handler .<br />
51 ∗ I f there i s already a handler s e t , the new one w i l l r e p l a c e<br />
52 ∗ the old one . Please note that the old event handler w i l l<br />
53 ∗ NOT be deleted !<br />
54 ∗ @param handler The event handler that i s r e s p o n s i b l e f o r<br />
55 ∗ events from now on .<br />
56 ∗/<br />
57 static void setEventHandler ( EventHandler ∗ handler )<br />
58 {<br />
59 event handler = handler ;<br />
60 }<br />
61<br />
62 /∗ This method i s c a l l e d to s t a r t the dispatcher loop . I t w i l l<br />
63 ∗ NOT return u n t i l the loop i s stopped because multithread<strong>in</strong>g<br />
64 ∗ i s not implemented here . To stop the loop the method<br />
65 ∗ stopDispatcher has to be c a l l e d . Then t h i s method w i l l return<br />
66 ∗ a f t e r the next word that has been dispatched .<br />
67 ∗ I f no handler i s s e t t h i s method w i l l return immediately ( j u s t<br />
68 ∗ because exceptions are not known yet ) .<br />
69 ∗/<br />
70 static void runDispatcher ( ) ;<br />
71
296 10. Memory – e<strong>in</strong> kle<strong>in</strong>es Beispiel<br />
72 /∗ Call t h i s method to stop the dispatcher loop . I f i t i s c a l l e d<br />
73 ∗ from with<strong>in</strong> the event handler while i t i s j u s t handl<strong>in</strong>g an<br />
74 ∗ event , the loop w i l l be stopped as soon as the handler<br />
75 ∗ n o t i f i c a t i o n method returns . I f i t i s c a l l e d from any other<br />
76 ∗ po<strong>in</strong>t <strong>in</strong> the program , the dispatcher loop w i l l be stopped<br />
77 ∗ a f t e r the next word event has been processed .<br />
78 ∗/<br />
79 static void stopDispatcher ( )<br />
80 {<br />
81 s t o p d i s p a t c h e r l o o p = true ;<br />
82 }<br />
83 } ;<br />
84<br />
85<br />
86 #endif // s i m p l e i n p u t h a n d l i n g h<br />
10.2.21 SimpleEventHandl<strong>in</strong>g<br />
Wo Events durch die Gegend geschickt werden, braucht es auch e<strong>in</strong>en Handler<br />
dazu. Die Basisklasse dafür, von der alle Möchtegern-Event-Handler abgeleitet<br />
se<strong>in</strong> müssen, sieht so aus:<br />
1 // simple event handler . h − a simple a b stract event handler<br />
2<br />
3 #ifndef s i m p l e e v e n t h a n d l e r h<br />
4 #def<strong>in</strong>e s i m p l e e v e n t h a n d l e r h<br />
5<br />
6 #<strong>in</strong>clude ” simple event . h”<br />
7<br />
8 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
9 /∗ A simple base c l a s s . All event handlers that s h a l l be used<br />
10 ∗ with SimpleInputHandl<strong>in</strong>g have to be derived from i t<br />
11 ∗/<br />
12<br />
13 class EventHandler<br />
14 {<br />
15 public :<br />
16<br />
17 /∗ Destructor<br />
18 ∗ Just to make sure that a v i r t u a l d e s t r u c t o r e x i s t s<br />
19 ∗/<br />
20 virtual ˜ EventHandler ( ) { }<br />
21<br />
22 /∗ This method i s the c a l l b a c k that i s c a l l e d f o r every s i n g l e<br />
23 ∗ event that i s dispatched .<br />
24 ∗ @param event The event that i s dispatched . I t can be an<br />
25 ∗ a r b i t r a r y s u b c l a s s of Event .<br />
26 ∗/<br />
27 virtual void handleEvent ( const Event &event ) = 0 ;<br />
28 } ;<br />
29<br />
30<br />
31 #endif // s i m p l e e v e n t h a n d l e r h<br />
10.2.22 MemoryGameControl<br />
Die Klasse MemoryGameControl ist die Drehscheibe, die alles steuert, was den<br />
Spielablauf betrifft. Sie ist e<strong>in</strong>erseits dafür verantwortlich, das Spielfeld mit
10.2 Das DDD 297<br />
entsprechenden Karten zu füllen, andererseits ist sie Handler für die Events,<br />
die aufgrund des User Inputs generiert werden.<br />
Als Handler hat diese Klasse dafür zu sorgen, dass die Karten gemäß<br />
den Wünschen der Spieler umgedreht werden und, falls sie e<strong>in</strong> passendes<br />
Paar waren, umgedreht bleiben. Falls sie ke<strong>in</strong> passendes Paar dargestellt haben,<br />
werden sie wieder verdeckt. Damit das Spiel e<strong>in</strong> Ende haben kann, ist<br />
MemoryGameControl natürlich auch dafür verantwortlich, immer den Überblick<br />
darüber zu behalten, ob es überhaupt noch Karten gibt, die umgedreht<br />
werden könnten.<br />
Die Verteilung der Karten soll folgendermaßen vor sich gehen:<br />
1. Es wird e<strong>in</strong>e entsprechende Anzahl von Paaren generiert. Dazu wird e<strong>in</strong><br />
entsprechender Symbolgenerator verwendet, der die Symbole gleich <strong>in</strong><br />
e<strong>in</strong>er zufälligen Reihenfolge liefert.<br />
2. Vom Symbolgenerator werden die generierten und bereits zufällig verteilten<br />
Symbole abgeholt und wiederum nach dem Zufallspr<strong>in</strong>zip je zwei<br />
Karten mit dem entsprechenden Symbol am Spielfeld platziert.<br />
Beim Behandeln des User Inputs passiert Folgendes:<br />
1. Es werden pro Karte, die umgedreht werden soll, immer zwei Wort Events<br />
erwartet. Jeder dieser Events enthält e<strong>in</strong>e Koord<strong>in</strong>ate. Die erste Koord<strong>in</strong>ate<br />
entspricht jeweils der Reihe, die zweite der Spalte der Karte, die<br />
umgedreht werden soll.<br />
2. Sobald beide Koord<strong>in</strong>aten für die erste Karte e<strong>in</strong>gelangt s<strong>in</strong>d, wird diese<br />
umgedreht.<br />
3. Sobald dann noch beide Koord<strong>in</strong>aten für die zweite Karte e<strong>in</strong>gelangt s<strong>in</strong>d,<br />
wird auch diese umgedreht.<br />
4. Jetzt wird entschieden, ob die beiden Karten zue<strong>in</strong>ander passen. Wenn<br />
ja, bleiben sie umgedreht. Wenn ne<strong>in</strong>, werden sie wieder verdeckt.<br />
5. E<strong>in</strong>e letzte Überprüfung muss noch stattf<strong>in</strong>den, falls e<strong>in</strong> passendes Paar<br />
erwischt wurde, nämlich, ob es überhaupt noch weitere verdeckte Karten<br />
gibt. Wenn ne<strong>in</strong>, wird das Spiel beendet.<br />
Bei jeder e<strong>in</strong>zelnen Aktion, <strong>in</strong> der Karten ihre sichtbare Seite wechseln, muss<br />
die Spielsteuerung den Output Handler veranlassen, e<strong>in</strong>e Anzeige zu triggern.<br />
Ansonsten würden die Spieler nicht wirklich viel von ihrem Erfolg zu sehen<br />
bekommen. Bei mir wäre das zwar egal, weil ich bestenfalls durch Zufall e<strong>in</strong><br />
richtiges Paar erwische, aber es soll ja auch andere Spieler geben :-).<br />
Außer der Steuerung des “normalen” Spielablaufs hat die Spielsteuerung<br />
noch e<strong>in</strong>e andere Aufgabe: Sie muss es ermöglichen, dass zu Beg<strong>in</strong>n des Spiels<br />
alle Karten e<strong>in</strong>mal mit der Symbolseite nach oben zu liegen kommen, damit<br />
sich die Spieler das Feld e<strong>in</strong>prägen können. Die e<strong>in</strong>fachste Variante hierfür,<br />
die auch implementiert werden soll, ist folgende:<br />
1. Durch E<strong>in</strong>gabe von s (=show) werden alle Karten umgedreht, sodass<br />
ihre Symbole sichtbar s<strong>in</strong>d.
298 10. Memory – e<strong>in</strong> kle<strong>in</strong>es Beispiel<br />
2. Durch E<strong>in</strong>gabe von h (=hide) werden sie wieder verdeckt.<br />
Um nun nicht mit zu vielen Spezialfällen kämpfen zu müssen, nehmen wir<br />
e<strong>in</strong>fach e<strong>in</strong>mal an, dass alle Spieler sich an die Regeln halten und zu Beg<strong>in</strong>n<br />
e<strong>in</strong>mal s drücken, sich die Karten kurz e<strong>in</strong>prägen, dann h drücken und danach<br />
regulär spielen. Es werden daher ke<strong>in</strong>e Sonderabfragen e<strong>in</strong>gebaut, ob e<strong>in</strong> Spiel<br />
bereits <strong>in</strong> Gange ist und deshalb s nicht mehr zulässig ist, etc...<br />
E<strong>in</strong>e kle<strong>in</strong>e Anregung me<strong>in</strong>erseits: Interessierte Leser könnten das Programm<br />
z.B. um diese Funktionalität ergänzen. Dem Spieltrieb s<strong>in</strong>d natürlich<br />
ke<strong>in</strong>e Grenzen gesetzt und man könnte auch noch e<strong>in</strong>e Hilfe implementieren,<br />
die durch E<strong>in</strong>gabe von h angezeigt wird. Im Rahmen des Buchs bleiben wir<br />
jedoch am Boden und bei der Grundfunktionalität, die sich <strong>in</strong> der folgenden<br />
Klasse niederschlägt:<br />
1 // memory game control . h − the ma<strong>in</strong> c o n t r o l o f the memory game<br />
2<br />
3 #ifndef memory game control h<br />
4 #def<strong>in</strong>e memory game control h<br />
5<br />
6 #<strong>in</strong>clude ” simple event handler . h”<br />
7 #<strong>in</strong>clude ”memory game control . h”<br />
8 #<strong>in</strong>clude ” simple event handler . h”<br />
9 #<strong>in</strong>clude ”memory gameboard . h”<br />
10 #<strong>in</strong>clude ” simple output handl<strong>in</strong>g . h”<br />
11 #<strong>in</strong>clude ”memory cardpair . h”<br />
12 #<strong>in</strong>clude ”memory game card v5 . h”<br />
13 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
14<br />
15 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
16 /∗<br />
17 ∗ MemoryGameControl<br />
18 ∗<br />
19 ∗ the ma<strong>in</strong> c o n t r o l of the memory game<br />
20 ∗<br />
21 ∗/<br />
22<br />
23 class MemoryGameControl : public EventHandler<br />
24 {<br />
25 protected :<br />
26<br />
27 /∗ The p o s s i b l e event handl<strong>in</strong>g s t a t e s f o r every s i n g l e card .<br />
28 ∗ Because the event handler d e l i v e r s <strong>in</strong>put word by word , the<br />
29 ∗ coord<strong>in</strong>ates come one a f t e r the other . Therefore i t i s<br />
30 ∗ necessary to keep track of what i s expected next .<br />
31 ∗/<br />
32 static const u<strong>in</strong>t8 WAITING FOR ROW AND COL = 0x0 ;<br />
33 static const u<strong>in</strong>t8 WAITING FOR COL = 0x1 ;<br />
34<br />
35 /∗ This i s the back s i d e symbol o f the cards<br />
36 ∗/<br />
37 static const char CARD BACK SYMBOL[ ] = ” . ” ;<br />
38<br />
39 /∗ The gameboard that t h i s <strong>in</strong>stance o f the game c o n t r o l i s<br />
40 ∗ r e s p o n s i b l e f o r .<br />
41 ∗/<br />
42 MemoryGameboard &gameboard ;<br />
43<br />
44 /∗ The output handler f o r the game . I f cards are turned the<br />
45 ∗ request to redraw the board has to go to t h i s handler .<br />
46 ∗/
47 SimpleOutputHandl<strong>in</strong>g & output handler ;<br />
48<br />
10.2 Das DDD 299<br />
49 /∗ Whenever a card i s s e l e c t e d f o r turn<strong>in</strong>g i t i s r e g i s t e r e d<br />
50 ∗ <strong>in</strong> the current cardpair . This v a r i a b l e can be asked i f i t<br />
51 ∗ already holds both cards or only one and then the game<br />
52 ∗ c o n t r o l can r e a c t accord<strong>in</strong>gly .<br />
53 ∗/<br />
54 MemoryCardpair c u r r e n t c a r d p a i r ;<br />
55<br />
56 /∗ This i s the i n t e r n a l event handl<strong>in</strong>g s t a t e and the v a r i a b l e<br />
57 ∗ can take one of the two values o f the constants above . I t<br />
58 ∗ i s always stored here , what the c o n t r o l i s wait<strong>in</strong>g f o r <strong>in</strong><br />
59 ∗ r e s p e c t to the coords . Either i t waits f o r both coords<br />
60 ∗ o f a card or the row already a r r i v e d and i t waits only f o r<br />
61 ∗ the column of a s e l e c t e d card .<br />
62 ∗/<br />
63 u<strong>in</strong>t8 c o o r d w a i t s t a t u s ;<br />
64<br />
65 /∗ I f the row was already passed as an event i t i s <strong>in</strong> the<br />
66 ∗ meantime stored <strong>in</strong> t h i s v a r i a b l e u n t i l the column a r r i v e s .<br />
67 ∗/<br />
68 u<strong>in</strong>t32 stored row num ;<br />
69<br />
70 public :<br />
71<br />
72 /∗ Standard Constructor<br />
73 ∗ The gameboard that t h i s c o n t r o l i n stance i s r e s p o n s i b l e f o r<br />
74 ∗ and the output handler f o r d i s p l a y i n g o f the gameboard<br />
75 ∗ are passed to i t and stored <strong>in</strong> the appropriate i n t e r n a l<br />
76 ∗ v a r i a b l e s . All other v a r i a b l e s have to be i n i t i a l i z e d to<br />
77 ∗ t h e i r accord<strong>in</strong>g 0 values<br />
78 ∗ @param gameboard The gameboard to be c o n t r o l l e d . I t comes<br />
79 ∗ without any cards on i t . A c a l l to the method<br />
80 ∗ putCardsOnGameboard then f i l l s i t with appropriate<br />
81 ∗ c a r d p a i r s .<br />
82 ∗ @param output handler The output handler that i s r e s p o n s i b l e<br />
83 ∗ f o r d i s p l a y i n g the gameboard .<br />
84 ∗/<br />
85 MemoryGameControl(MemoryGameboard &gameboard ,<br />
86 SimpleOutputHandl<strong>in</strong>g &output handler ) :<br />
87 gameboard ( gameboard ) ,<br />
88 output handler ( output handler ) ,<br />
89 c o o r d w a i t s t a t u s (WAITING FOR ROW AND COL) ,<br />
90 stored row num ( 0) { }<br />
91<br />
92<br />
93 /∗ Destructor<br />
94 ∗ Just to make sure that a v i r t u a l d e s t r u c t o r e x i s t s<br />
95 ∗/<br />
96 virtual ˜ MemoryGameControl ( ) { }<br />
97<br />
98 /∗ This method i s c a l l e d to t r i g g e r f i l l i n g o f the gameboard . I t<br />
99 ∗ u t i l i z e s an <strong>in</strong>stance of MemoryCardSymbolGenerator and then<br />
100 ∗ d i s t r i b u t e s a pair of cards f o r each symbol randomly on the<br />
101 ∗ board .<br />
102 ∗/<br />
103 virtual void putCardsOnGameboard ( ) ;<br />
104<br />
105 /∗ This i s the c a l l b a c k f o r word events that are a r e s u l t o f the<br />
106 ∗ u sers ’ <strong>in</strong>put . Here a l l the a c t i o n s are t r i g g e r e d accord<strong>in</strong>g to<br />
107 ∗ the game ’ s l o g i c .<br />
108 ∗ @param event An event com<strong>in</strong>g from the <strong>in</strong>put handler . Only<br />
109 ∗ events of c l a s s WordEvent a r r i v e here .<br />
110 ∗ @param dispatcher The Input handler that c a l l e d t h i s<br />
111 ∗ method to dispatch the event .<br />
112 ∗/
300 10. Memory – e<strong>in</strong> kle<strong>in</strong>es Beispiel<br />
113 virtual void handleEvent ( const Event &event ) ;<br />
114<br />
115 protected :<br />
116<br />
117 /∗ I n t e r n a l method that i s c a l l e d from with<strong>in</strong> handleEvent i f a<br />
118 ∗ coord<strong>in</strong>ate was passed . This method has to keep track o f<br />
119 ∗ the c o n t r o l flow that two coord<strong>in</strong>ates make one card . I t does<br />
120 ∗ not keep track of p a i r s , t h i s i s done by the i n t e r n a l method<br />
121 ∗ actOnCard below , which has to be c a l l e d f o r each p a i r o f<br />
122 ∗ coord<strong>in</strong>ates .<br />
123 ∗ Please note that i t i s not guaranteed that the word r e a l l y<br />
124 ∗ conta<strong>in</strong>s a v a l i d numeric word , t h e r e f o r e t h i s has to be checked .<br />
125 ∗ Please a l s o note the the user <strong>in</strong>put conta<strong>in</strong>s coord<strong>in</strong>ates with<br />
126 ∗ a range of 1 . . . n , whereas i n t e r n a l l y the range i s 0 . . . ( n−1).<br />
127 ∗ Therefore the transformation o f coord<strong>in</strong>ates has to take place<br />
128 ∗ here when c a l l i n g actOnCard .<br />
129 ∗ @param word The s t r i n g r e p r e s e n t a t i o n o f the ( maybe ) coord<br />
130 ∗/<br />
131 virtual void coordWasPassed ( const char ∗ word ) ;<br />
132<br />
133 /∗ I n t e r n a l method that i s c a l l e d f o r each p a i r o f row/ c o l<br />
134 ∗ to perform an appropriate action on a card . This method has<br />
135 ∗ to keep track of p a i r s of cards as w e l l and act accord<strong>in</strong>gly .<br />
136 ∗ Also the end of the game has to be detected here .<br />
137 ∗ @param row The row of the card on the board s t a r t i n g at 0<br />
138 ∗ @param c o l The column of the card on the board s t a r t i n g at 0<br />
139 ∗/<br />
140 virtual void actOnCard ( u<strong>in</strong>t32 row , u<strong>in</strong>t32 c o l ) ;<br />
141<br />
142 /∗ I n t e r n a l helper method that i s c a l l e d to put one card on<br />
143 ∗ the board randomly . This method i s c a l l e d f o r each card<br />
144 ∗ dur<strong>in</strong>g the card d i s t r i b u t i o n process .<br />
145 ∗ @param card The card to be put onto the board<br />
146 ∗ @param num rows The number of rows o f the board<br />
147 ∗ @param num cols The number of columns o f the board<br />
148 ∗/<br />
149 void putCardOnBoardRandomly(MemoryGameCard ∗ card ,<br />
150 u<strong>in</strong>t32 num rows ,<br />
151 u<strong>in</strong>t32 num cols ) ;<br />
152 } ;<br />
153<br />
154<br />
155 #endif // memory game control h<br />
10.2.23 MemoryCardSymbolGenerator<br />
Beim Symbolgenerator bleiben wir hier bei der e<strong>in</strong>fachsten Variante: Wir<br />
brauchen für unser e<strong>in</strong>faches Spiel nur e<strong>in</strong>en Generator, der Symbole im Bereich<br />
A–Z, a–z und 0–9 erzeugt. Man sagt dem Generator e<strong>in</strong>fach, wie viele<br />
Symbole man haben will, er generiert diese und danach liefert er auf Anfrage<br />
Symbol für Symbol <strong>in</strong> zufälliger Reihenfolge. Es wird garantiert, dass jedes<br />
Symbol, das der Generator liefert, nur e<strong>in</strong> e<strong>in</strong>ziges Mal vorkommen kann.<br />
Hier könnte man sich natürlich im S<strong>in</strong>ne der Wahrsche<strong>in</strong>lichkeitstheorie<br />
ziemlich austoben, aber das werden wir <strong>in</strong> diesem Beispiel zugunsten der<br />
Übersichtlichkeit bleiben lassen. Die folgende, e<strong>in</strong>fache Variante dieser Klasse<br />
genügt ja wirklich für unsere Zwecke:
1 // memory card symbol generator . h − simple symbol generator<br />
2<br />
3 #ifndef memory card symbol generator h<br />
4 #def<strong>in</strong>e memory card symbol generator h<br />
5<br />
6 #<strong>in</strong>clude ” s i m p l e v e c t o r . h”<br />
7 #<strong>in</strong>clude ” c o n c r e t e o b j e c t d e l e t o r s . h”<br />
8<br />
10.2 Das DDD 301<br />
9 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
10 /∗ A simple symbol generator that generates up to 6 2 d i f f e r e n t<br />
11 ∗ 1− character symbols [A−Za−z0 −9] <strong>in</strong> a random manner . The<br />
12 ∗ symbols can be obta<strong>in</strong>ed one by one by subsequent c a l l s to<br />
13 ∗ a method .<br />
14 ∗/<br />
15<br />
16 class MemoryCardSymbolGenerator<br />
17 {<br />
18 public :<br />
19<br />
20 /∗ The maximum number of symbols that can be generated by<br />
21 ∗ t h i s c l a s s<br />
22 ∗/<br />
23 static const u<strong>in</strong>t32 MAX NUM SYMBOLS = 6 2 ; // [A−Za−z0−9]<br />
24<br />
25 /∗ I n t e r n a l constant f o r random d i s t r i b u t i o n . The number<br />
26 ∗ o f swap operations i s c a l c u l a t e d by multiply<strong>in</strong>g the<br />
27 ∗ number of symbols generated by t h i s f a c t o r .<br />
28 ∗/<br />
29 static const u<strong>in</strong>t32 SYMBOL SWAP FACTOR = 3;<br />
30<br />
31 protected :<br />
32<br />
33 /∗ The number of symbols that have not been fetched yet<br />
34 ∗/<br />
35 u<strong>in</strong>t32 num symbols rema<strong>in</strong><strong>in</strong>g ;<br />
36<br />
37 /∗ The i n t e r n a l vector s t o r i n g the symbols that have been<br />
38 ∗ generated . The s i n g l e symbols are deleted <strong>in</strong> the<br />
39 ∗ d e s t r u c t o r .<br />
40 ∗/<br />
41 Vector symbols ;<br />
42<br />
43 /∗ I n t e r n a l method that f i l l s the vector with symbols .<br />
44 ∗ After f i l l i n g the vector , the symbols are <strong>in</strong> a<br />
45 ∗ consecutive order and they have to be randomly<br />
46 ∗ d i s t r i b u t e d by a c a l l to<br />
47 ∗ distributeSymbolsRandomlyInVector ( see below )<br />
48 ∗/<br />
49 void fillVectorWithSymbols ( ) ;<br />
50<br />
51 /∗ I n t e r n a l method that d i s t r i b u t e s the symbols <strong>in</strong><br />
52 ∗ the vector randomly us<strong>in</strong>g swap operations . The<br />
53 ∗ number of swap operations performed i s c a l c u l a t e d<br />
54 ∗ by multiply<strong>in</strong>g the number of symbols with the<br />
55 ∗ SYMBOL SWAP FACTOR constant .<br />
56 ∗/<br />
57 void distributeSymbolsRandomlyInVector ( ) ;<br />
58<br />
59 private :<br />
60<br />
61 /∗ Copy construction i s forbidden<br />
62 ∗/<br />
63 MemoryCardSymbolGenerator (<br />
64 const MemoryCardSymbolGenerator & s r c ) :<br />
65 symbols ( 0 , DontDelete : : getInstance ()){}
302 10. Memory – e<strong>in</strong> kle<strong>in</strong>es Beispiel<br />
66<br />
67 public :<br />
68<br />
69 /∗ Standard Constructor<br />
70 ∗ The constructor has to c a l l the appropriate methods to<br />
71 ∗ f i l l the vector and d i s t r i b u t e the symbols randomly <strong>in</strong><br />
72 ∗ i t . D i r e c t l y a f t e r construction the symbols can be<br />
73 ∗ obta<strong>in</strong>ed by subsequent c a l l s to getNextSymbol .<br />
74 ∗ I f the number of symbols to be generated i s g r e a t e r than<br />
75 ∗ MAX NUM SYMBOLS i t i s c o r r e c t e d accord<strong>in</strong>gly ( j u s t because<br />
76 ∗ exceptions are not known yet ) .<br />
77 ∗ @param num symbols The number o f symbols that have to<br />
78 ∗ be generated <strong>in</strong> a range from 1 . . .MAX NUM SYMBOLS<br />
79 ∗ @return<br />
80 ∗/<br />
81 explicit MemoryCardSymbolGenerator ( u<strong>in</strong>t32 num symbols ) ;<br />
82<br />
83 /∗ Destructor<br />
84 ∗ Just to make sure a v i r t u a l constructor e x i s t s<br />
85 ∗/<br />
86 virtual ˜ MemoryCardSymbolGenerator ( ) { }<br />
87<br />
88 /∗ D e l i v e r s the symbols one a f t e r the other . I f no more<br />
89 ∗ symbols e x i s t <strong>in</strong> the vector i t returns 0 ( j u s t because<br />
90 ∗ exceptions are not known yet ) .<br />
91 ∗/<br />
92 virtual char getNextSymbol ( ) ;<br />
93 } ;<br />
94<br />
95<br />
96 #endif // memory card symbol generator h<br />
10.2.24 MemoryCardpair<br />
Die Hilfsklasse MemoryCardpair dient zur Vere<strong>in</strong>fachung der Logik <strong>in</strong> der<br />
Spielsteuerung. Sie ist dafür verantwortlich, sich die Karten e<strong>in</strong>es Paars zu<br />
merken und zusätzlich noch zu speichern, was mit ihnen passiert ist, also ob<br />
sie umgedreht wurden oder nicht. Dadurch, dass hier die entsprechende Undo<br />
Information vorhanden ist, unterstützt diese Klasse s<strong>in</strong>nigerweise Methoden<br />
wie Orig<strong>in</strong>alzustand wiederherstellen und man kann sie auch fragen, ob die<br />
Symbole zue<strong>in</strong>ander passen. Dies liest sich dann als Source Code so:<br />
1 // memory cardpair . h − d e c l a r a t i o n o f a p a i r o f cards f o r memory<br />
2<br />
3 #ifndef memory cardpair h<br />
4 #def<strong>in</strong>e memory cardpair h<br />
5<br />
6 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
7 #<strong>in</strong>clude ”memory gameboard . h”<br />
8<br />
9 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
10 /∗ U t i l i t y c l a s s f o r manag<strong>in</strong>g c a r d p a i r s <strong>in</strong> the memory game .<br />
11 ∗/<br />
12<br />
13 class MemoryCardpair<br />
14 {<br />
15 public :<br />
16<br />
17 /∗ Status f l a g s to be able to f i n d out whether no card , one
18 ∗ card or both cards have already been s e t .<br />
19 ∗/<br />
20 const static u<strong>in</strong>t8 CARD ONE SET = 0x01 ;<br />
21 const static u<strong>in</strong>t8 CARD TWO SET = 0x02 ;<br />
22<br />
23 protected :<br />
24<br />
25 /∗ I n t e r n a l s t a t u s f l a g s to f i n d out whether e i t h e r o f the<br />
26 ∗ cards has been turned .<br />
27 ∗/<br />
28 const static u<strong>in</strong>t8 CARD ONE TURNED = 0 x40 ;<br />
29 const static u<strong>in</strong>t8 CARD TWO TURNED = 0 x80 ;<br />
30<br />
31 /∗ Bitmasks f o r the card−s e t and the card−turned b i t s . Both<br />
32 ∗ s t a t u s f l a g s are s e t <strong>in</strong> the same v a r i a b l e ( see below )<br />
33 ∗/<br />
34 const static u<strong>in</strong>t8 CARD SET BITS = 0x03 ;<br />
35 const static u<strong>in</strong>t8 CARD TURN BITS = 0xc0 ;<br />
36<br />
37 /∗ I n t e r n a l v a r i a b l e to s t o r e the s t a t u s f l a g s . Only the<br />
38 ∗ constants above may be s e t .<br />
39 ∗/<br />
40 u<strong>in</strong>t8 c a r d s e t s t a t u s ;<br />
41<br />
42 /∗ Status v a r i a b l e s f o r rows and columns o f the two cards<br />
43 ∗/<br />
44 u<strong>in</strong>t32 card1 row ;<br />
45 u<strong>in</strong>t32 c a r d 1 c o l ;<br />
46 u<strong>in</strong>t32 card2 row ;<br />
47 u<strong>in</strong>t32 c a r d 2 c o l ;<br />
48 public :<br />
49<br />
50 /∗ Default constructor<br />
51 ∗ Sets a l l s t a t u s v a r i a b l e s to t h e i r 0 s t a t e<br />
52 ∗/<br />
53 MemoryCardpair ( ) :<br />
54 c a r d s e t s t a t u s ( 0 ) ,<br />
55 card1 row ( 0 ) , c a r d 1 c o l ( 0 ) ,<br />
56 card2 row ( 0 ) , c a r d 2 c o l ( 0) { }<br />
57<br />
58 /∗ Destructor<br />
59 ∗ Just to make sure that a v i r t u a l d e s t r u c t o r e x i s t s<br />
60 ∗/<br />
61 virtual ˜ MemoryCardpair ( ) { }<br />
62<br />
10.2 Das DDD 303<br />
63 /∗ Called a f t e r the f i r s t card was chosen . This method s t o r e s<br />
64 ∗ the coord<strong>in</strong>ates of the card and turns i t f r o n t s i d e up , i f<br />
65 ∗ i t i s not already v i s i b l e . I t f u r t h e r s t o r e s , whether the<br />
66 ∗ card was i n i t i a l l y f r o n t or back s i d e up to be able to<br />
67 ∗ to perform an undo i f the pair does not match .<br />
68 ∗ @param row The row of the card to turn .<br />
69 ∗ @param c o l The column of the card to turn .<br />
70 ∗ @param gameboard The gameboard to work on .<br />
71 ∗/<br />
72 virtual void turnCardOneFrontSideUp (<br />
73 u<strong>in</strong>t32 row , u<strong>in</strong>t32 c o l , MemoryGameboard &gameboard ) ;<br />
74<br />
75 /∗ Analogous to the method above , j u s t f o r the second card<br />
76 ∗ o f the pair .<br />
77 ∗/<br />
78 virtual void turnCardTwoFrontSideUp (<br />
79 u<strong>in</strong>t32 row , u<strong>in</strong>t32 c o l , MemoryGameboard &gameboard ) ;<br />
80<br />
81 /∗ Returns the s t a t u s f l a g s that correspond to the b i t s that<br />
82 ∗ determ<strong>in</strong>e which cards were s e t .<br />
83 ∗ @return A b i t−comb<strong>in</strong>ation of the two bitmask constants that
304 10. Memory – e<strong>in</strong> kle<strong>in</strong>es Beispiel<br />
84 ∗ are used to s t o r e which cards are s e t<br />
85 ∗/<br />
86 virtual u<strong>in</strong>t8 whichCardsAreSet ( )<br />
87 {<br />
88 return ( c a r d s e t s t a t u s & CARD SET BITS ) ;<br />
89 }<br />
90<br />
91 /∗ Returns , whether both cards were i n i t i a l l y l y i n g on the f i e l d<br />
92 ∗ with t h e i r back s i d e s up<br />
93 ∗ @return true i f both cards were i n i t i a l l y hidden , f a l s e<br />
94 ∗ otherwise<br />
95 ∗/<br />
96 virtual bool wereBothCardsBackSideUp ( )<br />
97 {<br />
98 return ( ( c a r d s e t s t a t u s & CARD TURN BITS) ==<br />
99 CARD TURN BITS) ;<br />
100 }<br />
101<br />
102 /∗ Returns , whether the symbols o f the two cards are match<strong>in</strong>g .<br />
103 ∗ I f not both cards were already s e l e c t e d , the method returns<br />
104 ∗ f a l s e ( j u s t because exceptions are not known yet ) .<br />
105 ∗ @param gameboard The gameboard to work on<br />
106 ∗ @return true i f the symbols are match<strong>in</strong>g , f a l s e otherwise<br />
107 ∗/<br />
108 virtual bool cardSymbolsMatch<strong>in</strong>g (<br />
109 MemoryGameboard &gameboard ) ;<br />
110<br />
111 /∗ Undo . . . r e s t o r e s the o r i g i n a l v i s i b l e s t a t e o f each o f the<br />
112 ∗ two cards<br />
113 ∗ @param gameboard The gameboard to work on .<br />
114 ∗/<br />
115 virtual void turnCardsBackToTheirOrig<strong>in</strong>alState (<br />
116 MemoryGameboard &gameboard ) ;<br />
117<br />
118 /∗ Clears a l l the s t a t u s f i e l d s o f t h i s i n s t a n c e<br />
119 ∗/<br />
120 virtual void c l e a r ( ) ;<br />
121 } ;<br />
122<br />
123<br />
124 #endif // memory cardpair h<br />
10.3 Auszüge aus der Implementation<br />
Um das Verständnis für die Zusammenhänge und die Abläufe im Spiel etwas<br />
zu vertiefen, möchte ich an dieser Stelle noch e<strong>in</strong>e paar Auszüge aus den<br />
Implementationen der Klassen ganz kurz besprechen. Der vollständige Code<br />
ist <strong>in</strong> Anhang B zu f<strong>in</strong>den.<br />
Am schnellsten kommt man durch e<strong>in</strong>en kurzen Blick auf das Hauptprogramm<br />
zum tieferen Verständnis:<br />
1 //memory ma<strong>in</strong> . cpp − ma<strong>in</strong> program that s t a r t s memory<br />
2<br />
3 #<strong>in</strong>clude <br />
4<br />
5 #<strong>in</strong>clude ” simple commandl<strong>in</strong>e handl<strong>in</strong>g . h”<br />
6 #<strong>in</strong>clude ”memory commandl<strong>in</strong>e arg handler . h”<br />
7 #<strong>in</strong>clude ”memory gameboard . h”
8 #<strong>in</strong>clude ” simple output handl<strong>in</strong>g . h”<br />
9 #<strong>in</strong>clude ” s i m p l e d i s p l a y a b l e . h”<br />
10 #<strong>in</strong>clude ”memory game control . h”<br />
11 #<strong>in</strong>clude ” simple <strong>in</strong>put handl<strong>in</strong>g . h”<br />
12<br />
13 us<strong>in</strong>g std : : c e r r ;<br />
14 us<strong>in</strong>g std : : endl ;<br />
15<br />
10.3 Auszüge aus der Implementation 305<br />
16 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
17 {<br />
18 // i n s t a n t i a t e the commandl<strong>in</strong>e handler , d e c l a r e the<br />
19 // required arguments ( row / column ) and l e t i t parse<br />
20 // the commandl<strong>in</strong>e .<br />
21 Commandl<strong>in</strong>eHandl<strong>in</strong>g commandl<strong>in</strong>e handler ( 3 ) ;<br />
22 MemoryCommandl<strong>in</strong>eArgumentHandler arg handler ;<br />
23 commandl<strong>in</strong>e handler . declareArgument (<br />
24 1 , Commandl<strong>in</strong>eHandl<strong>in</strong>g : : UINT32 ARG,& arg handler ) ;<br />
25 commandl<strong>in</strong>e handler . declareArgument (<br />
26 2 , Commandl<strong>in</strong>eHandl<strong>in</strong>g : : UINT32 ARG,& arg handler ) ;<br />
27 commandl<strong>in</strong>e handler . handleCommandl<strong>in</strong>e ( argc , argv ) ;<br />
28<br />
29 // analyse the number of rows and columns<br />
30 u<strong>in</strong>t32 rows = arg handler . getRows ( ) ;<br />
31 u<strong>in</strong>t32 c o l s = arg handler . getCols ( ) ;<br />
32 i f ( ( rows < = 0 ) | | ( c o l s 9 ) | | ( c o l s > 9))<br />
34 {<br />
35 c e r r
306 10. Memory – e<strong>in</strong> kle<strong>in</strong>es Beispiel<br />
74 output handler . writeOutput ( ) ;<br />
75 SimpleInputHandl<strong>in</strong>g : : setEventHandler(&game control ) ;<br />
76 SimpleInputHandl<strong>in</strong>g : : runDispatcher ( ) ;<br />
77 return ( 0 ) ;<br />
78 }<br />
Gemäß unserer Designentscheidungen läuft hier Folgendes ab:<br />
1. Zuerst wird die Commandl<strong>in</strong>e ausgewertet. Dazu wird <strong>in</strong> Zeile 21 e<strong>in</strong>e<br />
Instanz des entsprechenden Handlers erzeugt. Dass diese für drei Argumente<br />
<strong>in</strong>itialisiert wird, versteht sich auch von selbst, denn wir erwarten<br />
ja den Programmnamen, die Anzahl der Reihen und die Anzahl der Spalten<br />
als Parameter. Die Anzahl der Reihen und Spalten werden auch als<br />
Argumente <strong>in</strong> den Zeilen 23–26 deklariert und für beide ist unser besonderer<br />
Argument Handler aus Zeile 22 verantwortlich. Die tatsächliche<br />
Auswertung f<strong>in</strong>det dann durch den Aufruf <strong>in</strong> Zeile 27 statt. Danach hat<br />
der Handler die erhaltenen Werte gespeichert und sie stehen zur Abfrage<br />
<strong>in</strong> den Zeilen 30–31 bereit. In den Zeilen 32–65 f<strong>in</strong>det nur e<strong>in</strong>e Plausibilitätskontrolle<br />
und eventuelle Korrektur der Anzahl der Reihen und<br />
Spalten statt.<br />
2. Richtig <strong>in</strong>teressant wird es wieder <strong>in</strong> Zeile 68: Dort wird der Output<br />
Kontext erzeugt, den das Programm zum Schreiben verwendet.<br />
3. In Zeile 69 wird der Output Handler mit dem entsprechenden Kontext<br />
generiert. Dieser wird nur für e<strong>in</strong> Displayable <strong>in</strong>itialisiert, denn mehr<br />
als das Spielbrett selbst bekommt dieser Handler nicht zu sehen.<br />
4. Das Spielbrett wird <strong>in</strong> Zeile 70 angelegt.<br />
5. In Zeile 71 wird die Spielsteuerung erzeugt und mit dem Spielbrett und<br />
dem Output Handler <strong>in</strong>itialisiert. Damit wäre eigentlich bereits das ganze<br />
Spiel von der Systematik her am Laufen.<br />
6. Da es sich ohne Karten schlecht spielt, werden diese durch den Aufruf <strong>in</strong><br />
Zeile 72 am Brett verteilt.<br />
7. In Zeile 73 wird das Spielbrett endgültig beim Output Handler angemeldet.<br />
Dies darf aus <strong>in</strong>ternen Gründen nicht vor dem Verteilen der Karten<br />
passieren, da sonst dessen Conta<strong>in</strong>erfunktionalität gestört wäre. Ja, ja,<br />
ich weiß, das sollte man besser machen, genau deshalb möchte ich es<br />
gerne <strong>in</strong>teressierten Lesern als Aufgabe zum Spielen überlassen :-).<br />
8. Der Aufruf <strong>in</strong> Zeile 74 sorgt dafür, dass das Brett <strong>in</strong> se<strong>in</strong>em Initialzustand<br />
e<strong>in</strong>mal am Bildschirm angezeigt wird.<br />
9. In den Zeilen 75–76 wird nur noch der Event Handler <strong>in</strong>itialisiert und der<br />
Dispatcher gestartet. Dieser Dispatcher läuft so lange, bis das Spiel vorbei<br />
ist, dann wird er aus der Spielsteuerung heraus gestoppt. Damit wird<br />
dann auch gleich automatisch das Programm beendet, denn <strong>in</strong> Zeile 77<br />
steht ja das entsprechende return, das die ma<strong>in</strong> Funktion verlässt.<br />
Leser, die dieses Spiel nun selbst ausprobieren wollen, können das ganz e<strong>in</strong>fach<br />
tun:
10.3 Auszüge aus der Implementation 307<br />
1. make -f MemoryMakefile aufrufen (oder <strong>in</strong> ihrer eigenen Umgebung<br />
entsprechend das Programm compilieren).<br />
2. memory 6 6 oder ähnlich starten. Die Anzahl der Reihen und der Spalten<br />
kann jeweils zwischen 1–9 se<strong>in</strong>.<br />
3. Wenn das Feld mit den verdeckten Karten (dargestellt als .) ersche<strong>in</strong>t,<br />
s e<strong>in</strong>geben, gefolgt von Return. Dadurch werden alle Karten angezeigt.<br />
4. Nach dem E<strong>in</strong>prägen der Karten h e<strong>in</strong>geben, gefolgt von Return. Dadurch<br />
werden die Karten wieder umgedreht.<br />
5. Koord<strong>in</strong>atenpaare durch Leerzeichen bzw. Return getrennt e<strong>in</strong>tippen,<br />
die jeweils e<strong>in</strong>e Karte aufdecken sollen. Z.B. deckt die E<strong>in</strong>gabe von 2 3<br />
Return die Karte <strong>in</strong> Reihe 2 und Spalte 3 auf.<br />
6. Wenn man das Spiel gew<strong>in</strong>nt, so beendet es sich automatisch. Sollte<br />
man mitten unter dem Spiel die Lust verlieren, so hilft die E<strong>in</strong>gabe von<br />
q Return. Ganz ungeduldige Spieler können auch mittels Control-c dem<br />
Gemetzel e<strong>in</strong> Ende bereiten :-).<br />
Von der Logik her <strong>in</strong>teressant und deshalb e<strong>in</strong>en Blick wert ist auch die Implementation<br />
der Klasse MemoryGameboard: Wie bereits bei den Deklarationen<br />
besprochen wurde, werden alle darstellbaren Objekte <strong>in</strong> Vektoren gespeichert,<br />
damit sie im Destruktor auch sicher wieder freigegeben werden. Die Vektoren<br />
bekommen dazu den entsprechenden DisplayableDeletor spendiert. Wirft<br />
man jedoch e<strong>in</strong>en Blick auf den Konstruktor der Klasse, der <strong>in</strong> der Folge<br />
abgedruckt ist, so ist leicht zu erkennen, dass hierbei noch überhaupt ke<strong>in</strong>e<br />
Displayables angelegt werden:<br />
10 MemoryGameboard : : MemoryGameboard( u<strong>in</strong>t32 num rows ,<br />
11 u<strong>in</strong>t32 num cols ) :<br />
12 num rows ( num rows ) ,<br />
13 num cols ( num cols ) ,<br />
14 num cards front side up ( 0 ) ,<br />
15 row vector ( 0 ) ,<br />
16 r o w h e a d i n g d i s p l a y a b l e s ( 0 ) ,<br />
17 c o l h e a d i n g d i s p l a y a b l e s ( 0 ) ,<br />
18 e o l d i s p l a y a b l e s ( 0 ) ,<br />
19 conta<strong>in</strong>er output handl<strong>in</strong>g ( 0 ) ,<br />
20 c o n t a i n e r o u t p u t c o n t e x t (0)<br />
21 {<br />
22 i f ( ( num rows ∗ num cols ) % 2) // not an even number<br />
23 num rows ++;<br />
24<br />
25 row vector = new Vector ( num rows ,<br />
26 VectorDeletor : : getInstance ( ) ) ;<br />
27 for ( u<strong>in</strong>t32 current row = 0 ; current row < num rows ;<br />
28 current row ++)<br />
29 row vector −>setElementAt (<br />
30 current row ,new Vector ( num cols ,<br />
31 DisplayableDeletor : : getInstance ( ) ) ) ;<br />
32 }<br />
Des Rätsels Lösung, wann die darstellbaren Objekte erzeugt werden, ergibt<br />
sich aus e<strong>in</strong>em Blick auf die Methode displayableRegistered:
308 10. Memory – e<strong>in</strong> kle<strong>in</strong>es Beispiel<br />
59 void MemoryGameboard : : d i s p l a y a b l e R e g i s t e r e d (<br />
60 SimpleOutputHandl<strong>in</strong>g &handler )<br />
61 {<br />
62 // already r e g i s t e r e d with some handler , no f u r t h e r<br />
63 // setup necessary<br />
64 i f ( r o w h e a d i n g d i s p l a y a b l e s )<br />
65 return ;<br />
66<br />
67 u<strong>in</strong>t32 num displayables = ( num rows ∗ num cols ) + // cards<br />
68 num rows + 1 + // EOL elements<br />
69 num rows + 1 + // c o l head<strong>in</strong>gs<br />
70 num cols ; // row head<strong>in</strong>gs<br />
71 r o w h e a d i n g d i s p l a y a b l e s = new Vector (<br />
72 num cols , DisplayableDeletor : : getInstance ( ) ) ;<br />
73 c o l h e a d i n g d i s p l a y a b l e s = new Vector (<br />
74 num rows + 1, DisplayableDeletor : : getInstance ( ) ) ;<br />
75 e o l d i s p l a y a b l e s = new Vector (<br />
76 num rows + 1, DisplayableDeletor : : getInstance ( ) ) ;<br />
77 c o n t a i n e r o u t p u t c o n t e x t = handler . getOutputContextClone ( ) ;<br />
78 conta<strong>in</strong>er output handl<strong>in</strong>g = new SimpleOutputHandl<strong>in</strong>g (<br />
79 num displayables , ∗ c o n t a i n e r o u t p u t c o n t e x t ) ;<br />
80<br />
81 // j u s t because some compilers have a wrong treatment f o r<br />
82 // v a r i a b l e s <strong>in</strong> c o n t r o l statements . . .<br />
83 u<strong>in</strong>t32 row count = 0;<br />
84 u<strong>in</strong>t32 col count = 0;<br />
85<br />
86 // generate the top l i n e with the column head<strong>in</strong>gs and<br />
87 // r e g i s t e r i t<br />
88 IntDisplayable ∗ head<strong>in</strong>g = 0;<br />
89 TextDisplayable ∗ text = 0;<br />
90 // a space needs to be the f i r s t element o f the head<strong>in</strong>gs<br />
91 text = new TextDisplayable ( ” ” ) ;<br />
92 c o l h e a d i n g d i s p l a y a b l e s −>setElementAt ( 0 , text ) ;<br />
93 conta<strong>in</strong>er output handl<strong>in</strong>g −>addDisplayable (∗ text ) ;<br />
94 // the r e s t of the head<strong>in</strong>g l i n e conta<strong>in</strong>s the consecutive<br />
95 // numbers of the columns<br />
96 for ( col count = 1 ; col count setElementAt ( c o l count , head<strong>in</strong>g ) ;<br />
100 conta<strong>in</strong>er output handl<strong>in</strong>g −>addDisplayable (∗ head<strong>in</strong>g ) ;<br />
101 }<br />
102 // an e o l i s placed at the end o f the l i n e<br />
103 text = new TextDisplayable ( ”\n” ) ;<br />
104 e o l d i s p l a y a b l e s −>setElementAt ( 0 , text ) ;<br />
105 conta<strong>in</strong>er output handl<strong>in</strong>g −>addDisplayable (∗ text ) ;<br />
106 // now f o r each row :<br />
107 // ( 1 ) generate the row head<strong>in</strong>g and r e g i s t e r i t<br />
108 // ( 2 ) r e g i s t e r a l l cards <strong>in</strong> the row .<br />
109 // ( 3 ) generate an EOL d i s p l a y a b l e and r e g i s t e r i t<br />
110 for ( row count = 0 ; row count < num rows ; row count++)<br />
111 {<br />
112 head<strong>in</strong>g = new IntDisplayable ( row count + 1 ) ;<br />
113 r o w h e a d i n g d i s p l a y a b l e s −>setElementAt ( row count , head<strong>in</strong>g ) ;<br />
114 conta<strong>in</strong>er output handl<strong>in</strong>g −>addDisplayable (∗ head<strong>in</strong>g ) ;<br />
115 for ( col count = 0 ; col count < num cols ; c o l c o unt++)<br />
116 {<br />
117 // not very e f f i c i e n t but e a s i e r to read<br />
118 conta<strong>in</strong>er output handl<strong>in</strong>g −>addDisplayable (<br />
119 ∗dynamic cast(<br />
120 <strong>in</strong>ternalGetCard ( row count , c o l count ) ) ) ;<br />
121 }<br />
122 text = new TextDisplayable ( ”\n” ) ;<br />
123 e o l d i s p l a y a b l e s −>setElementAt ( row count + 1, text ) ;
10.3 Auszüge aus der Implementation 309<br />
124 conta<strong>in</strong>er output handl<strong>in</strong>g −>addDisplayable (∗ text ) ;<br />
125 }<br />
126 }<br />
Hier wird <strong>in</strong> Zeile 64 zuerst kontrolliert, ob überhaupt noch etwas getan<br />
werden muss, oder ob bereits alles fertig angelegt ist. Das ist notwendig,<br />
denn es könnte ja diese Klasse auch bei zwei Handlers angemeldet werden.<br />
Dann würde ohne diese Abfrage e<strong>in</strong> Speicherloch entstehen.<br />
In den Zeilen 67–70 wird ausgerechnet, mit wie vielen darstellbaren Objekten<br />
wir es hier überhaupt zu tun haben. Danach werden die entsprechenden<br />
Vektoren <strong>in</strong> den Zeilen 71–76 angelegt. In Zeile 77 wird e<strong>in</strong> Clone des<br />
Output Contexts besorgt, der dem neu angelegten Output Handler für diesen<br />
Conta<strong>in</strong>er <strong>in</strong> den Zeilen 78–79 spendiert wird. In den Zeilen 88–125 werden<br />
dann die entsprechenden Reihen- und Spaltenköpfe, sowie die Zeilenumbrüche<br />
angelegt und geme<strong>in</strong>sam mit den Spielkarten beim Output Handler genau <strong>in</strong><br />
der Reihenfolge registriert, <strong>in</strong> der sie dargestellt werden sollen. In der Deklaration<br />
dieser Klasse war schon zu sehen, dass dieser Handler jedes Mal <strong>in</strong><br />
der <strong>in</strong>l<strong>in</strong>e Methode writeDisplayRep aufgefordert wird, se<strong>in</strong>en Output zu<br />
schreiben.<br />
Interessierte Leser können jetzt gleich zur Programmänderung schreiten<br />
und e<strong>in</strong>en entsprechenden DisplayableConta<strong>in</strong>er implementieren, der als<br />
Basis für alle Displayables mit Conta<strong>in</strong>erfunktionalität dient. Von diesem<br />
wäre dann das MemoryGameboard abzuleiten, um zu e<strong>in</strong>er sauberen Lösung<br />
zu kommen. Noch e<strong>in</strong> kle<strong>in</strong>er Tipp: Die Idee, selbst den Output Handler zu<br />
vergewaltigen und ihn mit e<strong>in</strong>em Clone des Output Contexts ans Werk zu<br />
schicken ist auch nicht ganz sauber und entsprechend verbesserungswürdig.<br />
Mit diesem hier kurz zusammengefassten Wissen über die Systematik<br />
im Programm sollte der restliche Source Code leicht verständlich se<strong>in</strong>. Die<br />
gesamte Implementation, wie sie <strong>in</strong> Anhang B abgedruckt ist, ist straightforward.<br />
Aus diesem Grund möchte ich hier auch ke<strong>in</strong>en weiteren Platz dafür<br />
verschwenden.
11. Exceptions<br />
In den bisherigen Beispielen war es öfters der Fall, dass z.B. e<strong>in</strong>e Methode mit<br />
unzulässigen Parametern aufgerufen wird. E<strong>in</strong> typisches Beispiel f<strong>in</strong>det sich<br />
<strong>in</strong> der Implementation der allgeme<strong>in</strong>en Vector Klasse aus unserem Memory-<br />
Spiel. Dort gibt es z.B. e<strong>in</strong>e Methode getElementAt, die das Element liefert,<br />
das an e<strong>in</strong>er bestimmten Stelle im entsprechenden Vector Objekt steht.<br />
Wenn allerd<strong>in</strong>gs der geforderte Index außerhalb des erlaubten Bereichs liegt,<br />
dann wird <strong>in</strong> dieser Implementation kurzerhand 0 geliefert. Ähnliches passiert<br />
dort <strong>in</strong> der Methode setElementAt: Liegt der Index außerhalb des<br />
erlaubten Bereichs, dann wird diese Operation e<strong>in</strong>fach kommentarlos nicht<br />
durchgeführt. Dass dieses Verhalten nicht wirklich zur Stabilität von Software<br />
beiträgt, versteht sich von selbst, denn wie soll man denn e<strong>in</strong>en Fehler<br />
f<strong>in</strong>den, wenn dieser nicht e<strong>in</strong>mal gemeldet wird? Und genau damit, wie man<br />
e<strong>in</strong>en solchen Fehler ordnungsgemäß und sauber mitteilt, um ihn auch e<strong>in</strong>er<br />
sauberen Behandlung zuzuführen, beschäftigt sich dieses Kapitel: Das Mittel<br />
der Wahl für diese Fälle s<strong>in</strong>d die sogenannten Exceptions, durch die, wie der<br />
Name schon sagt, Ausnahmezustände <strong>in</strong> e<strong>in</strong>em Programm signalisiert und<br />
damit auch sauber behandelt werden können.<br />
Vor der Besprechung der technischen Details von Exceptions <strong>in</strong> C ++<br />
möchte ich noch unbed<strong>in</strong>gt e<strong>in</strong> paar Worte zu deren s<strong>in</strong>nvoller und sauberer<br />
Anwendung verlieren. Oft wird nämlich von Entwicklern die von den Erf<strong>in</strong>dern<br />
beabsichtigte Semantik von Exceptions missverstanden, wodurch sich<br />
e<strong>in</strong>ige Ungereimtheiten ergeben können.<br />
Pr<strong>in</strong>zipiell ist die Verwendung von Exceptions für den Fall gedacht, dass<br />
Entwickler zu e<strong>in</strong>em bestimmten Zeitpunkt zur Laufzeit feststellen können,<br />
dass e<strong>in</strong> Problem vorliegt, jedoch nicht wissen können, wie sie dieses Problem<br />
behandeln sollen. Im oben zitierten Beispiel mit der Vector Klasse<br />
kann man leicht e<strong>in</strong>en unerlaubten Index feststellen. Weil aber nicht bekannt<br />
ist, wofür e<strong>in</strong> Vector Objekt zur Laufzeit gerade verwendet wird und<br />
wodurch also der unerlaubte Index zustande kommt, kann man ke<strong>in</strong>e Behandlung<br />
durchführen. Jedoch kann man davon ausgehen, dass der Teil des<br />
Programms, der den falschen Aufruf abgesetzt hat, auf das Problem reagieren<br />
kann. Folgende Logik versteckt sich nun h<strong>in</strong>ter dem Exception Mechanismus,<br />
um die Problemerkennung von der Problembehandlung zu trennen:
312 11. Exceptions<br />
1. Der Teil der Software, der e<strong>in</strong> Problem erkennt, wirft e<strong>in</strong>e Exception<br />
(daher kommt auch das Keyword throw, das wir noch kennen lernen<br />
werden).<br />
2. Der Teil der Software, der sich bereit erklärt, bestimmte Probleme behandeln<br />
zu können, signalisiert diese Bereitschaft durch e<strong>in</strong>e Deklaration,<br />
e<strong>in</strong>e Exception fangen zu können (daher kommt auch das Keyword<br />
catch, das wir noch kennen lernen werden).<br />
3. Wenn also e<strong>in</strong>e Exception geworfen wird, dann wird <strong>in</strong> der Methodenund<br />
Funktionen-Aufrufhierarchie des Programms so lange zurück zum<br />
Aufrufenden gesprungen (dies nennt sich auch Stack Unw<strong>in</strong>d<strong>in</strong>g), bis<br />
e<strong>in</strong> fangender Teil gefunden wird. Diesem wird die Exception dann zur<br />
Behandlung zugeführt.<br />
Um nun e<strong>in</strong>e gezielte Fehlerbehandlung zu gestatten, gibt es beliebige, durch<br />
die Entwickler def<strong>in</strong>ierbare Typen von Exceptions. Es ist auch leicht e<strong>in</strong>zusehen,<br />
warum man solche Typen unterscheidet, denn wenn e<strong>in</strong> Programmteil<br />
z.B. weiß, wie e<strong>in</strong> Zugriffs-Indexfehler zu behandeln ist, dann muss derselbe<br />
Teil noch lange nicht wissen, wie man mit e<strong>in</strong>em Out-of-Memory Fehler<br />
umgeht. Mit e<strong>in</strong>em verbreiteten Missverständnis möchte ich an dieser Stelle<br />
auch noch aufräumen: E<strong>in</strong>e Exception zu werfen bedeutet nicht, dass unbed<strong>in</strong>gt<br />
etwas völlig Katastrophales passiert wäre. Es ist auch ke<strong>in</strong>eswegs<br />
gesagt, dass Exceptions “so gut wie nie” auftreten bzw. auftreten dürfen.<br />
Exceptions werden immer dann gebraucht, wenn etwas passiert, was dem<br />
regulär geplanten Programmablauf zuwiderläuft.<br />
Es gibt noch e<strong>in</strong>ige Aspekte, die unbed<strong>in</strong>gt Beachtung f<strong>in</strong>den müssen, um<br />
Exceptions wirklich s<strong>in</strong>nvoll e<strong>in</strong>zusetzen. Um diese besprechen zu können,<br />
möchte ich e<strong>in</strong>mal an e<strong>in</strong>em kle<strong>in</strong>en Beispiel die Realisierung des Exception-<br />
Mechanismus <strong>in</strong> C ++ zeigen. Hierzu ziehen wir am besten gleich unsere Klasse<br />
Vector heran und arbeiten sie entsprechend um. Das Resultat liest sich dann<br />
wie folgt (first_exception_demo.cpp):<br />
1 // f i r s t e x c e p t i o n d e m o . cpp − a small demo , how the exception<br />
2 // mechanism works <strong>in</strong> <strong>C++</strong><br />
3<br />
4 #<strong>in</strong>clude <br />
5 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
6<br />
7 us<strong>in</strong>g std : : cout ;<br />
8 us<strong>in</strong>g std : : endl ;<br />
9<br />
10 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
11 /∗<br />
12 ∗ Exception<br />
13 ∗<br />
14 ∗ base c l a s s f o r a l l exceptions<br />
15 ∗<br />
16 ∗/<br />
17<br />
18 class Exception<br />
19 {<br />
20 protected :<br />
21 static u<strong>in</strong>t32 c u r r e n t i d ;
22 u<strong>in</strong>t32 my id ;<br />
23 public :<br />
24 Exception ( )<br />
25 {<br />
26 my id = c u r r e n t i d ++;<br />
27 cout
314 11. Exceptions<br />
88<br />
89 virtual void setElementAt ( u<strong>in</strong>t32 <strong>in</strong>dex , void ∗ element )<br />
90 throw( IndexOutOfBoundsException ) ;<br />
91<br />
92 virtual void ∗ getElementAt ( u<strong>in</strong>t32 <strong>in</strong>dex ) const<br />
93 throw( IndexOutOfBoundsException ) ;<br />
94<br />
95 virtual u<strong>in</strong>t32 getMaxNumElements ( ) const<br />
96 {<br />
97 return ( num elements ) ;<br />
98 }<br />
99 } ;<br />
100<br />
101 u<strong>in</strong>t32 Exception : : c u r r e n t i d = 0;<br />
102<br />
103 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
104 /∗ constructor<br />
105 ∗<br />
106 ∗ @param num elements the number o f elements that the vector can hold<br />
107 ∗/<br />
108<br />
109 Vector : : Vector ( u<strong>in</strong>t32 num elements )<br />
110 {<br />
111 num elements = num elements ;<br />
112 i f ( num elements )<br />
113 {<br />
114 elements = new void ∗ [ num elements ] ;<br />
115 while ( num elements−−)<br />
116 elements [ num elements ] = 0 ;<br />
117 }<br />
118 else<br />
119 elements = 0;<br />
120 }<br />
121<br />
122 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
123 /∗ s e t s the element at the d e s i r e d <strong>in</strong>dex<br />
124 ∗<br />
125 ∗ @param <strong>in</strong>dex the <strong>in</strong>dex of the element to s e t<br />
126 ∗ @param element the element to s e t<br />
127 ∗ @exception IndexOutOfBoundsException i f the <strong>in</strong>dex i s outside<br />
128 ∗ of the allowed range<br />
129 ∗/<br />
130<br />
131 void Vector : : setElementAt ( u<strong>in</strong>t32 <strong>in</strong>dex , void ∗ element )<br />
132 throw( IndexOutOfBoundsException )<br />
133 {<br />
134 i f ( <strong>in</strong>dex >= num elements )<br />
135 throw IndexOutOfBoundsException ( ) ;<br />
136 elements [ <strong>in</strong>dex ] = element ;<br />
137 }<br />
138<br />
139 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
140 /∗<br />
141 ∗ @param <strong>in</strong>dex the <strong>in</strong>dex of the element to be returned<br />
142 ∗ @return the d e s i r e d element<br />
143 ∗ @exception IndexOutOfBoundsException i f the <strong>in</strong>dex i s outside<br />
144 ∗ of the allowed range<br />
145 ∗/<br />
146<br />
147 void ∗ Vector : : getElementAt ( u<strong>in</strong>t32 <strong>in</strong>dex ) const<br />
148 throw( IndexOutOfBoundsException )<br />
149 {<br />
150 i f ( <strong>in</strong>dex >= num elements )<br />
151 throw IndexOutOfBoundsException ( ) ;<br />
152 return ( elements [ <strong>in</strong>dex ] ) ;<br />
153 }
154<br />
11. Exceptions 315<br />
155 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
156 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
157 {<br />
158 Vector t e s t v e c t o r ( 3 ) ;<br />
159 <strong>in</strong>t32 just an element = 0;<br />
160<br />
161 try<br />
162 {<br />
163 cout
316 11. Exceptions<br />
deklariert sie mittels Angabe von throw(...). Möchte man mehrere Exceptions<br />
deklarieren, dann werden diese <strong>in</strong>nerhalb der Klammern durch<br />
Komma getrennt angegeben. Weil throw, gleich wie z.B. const, zum Teil<br />
der Signatur e<strong>in</strong>er Methode wird, muss dieselbe Angabe auch <strong>in</strong> der Def<strong>in</strong>ition<br />
der Methode mit angeführt werden. Dies sieht man z.B. <strong>in</strong> den<br />
Zeilen 131–132. Dass diese Art der Deklaration natürlich nicht nur für<br />
Methoden, sondern auch für Funktionen gilt, versteht sich von selbst.<br />
Schreibt man zur Deklaration e<strong>in</strong>er Methode ke<strong>in</strong>e throw Angabe, so bedeutet<br />
dies (leider) nicht, dass ke<strong>in</strong>e Exception geworfen werden kann,<br />
sondern genau das Gegenteil: Es können <strong>in</strong> diesem Fall beliebigste Exceptions<br />
geworfen werden! Diese Konvention wurde <strong>in</strong> C ++ aus Gründen der<br />
Kompatibilität zu altem Source-Code gewählt. Möchte man explizit deklarieren,<br />
dass ke<strong>in</strong>e Exception geworfen werden kann, so muss man dies<br />
durch e<strong>in</strong> explizites throw() ohne Angabe von Exceptions tun.<br />
Es gibt immer wieder Diskussionen, was die explizite throw Angabe betrifft.<br />
E<strong>in</strong> Teil der Entwickler me<strong>in</strong>t, man solle per Cod<strong>in</strong>g Standard auf<br />
e<strong>in</strong>e explizite Angabe bestehen, e<strong>in</strong> anderer Teil me<strong>in</strong>t, man solle auf diese<br />
verzichten. Die Hauptargumente der beiden Fraktionen s<strong>in</strong>d folgende:<br />
Gegen explizite Angabe:<br />
– Bei Programmänderungen kann es <strong>in</strong> Ableitungshierarchien passieren,<br />
dass man neu h<strong>in</strong>zugekommene Exceptions <strong>in</strong> der gesamten Hierarchie<br />
ergänzen muss.<br />
– Je nach Compiler kann die explizite Angabe von Exceptions das Programm<br />
verlangsamen, da zur Zusicherung der E<strong>in</strong>haltung der Angabe<br />
h<strong>in</strong>ter den Kulissen versteckte try ... catch Blöcke erzeugt werden.<br />
– Wenn wirklich e<strong>in</strong>e Exception geworfen wird, die nicht angegeben wurde,<br />
ist die Standardbehandlung, die vom Compiler e<strong>in</strong>gesetzt wird<br />
e<strong>in</strong> abort(). Dies kann zu unerwünschten Effekten führen, da auto-<br />
Variablen <strong>in</strong> diesem Fall nicht mehr ordnungsgemäß destruiert werden.<br />
– Beim Erstellen von Templates (siehe Kapitel 13) kann es Probleme geben,<br />
weil man gewisse datentypabhängige Verhaltensweise beim besten<br />
Willen nicht voraussehen kann.<br />
Für explizite Angabe:<br />
– Der Code wird robuster, da ke<strong>in</strong>e unerwarteten Exceptions geworfen<br />
werden und man immer weiß, was man eventuell zu behandeln hat.<br />
Da ich auf dem Standpunkt stehe, dass saubere, robuste Software das<br />
oberste Ziel <strong>in</strong> e<strong>in</strong>er Entwicklung se<strong>in</strong> muss, b<strong>in</strong> ich persönlich e<strong>in</strong> Mitglied<br />
der Fraktion, die für die explizite Angabe plädiert. Zu den beiden<br />
Hauptargumenten der anderen Fraktion möchte ich Folgendes anmerken:<br />
Wenn man <strong>in</strong> der Designphase vorausschauend genug arbeitet, dann passieren<br />
Änderungen, die sich durch die gesamte Hierarchie ziehen sehr selten<br />
bis gar nicht. Der Performanceverlust durch implizite try ... catch<br />
Blöcke ist so m<strong>in</strong>imal, dass er <strong>in</strong> den allermeisten Fällen ohne besondere<br />
Messung überhaupt nicht bemerkbar ist. E<strong>in</strong>zig <strong>in</strong> absoluten Hoch-
11. Exceptions 317<br />
geschw<strong>in</strong>digkeitsrout<strong>in</strong>en könnte (!!!) es se<strong>in</strong>, dass das allerletzte kle<strong>in</strong>e<br />
Bisschen an Performance noch um jeden Preis herausgequetscht werden<br />
muss. Dort könnte (!!!) man <strong>in</strong> e<strong>in</strong>em solchen Fall über e<strong>in</strong>en begrenzten<br />
Bruch mit den expliziten Angaben nachdenken. Me<strong>in</strong>e Erfahrung spricht<br />
allerd<strong>in</strong>gs dagegen. Das Problem beim Erstellen von Templates muss man<br />
zum Teil akzeptieren und hier ist im Spezialfall wirklich e<strong>in</strong>e Ausnahme<br />
zu machen. Das Argument jedoch, dass abort() zu Problemen führt, ist<br />
zwar korrekt, aber ich kann diese Argumentation nicht akzeptieren. Dieser<br />
Fall darf bei sauberer Entwicklung nur <strong>in</strong> der Testphase auftreten, nicht <strong>in</strong><br />
e<strong>in</strong>em Produkt!<br />
Alle Leser mögen nun selbst entscheiden, welchen Standpunkt sie e<strong>in</strong>sichtiger<br />
f<strong>in</strong>den. Im S<strong>in</strong>ne e<strong>in</strong>es guten Programmierstils und im S<strong>in</strong>ne der<br />
Lesbarkeit, Wartbarkeit und Robustheit von Software möchte ich jedoch<br />
raten, Exceptions auch wirklich explizit mittels throw anzuführen. Per<br />
Konvention sollte man dann auch konsequenterweise ke<strong>in</strong>e Exceptions aus<br />
Methoden oder Funktionen werfen, bei denen ke<strong>in</strong>e throw Angabe existiert.<br />
Nebenbei bemerkt: E<strong>in</strong>e allgeme<strong>in</strong> übliche Bezeichnung für das unschöne<br />
Werfen von Exceptions, obwohl ke<strong>in</strong>e solchen deklariert wurden, ist der<br />
Begriff silent throw.<br />
• In Zeile 135 sieht man, wie e<strong>in</strong>e Exception geworfen wird: Es wird hierzu<br />
e<strong>in</strong> temporäres Objekt erzeugt. Warum wir hier ausgerechnet mit e<strong>in</strong>em<br />
temporären Objekt arbeiten und was bei Abarbeitung des throw genau<br />
h<strong>in</strong>ter den Kulissen passiert, wird <strong>in</strong> der Folge noch besprochen werden.<br />
• In den Zeilen 161–173 sieht man schlussendlich, wie man sich bereit erklärt,<br />
Exceptions auch zu fangen, die auftreten könnten: Der Teil des Codes,<br />
für den man sich <strong>in</strong> puncto Exceptions zuständig fühlt, wird durch e<strong>in</strong>en<br />
try Block umschlossen. Direkt im Anschluss an diesen stehen dann e<strong>in</strong><br />
oder mehrere catch Blöcke <strong>in</strong> denen die entsprechenden Exceptions auch<br />
behandelt werden können. In unserem Beispiel ist dies genau e<strong>in</strong> e<strong>in</strong>ziges<br />
catch, das auf e<strong>in</strong>e IndexOutOfBoundsException reagiert.<br />
Das try-catch Konstrukt ist folgendermaßen zu verstehen: In welcher Zeile<br />
auch immer <strong>in</strong>nerhalb des try Blocks e<strong>in</strong>e Exception geworfen wird, wird<br />
sie dem entsprechenden catch zugeführt. Sollte ke<strong>in</strong>e Exception auftreten,<br />
so wird auch ke<strong>in</strong>er der catch Blöcke angesprungen und das Programm<br />
läuft erst unterhalb aller dieser Blöcke weiter.<br />
Die Exception, die <strong>in</strong> unserem Demoprogramm auftritt, wird verursacht<br />
durch den Aufruf von setElementAt <strong>in</strong> Zeile 167. Dort wird nämlich mit<br />
e<strong>in</strong>em illegalen Index auf den Vektor zugegriffen. Dadurch, dass dort e<strong>in</strong>e<br />
Exception geworfen wird, wird das Statement <strong>in</strong> Zeile 168 nicht mehr<br />
erreicht, denn das Auftreten der Exception bewirkt, dass das Programm<br />
mit der Behandlung der Exception im catch Block <strong>in</strong> den Zeilen 170–173<br />
fortfährt.
318 11. Exceptions<br />
Vorsicht Falle: Leider wird bei der Behandlung von Exceptions sehr oft<br />
e<strong>in</strong> gravierender Fehler gemacht, der zum Teil auf Schlampigkeit, zum Teil<br />
aber auch auf Unwissenheit beruht: Jede Exception, die <strong>in</strong>nerhalb e<strong>in</strong>es try<br />
Blocks auftritt, bewirkt sofort das Anspr<strong>in</strong>gen des entsprechenden catch.<br />
Das bedeutet, dass alle Statements, die unterhalb der Zeile stehen, <strong>in</strong> der die<br />
Exception aufgetreten ist, nicht mehr ausgeführt werden. Sollte dort Code<br />
stehen, der unbed<strong>in</strong>gt immer ausgeführt werden muss (z.B. irgende<strong>in</strong> delete<br />
von dynamisch allokiertem Speicher), dann hat man e<strong>in</strong> Problem, denn bis zu<br />
dieser Zeile kommt man im Falle e<strong>in</strong>er Exception ja nicht! Aus diesem Grund<br />
muss man sich sehr gut überlegen, wie man try Blöcke setzt, damit man nicht<br />
<strong>in</strong>s offene Messer läuft und e<strong>in</strong>erseits zwar e<strong>in</strong>e “schöne” Fehlerbehandlung<br />
implementiert, die aber selbst gleich noch schlimmere Fehler macht und z.B.<br />
wachsende Programme oder sonstige schlimme Inkonsistenzen verursacht!<br />
Es stellt sich nun die Frage, wie wir die Exception, die <strong>in</strong> Zeile 135 geworfen<br />
wurde, durch catch an e<strong>in</strong>er ganz anderen Stelle im Programm fangen<br />
können. Eigentlich müsste das Exception Objekt ja zu diesem Zeitpunkt<br />
schon lange tot se<strong>in</strong>, da die throw Expression, <strong>in</strong> deren Rahmen das temporäre<br />
Objekt generiert wurde, bereits fertig abgearbeitet ist.<br />
Die Antwort darauf klärt auch gleich das Phänomen der zwei erzeugten<br />
Objekte auf: Wird e<strong>in</strong>e Exception geworfen, so wird immer (!) automatisch<br />
e<strong>in</strong>e Kopie dieser Exception generiert. Genau diese Kopie ist es, die wir<br />
auch durch unser catch fangen und nicht, wie viele Entwickler annehmen,<br />
das Orig<strong>in</strong>al! Die Kopie ist ebenfalls e<strong>in</strong> temporäres Objekt und die Lifetime<br />
dieses Objekts endet, sobald der catch Block verlassen wurde, denn dort ist<br />
für den Compiler dann diese Expression zu Ende.<br />
Jetzt, wo diese Eigenheit geklärt ist, können wir den Output zu Ende<br />
analysieren:<br />
1. In Zeile 167 wird e<strong>in</strong> Aufruf auf setElementAt mit Index 5 als Parameter<br />
durchgeführt.<br />
2. In Zeile 134, <strong>in</strong> der Implementation von setElementAt, wird e<strong>in</strong>e Überprüfung<br />
vorgenommen, ob dieser Index auch wirklich zulässig ist. Weil<br />
wir aber die Instanz von Vector so angelegt haben, dass sie maximal 3<br />
Elemente halten kann, wird entschieden, dass es mit der Zulässigkeit gar<br />
nicht so weit her ist.<br />
3. Es wird also entsprechend <strong>in</strong> Zeile 135 e<strong>in</strong>e IndexOutOfBoundsException<br />
als temporäres Objekt geworfen und dadurch wird die Methode an dieser<br />
Stelle auch verlassen. Der Konstruktoraufruf des temporären Objekts<br />
äußert sich im Output durch die Meldungen<br />
default - construct<strong>in</strong>g Exception, id = 0<br />
default - construct<strong>in</strong>g IndexOutOfBoundsException, id = 0<br />
4. Wie bereits erwähnt, wird von throw sofort e<strong>in</strong>e Kopie dieses Objekts<br />
angelegt. Da wir e<strong>in</strong>en Copy Constructor bereitgestellt haben, wird die-
11. Exceptions 319<br />
ser auch verwendet. Dies sieht man im Output an den Meldungen<br />
copy - construct<strong>in</strong>g Exception, id = 1<br />
copy - construct<strong>in</strong>g IndexOutOfBoundsException, id = 1<br />
5. Nach Anlegen dieser Kopie ist die throw Expression abgearbeitet. Es<br />
wird also das ursprüngliche Exception Objekt damit zerstört. Dies äußert<br />
sich an den Meldungen<br />
destruct<strong>in</strong>g IndexOutOfBoundsException, id = 0<br />
destruct<strong>in</strong>g Exception, id = 0<br />
Dieses Zerstören ist auch der Grund, warum ich im Demoprogramm den<br />
e<strong>in</strong>zelnen Objekten den Member my_id_ verpasst habe: Man sieht, welche<br />
Instanz zerstört wird. Bei uns hat das Orig<strong>in</strong>al die Id 0 und die Kopie<br />
hat die Id 1.<br />
6. Nach dem Zerstören des Orig<strong>in</strong>als beg<strong>in</strong>nt das Stack Unw<strong>in</strong>d<strong>in</strong>g, also<br />
das Zurückgehen <strong>in</strong> der Aufrufhierarchie am Stack, bis e<strong>in</strong> entsprechender<br />
try Block mit zugehörigem catch gefunden wird. Hierbei wird<br />
setElementAt verlassen und das erste passende catch f<strong>in</strong>det sich direkt<br />
<strong>in</strong> der aufrufenden ma<strong>in</strong> Funktion <strong>in</strong> Zeile 170. Genau dort läuft das<br />
Programm dann weiter, wie sich am Output leicht erkennen lässt.<br />
7. Beim Verlassen des catch Blocks ist auch die Lifetime der Kopie des<br />
temporären Objekts zu Ende (diese war natürlich selbst e<strong>in</strong> temporäres<br />
Objekt, wie sich leicht überlegen lässt). Entsprechend f<strong>in</strong>den wir auch<br />
im Output die entsprechenden Meldungen zu den Destruktoraufrufen<br />
destruct<strong>in</strong>g IndexOutOfBoundsException, id = 1<br />
destruct<strong>in</strong>g Exception, id = 1<br />
Das Grundpr<strong>in</strong>zip des Exception Mechanismus ist im Pr<strong>in</strong>zip also recht klar<br />
und e<strong>in</strong>fach nachvollziehbar. Es wurde auch bereits erwähnt, dass es verschiedene<br />
catch Blöcke für verschiedene Exceptions geben kann. Nun stellt sich<br />
nur noch die Frage, wie diese verschiedenen Exceptions unterschieden werden.<br />
Die Antwort ist e<strong>in</strong>fach: aufgrund ihres Typs. E<strong>in</strong> bestimmter catch<br />
Block wird angesprungen, wenn die Exception, die geworfen wurde, e<strong>in</strong>e der<br />
folgenden Bed<strong>in</strong>gungen erfüllt:<br />
• Die geworfene Exception hat genau denselben Typ, wie im catch Statement<br />
als Parameter angegeben.<br />
• Die im catch als Parameter angegebene Exception ist e<strong>in</strong>e public Basisklasse<br />
der geworfenen Exception. Dann s<strong>in</strong>d diese beiden Typen ja bekannterweise<br />
voll kompatibel.<br />
• Wenn die geworfene Exception e<strong>in</strong> Po<strong>in</strong>ter ist (ja, auch das geht!) und<br />
dieser Po<strong>in</strong>ter zu e<strong>in</strong>em Po<strong>in</strong>ter-Parameter <strong>in</strong> e<strong>in</strong>em catch Statement typkompatibel<br />
ist.<br />
• Wenn die geworfene Exception e<strong>in</strong>e Referenz ist (wie zu erwarten, geht das<br />
auch) und diese Referenz zu e<strong>in</strong>em Referenz-Parameter <strong>in</strong> e<strong>in</strong>em catch<br />
Statement typkompatibel ist.
320 11. Exceptions<br />
Außerdem gilt noch, dass immer der erste catch Block angesprungen wird,<br />
der e<strong>in</strong>e dieser Bed<strong>in</strong>gungen erfüllt. Sollten also aus Gründen der Typenkompatibilität<br />
mehrere catch Blöcke <strong>in</strong> Frage kommen, so wird der erste<br />
davon genommen. Das bedeutet also, dass auch die Reihenfolge, <strong>in</strong> der die<br />
catch Blöcke vorkommen, e<strong>in</strong>e große Bedeutung spielt. Sehen wir uns das<br />
am besten an e<strong>in</strong>em Beispiel an (second_exception_demo.cpp):<br />
1 // second exception demo . cpp − another demo , how the exception<br />
2 // mechanism works <strong>in</strong> <strong>C++</strong><br />
3<br />
4 #<strong>in</strong>clude <br />
5 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
6<br />
7 us<strong>in</strong>g std : : cout ;<br />
8 us<strong>in</strong>g std : : endl ;<br />
9<br />
10 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
11 class Exception<br />
12 {<br />
13 public :<br />
14 Exception ( ) { }<br />
15 virtual ˜ Exception ( ) { }<br />
16 } ;<br />
17<br />
18 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
19 class DerivedAException : public virtual Exception<br />
20 {<br />
21 public :<br />
22 DerivedAException ( ) { }<br />
23 virtual ˜ DerivedAException ( ) { }<br />
24 } ;<br />
25<br />
26 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
27 class DerivedBException : public virtual Exception<br />
28 {<br />
29 public :<br />
30 DerivedBException ( ) { }<br />
31 virtual ˜ DerivedBException ( ) { }<br />
32 } ;<br />
33<br />
34 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
35 class DerivedCException : public DerivedAException<br />
36 {<br />
37 public :<br />
38 DerivedCException ( ) { }<br />
39 virtual ˜ DerivedCException ( ) { }<br />
40 } ;<br />
41<br />
42 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
43 class DerivedDException : public DerivedAException ,<br />
44 public DerivedBException<br />
45 {<br />
46 public :<br />
47 DerivedDException ( ) { }<br />
48 virtual ˜ DerivedDException ( ) { }<br />
49 } ;<br />
50<br />
51 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
52 void demoFuncException ( )<br />
53 throw( Exception )<br />
54 {<br />
55 throw Exception ( ) ;<br />
56 }
57<br />
11. Exceptions 321<br />
58 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
59 void demoFuncDerivedAException ( )<br />
60 throw( DerivedAException )<br />
61 {<br />
62 throw DerivedAException ( ) ;<br />
63 }<br />
64 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
65 void demoFuncDerivedBException ( )<br />
66 throw( DerivedBException )<br />
67 {<br />
68 throw DerivedBException ( ) ;<br />
69 }<br />
70 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
71 void demoFuncDerivedCException ( )<br />
72 throw( DerivedCException )<br />
73 {<br />
74 throw DerivedCException ( ) ;<br />
75 }<br />
76 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
77 void demoFuncDerivedDException ( )<br />
78 throw( DerivedDException )<br />
79 {<br />
80 throw DerivedDException ( ) ;<br />
81 }<br />
82<br />
83 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
84 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
85 {<br />
86 try<br />
87 {<br />
88 u<strong>in</strong>t32 num runs = 5;<br />
89 while ( num runs−−)<br />
90 {<br />
91 try<br />
92 {<br />
93 switch ( num runs )<br />
94 {<br />
95 case 4 :<br />
96 demoFuncDerivedDException ( ) ;<br />
97 break ;<br />
98 case 3 :<br />
99 demoFuncDerivedCException ( ) ;<br />
100 break ;<br />
101 case 2 :<br />
102 demoFuncDerivedBException ( ) ;<br />
103 break ;<br />
104 case 1 :<br />
105 demoFuncDerivedAException ( ) ;<br />
106 break ;<br />
107 case 0 :<br />
108 demoFuncException ( ) ;<br />
109 break ;<br />
110 }<br />
111 }<br />
112 catch ( DerivedCException &exc )<br />
113 {<br />
114 cout
322 11. Exceptions<br />
123 }<br />
124 catch ( DerivedDException &exc )<br />
125 {<br />
126 // ATTENTION ! ! ! This one w i l l never become a c t i v e !<br />
127 cout
11. Exceptions 323<br />
Vorsicht Falle: Wie bereits demonstriert wurde, kann es durch Unachtsamkeiten<br />
bei der Anordnung der catch Blöcke dazu kommen, dass manche davon<br />
nicht erreichbar s<strong>in</strong>d. Dieses Problem kann man leicht vermeiden, <strong>in</strong>dem<br />
man Klassen, die tiefer <strong>in</strong> der Ableitungshierarchie stehen, weiter oben fängt<br />
und solche, die höher stehen, entsprechend weiter unten, um “den Rest” der<br />
Exceptions, die nicht explizit mit ihrer genauen Klasse angegeben wurden,<br />
auch noch s<strong>in</strong>nvoll zu fangen.<br />
Das Verhalten von Exceptions, dass sie entsprechend ihrer Typenhierarchie<br />
behandelbar s<strong>in</strong>d, hilft enorm beim Design großer Libararies. Es hat<br />
sich s<strong>in</strong>nvollerweise e<strong>in</strong>gebürgert, dass es für e<strong>in</strong>e Library e<strong>in</strong>en bestimmten<br />
Basis Exceptiontyp gibt, von dem alle anderen abgeleitet s<strong>in</strong>d. Zum Beispiel<br />
könnte man <strong>in</strong> e<strong>in</strong>er Library mit mathematischen Klassen e<strong>in</strong>e Basis<br />
Exception MathException def<strong>in</strong>ieren. Davon abgeleitet wäre dann z.B. e<strong>in</strong>e<br />
DivisionByZeroException, e<strong>in</strong>e VectorException, e<strong>in</strong>e MatrixException,<br />
etc. Sollte man bestimmte Exceptions gesondert behandeln wollen, fängt<br />
man diese speziell, wenn nicht, reicht e<strong>in</strong> catch auf e<strong>in</strong>e MathException und<br />
dadurch fängt man automatisch alle möglicherweise auftretenden Exceptions<br />
aus dieser Library.<br />
Jetzt bleibt noch e<strong>in</strong>e Frage offen: Was passiert, wenn e<strong>in</strong>e Exception geworfen<br />
wird, aber ke<strong>in</strong> passendes catch dazu auff<strong>in</strong>dbar ist? Irgendwann ist<br />
ja das Stack Unw<strong>in</strong>d<strong>in</strong>g e<strong>in</strong>mal am Ende des Stacks angelangt. In diesem<br />
Fall wird e<strong>in</strong>e Standard Behandlung durchgeführt, die üblicherweise zum<br />
Programmabbruch mittels abort() führt. Wie dieser Mechanismus genau<br />
funktioniert und wie man sich z.B. zu Debugg<strong>in</strong>g Zwecken noch <strong>in</strong> diesen<br />
e<strong>in</strong>kl<strong>in</strong>ken kann, wird <strong>in</strong> Abschnitt 15.7 besprochen. Im Augenblick genügt<br />
es, zu wissen, wie man Exceptions s<strong>in</strong>nvoll fängt und ich möchte allen Lesern<br />
unbed<strong>in</strong>gt anraten, die Behandlung von Exceptions auch gewissenhaft<br />
durchzuführen, damit es gar nicht erst zu sogenannten uncaught Exceptions<br />
kommen kann.<br />
E<strong>in</strong>e Anmerkung hätte ich noch zur Deklaration von möglichen Exceptions,<br />
die aus e<strong>in</strong>er Methode oder Funktion geworfen werden, mittels<br />
throw(...): Ebenso wie beim Fangen von Exceptions, bei der nicht der<br />
genaue Typ, sondern die Typenkompatibilität zählt, verhält es sich bei<br />
der Angabe von Exceptions, die geworfen werden können. In der Angabe<br />
mit throw(...) muss nur e<strong>in</strong> kompatibler Typ, also entweder die genaue<br />
Exception oder e<strong>in</strong>e ihrer Basisklassen angegeben se<strong>in</strong>. Man könnte<br />
also zum Beispiel <strong>in</strong> unserem Demoprogramm <strong>in</strong> Zeile 78 auch e<strong>in</strong>fach<br />
throw(Exception) schreiben und würde damit ke<strong>in</strong> Problem heraufbeschwören,<br />
denn DerivedDException ist ja von Exception abgeleitet. Im<br />
Falle, dass man e<strong>in</strong> Overrid<strong>in</strong>g e<strong>in</strong>er Methode macht, bei der bereits e<strong>in</strong> throw<br />
deklariert wurde, kann diese Deklaration <strong>in</strong> der abgeleiteten Klasse entweder<br />
gleich se<strong>in</strong>, oder die Orig<strong>in</strong>aldeklaration e<strong>in</strong>schränken. Das ist auch völlig logisch,<br />
denn damit erreicht man, dass immer maximal die Exceptions geworfen
324 11. Exceptions<br />
werden können, die ursprünglich deklariert waren. Dadurch ist sichergestellt,<br />
dass ke<strong>in</strong>e neuen Exceptions geworfen werden können, auf die Code, der mit<br />
der Basisklasse zu tun hatte, eventuell gar nicht reagieren kann.<br />
Es wurde bereits erwähnt, dass man Exceptions auch “weiterwerfen”<br />
kann. Ich möchte gleich vorausschicken, dass dieses Konstrukt <strong>in</strong> nicht allzu<br />
vielen Fällen als schön bezeichnet werden kann, denn entweder kann man e<strong>in</strong>e<br />
Exception behandeln, dann soll man sie fangen, oder nicht, dann soll man sie<br />
<strong>in</strong> Ruhe lassen. E<strong>in</strong>e der ganz wenigen Ausnahmen zu dieser Regel ist der<br />
Fall, dass man für e<strong>in</strong>e Exception zwar ke<strong>in</strong>e Behandlung durchführen kann,<br />
aber noch e<strong>in</strong> paar D<strong>in</strong>ge aufräumen muss, bevor diese Exception dann ihrer<br />
tatsächlichen Behandlung zugeführt wird. Um z.B. für diesen Fall zu wissen,<br />
wie man dies <strong>in</strong> C ++ bewerkstelligt, sehen wir uns am besten e<strong>in</strong> kle<strong>in</strong>es<br />
Beispiel an (rethrow_exc_demo.cpp):<br />
1 // rethrow exc demo . cpp − a demo , how re−throw<strong>in</strong>g an exception<br />
2 // works <strong>in</strong> <strong>C++</strong><br />
3<br />
4 #<strong>in</strong>clude <br />
5 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
6<br />
7 us<strong>in</strong>g std : : cout ;<br />
8 us<strong>in</strong>g std : : endl ;<br />
9<br />
10 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
11 class Exception<br />
12 {<br />
13 public :<br />
14 Exception ( )<br />
15 {<br />
16 cout
46 }<br />
47 catch ( Exception &exc )<br />
48 {<br />
49 cout
326 11. Exceptions<br />
29<br />
30 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
31 Dummy: :Dummy( )<br />
32 throw( Exception )<br />
33 {<br />
34 cout
11. Exceptions 327<br />
Objekte <strong>in</strong> ihrem “halbtoten” Zustand belassen, denn das resultiert <strong>in</strong> ganz<br />
tollen und langen Debugg<strong>in</strong>g-Sessions, <strong>in</strong> denen man teuflische Gespenster<br />
jagen darf.<br />
In unserem Fall ist dieses Problem leicht <strong>in</strong> den Griff zu bekommen, man<br />
braucht nur e<strong>in</strong> entsprechendes delete durchzuführen, bevor man die Exception<br />
im Konstruktor wirft. Damit hat man s<strong>in</strong>nvoller- und robusterweise<br />
ke<strong>in</strong> “halbtotes” Objekt konstruiert, sondern die “halbe” Konstruktion wieder<br />
rückgängig gemacht.<br />
Noch e<strong>in</strong> Punkt kommt hier <strong>in</strong>s Spiel: Es wurde bereits gesagt, dass alle<br />
Objekte, die fertig konstruiert wurden, auch wieder destruiert werden. Dies<br />
gilt natürlich auch für alle Member Variablen e<strong>in</strong>es Objekts, selbst wenn im<br />
Konstruktor des Objekts selbst e<strong>in</strong>e Exception geworfen wurde. Sobald diese<br />
fertig konstruiert waren, stellen sie ke<strong>in</strong>e Gefahr mehr dar. Selbiges gilt<br />
auch <strong>in</strong> Ableitungshierarchien: Sobald der Konstruktor e<strong>in</strong>er Basisklasse korrekt<br />
abgearbeitet war, wird auch deren Destruktor aufgerufen, selbst wenn<br />
im Konstruktor e<strong>in</strong>er abgeleiteten Klasse e<strong>in</strong>e Exception <strong>in</strong> Bodenhöhe geflogen<br />
kommt. Den Beweis für diese Aussagen tritt das folgende Beispiel an<br />
(another_exc_<strong>in</strong>_constructor_demo.cpp):<br />
1 // another exc <strong>in</strong> constructor demo . cpp − what happens when throw<strong>in</strong>g an<br />
2 // exception <strong>in</strong> a constructor ?<br />
3<br />
4 #<strong>in</strong>clude <br />
5 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
6<br />
7 us<strong>in</strong>g std : : cout ;<br />
8 us<strong>in</strong>g std : : endl ;<br />
9<br />
10 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
11 class Exception<br />
12 {<br />
13 public :<br />
14 Exception ( ) { }<br />
15 Exception ( const Exception& exc ) {}<br />
16 virtual ˜ Exception ( ) { }<br />
17 } ;<br />
18<br />
19 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
20 class Whatever<br />
21 {<br />
22 public :<br />
23 Whatever ( ) { cout
328 11. Exceptions<br />
38 protected :<br />
39 Whatever a v a r i a b l e ;<br />
40 public :<br />
41 Dummy( )<br />
42 throw( Exception ) ;<br />
43 virtual ˜Dummy( ) ;<br />
44 } ;<br />
45<br />
46 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
47 Dummy: :Dummy( )<br />
48 throw( Exception )<br />
49 {<br />
50 cout
11. Exceptions 329<br />
Der Grund hierfür ist leicht e<strong>in</strong>zusehen, wenn man sich kurz überlegt, was<br />
bei Exceptions passiert. Wir haben bereits diskutiert, dass bei e<strong>in</strong>er Exception<br />
das sogenannte Stack Unw<strong>in</strong>d<strong>in</strong>g stattf<strong>in</strong>det. Dabei werden natürlich<br />
alle auto-Variablen aus dem Speicher entfernt (und natürlich destruiert), die<br />
ihre Lifetime h<strong>in</strong>ter sich haben, wenn der Block, <strong>in</strong> dem sie def<strong>in</strong>iert wurden,<br />
verlassen wird. Und jetzt stellen wir uns vor, dass e<strong>in</strong>e Variable im Zuge des<br />
Stack Unw<strong>in</strong>d<strong>in</strong>gs destruiert wird und selbst e<strong>in</strong>e Exception wirft. Welche<br />
der beiden Exceptions soll jetzt behandelt werden? Die erste, die das Stack<br />
Unw<strong>in</strong>d<strong>in</strong>g <strong>in</strong> Gang gesetzt hat oder die zweite, die während des Unw<strong>in</strong>d<strong>in</strong>gs<br />
passiert ist? Beide gleichzeitig behandeln geht nicht, e<strong>in</strong>e davon e<strong>in</strong>fach verwerfen<br />
geht auch nicht, e<strong>in</strong> tolles Dilemma! Es wird <strong>in</strong> diesem Fall e<strong>in</strong>fach der<br />
brutale Weg gegangen und das Programm <strong>in</strong>tern mittels abort abgewürgt.<br />
Das wollen wir natürlich alle nicht, dementsprechend ist die starke Empfehlung<br />
von oben unbed<strong>in</strong>gt e<strong>in</strong>zuhalten!<br />
Vorsicht Falle: Wenn man unvorsichtig ist, kann es passieren, dass <strong>in</strong> e<strong>in</strong>em<br />
Destruktor e<strong>in</strong>e Methode bzw. Funktion aufgerufen wird, die selbst e<strong>in</strong>e Exception<br />
werfen kann. In solchen Fällen darf niemals übersehen werden, dass<br />
man im Destruktor selbst e<strong>in</strong> entsprechendes try-catch Konstrukt unterbr<strong>in</strong>gt,<br />
ansonsten würde ja unabsichtlich die Exception aus dem Destruktor<br />
h<strong>in</strong>aus geworfen werden!<br />
Vorsicht Falle: Leider gibt es immer wieder Entwickler, die der Me<strong>in</strong>ung<br />
s<strong>in</strong>d, dass man Exceptions am besten e<strong>in</strong>fach ignoriert, um sie nach außen<br />
zu verstecken. Zu diesem Zweck bauen sie sogenannte silent Catches <strong>in</strong> ihren<br />
Code e<strong>in</strong>, also solche, die e<strong>in</strong>fach e<strong>in</strong>en leeren catch Block besitzen. Dies<br />
darf man niemals machen, denn damit gel<strong>in</strong>gt es bravourös, dass man Fehler,<br />
die auftreten und sauber gemeldet würden, kommentarlos ignoriert. Damit<br />
können sich dann Programme unheimlich komisch verhalten, weil sie eigentlich<br />
<strong>in</strong>tern <strong>in</strong> e<strong>in</strong>em Fehlerzustand s<strong>in</strong>d, aber trotzdem weiterlaufen, als wäre<br />
nichts geschehen.<br />
Die allerschlimmsten Exemplare von silent Catches, die man immer wieder<br />
f<strong>in</strong>det, s<strong>in</strong>d solche, bei denen im leeren catch Block e<strong>in</strong> Kommentar à la<br />
// this exception can never happen<br />
steht. Der richtige Standpunkt dazu ist allerd<strong>in</strong>gs: Wenn die Exception nicht<br />
auftreten kann, dann muss man zum<strong>in</strong>dest e<strong>in</strong>en Output e<strong>in</strong>bauen, der den<br />
“unmöglichen” Fall meldet. Hatte man Recht und die Exception tritt nie<br />
auf, dann stört auch der Output nicht. Hatte man allerd<strong>in</strong>gs unrecht (sehr<br />
oft, v.a. nach Programmänderungen!!!), dann sieht man wenigstens, dass e<strong>in</strong><br />
Fehler aufgetreten ist!<br />
Vor allem sollte man sich wirklich überlegen, ob man e<strong>in</strong> solches catch<br />
nicht gleich bleiben lässt und jemandem “weiter oben” die Behandlung<br />
überlässt, für den Fall, dass die Exception doch auftritt.
330 11. Exceptions<br />
Vorsicht Falle: Manche Entwickler verwenden Exceptions bewusst zum<br />
Modellieren von bestimmten Control-Flows, z.B. zur Steuerung von Schleifen.<br />
Erstens stellt dies e<strong>in</strong>en groben Widerspruch zur Semantik von Exceptions<br />
dar, zweitens kann man damit besonders hübsche Zeitbomben bauen, die<br />
viele Leute an den Rand der Verzweiflung treiben können.<br />
Nehmen wir zum Beispiel an, dass e<strong>in</strong> Entwickler <strong>in</strong> e<strong>in</strong>er Schleife e<strong>in</strong><br />
Vektor-Objekt Element für Element durchlaufen will und <strong>in</strong>nerhalb der<br />
Schleife irgendwelche Methoden auf diesen e<strong>in</strong>zelnen Objekten aufruft. Nehmen<br />
wir weiters an, dass der Vektor so implementiert ist, dass bei unerlaubter<br />
Indizierung e<strong>in</strong>e IndexOutOfBoundsException geworfen wird. Der Entwickler<br />
kommt auf die glorreiche Idee, e<strong>in</strong>e Endlosschleife zu implementieren und<br />
diese <strong>in</strong> e<strong>in</strong> try mit zugehörigem catch auf die IndexOutOfBoundsException<br />
e<strong>in</strong>zubetten. Sobald die Exception geworfen wird, wird angenommen, dass<br />
alle Elemente im Vektor abgearbeitet wurden, denn man hat mit e<strong>in</strong>em zu<br />
großen Index zugegriffen.<br />
Jetzt passiert allerd<strong>in</strong>gs Folgendes: In e<strong>in</strong>er der Methoden, die auf e<strong>in</strong> E<strong>in</strong>zelobjekt<br />
aufgerufen werden, tritt aufgrund e<strong>in</strong>es Softwarefehlers e<strong>in</strong>e ganz<br />
anders begründete IndexOutOfBoundsException auf, als die, die erwartet<br />
wird. Diese bewirkt dann, dass die Schleife unabsichtlich verlassen wird, bevor<br />
noch alle Objekte abgearbeitet wurden! Die Implementation geht aber<br />
davon aus, dass die Exception dadurch entstand, dass der Vektor fertig abgearbeitet<br />
war. Somit hat man tollerweise nur e<strong>in</strong>en Teil des Vektors abgearbeitet<br />
und sucht verzweifelt nach irgendwelchen verlorenen Objekten!<br />
Vorsicht Falle: Wie bereits erwähnt, können nicht nur Objekte, sondern<br />
auch Po<strong>in</strong>ter auf Objekte als Exceptions verwendet werden. Aus Performancegründen<br />
kann dies auch sehr geistreich se<strong>in</strong>, denn z.B. e<strong>in</strong>e Kopie e<strong>in</strong>es<br />
Po<strong>in</strong>ters anzulegen, ist im Regelfall deutlich schneller, als e<strong>in</strong>e Kopie<br />
e<strong>in</strong>es gesamten Objekts anzulegen. Nur leider übersehen manche Entwickler,<br />
dass e<strong>in</strong>e dynamisch angelegte Exception auch s<strong>in</strong>nigerweise wieder irgendwo<br />
freigegeben werden muss, wie man an folgendem Beispiel beobachten kann<br />
(exception_memory_leak_problem.cpp):<br />
1 // exception memory leak problem . cpp − demo , how a memory leak<br />
2 // can be caused by bad exception throw<strong>in</strong>g .<br />
3<br />
4<br />
5 #<strong>in</strong>clude <br />
6 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
7<br />
8 us<strong>in</strong>g std : : cout ;<br />
9 us<strong>in</strong>g std : : endl ;<br />
10<br />
11 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
12 class Exception<br />
13 {<br />
14 public :<br />
15 Exception ( ) { cout
11. Exceptions 331<br />
16 virtual ˜ Exception ( ) { cout
332 11. Exceptions<br />
noch e<strong>in</strong>e zusätzliche Kopie der Exception angelegt wird, da ja call-by-value<br />
<strong>in</strong> C ++ der Standardmechanismus ist.<br />
Um nicht missverstanden zu werden: Die Fallen, <strong>in</strong> die man beim Exception<br />
Handl<strong>in</strong>g stolpern kann, sollen niemanden abschrecken, sondern nur<br />
warnen, worauf man aufpassen muss. Exceptions s<strong>in</strong>d etwas sehr Brauchbares<br />
und sollen auch unbed<strong>in</strong>gt verwendet werden! Die Behandlung von<br />
irgendwelchen “Ausnahmezuständen” ist seit den Urzeiten der <strong>Softwareentwicklung</strong><br />
e<strong>in</strong> heißes Thema und Exceptions s<strong>in</strong>d die sauberste und robusteste<br />
Möglichkeit, mit unerwarteten Ereignissen umzugehen. Code, der robust und<br />
fehlertolerant ist, ist eben bei weitem komplexer als Code, der nur so lange<br />
funktioniert, wie alles im “Normalzustand” bleibt. Wer kann zum Beispiel<br />
behaupten, dass im Falle von Netzwerken der andere Rechner immer erreichbar<br />
ist oder überhaupt existiert? Wer kann vorherbestimmen, ob e<strong>in</strong> File,<br />
das man öffnen will, garantiert vorhanden ist? Es ist also notwendig, e<strong>in</strong>e<br />
Fehlererkennung und entsprechende Behandlung <strong>in</strong> den Code e<strong>in</strong>zubauen.<br />
Es kursiert leider auch noch immer die große Angst vor Performanceproblemen<br />
bei der Verwendung von Exceptions, da e<strong>in</strong> try ... catch e<strong>in</strong><br />
wenig <strong>in</strong>effizienter ist, als z.B. e<strong>in</strong>e Abfrage von return Werten. Diese Angst<br />
ist allerd<strong>in</strong>gs im Normalfall unbegründet. Man darf nämlich nicht außer Acht<br />
lassen, dass e<strong>in</strong>e saubere und übersichtliche Programmstruktur <strong>in</strong> der Regel<br />
e<strong>in</strong>e effizientere Implementation von Algorithmen begünstigt.<br />
E<strong>in</strong> kurzer Vergleich des Exception Mechanismus mit alternativen Techniken<br />
zur Fehlerbehandlung zeigt se<strong>in</strong>e Stärken:<br />
• Man kann e<strong>in</strong>en Fehler ignorieren und e<strong>in</strong>fach probieren, weiterzumachen.<br />
Dass das katastrophale Folgen haben kann, versteht sich von selbst und<br />
deswegen möchte ich diese Art der “Fehlerbehandlung” gar nicht näher<br />
kommentieren.<br />
• Man kann e<strong>in</strong>fach bei e<strong>in</strong>em Fehler das Programm beenden. Dieses Verhalten<br />
führt früher oder später sicher zu e<strong>in</strong>em größeren Desaster. Erstens ist<br />
e<strong>in</strong> Programm, das sich bei jeder Gelegenheit beendet, e<strong>in</strong>e Katastrophe<br />
für die Benutzer. Zweitens bedeutet dieses Verhalten, dass <strong>in</strong>tern durch das<br />
willkürliche Unterbrechen von Abläufen schlimmste Inkonsistenzen entstehen<br />
können. Sobald dann e<strong>in</strong>mal aufgrund e<strong>in</strong>es solchen Verhaltens e<strong>in</strong>e<br />
größere Datenbank <strong>in</strong>konsistent ist, weil e<strong>in</strong>fach beim Beenden nur die halben<br />
Daten geschrieben werden konnten, kann man sich das Lachen kaum<br />
noch verhalten. Dieses Verhalten ist also absolut unbrauchbar!<br />
• Man kann im Fehlerfall e<strong>in</strong>e Meldung z.B. auf Standard-Error schreiben.<br />
Das Problem dabei ist, dass man ja trotzdem irgendwie mit dem Programm<br />
fortfahren muss. Es muss also sowieso e<strong>in</strong>e der anderen, hier erwähnten,<br />
Strategien zusätzlich implementiert werden, denn sonst käme dieses Verhalten<br />
dem Ignorieren des Fehlers gleich.<br />
• Man kann aus allen Methoden und Funktionen im Fehlerfall bewusst<br />
“unmögliche” Ergebnisse zum Signalisieren e<strong>in</strong>es Fehlers liefern. Dies war
11. Exceptions 333<br />
lange Zeit gängige Praxis, vor allem <strong>in</strong> C-Programmen. Allerd<strong>in</strong>gs hat dieses<br />
Verhalten auch große Nachteile. E<strong>in</strong>erseits muss man bei jedem e<strong>in</strong>zelnen<br />
Aufruf den return-Wert abfragen und entsprechenden Error-Handl<strong>in</strong>g<br />
Code schreiben, zweitens gibt es genug Fälle, <strong>in</strong> denen e<strong>in</strong> “unmöglicher”<br />
Wert gar nicht wirklich vorhanden ist! Auch das Speichern e<strong>in</strong>es Fehlers<br />
<strong>in</strong> e<strong>in</strong>er globalen Variable (z.B. errno) ist nicht so toll, denn z.B.<br />
im Falle von Multithread<strong>in</strong>g Umgebungen ist dies nicht synchronisierbar.<br />
Außerdem hat man das Problem, dass das Melden von Fehlern und die<br />
Behandlung derselben nicht sauber getrennt vonstatten gehen kann, denn<br />
der Fehlerbehandlungscode kommt immer <strong>in</strong>mitten irgendwelcher Blöcke<br />
vor. Dieses Verhalten ist also auch nicht so toll.<br />
• Man kann mittels Callbacks (z.B. über Funktionspo<strong>in</strong>ter) Fehler melden.<br />
Das funktioniert zwar im Pr<strong>in</strong>zip nicht so schlecht, leider hat es den entscheidenden<br />
Nachteil, dass im Fehlerfall der Ausstieg aus e<strong>in</strong>er Methode<br />
bzw. Funktion und das Resume im Error-Handl<strong>in</strong>g Code nicht sauber passieren<br />
kann. Hier kommt es dann zu schlimmen Orgien mit Status- und<br />
Merkervariablen, die erstens ungewollte Abhängigkeiten und Seiteneffekte<br />
nach sich ziehen und zweitens den Code relativ undurchschaubar machen.<br />
• Man kann im Fehlerfall e<strong>in</strong>en Dialog mit dem User beg<strong>in</strong>nen, der entscheiden<br />
soll, wie jetzt fortgefahren wird. Dieses Verhalten ist absolut katastrophal,<br />
denn was soll den e<strong>in</strong> Benutzer mit der Frage anfangen, wie jetzt bei<br />
e<strong>in</strong>em Illegalen Zugriff auf Adresse 0x17a5972b vorzugehen ist???<br />
S<strong>in</strong>nigerweise werden Exceptions also auch <strong>in</strong> der Standard-Library von C ++<br />
verwendet. Aus diesem Grund möchte ich hier noch e<strong>in</strong> paar Kle<strong>in</strong>igkeiten<br />
ergänzen, die ich bisher schuldig geblieben b<strong>in</strong>:<br />
• Der Operator new wirft e<strong>in</strong>e Exception vom Typ bad_alloc, wenn beim<br />
Anfordern von Speicher etwas schief geht. Um mit dieser Exception umgehen<br />
zu können, ist e<strong>in</strong> #<strong>in</strong>clude im Code notwendig.<br />
• Der dynamic_cast Operator wirft e<strong>in</strong>e Exception vom Typ bad_cast,<br />
wenn der Cast fehlschlägt. E<strong>in</strong> #<strong>in</strong>clude ist zum Umgang<br />
mit dieser notwendig.<br />
• Der typeid Operator wirft e<strong>in</strong>e Exception vom Typ bad_typeid, wenn<br />
etwas fehlschlägt. Auch hier ist #<strong>in</strong>clude zum Umgang mit<br />
demselben notwendig.<br />
Viele andere Methoden, Operatoren und Funktionen aus der Standard Library<br />
werfen Exceptions im Fehlerfall. Welche, kann man den entsprechenden<br />
Manual Pages entnehmen. Leider folgen die Standard Exceptions ke<strong>in</strong>em e<strong>in</strong>deutigen<br />
Namensschema, aber damit muss man wohl leben, denn e<strong>in</strong>e Änderung<br />
dieser Exceptions würde weltweit e<strong>in</strong>e Unmenge von C ++ Entwicklern<br />
zur Verzweiflung treiben.
12. Operator Overload<strong>in</strong>g<br />
Bei den bisherigen Betrachtungen zu Klassen und Objekten haben wir bereits<br />
die verschiedenen Arten von Members, also Variablen und Methoden, kennen<br />
gelernt. E<strong>in</strong>es fehlt noch, um Klassen und Objekte zu e<strong>in</strong>em Gesamtkonzept<br />
zu machen, nämlich Operatoren! Je nachdem, mit welchen Objekten man es<br />
zu tun hat, ist e<strong>in</strong>e Verknüpfung derselben mittels Operatoren oft e<strong>in</strong> sehr<br />
logischer Zugang zu deren Konzept. Wenn man zwei Ganzzahlenwerte addieren<br />
kann, wieso sollte man das mit “höheren” Datentypen (z.B. Vektoren)<br />
nicht tun können?<br />
12.1 Grundpr<strong>in</strong>zipien des Operator Overload<strong>in</strong>gs<br />
Um nun nicht gleich zu abstrakt zu werden, nehmen wir e<strong>in</strong>mal als E<strong>in</strong>stiegsdroge<br />
für das Operator Overload<strong>in</strong>g e<strong>in</strong>e e<strong>in</strong>fache (!) mathematische<br />
Vektor-Klasse. Ich gebe schon zu, dass dies wirklich nicht das spannendste<br />
Beispiel ist und dass es bereits vielfach <strong>in</strong> der Literatur <strong>in</strong> verschiedensten<br />
Formen abgehandelt ist. Für den E<strong>in</strong>stieg ist es allerd<strong>in</strong>gs e<strong>in</strong>es der demonstrativsten<br />
Beispiele. Sobald wir über die Grundpr<strong>in</strong>zipien h<strong>in</strong>aus s<strong>in</strong>d,<br />
wird es dann wieder spannender. Von unserer M<strong>in</strong>imalversion e<strong>in</strong>es Vektors<br />
erwarten wir folgende D<strong>in</strong>ge:<br />
• E<strong>in</strong> Vektor-Objekt speichert e<strong>in</strong>e gewisse Anzahl von double Elementen.<br />
Beim Erzeugen e<strong>in</strong>es solchen Objekts wird diese Anzahl (durch entsprechenden<br />
Konstruktoraufruf) festgelegt.<br />
• Der Zugriff auf die e<strong>in</strong>zelnen Elemente e<strong>in</strong>es Vektors soll mittels Angabe<br />
e<strong>in</strong>es Index <strong>in</strong> eckigen Klammern h<strong>in</strong>ter e<strong>in</strong>er Vektor-Variable erfolgen, wie<br />
es auch bei “normalen” Arrays <strong>in</strong> C ++ üblich ist.<br />
• Zwei Vektoren sollen addierbar se<strong>in</strong>. Dies soll <strong>in</strong>tuitiverweise e<strong>in</strong>fach durch<br />
e<strong>in</strong> + zwischen zwei Vektoren geschehen. Die Addition von zwei Vektoren<br />
soll mathematisch korrekt durchgeführt werden, <strong>in</strong>dem e<strong>in</strong>fach die jeweiligen<br />
E<strong>in</strong>zelelemente an jeder Index-Position mite<strong>in</strong>ander addiert werden.<br />
• Es soll e<strong>in</strong>e Subtraktion e<strong>in</strong>es Vektors von e<strong>in</strong>em anderen möglich se<strong>in</strong>.<br />
Dazu will man e<strong>in</strong>fach e<strong>in</strong> - zwischen diese beiden Vektoren schreiben. Die<br />
Subtraktion e<strong>in</strong>es Vektors von e<strong>in</strong>em anderen soll mathematisch korrekt<br />
geschehen, <strong>in</strong>dem e<strong>in</strong>fach die jeweiligen E<strong>in</strong>zelelemente subtrahiert werden.
336 12. Operator Overload<strong>in</strong>g<br />
• Positives und negatives Vorzeichen sollen im Zusammenhang mit Vektoren<br />
verwendet werden können.<br />
• E<strong>in</strong> Vektor soll mit e<strong>in</strong>em Skalar (also bei uns mit e<strong>in</strong>em double Wert)<br />
multiplizierbar se<strong>in</strong>. Dabei soll mathematisch korrekt jedes E<strong>in</strong>zelelement<br />
des Vektors mit dem Skalar multipliziert werden.<br />
• Die Zuweisung e<strong>in</strong>es Vektors auf e<strong>in</strong>en anderen mittels = Operator soll<br />
möglich se<strong>in</strong>.<br />
• Für Addition, Subtraktion und Multiplikation soll es auch die Zuweisungs-<br />
Kurzformen +=, -= und *= geben.<br />
• Der Vergleich zweier Vektoren auf Gleichheit sowie auf Ungleichheit soll<br />
<strong>in</strong>tuitiverweise mittels == bzw. != möglich se<strong>in</strong>.<br />
Im Pr<strong>in</strong>zip stellt sich also hier nur die Frage, wie man <strong>in</strong> C ++ dem Compiler<br />
erklärt, was e<strong>in</strong> Operator zu tun hat. Denkt man e<strong>in</strong> wenig näher über<br />
die Eigenschaften von Operatoren nach, so lässt sich leicht erkennen, dass<br />
sie eigentlich auch nichts anderes als Methoden bzw. Funktionen se<strong>in</strong> müssten.<br />
Und bis auf die spezielle Syntax, die Operatoren zugrunde liegt, ist das<br />
auch so. Nehmen wir z.B. e<strong>in</strong>mal den folgenden Ausdruck her, bei dem wir<br />
annehmen, dass beide Variablen vom selben Typ MathVector s<strong>in</strong>d:<br />
vector1 + vector2<br />
Das bedeutet für den Compiler salopp gesprochen: Ich habe e<strong>in</strong>en b<strong>in</strong>ären<br />
Plus-Operator, der als se<strong>in</strong>e Operanden jeweils etwas vom Typ MathVector<br />
oder etwas Kompatibles nimmt.<br />
Oder nehmen wir folgenden Ausdruck unter denselben Typ-Voraussetzungen:<br />
-vector1<br />
Das bedeutet für den Compiler salopp gesprochen: Ich habe e<strong>in</strong>en unären<br />
M<strong>in</strong>us-Operator, der als Operanden etwas vom Typ MathVector oder etwas<br />
Kompatibles nimmt.<br />
Solche Betrachtungen des Compilers gelten natürlich für alle Operatoren,<br />
die <strong>in</strong> C ++ def<strong>in</strong>iert s<strong>in</strong>d, also auch Index- und Typumwandlungs-Operatoren<br />
und z.B. sogar für die Operatoren new und delete.<br />
Der Operator ist <strong>in</strong> allen Fällen für den Compiler irgendetwas Aufrufbares,<br />
das die Aufgabe schon erledigen wird, also ausführbarer Code, der die<br />
Implementation des Operators repräsentiert.<br />
Wenn wir nun noch weiter <strong>in</strong> Betracht ziehen, dass der Compiler Ausdrücke<br />
von l<strong>in</strong>ks nach rechts auswertet, dann kann man auch e<strong>in</strong>en s<strong>in</strong>nvollen<br />
Platz def<strong>in</strong>ieren, an dem der Compiler diese Def<strong>in</strong>ition sucht:<br />
• Bei b<strong>in</strong>ären Operatoren <strong>in</strong> der Deklaration der Klasse, der der l<strong>in</strong>ke Operand<br />
angehört.<br />
• Bei unären Operatoren <strong>in</strong> der Klasse, der der (e<strong>in</strong>zige) Operand angehört.<br />
Bei e<strong>in</strong>em unären M<strong>in</strong>us also ist dies der Operand rechts neben dem M<strong>in</strong>us,<br />
beim Index Operator ist es der Operand l<strong>in</strong>ks davon.<br />
Allgeme<strong>in</strong> gesprochen sucht der Compiler immer <strong>in</strong> der Klasse des Operanden<br />
nach der Operator-Def<strong>in</strong>ition, auf die sich der Operator bezieht. Im Falle,
12.1 Grundpr<strong>in</strong>zipien des Operator Overload<strong>in</strong>gs 337<br />
dass sich e<strong>in</strong> Operator auf zwei Operanden bezieht, also bei allen b<strong>in</strong>ären<br />
Operatoren, sucht der Compiler <strong>in</strong> der Klasse des l<strong>in</strong>ken Operanden. Ich<br />
schicke gleich voraus, dass dies nur e<strong>in</strong> Teil der Wahrheit ist, den Rest werden<br />
wir <strong>in</strong> Kürze noch besprechen. Für den Augenblick reicht diese Betrachtung<br />
e<strong>in</strong>mal aus.<br />
Es ist also für unsere Beispiele nur notwendig, dass wir <strong>in</strong> der Klasse<br />
MathVector die geforderten Operatoren als Members deklarieren und def<strong>in</strong>ieren<br />
und schon funktioniert das Spielchen. Die Deklaration e<strong>in</strong>er MathVector<br />
Klasse, die die oben angesprochenen Forderungen erfüllt und entsprechende<br />
Operatoren implementiert, sieht dann am Beispiel folgendermaßen aus<br />
(math_vector_v1.h):<br />
1 // math vector v1 . h − a simple mathematical vector c l a s s as a<br />
2 // demo f o r operator overload<strong>in</strong>g<br />
3<br />
4 #ifndef math vector v1 h<br />
5 #def<strong>in</strong>e math vector v1 h<br />
6<br />
7 #<strong>in</strong>clude <br />
8 #<strong>in</strong>clude <br />
9 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
10<br />
11 us<strong>in</strong>g std : : <strong>in</strong>valid argument ;<br />
12 us<strong>in</strong>g std : : b a d a l l o c ;<br />
13 us<strong>in</strong>g std : : r a n g e e r r o r ;<br />
14<br />
15 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
16 /∗<br />
17 ∗ MathVector<br />
18 ∗<br />
19 ∗ A simple mathematical vector c l a s s<br />
20 ∗<br />
21 ∗/<br />
22<br />
23 class MathVector<br />
24 {<br />
25 protected :<br />
26 static u<strong>in</strong>t32 c u r r e n t i d ;<br />
27<br />
28 u<strong>in</strong>t32 i n t e r n a l i d ;<br />
29 u<strong>in</strong>t32 num elements ;<br />
30 double ∗ elements ;<br />
31<br />
32 virtual void performAddOperation ( const MathVector &vec to add )<br />
33 throw ( ) ;<br />
34<br />
35 virtual void performSubtractOperation (<br />
36 const MathVector & v e c t o s u b t r a c t )<br />
37 throw ( ) ;<br />
38 public :<br />
39 MathVector ( u<strong>in</strong>t32 num elements )<br />
40 throw( <strong>in</strong>valid argument , b a d a l l o c ) ;<br />
41<br />
42 MathVector ( const MathVector &vec )<br />
43 throw( <strong>in</strong>valid argument , b a d a l l o c ) ;<br />
44<br />
45 virtual ˜ MathVector ( )<br />
46 throw ( ) ;<br />
47<br />
48 virtual double &operator [ ] ( u<strong>in</strong>t32 <strong>in</strong>dex ) const
338 12. Operator Overload<strong>in</strong>g<br />
49 throw( r a n g e e r r o r ) ;<br />
50<br />
51 virtual MathVector operator + ( const MathVector &vec to add ) const<br />
52 throw( <strong>in</strong>valid argument , b a d a l l o c ) ;<br />
53<br />
54 virtual MathVector &operator + ( ) const<br />
55 throw ( ) ;<br />
56<br />
57 virtual MathVector &operator += (const MathVector &vec to add )<br />
58 throw( <strong>in</strong>valid argument ) ;<br />
59<br />
60 virtual MathVector operator − ( const MathVector & v e c t o s u b t r a c t ) const<br />
61 throw( <strong>in</strong>valid argument , b a d a l l o c ) ;<br />
62<br />
63 virtual MathVector operator − ( ) const<br />
64 throw ( ) ;<br />
65<br />
66 virtual MathVector &operator −= (const MathVector & v e c t o s u b t r a c t )<br />
67 throw( <strong>in</strong>valid argument ) ;<br />
68<br />
69 virtual MathVector operator ∗ ( double num to multiply by ) const<br />
70 throw( b a d a l l o c ) ;<br />
71<br />
72 virtual MathVector &operator ∗= (double num to multiply by )<br />
73 throw ( ) ;<br />
74<br />
75 virtual MathVector &operator = ( const MathVector & v e c t o a s s i g n )<br />
76 throw( <strong>in</strong>valid argument ) ;<br />
77<br />
78 virtual bool operator == (const MathVector &vec to compare with ) const<br />
79 throw ( ) ;<br />
80<br />
81 virtual bool operator ! = ( const MathVector &vec to compare with ) const<br />
82 throw ( ) ;<br />
83 } ;<br />
84<br />
85<br />
86 #endif // math vector v1 h<br />
In den Zeilen 48–82 s<strong>in</strong>d die e<strong>in</strong>zelnen Operatoren deklariert, die von dieser<br />
Version des Vektors unterstützt werden. Wie sich leicht erkennen lässt, funktioniert<br />
die Deklaration von Operatoren genau gleich wie die Deklaration von<br />
Methoden, nur dass statt des Methodennamens das Keyword operator, gefolgt<br />
vom gewünschten Operator geschrieben wird. Natürlich gelten auch für<br />
Operatoren genau dieselben Regeln für Overload<strong>in</strong>g, Overrid<strong>in</strong>g, static und<br />
dynamic B<strong>in</strong>d<strong>in</strong>g, wie wir sie bereits bei Methoden kennen gelernt haben.<br />
Gehen wir die Operatoren also e<strong>in</strong>mal der Reihe nach durch um zu sehen,<br />
wann und wie sie vom Compiler e<strong>in</strong>gesetzt werden:<br />
• In den Zeilen 48–49 ist der Index-Operator deklariert. Dieser nimmt e<strong>in</strong>en<br />
u<strong>in</strong>t32 als Parameter und liefert e<strong>in</strong>e Reference auf e<strong>in</strong>en double. Im Fall,<br />
dass der Index ungültig ist, wird e<strong>in</strong> range_error geworfen. Ich habe mich<br />
hier bewusst für die Verwendung der vordef<strong>in</strong>ierten Exceptions aus der<br />
Standard-Library entschieden, da diese Exceptions allen C ++ Entwicklern<br />
geläufig s<strong>in</strong>d (obwohl ich z.B. den Namen range_error nicht so toll f<strong>in</strong>de).<br />
Wenn der Compiler also irgendwo im Code z.B. das Statement<br />
vector1[5]
12.1 Grundpr<strong>in</strong>zipien des Operator Overload<strong>in</strong>gs 339<br />
f<strong>in</strong>det, dann wird er, unter der Voraussetzung, dass vector1 vom Typ<br />
MathVector ist (was ab jetzt vorausgesetzt wird), die Implementation dieses<br />
Operators mit 5 als Parameter aufrufen. Die Implementation hat dann<br />
die Aufgabe, e<strong>in</strong>e Referenz auf das Element mit Index 5 zu liefern oder e<strong>in</strong>e<br />
Exception zu werfen, falls der vector1 z.B. nur für 3 Elemente angelegt<br />
wurde.<br />
Für alle Leser, die sich nun fragen, warum hier e<strong>in</strong>e Referenz geliefert wird<br />
und nicht e<strong>in</strong>fach nur e<strong>in</strong> double, soll folgendes Beispiel Klarheit schaffen:<br />
Mit dem Statement<br />
vector1[5] = 17.5;<br />
will man diesem Element ja e<strong>in</strong>en Wert zuweisen. Und das geht nur, wenn<br />
man mit Referenzen arbeitet. Wie sollte man sonst auch auf die richtige<br />
Speicherstelle zugreifen?<br />
Vorsicht Falle: Das <strong>in</strong> Abschnitt 9.4.6 besprochene Overload<strong>in</strong>g für<br />
const und non-const Methoden gilt natürlich <strong>in</strong> derselben Form für Operatoren.<br />
Viele C ++ Neul<strong>in</strong>ge glauben allerd<strong>in</strong>gs, dass sie hiermit zwei verschiedene<br />
Index-Operatoren so def<strong>in</strong>ieren könnten, dass die const Variante<br />
nur zum Lesen dient und die non-const Variante auch zum Schreiben. Der<br />
Compiler kann aber gar nicht wissen, was man mit dem Ergebnis des Operators<br />
machen will, woher denn auch? Die e<strong>in</strong>zige Basis, auf der er se<strong>in</strong>e<br />
Entscheidung trifft, ist die Konstantheit des Objekts selbst, auf das der<br />
Operator angewandt wird. Ist dieses konstant, so wird die const Variante<br />
des Operators aufgerufen, sonst nicht (alles natürlich genau den Regeln<br />
aus Abschnitt 9.4.6 entsprechend).<br />
• In den Zeilen 51–52 ist der Additions-Operator deklariert, der zwei Vektoren<br />
addieren kann. Das Ergebnis dieser Operation ist e<strong>in</strong> neuer Vektor,<br />
denn man will die beiden Operanden natürlich nicht verändern. Aus diesem<br />
Grund wird auch e<strong>in</strong> Objekt vom Typ MathVector als return Wert<br />
geliefert und ke<strong>in</strong>e Referenz auf e<strong>in</strong>en Vektor. Das Statement<br />
vector1 + vector2<br />
wird also folgendermaßen vom Compiler aufgelöst: Es wird der Additions-<br />
Operator von vector1 mit vector2 als Parameter aufgerufen. Dieser muss<br />
e<strong>in</strong> Objekt liefern, das durch die Durchführung der Addition entsteht.<br />
Noch etwas läßt sich hier leicht erkennen: Das Resultat der Operation ist<br />
e<strong>in</strong> neues Objekt. Dieses wird allerd<strong>in</strong>gs nur temporär gebraucht und darf<br />
nicht “für ewig” im Speicher liegen bleiben. Wir haben es also hier wieder<br />
mit e<strong>in</strong>em temporären Objekt zu tun, das nach Abschluss der Expression<br />
verworfen wird. Bei der Implementation dieses Operators werden wir uns<br />
dieser Eigenschaft dann noch näher zuwenden.<br />
• In den Zeilen 54-55 ist der unäre Plus-Operator def<strong>in</strong>iert. Dieser ist daran<br />
zu erkennen, dass er ke<strong>in</strong>en Parameter nimmt, weil er ja unär ist :-).<br />
Der Grund, warum hier e<strong>in</strong>e Referenz als return Wert geliefert wird, ist<br />
<strong>in</strong> Performancebetrachtungen zu f<strong>in</strong>den: Wozu soll man e<strong>in</strong> neues Objekt
340 12. Operator Overload<strong>in</strong>g<br />
erzeugen, das ja nur temporär ist und danach sowieso wieder weggeworfen<br />
wird? Das unäre Plus bewirkt ja überhaupt ke<strong>in</strong>e Änderung am Inhalt des<br />
Vektors. Also kann die Implementation gleich e<strong>in</strong>e Referenz auf sich selbst<br />
liefern um unnötiges Erzeugen und wieder Wegwerfen zu verh<strong>in</strong>dern. Das<br />
Statement<br />
+vector1<br />
wird also vom Compiler der Behandlung durch diesen Operator zugeführt.<br />
• In den Zeilen 57–58 f<strong>in</strong>det sich der Additions-Kurzzuweisungs-Operator.<br />
E<strong>in</strong>e Anwendung dieses Operators bewirkt natürlich e<strong>in</strong>e Veränderung des<br />
Inhalts des Objekts. Deshalb kann auch gleich e<strong>in</strong>e Referenz auf das<br />
geänderte Objekt als return-Wert geliefert werden. F<strong>in</strong>det der Compiler<br />
also irgendwo im Code das Statement<br />
vector1 += vector2;<br />
dann wird dieser Operator auf vector1 mit vector2 als Parameter aufgerufen.<br />
Lesern, die sich nun fragen, warum dieser Operator überhaupt<br />
e<strong>in</strong>en return-Wert liefert, möchte ich folgendes Beispiel zeigen, das absolut<br />
regulär ist:<br />
vector3 = vector1 += vector2;<br />
Hier wird zuerst e<strong>in</strong>e Additions-Kurzzuweisung von vector2 auf vector1<br />
gemacht und das Ergebnis dieser Operation wird dann vector3 zugewiesen<br />
(siehe auch Zuweisungs-Operator weiter unten). Wäre der Operator als<br />
void Operator def<strong>in</strong>iert (was theoretisch möglich ist), dann wäre e<strong>in</strong> solches<br />
Statement hierdurch mit Erfolg verh<strong>in</strong>dert worden und der Compiler würde<br />
sich beschweren.<br />
• In den Zeilen 60–67 f<strong>in</strong>den sich die verschiedenen M<strong>in</strong>us-Operatoren. Diese<br />
funktionieren analog zu den soeben besprochenen Plus-Operatoren, allerd<strong>in</strong>gs<br />
mit e<strong>in</strong>er Ausnahme: Das unäre M<strong>in</strong>us liefert als return-Wert ke<strong>in</strong>e<br />
Referenz, sondern e<strong>in</strong> Objekt. Muss es auch, denn hier wird ja der entsprechende<br />
negative Vektor erwartet, allerd<strong>in</strong>gs ohne das Orig<strong>in</strong>al zu ändern.<br />
• In den Zeilen 69–73 s<strong>in</strong>d die entsprechenden Multiplikations-Operatoren<br />
deklariert. Ich denke, zu diesen kann ich mir die Erklärung jetzt auch<br />
ersparen, denn sie funktionieren praktisch analog zu den Additions- und<br />
Subtraktionsoperatoren. Nur nehmen sie als zweites Argument eben ke<strong>in</strong>en<br />
MathVector, sondern e<strong>in</strong>en double Wert. Wer Lust hat, kann ja auch die<br />
Multiplikation von zwei Vektoren (also das sog. Kreuzprodukt) implementieren.<br />
Dies geschieht durch e<strong>in</strong> e<strong>in</strong>faches Overload<strong>in</strong>g des entsprechenden<br />
Multiplikations-Operators durch e<strong>in</strong>en, der als Parameter e<strong>in</strong> Element vom<br />
Typ MathVector nimmt.<br />
• In den Zeilen 75–76 ist der Zuweisungs-Operator def<strong>in</strong>iert. Wie dieser<br />
funktioniert, lässt sich leicht vorstellen. Dass auch dieser Operator e<strong>in</strong>en<br />
return-Wert liefert ist ebenso logisch, denn Statements wie<br />
vector1 = vector2 = vector3;<br />
sollen natürlich auch unterstützt werden.
12.1 Grundpr<strong>in</strong>zipien des Operator Overload<strong>in</strong>gs 341<br />
• In den Zeilen 78–82 s<strong>in</strong>d die logischen Vergleichsoperatoren deklariert.<br />
Auch hier denke ich mir, dass e<strong>in</strong>e genaue Erklärung überflüssig ist.<br />
Da die Implementation relativ lang ist, führen wir uns diese häppchenweise<br />
zu Gemüte. Gleich vorausschicken möchte ich, dass aus Gründen der Lesbarkeit<br />
bei der Implementation nicht das letzte Bisschen möglicher Performance<br />
herausgequetscht wird. Vielmehr soll der hier abgedruckte Code übersichtlich<br />
se<strong>in</strong>. Beg<strong>in</strong>nen wir die Betrachtungen am besten bei den beiden Konstruktoren<br />
und dem Destruktor (math_vector_v1.cpp):<br />
11 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
12 /∗ standard constructor<br />
13 ∗/<br />
14 MathVector : : MathVector ( u<strong>in</strong>t32 num elements )<br />
15 throw( <strong>in</strong>valid argument , b a d a l l o c )<br />
16 {<br />
17 i n t e r n a l i d = c u r r e n t i d ++;<br />
18 cout
342 12. Operator Overload<strong>in</strong>g<br />
In den Zeilen 17–19 sowie <strong>in</strong> den Zeilen 35–38, sieht man, dass jede Instanz<br />
e<strong>in</strong>es Vektors e<strong>in</strong>e eigene <strong>in</strong>terne Identifikation bekommt und e<strong>in</strong> Output erzeugt<br />
wird, der es erlaubt, am Bildschirm zu sehen, was gerade passiert. Dies<br />
wurde deshalb e<strong>in</strong>gebaut, um sehen zu können, wann temporäre Objekte erzeugt<br />
und wieder verworfen werden. Dadurch lässt sich am Output leicht<br />
erkennen, welcher Schritt e<strong>in</strong>er Auflösung von Ausdrücken gerade abgearbeitet<br />
wird.<br />
In den Zeilen 21–23 und 40–42 ist zu erkennen, dass der Versuch der<br />
Erzeugung e<strong>in</strong>es Vektors mit 0 Elementen nicht erlaubt ist und mit e<strong>in</strong>er<br />
entsprechenden Exception quittiert wird. Die Abfrage auf die Anzahl der<br />
Elemente ist natürlich auch im Copy-Constructor notwendig, denn wer h<strong>in</strong>dert<br />
jemanden daran, zu versuchen, von e<strong>in</strong>em “halbtoten” Objekt (siehe<br />
Kapitel 11) e<strong>in</strong>e Kopie anzulegen? Dass beim Werfen von Exceptions aus<br />
dem Konstruktor natürlich im H<strong>in</strong>terkopf behalten werden muss, dass dadurch<br />
ja niemals wieder der Destruktor dieses Objekts aufgerufen wird (siehe<br />
Kapitel 11), versteht sich von selbst. E<strong>in</strong>e Frage stellt sich allerd<strong>in</strong>gs noch:<br />
Es wird e<strong>in</strong>e bad_alloc Exception deklariert, aber nirgends im Code wird<br />
tatsächlich e<strong>in</strong>e geworfen. Was soll das nun wieder? Ganz e<strong>in</strong>fach: Man<br />
deklariert nicht nur Exceptions, die man selbst wirft, sondern natürlich auch<br />
solche, die geworfen werden können und die man nicht selbst behandelt. Diese<br />
fallen dadurch praktisch weiter nach oben durch. Genau das passiert bei<br />
new: Wenn nicht mehr genügend Speicher vorhanden ist, um den Request zu<br />
erfüllen, dann wird e<strong>in</strong>e bad_alloc Exception geworfen. Unser Vektor kann<br />
weder dagegen etwas tun, noch weiß er, wie mit e<strong>in</strong>em solchen Fehler umgegangen<br />
werden könnte, also überlassen wir das dem aufrufenden Codeteil.<br />
Der Destruktor <strong>in</strong> den Zeilen 54–60 bietet erwartungsgemäß ke<strong>in</strong>e besonderen<br />
Überraschungen. Natürlich wird auch hier entsprechender Output erzeugt,<br />
um die Lifetime aller Objekte am Bildschirm nachverfolgen zu können.<br />
Sehen wir uns noch schnell die Implementation des Index-Operators an,<br />
bevor wir zum ersten Demoprogramm schreiten, das unser Meisterwerk verwendet:<br />
65 double &MathVector : : operator [ ] ( u<strong>in</strong>t32 <strong>in</strong>dex ) const<br />
66 throw( r a n g e e r r o r )<br />
67 {<br />
68 cout
12.1 Grundpr<strong>in</strong>zipien des Operator Overload<strong>in</strong>gs 343<br />
1 // math vector v1 test1 . cpp − f i r s t t e s t program f o r math v e c t o r s<br />
2<br />
3 #<strong>in</strong>clude <br />
4<br />
5 #<strong>in</strong>clude ” math vector v1 . h”<br />
6<br />
7 us<strong>in</strong>g std : : cout ;<br />
8 us<strong>in</strong>g std : : c e r r ;<br />
9 us<strong>in</strong>g std : : endl ;<br />
10<br />
11 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
12 {<br />
13 try<br />
14 {<br />
15 MathVector vector1 ( 3 ) ;<br />
16 MathVector vector2 ( 3 ) ;<br />
17 MathVector vector3 ( 3 ) ;<br />
18<br />
19 u<strong>in</strong>t32 <strong>in</strong>dex = 3;<br />
20 while ( <strong>in</strong>dex−−)<br />
21 vector1 [ <strong>in</strong>dex ] = vector2 [ <strong>in</strong>dex ] = vector3 [ <strong>in</strong>dex ] = <strong>in</strong>dex ;<br />
22<br />
23 cout
344 12. Operator Overload<strong>in</strong>g<br />
5 MathVector , id = 1 : operator [ ]<br />
6 MathVector , id = 2 : operator [ ]<br />
7 MathVector , id = 0 : operator [ ]<br />
8 MathVector , id = 1 : operator [ ]<br />
9 MathVector , id = 2 : operator [ ]<br />
10 MathVector , id = 0 : operator [ ]<br />
11 MathVector , id = 1 : operator [ ]<br />
12 MathVector , id = 2 : operator [ ]<br />
13 Vectors i n i t i a l i z e d to :<br />
14 vector1 : MathVector , id = 0 : operator [ ]<br />
15 0 MathVector , id = 0 : operator [ ]<br />
16 1 MathVector , id = 0 : operator [ ]<br />
17 2<br />
18 vector2 : MathVector , id = 1 : operator [ ]<br />
19 0 MathVector , id = 1 : operator [ ]<br />
20 1 MathVector , id = 1 : operator [ ]<br />
21 2<br />
22 vector3 : MathVector , id = 2 : operator [ ]<br />
23 0 MathVector , id = 2 : operator [ ]<br />
24 1 MathVector , id = 2 : operator [ ]<br />
25 2<br />
26 MathVector , id = 2 : d e s t r u c t o r<br />
27 MathVector , id = 1 : d e s t r u c t o r<br />
28 MathVector , id = 0 : d e s t r u c t o r<br />
Im Testprogramm <strong>in</strong> den Zeilen 15–17 werden drei Vektoren erzeugt, jeder<br />
von ihnen kann drei Elemente halten. Am Output erkennen wir dies <strong>in</strong> den<br />
Zeilen 1–3 und sehen, dass vector1 die Id 0, vector2 die Id 1 und vector3<br />
die Id 2 bekommen hat.<br />
In den Zeilen 20–21 des Testprogramms werden den e<strong>in</strong>zelnen Elementen<br />
der e<strong>in</strong>zelnen Vektoren Werte zugewiesen. Hierbei kommt erwartungsgemäß<br />
unser selbstdef<strong>in</strong>ierter Index-Operator zum Tragen, wie sich am Output <strong>in</strong><br />
den Zeilen 4–12 zeigt.<br />
Die Zeilen 23–35 demonstrieren nur noch, dass der Index-Operator auch<br />
wirklich funktioniert hat, <strong>in</strong>dem die e<strong>in</strong>zelnen Elemente der e<strong>in</strong>zelnen Vektoren<br />
ausgegeben werden. Im Output vermischt sich dadurch die Anzeige<br />
des Operator-Aufrufs und des Ergebnisses. Die Zeilen 14–17 des Outputs<br />
zeigen für vector1, dass immer zuerst unser Index-Operator aufgerufen<br />
wird, der den entsprechenden double Wert liefert, der wiederum ausgegeben<br />
wird. Selbiges gilt für die Zeilen 18–21 (=vector2) und für die Zeilen 22–25<br />
(=vector3). Da sich nach Zeile 35 die Lifetime unserer Vektoren ihrem Ende<br />
zuneigt, sieht man im Output <strong>in</strong> den Zeilen 26–28, wie sie ordnungsgemäß<br />
destruiert werden.<br />
In den Zeilen 40, 45 und 50 sieht man, wie man bei den Standard-<br />
Exceptions zum Text kommt, der ihnen beim Konstruieren übergeben wurde:<br />
Man ruft e<strong>in</strong>fach die Methode what auf.<br />
Lesern, die sich mit diesem Testprogramm spielen wollen, möchte ich<br />
natürlich nicht vorenthalten, dass das Makefile dazu unter dem Namen<br />
MathVectorV1Test1Makefile auf der beiliegenden CD-ROM zu f<strong>in</strong>den ist.<br />
Nach diesem E<strong>in</strong>stieg <strong>in</strong> die Welt der Operatoren über den Index-Operator<br />
wird es Zeit, dass wir zu den <strong>in</strong>teressanteren Vertretern der Operatoren-<br />
Spezies kommen. Nehmen wir also die verschiedenen M<strong>in</strong>us-Operatoren e<strong>in</strong>mal<br />
e<strong>in</strong> wenig genauer unter die Lupe:
12.1 Grundpr<strong>in</strong>zipien des Operator Overload<strong>in</strong>gs 345<br />
119 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
120 /∗<br />
121 ∗/<br />
122 MathVector MathVector : : operator − (<br />
123 const MathVector & v e c t o s u b t r a c t ) const<br />
124 throw( <strong>in</strong>valid argument , b a d a l l o c )<br />
125 {<br />
126 cout
346 12. Operator Overload<strong>in</strong>g<br />
In den Zeilen 122–135 ist der “normale” Subtraktions-Operator def<strong>in</strong>iert.<br />
Dass vor der Subtraktion e<strong>in</strong>e Überprüfung stattf<strong>in</strong>det, ob die beiden Vektoren<br />
dieselbe Größe haben, ist nicht weiter neu. Genauso wenig Neuigkeiten<br />
bietet die Exception, die geworfen wird, falls ebendiese Größe nicht übere<strong>in</strong>stimmt.<br />
Auch die Deklaration der bad_alloc Exception ist klar, denn<br />
<strong>in</strong> Zeile 132 kann es ja passieren, dass beim Anlegen des Resultat Vektors<br />
die Speicheranforderung schief geht. Weil es ke<strong>in</strong>e s<strong>in</strong>nvolle Möglichkeit gibt,<br />
dieses Problem <strong>in</strong>nerhalb unseres Operators zu beheben, lassen wir die Exception<br />
nach oben durchfallen.<br />
In Zeile 132 kommt allerd<strong>in</strong>gs jetzt etwas vor, das bisher noch unbekannt<br />
ist: this. Das Keyword this steht für den Po<strong>in</strong>ter auf sich selbst,<br />
auch self Po<strong>in</strong>ter oder this Po<strong>in</strong>ter genannt. Dadurch haben alle Objekte<br />
die Möglichkeit <strong>in</strong>nerhalb ihrer Methoden auf die Adresse ihrer eigenen Instanz<br />
zuzugreifen. Natürlich gilt das nur für Methoden, die nicht static<br />
s<strong>in</strong>d, denn diese gehören ja zur Klasse und nicht zu e<strong>in</strong>er Instanz. Zeile 132<br />
legt also e<strong>in</strong>e Kopie der aktuellen Instanz an, auf der der Operator aufgerufen<br />
wurde. Von dieser Kopie wird dann <strong>in</strong> Zeile 133 durch den Aufruf von<br />
performSubtractOperation der andere Vektor abgezogen. Das daraus entstehende<br />
Resultat wird als return-Wert geliefert. Genau <strong>in</strong> Zeile 134 sieht<br />
man, wann der Block, der e<strong>in</strong>en Funktionsrumpf umschließt, als geschlossen<br />
betrachtet wird und damit die Lifetime von Variablen <strong>in</strong> diesem zu Ende<br />
ist: Erst, wenn return abgeschlossen und das Resultat “verarbeitet” wurde.<br />
Ansonsten müsste ja direkt beim Rücksprung bereits result destruiert werden,<br />
bloß dann würde man e<strong>in</strong> totes Objekt retournieren. Das wäre natürlich<br />
nicht im S<strong>in</strong>ne des Erf<strong>in</strong>ders.<br />
Die Methode performSubtractOperation wurde bewusst aus dem Operator<br />
herausgezogen, da sie auch beim Kurzzuweisungs-Subtraktionsoperator<br />
gebraucht wird. Oft wird dies anders modelliert und e<strong>in</strong> Operator verwendet<br />
e<strong>in</strong>fach e<strong>in</strong>en anderen Operator, z.B. - legt e<strong>in</strong> result Objekt an und wendet<br />
auf dieses dann den -= Operator an, bevor es retourniert wird. Davor<br />
möchte ich allerd<strong>in</strong>gs abraten, denn damit kommt es fast garantiert entweder<br />
zu doppelten Überprüfungen e<strong>in</strong>- und derselben E<strong>in</strong>schränkungen, oder es<br />
wird unnötigerweise e<strong>in</strong> Objekt angelegt, das im Fall e<strong>in</strong>er Exception wieder<br />
verworfen wird oder andere unnötige D<strong>in</strong>ge passieren. Als Grundregel möchte<br />
ich für solche Fälle sagen, dass es immer besser ist, e<strong>in</strong>e public Methode bzw.<br />
e<strong>in</strong>en public Operator so zu schreiben, dass alle Überprüfungen dar<strong>in</strong> vorgenommen<br />
werden und danach wird e<strong>in</strong>e <strong>in</strong>terne Methode aufgerufen, die nur<br />
noch die Operation ohne weitere Überprüfungen ausführt.<br />
Die Implementation des Subtraktions-Kurzzuweisungs-Operators <strong>in</strong> den<br />
Zeilen 156–168 ist im Pr<strong>in</strong>zip dieselbe, wie die des “normalen” Subtraktions-<br />
Operators. Der e<strong>in</strong>zige Unterschied besteht dar<strong>in</strong>, dass ke<strong>in</strong> result Objekt<br />
angelegt wird, sondern das Objekt selbst, auf dem der Operator aufgerufen<br />
wurde, verändert wird.
12.1 Grundpr<strong>in</strong>zipien des Operator Overload<strong>in</strong>gs 347<br />
Die Implementation des unären M<strong>in</strong>us-Operators <strong>in</strong> den Zeilen 140–151 ist<br />
straightforward: Es wird e<strong>in</strong> result Objekt angelegt, <strong>in</strong> dem jedes e<strong>in</strong>zelne<br />
Element durch se<strong>in</strong> negatives Pendant ersetzt wird. Dies geschieht <strong>in</strong> der<br />
Schleife <strong>in</strong> den Zeilen 148–149.<br />
Die Additions-Operatoren s<strong>in</strong>d analog zu den Subtraktions-Operatoren<br />
implementiert. Aus diesem Grund erspare ich den Lesern hier das Abdrucken<br />
derselben. Wenden wir uns nun also lieber unseren Multiplikations-<br />
Operatoren zu:<br />
173 MathVector MathVector : : operator ∗ ( double num to multiply by ) const<br />
174 throw( b a d a l l o c )<br />
175 {<br />
176 cout
348 12. Operator Overload<strong>in</strong>g<br />
216 double ∗ dst = elements ;<br />
217 while ( count−−)<br />
218 ∗ dst++ = ∗s r c++;<br />
219 return (∗ this ) ;<br />
220 }<br />
Nur um e<strong>in</strong> kle<strong>in</strong>es bisschen <strong>in</strong>teressanter s<strong>in</strong>d die beiden Vergleichsoperatoren,<br />
denn dort sche<strong>in</strong>t sich e<strong>in</strong> Widerspruch zur oben erwähnten Grundregel<br />
mit <strong>in</strong>ternen Methoden und public Operatoren und deren Verwendung bzw.<br />
Durchmischung e<strong>in</strong>geschlichen zu haben:<br />
222 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
223 /∗<br />
224 ∗/<br />
225 bool MathVector : : operator == (<br />
226 const MathVector &vec to compare with ) const<br />
227 throw( )<br />
228 {<br />
229 i f ( vec to compare with . num elements ! = num elements )<br />
230 return ( false ) ;<br />
231 u<strong>in</strong>t32 count = num elements ;<br />
232 double ∗ s r c = vec to compare with . elements ;<br />
233 double ∗ dst = elements ;<br />
234 while ( count−−)<br />
235 {<br />
236 i f ( ∗ dst ++ != ∗ s r c++)<br />
237 return ( false ) ;<br />
238 }<br />
239 return ( true ) ;<br />
240 }<br />
241<br />
242 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
243 /∗<br />
244 ∗/<br />
245 bool MathVector : : operator ! = (<br />
246 const MathVector &vec to compare with ) const<br />
247 throw( )<br />
248 {<br />
249 return ( ! ( ∗ this == vec to compare with ) ) ;<br />
250 }<br />
In Zeile 249 wird jetzt plötzlich der == Operator auch <strong>in</strong>tern verwendet, obwohl<br />
ich geme<strong>in</strong>t habe, dass dies verpönt sei. Der Grund dafür ist e<strong>in</strong>fach: In<br />
der Implementation des != Operators wird wirklich absolut nichts gemacht,<br />
als das Ergebnis des == Operators zu negieren. Es f<strong>in</strong>den ke<strong>in</strong>e Überprüfungen<br />
statt und es gibt auch sonst ke<strong>in</strong>en Code <strong>in</strong> dieser Methode. Aus diesem<br />
Grund ist der Regelbruch hier zulässig. Im Normalfall würde man diesen<br />
Operator außerdem aufgrund se<strong>in</strong>er Kürze als <strong>in</strong>l<strong>in</strong>e implementieren.<br />
Um uns nun noch e<strong>in</strong> kurzes Bild von der Verwendung der Operatoren<br />
und den <strong>in</strong>ternen Vorgängen machen zu können, werfen wir e<strong>in</strong>en Blick auf<br />
das folgende Testprogramm (math_vector_v1_test2.cpp):
12.1 Grundpr<strong>in</strong>zipien des Operator Overload<strong>in</strong>gs 349<br />
1 // math vector v1 test2 . cpp − another t e s t program f o r math v e c t o r s<br />
2<br />
3 #<strong>in</strong>clude <br />
4<br />
5 #<strong>in</strong>clude ” math vector v1 . h”<br />
6<br />
7 us<strong>in</strong>g std : : cout ;<br />
8 us<strong>in</strong>g std : : c e r r ;<br />
9 us<strong>in</strong>g std : : endl ;<br />
10<br />
11 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
12 {<br />
13 try<br />
14 {<br />
15 MathVector vector1 ( 3 ) ;<br />
16 MathVector vector2 ( 3 ) ;<br />
17 MathVector vector3 ( 3 ) ;<br />
18<br />
19 u<strong>in</strong>t32 <strong>in</strong>dex = 3;<br />
20 while ( <strong>in</strong>dex−−)<br />
21 vector2 [ <strong>in</strong>dex ] = vector3 [ <strong>in</strong>dex ] = <strong>in</strong>dex ;<br />
22<br />
23 // here the copy constructor i s c a l l e d !<br />
24 MathVector vector4 = vector3 ;<br />
25<br />
26 vector1 = ( vector2 ∗ 2 ) + ( vector3 ∗ 2 ) ;<br />
27 vector4 −= vector3 ;<br />
28 }<br />
29 catch ( <strong>in</strong>valid argument &exc )<br />
30 {<br />
31 c e r r
350 12. Operator Overload<strong>in</strong>g<br />
15 MathVector , id = 4 : operator + , other id = 5<br />
16 MathVector , id = 6 : copy constructor , other id = 4<br />
17 MathVector , id = 0 : operator = , other id = 6<br />
18 MathVector , id = 6 : d e s t r u c t o r<br />
19 MathVector , id = 5 : d e s t r u c t o r<br />
20 MathVector , id = 4 : d e s t r u c t o r<br />
21 MathVector , id = 3 : operator −= , other id = 2<br />
22 MathVector , id = 3 : d e s t r u c t o r<br />
23 MathVector , id = 2 : d e s t r u c t o r<br />
24 MathVector , id = 1 : d e s t r u c t o r<br />
25 MathVector , id = 0 : d e s t r u c t o r<br />
Die Zeilen 15–17 des Testprogramms erzeugen wie gehabt die Zeilen 1–3 im<br />
Output. Die Zuweisungs-Orgie <strong>in</strong> den Zeilen 20–21 f<strong>in</strong>det sich im Output<br />
<strong>in</strong> den Zeilen 4–9 wieder. In Zeile 24 des Programms zeigt sich erneut, was<br />
bereits <strong>in</strong> Abschnitt 9.2.3 erwähnt wurde: Die Initialisierung e<strong>in</strong>er Variable<br />
ist def<strong>in</strong>itiv nicht dasselbe, wie e<strong>in</strong>e Zuweisung e<strong>in</strong>es Wertes an e<strong>in</strong>e Variable.<br />
Sobald e<strong>in</strong>e Instanz e<strong>in</strong>er Klasse erzeugt wird und <strong>in</strong> der Initialisierung e<strong>in</strong>e<br />
“Zuweisung” e<strong>in</strong>es Wertes stattf<strong>in</strong>det, wird vom Compiler nachgesehen, ob<br />
es e<strong>in</strong>en passenden Konstruktor gibt. Wenn ja, wird dieser verwendet, wie<br />
es bei uns mit dem Copy-Constructor passiert. Gibt es ke<strong>in</strong>en, der genau<br />
passt, dann wird nach den üblichen Regeln derjenige ausgesucht, der “am<br />
besten” passt. Was nicht passiert, ist, dass zuerst der default Konstruktor<br />
verwendet wird und danach e<strong>in</strong> Aufruf des Zuweisungs-Operators erfolgt!<br />
F<strong>in</strong>det der Compiler ke<strong>in</strong>en passenden Konstruktor, quittiert er dies mit e<strong>in</strong>er<br />
Fehlermeldung!<br />
Das bedeutet, dass die beiden folgenden Codestücke<br />
MyClass var1 = 17;<br />
und<br />
MyClass var1;<br />
var1 = 17;<br />
absolut nicht dasselbe bewirken, obwohl dies oft fälschlich angenommen wird!<br />
Im ersten Fall wird e<strong>in</strong> Konstruktor gesucht, der als Parameter e<strong>in</strong>en <strong>in</strong>t<br />
nehmen kann. Im zweiten Fall wird zuerst das Objekt über den default<br />
Konstruktor konstruiert und danach wird über den Zuweisungsoperator 17<br />
zugewiesen (falls e<strong>in</strong> solcher Operator existiert).<br />
Genau dieser Unterschied ist es auch, warum man immer die Konstruktion<br />
und die damit e<strong>in</strong>hergehende Initialisierung von Members im Initialisierungsteil<br />
des Konstruktors (also nach dem : und vor dem Rumpf) vornehmen<br />
sollte, anstatt erst im Rumpf des Konstruktors e<strong>in</strong>e Zuweisung zu machen.<br />
Natürlich funktioniert das nur, wenn e<strong>in</strong> entsprechender Konstruktor zur<br />
Verfügung steht :-).<br />
Jetzt aber wieder zurück zu unserem Programm: Sehen wir uns an, was<br />
alles passiert, wenn Zeile 26 ausgewertet wird: Im Output schlägt sich diese<br />
Zeile nämlich gleich mit vielen Meldungen zu Buche, die von Zeile 11–20<br />
reichen:<br />
• Zu Beg<strong>in</strong>n wird der Operator * aufgerufen, der vector2 mit 2 multipliziert,<br />
wie <strong>in</strong> Zeile 11 des Outputs zu sehen ist.
12.1 Grundpr<strong>in</strong>zipien des Operator Overload<strong>in</strong>gs 351<br />
• Die Meldung über den Aufruf des Copy-Constructors <strong>in</strong> Zeile 12 des Outputs<br />
stammt von der Implementation dieses Operators, der ja e<strong>in</strong> result<br />
Objekt anlegt.<br />
• Die Zeilen 13–14 geben darüber Auskunft, dass gerade die zweite Multiplikation,<br />
also vector3 mit 2, stattf<strong>in</strong>det.<br />
• Zeile 15 meldet den Aufruf des + Operators auf dem Resultat der ersten<br />
Multiplikation (also dem temporären Objekt). Diese Addition wird erwartungsgemäß<br />
mit dem zweiten temporären Objekt, also dem Resultat aus<br />
der zweiten Multiplikation, durchgeführt.<br />
• In Zeile 16 des Outputs sieht man, dass bei dieser Addition der beiden<br />
temporären Objekte natürlich gemäß der Implementation des + Operators<br />
e<strong>in</strong> weiteres temporäres Objekt erzeugt wird.<br />
• Dieses weitere temporäre Objekt wird dann endlich unserem vector1 zugewiesen,<br />
wie Zeile 17 zeigt.<br />
• In den Zeilen 18–20 sieht man, dass nach Ende der Operation auch alles<br />
wieder brav aufgeräumt wird.<br />
Vergleicht man diese temporäre Objektorgie mit dem Aufruf des Operators<br />
-=, dessen Output <strong>in</strong> Zeile 21 ersche<strong>in</strong>t, dann erkennt man e<strong>in</strong>en “ger<strong>in</strong>gfügigen”<br />
Unterschied: Hierbei wird nämlich ke<strong>in</strong> e<strong>in</strong>ziges temporäres Objekt angelegt<br />
und wieder verworfen! Die Zeilen 22–25 zeigen nur noch das Destruieren<br />
unserer vier Vektoren am Ende ihrer Lifetime.<br />
Vorsicht Falle: Die zuvor demonstrierte Operation, <strong>in</strong> der massig temporäre<br />
Objekte angelegt und wieder zerstört werden, zeigt, dass man wirklich immer<br />
bedenken sollte, wie man gewisse Codezeilen schreibt! Es ist ja nicht nur bei<br />
unserem kle<strong>in</strong>en Demo-Vektor so, dass je nach Operation temporäre Objekte<br />
gebraucht werden. Leider wird von allzu vielen Entwicklern immer noch<br />
die Existenz der Zuweisungs-Kurzformen missachtet und das kann <strong>in</strong> enorm<br />
<strong>in</strong>effizientem Code enden, obwohl sich die Entwickler ke<strong>in</strong>er Schuld bewusst<br />
s<strong>in</strong>d. Allerd<strong>in</strong>gs ist es nun e<strong>in</strong>mal so, dass die folgenden zwei Zeilen<br />
var1 = var1 + var2;<br />
var1 += var2;<br />
ke<strong>in</strong>esfalls dasselbe bedeuten und <strong>in</strong>tern auch zu völlig verschiedenem Verhalten<br />
führen, auch wenn das Ergebnis <strong>in</strong> beiden Fällen gleich aussieht! In<br />
der ersten Zeile wird e<strong>in</strong> temporäres Objekt erzeugt, zugewiesen und wieder<br />
verworfen. In der zweiten Zeile wird e<strong>in</strong>fach die Addition durchgeführt und<br />
das war’s.<br />
Was sieht man hier? Auch bei sche<strong>in</strong>bar belanglosen D<strong>in</strong>gen ist e<strong>in</strong> tieferes<br />
Grundverständnis für den Computer, die verwendete Sprache und allgeme<strong>in</strong>e<br />
Restriktionen absolut unabd<strong>in</strong>gbar um saubere Software zu schreiben!<br />
Ich habe es bereits mehrfach erwähnt, aber aus gutem Grund tue ich es<br />
hier noch e<strong>in</strong>mal: Ich möchte mit den H<strong>in</strong>weisen auf solche Fallen ke<strong>in</strong>esfalls<br />
jemanden verschrecken, ganz im Gegenteil! Ich möchte nur alle Leser darauf<br />
aufmerksam machen, dass ausprobieren, spielen und verstehen gefragt s<strong>in</strong>d!
352 12. Operator Overload<strong>in</strong>g<br />
Je mehr man probiert und analysiert, desto besser wird man gewisse Phänomene<br />
kennen lernen und mit ihnen umgehen lernen. Das ist ja gerade der<br />
Grund, warum Erfahrung <strong>in</strong> der <strong>Softwareentwicklung</strong> so viel wert ist!<br />
E<strong>in</strong> kle<strong>in</strong>er Exkurs: Sieht man sich z.B. die Implementation des Subtraktionsoperators<br />
an, so wird hierbei e<strong>in</strong> Objekt result geliefert. In unserem<br />
Fall zieht dieses der Compiler direkt für die Weiterverarbeitung heran. Je<br />
nach Compiler könnte es <strong>in</strong> diesem Fall auch passieren, dass er naiverweise<br />
e<strong>in</strong>e temporäres Objekt als Kopie von result für die Weiterverarbeitung<br />
anlegt. Darunter würde die Performance dieses Operators im Vergleich zum<br />
entsprechenden Kurzzuweisungs Operator natürlich noch zusätzlich leiden.<br />
Ich würde allen Lesern anraten, entsprechende Tests mit ihrer verwendeten<br />
Entwicklungsumgebung durchzuführen.<br />
Vorsicht Falle: Ich habe es hier beim Vektor nicht implementiert, um nicht<br />
völlige Verwirrung zu stiften, aber e<strong>in</strong>e große Falle tut sich, je nach Implementation,<br />
beim Zuweisungs- und beim Vergleichsoperator auf: Was passiert<br />
bei e<strong>in</strong>er Selbstzuweisung bzw. bei e<strong>in</strong>em Vergleich mit sich selbst? In unserem<br />
Beispiel gibt es hierbei e<strong>in</strong> Problem. Deshalb sollte man <strong>in</strong> diesen Fällen<br />
immer gleich als erste Zeile <strong>in</strong> den entsprechenden Operatoren durch e<strong>in</strong>en<br />
Vergleich des Parameters mit this feststellen, ob man nicht gerade mit sich<br />
selbst <strong>in</strong> e<strong>in</strong>er Operation konfrontiert ist. Ist man hierbei unvorsichtig, dann<br />
können sich daraus sehr <strong>in</strong>teressante Debugg<strong>in</strong>g Sessions ergeben :-).<br />
Alle Leser, die spätestens jetzt bed<strong>in</strong>gt durch Angst (hoffentlich nicht)<br />
oder bed<strong>in</strong>gt durch Neugierde (hoffentlich schon) der Spieltrieb gepackt hat,<br />
f<strong>in</strong>den das Makefile zu diesem Programm unter dem Namen<br />
MathVectorV1Test2Makefile auf der beiliegenden CD-ROM.<br />
E<strong>in</strong> kle<strong>in</strong>eres Problemchen gilt es noch bei unserer Klasse MathVector zu<br />
lösen: Das folgende Statement<br />
2.0 * DEC1<br />
kann vom Compiler <strong>in</strong> der jetzigen Version der Klasse nicht verarbeitet werden.<br />
Nach unseren Regeln, die wir oben def<strong>in</strong>iert haben, müsste er jetzt<br />
nämlich im double nach e<strong>in</strong>em Operator suchen, der diesen mit e<strong>in</strong>em<br />
MathVector multipliziert. Den gibt es natürlich nicht. Genau hier kommt<br />
<strong>in</strong>s Spiel, warum ich über die Suche nach den Operatoren gesagt habe, dass<br />
das noch nicht die ganze Wahrheit wäre. Es gibt nämlich sehr wohl e<strong>in</strong>e<br />
Möglichkeit e<strong>in</strong>en Operator zu def<strong>in</strong>ieren, der diesen Fall abdeckt. Jedoch ist<br />
dies ke<strong>in</strong>e Methode unserer Klasse MathVector mehr, sondern e<strong>in</strong>e Funktion,<br />
die zwei Parameter nimmt: e<strong>in</strong>en l<strong>in</strong>ken und e<strong>in</strong>en rechten Operanden.<br />
Ergänzen wir also unseren Header um die Deklaration dieser Funktion und<br />
fügen die Implementation derselben zu unserer Implementation des restlichen<br />
Vektors h<strong>in</strong>zu, dann funktioniert auch das. Das Ergebnis dieses Umbaus s<strong>in</strong>d
12.1 Grundpr<strong>in</strong>zipien des Operator Overload<strong>in</strong>gs 353<br />
die Files math_vector_v2.h und math_vector_v2.cpp, von denen ich hier<br />
nur die ergänzten Ausschnitte <strong>in</strong>kludiere:<br />
84 MathVector operator ∗ ( double num to multiply by ,<br />
85 const MathVector& vec )<br />
86 throw( b a d a l l o c ) ;<br />
283 MathVector operator ∗ ( double num to multiply by ,<br />
284 const MathVector& vec )<br />
285 throw( b a d a l l o c )<br />
286 {<br />
287 return ( vec ∗ num to multiply by ) ;<br />
288 }<br />
Um kontrollieren zu können, ob alles erwartungsgemäß funktioniert, gibt es<br />
das folgende Testprogramm (math_vector_v2_test.cpp):<br />
1 // math vector v2 test . cpp − t e s t program f o r extended math v e c t o r s<br />
2<br />
3 #<strong>in</strong>clude <br />
4<br />
5 #<strong>in</strong>clude ” math vector v2 . h”<br />
6<br />
7 us<strong>in</strong>g std : : cout ;<br />
8 us<strong>in</strong>g std : : c e r r ;<br />
9 us<strong>in</strong>g std : : endl ;<br />
10<br />
11 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
12 {<br />
13 try<br />
14 {<br />
15 MathVector vector1 ( 3 ) ;<br />
16 MathVector vector2 = 3 ∗ vector1 ;<br />
17 }<br />
18 catch ( <strong>in</strong>valid argument &exc )<br />
19 {<br />
20 c e r r
354 12. Operator Overload<strong>in</strong>g<br />
MathVector , id = 0 : standard constructor<br />
MathVector , id = 0 : operator ∗<br />
MathVector , id = 1 : copy constructor , other id = 0<br />
MathVector , id = 1 : d e s t r u c t o r<br />
MathVector , id = 0 : d e s t r u c t o r<br />
Das Makefile zu diesem Meisterwerk f<strong>in</strong>det man erwartungsgemäß auf der<br />
beiliegenden CD-ROM unter dem Namen MathVectorV2TestMakefile.<br />
Vorsicht Falle: Immer wenn man glaubt, endlich e<strong>in</strong>e gute Lösung gefunden<br />
zu haben, stolpert man über e<strong>in</strong>en weiteren Aspekt, der die gute<br />
Lösung dann doch nicht so gut aussehen lässt. So auch bei unserer Operatorfunktion:<br />
Nehmen wir e<strong>in</strong>fach an, wir würden e<strong>in</strong>e spezielle Vektor-Klasse<br />
implementieren, die von unserem Basis Vektor abgeleitet ist und die aus irgendwelchen<br />
Gründen e<strong>in</strong>e ger<strong>in</strong>gfügig andere Implementation der Subtraktion<br />
brauchen würde als die Basis. Nun gut, mögen jetzt viele Leser sagen,<br />
dann implementieren wir eben für die Ableitung e<strong>in</strong>e neue Operatorfunktion<br />
für die Subtraktion und die Sache ist gegessen. Leider ist aber die Situation<br />
nicht ganz so e<strong>in</strong>fach. Er<strong>in</strong>nern wir uns daran, dass wir aus Gründen<br />
der Polymorphismus-Eigenschaft von Klassen das dynamic B<strong>in</strong>d<strong>in</strong>g mittels<br />
virtual kennen gelernt haben. Man kann aber nur Methoden als virtual<br />
deklarieren, mit Funktionen geht das nicht! Das bedeutet, dass unsere Operatorfunktion<br />
den Polymorphismus durchbricht! In unserem Fall haben wir<br />
Glück, denn die Operatorfunktion ist so implementiert, dass sie <strong>in</strong> Wirklichkeit<br />
nicht selbst rechnet, sondern den virtual Operator * aufruft. Das muss<br />
aber nicht immer der Fall se<strong>in</strong>.<br />
Wie sorgen wir also nun dafür, dass dieses Loch <strong>in</strong> der Implementation<br />
gestopft wird? Ganz e<strong>in</strong>fach: Wir führen die Konvention e<strong>in</strong>, dass e<strong>in</strong>e<br />
Operatorfunktion niemals selbst e<strong>in</strong>e Operation durchführen darf, sondern<br />
immer nur e<strong>in</strong>e Delegation an e<strong>in</strong>e virtual Methode vornehmen darf. In<br />
dieser steckt dann erst wirklich die Logik der Operation.<br />
E<strong>in</strong> weiteres Beispiel zu diesem Thema, bei dem es essentiell ist, diese<br />
Konvention e<strong>in</strong>zuhalten, f<strong>in</strong>det sich <strong>in</strong> Abschnitt 16.6.<br />
12.2 Typumwandlungen<br />
Oft passiert es, dass verschiedene Datentypen zue<strong>in</strong>ander bis zu e<strong>in</strong>em gewissen<br />
Grad kompatibel s<strong>in</strong>d und dementsprechend auch s<strong>in</strong>nvoll <strong>in</strong>e<strong>in</strong>ander umgewandelt<br />
werden können. Nehmen wir als kle<strong>in</strong>es Beispiel zur Demonstration<br />
e<strong>in</strong>e Klasse RangeControlledInt, die e<strong>in</strong>en Ganzzahlenwert repräsentiert,<br />
dessen gültiger Wertebereich von der Applikation kontrolliert werden kann.<br />
E<strong>in</strong>em Objekt dieser Klasse wird beim Konstruieren der gewünschte gültige<br />
Wertebereich übergeben. Ansonsten soll e<strong>in</strong> solches Objekt voll kompatibel<br />
zu e<strong>in</strong>em <strong>in</strong>t se<strong>in</strong>, mit der e<strong>in</strong>zigen Ausnahme, dass es die Zuweisung von<br />
Werten, die außerhalb des erlaubten Bereichs liegen, nicht zulässt und mit
12.2 Typumwandlungen 355<br />
e<strong>in</strong>er entsprechen Exception quittiert. Der spr<strong>in</strong>gende Punkt hierbei liegt <strong>in</strong><br />
der Forderung der vollen Kompatibilität zu e<strong>in</strong>em <strong>in</strong>t. Es sollen also nicht<br />
nur alle Operationen gleich funktionieren wie bei e<strong>in</strong>em <strong>in</strong>t, sondern e<strong>in</strong> solches<br />
Objekt soll auch <strong>in</strong> Operationen beliebig mit <strong>in</strong>t-Variablen gemischt<br />
werden können.<br />
E<strong>in</strong>e brute-force Variante diese Klasse zu implementieren würde dar<strong>in</strong><br />
bestehen, alle Operatoren, die für <strong>in</strong>t def<strong>in</strong>iert s<strong>in</strong>d, auch tatsächlich für<br />
diese Klasse als entsprechende Operatoren zu def<strong>in</strong>ieren. Diese Variante ist<br />
aber sehr unschön, da <strong>in</strong> jedem e<strong>in</strong>zelnen Operator sowieso wieder nur der<br />
entsprechende system<strong>in</strong>terne Operator für <strong>in</strong>t aufgerufen würde.<br />
Besser wäre da schon die Möglichkeit, unser Objekt zu e<strong>in</strong>em “echten”<br />
<strong>in</strong>t wandeln zu können. Dann nämlich würden alle für <strong>in</strong>t def<strong>in</strong>ierten Operatoren<br />
automatisch funktionieren und bräuchten nicht extra implementiert<br />
werden. Die Überprüfung des vorgegebenen Wertebereichs müsste nur <strong>in</strong><br />
den entsprechenden Zuweisungsoperatoren erfolgen und alle unsere Wünsche<br />
wären erfüllt. Genau diese Möglichkeit gibt es, wenn man e<strong>in</strong> Overload<strong>in</strong>g<br />
der entsprechenden Operatoren zur Typumwandlung vornimmt, wie im folgenden<br />
Beispiel gezeigt wird (range_controlled_<strong>in</strong>t.cpp):<br />
1<br />
2 // r a n g e c o n t r o l l e d i n t . cpp : demo f o r type conversionoperators<br />
3<br />
4 #<strong>in</strong>clude <br />
5 #<strong>in</strong>clude <br />
6 #<strong>in</strong>clude <br />
7<br />
8 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
9<br />
10 us<strong>in</strong>g std : : <strong>in</strong>valid argument ;<br />
11 us<strong>in</strong>g std : : b a d a l l o c ;<br />
12 us<strong>in</strong>g std : : r a n g e e r r o r ;<br />
13<br />
14 us<strong>in</strong>g std : : cout ;<br />
15 us<strong>in</strong>g std : : c e r r ;<br />
16 us<strong>in</strong>g std : : endl ;<br />
17<br />
18 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
19 /∗<br />
20 ∗ RangeControlledInt<br />
21 ∗<br />
22 ∗ implements i n t e g e r s with a c o n t r o l l e d range<br />
23 ∗<br />
24 ∗/<br />
25<br />
26 class RangeControlledInt<br />
27 {<br />
28 protected :<br />
29 <strong>in</strong>t32 m<strong>in</strong> ;<br />
30 <strong>in</strong>t32 max ;<br />
31 <strong>in</strong>t32 value ;<br />
32 public :<br />
33 RangeControlledInt ( <strong>in</strong>t32 m<strong>in</strong> , <strong>in</strong>t32 max , <strong>in</strong>t32 value = 0)<br />
34 throw( <strong>in</strong>valid argument , r a n g e e r r o r ) ;<br />
35<br />
36 RangeControlledInt ( const RangeControlledInt & s r c ) ;<br />
37<br />
38 virtual ˜ RangeControlledInt ( ) { }
356 12. Operator Overload<strong>in</strong>g<br />
39<br />
40 virtual RangeControlledInt &operator = (<br />
41 const RangeControlledInt & s r c )<br />
42 throw( r a n g e e r r o r ) ;<br />
43<br />
44 virtual RangeControlledInt &operator = ( <strong>in</strong>t32 value )<br />
45 throw( r a n g e e r r o r ) ;<br />
46<br />
47 virtual operator <strong>in</strong>t32 ( ) const ;<br />
48 } ;<br />
49<br />
50 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
51 /∗<br />
52 ∗/<br />
53 RangeControlledInt : : RangeControlledInt ( <strong>in</strong>t32 m<strong>in</strong> ,<br />
54 <strong>in</strong>t32 max,<br />
55 <strong>in</strong>t32 value )<br />
56 throw( <strong>in</strong>valid argument , r a n g e e r r o r )<br />
57 {<br />
58 cout
105 ( value > max ) )<br />
106 throw r a n g e e r r o r ( ” value out o f range ” ) ;<br />
107 value = value ;<br />
108 return (∗ this ) ;<br />
109 }<br />
110<br />
12.2 Typumwandlungen 357<br />
111 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
112 /∗<br />
113 ∗/<br />
114 RangeControlledInt : : operator <strong>in</strong>t32 ( ) const<br />
115 {<br />
116 cout
358 12. Operator Overload<strong>in</strong>g<br />
12 6<br />
13 ∗∗∗∗ RangeControlledInt : conversion to <strong>in</strong>t32<br />
14 RangeControlledInt : = with <strong>in</strong>t32<br />
15 Oops − caught r a n g e e r r o r : value out o f range<br />
Aufmerksame Leser haben sicherlich bereits erkannt, dass die Deklaration des<br />
Typumwandlungs-Operators <strong>in</strong> Zeile 46 zu f<strong>in</strong>den ist. Allerd<strong>in</strong>gs liest sich dieser<br />
Operator doch etwas komisch, wenn man ihn mit den anderen Operatoren<br />
vergleicht. Der Name des Operators ist <strong>in</strong>t32 und das Fehlen von Parametern<br />
zwischen den Klammern sagt uns, dass er e<strong>in</strong> unärer Operator ist. Allerd<strong>in</strong>gs<br />
ist im Gegensatz zu anderen Operatoren ke<strong>in</strong> expliziter return-Value<br />
angegeben! Wenn man kurz nachdenkt, wird auch klar warum: Was soll e<strong>in</strong>e<br />
Umwandlung auf e<strong>in</strong>en <strong>in</strong>t32 denn auch anderes liefern als e<strong>in</strong>en <strong>in</strong>t32? Def<strong>in</strong>iert<br />
man also e<strong>in</strong>en Typumwandlungs-Operator, so schreibt man e<strong>in</strong>fach<br />
das Keyword operator, gefolgt vom Datentyp, <strong>in</strong> den man umwandeln will,<br />
gefolgt von öffnender und schließender runder Klammer, das war’s. Dass sich<br />
h<strong>in</strong>ter der Syntax mit dem fehlenden return-Value e<strong>in</strong>e Erleichterung für die<br />
Compilerbauer versteckt, ist e<strong>in</strong>e andere Geschichte :-).<br />
Die Implementation des Typumwandlungs-Operators <strong>in</strong> den Zeilen 113–<br />
117 braucht sicherlich ke<strong>in</strong>e nähere Erklärung. Um genau verfolgen zu<br />
können, was h<strong>in</strong>ter den Kulissen passiert, wurde <strong>in</strong> gewohnter Weise <strong>in</strong> alle<br />
Methoden e<strong>in</strong> entsprechender Output e<strong>in</strong>gebaut. Werfen wir also nun e<strong>in</strong>en<br />
Blick auf denselben:<br />
Die Zeilen 1–2 des Outputs werden beim Anlegen der entsprechenden<br />
Variablen <strong>in</strong> den Zeilen 126–127 des Programms erzeugt. Dass die Zeilen<br />
3–4 des Outputs ihren Ursprung <strong>in</strong> den Zuweisungen aus den Zeilen 129–<br />
130 des Programms haben, ist auch nicht weiter verwunderlich. Zeile 131<br />
des Programms wird <strong>in</strong> dieser Beziehung schon <strong>in</strong>teressanter, denn von ihr<br />
rühren die Zeilen 5–6 des Outputs her, die zeigen, dass hier zum ersten<br />
Mal unser Typumwandlungs-Operator <strong>in</strong>s Spiel kommt: Hier wird zuerst<br />
controlled_var2 <strong>in</strong> e<strong>in</strong>en <strong>in</strong>t32 verwandelt, danach wird die Subtraktion<br />
durchgeführt und das Ergebnis wird dann entsprechend zugewiesen. Man<br />
sieht also, dass der Compiler aus dem Subtraktionsoperator und dem l<strong>in</strong>ks<br />
von ihm stehenden <strong>in</strong>t32 den Schluss gezogen hat, dass rechts wohl auch<br />
e<strong>in</strong> <strong>in</strong>t32 stehen sollte, damit die Operation ordnungsgemäß ausgeführt<br />
werden kann. In unserer Klassendeklaration hat er den entsprechenden<br />
Typumwandlungs-Operator gefunden und automatisch e<strong>in</strong>gesetzt.<br />
Dass dies nicht nur funktioniert, wenn l<strong>in</strong>ks des Operators e<strong>in</strong> <strong>in</strong>t32<br />
steht, sondern auch umgekehrt und sogar <strong>in</strong> beliebiger Komb<strong>in</strong>ation, beweisen<br />
die Zeilen 132–133 des Programms, die im Output die Zeilen 7–10<br />
erzeugen. Salopp gesagt arbeitet der Compiler also so, dass er zuerst e<strong>in</strong>en<br />
Ausdruck daraufh<strong>in</strong> analysiert, was denn s<strong>in</strong>nvolle Komb<strong>in</strong>ationen von Datentypen<br />
wären, um die gewünschten Operationen auszuführen. F<strong>in</strong>det er<br />
dann entsprechende Typumwandlungen, so werden diese e<strong>in</strong>gesetzt und alles<br />
geht se<strong>in</strong>en erwarteten Weg.
12.2 Typumwandlungen 359<br />
Dass dieses Verhalten nicht nur auf mathematische Operationen beschränkt<br />
ist, zeigt sich sehr schön <strong>in</strong> Zeile 134 des Programms: Wir können<br />
e<strong>in</strong>fach controlled_var1 mittels
360 12. Operator Overload<strong>in</strong>g<br />
der Folge zu sehen ist. Die Deklaration f<strong>in</strong>det sich <strong>in</strong> math_vector_v3.h und<br />
sieht folgendermaßen aus:<br />
48 virtual operator double ∗ ( ) const<br />
49 throw( bad cast ) ;<br />
Implementiert wird der Operator <strong>in</strong> math_vector_v3.cpp:<br />
65 MathVector : : operator double ∗ ( ) const<br />
66 throw( bad cast )<br />
67 {<br />
68 cout
35 exc . what()
362 12. Operator Overload<strong>in</strong>g<br />
vergessen, so haben wir e<strong>in</strong> wunderbar wachsendes Programm erzeugt, wobei<br />
die Ursache für das entstandene Speicherloch ausgesprochen schwer zu f<strong>in</strong>den<br />
ist und den Entwicklern sicherlich e<strong>in</strong>ige lange Nächte beschert.<br />
Vorsicht Falle: Wie wir gesehen haben, s<strong>in</strong>d selbstdef<strong>in</strong>ierte Typumwandlungs-Operatoren<br />
auf Po<strong>in</strong>ter-Typen e<strong>in</strong>e ziemlich heikle Angelegenheit, bei<br />
der sehr viel schief gehen kann. In unserem Fall wird durch die Def<strong>in</strong>ition<br />
e<strong>in</strong>er expliziten Umwandlungsmethode, wie weiter oben vorgeschlagen wurde,<br />
auch nur e<strong>in</strong> Teil des Problems gelöst, die Zeitbombe “Array” bleibt allerd<strong>in</strong>gs<br />
<strong>in</strong> e<strong>in</strong>er oder der anderen Form bestehen. Ich kann also allen Lesern hier nur<br />
Folgendes raten:<br />
Das Mischen von Objekten mit dazu (teil-)kompatiblen Array-Datentypen<br />
ist pr<strong>in</strong>zipiell zu vermeiden!<br />
Sollte man aus irgendwelchen Gründen auf e<strong>in</strong>e solche Funktionalität<br />
nicht verzichten können, so sollte man dafür e<strong>in</strong>e eigene Zugriffsmethode<br />
implementieren (und ihre Gefahren gut dokumentieren :-)).<br />
Vorsicht Falle: Manchmal wird <strong>in</strong> gewissen Designs dem Compiler zu viel<br />
zugetraut, haben doch implizite Typumwandlungen durch den Compiler auch<br />
ihre Grenzen. Der Compiler weigert sich (zum Glück), mehrere verschiedene<br />
Umwandlungen h<strong>in</strong>tere<strong>in</strong>ander auszuführen. Was das bedeutet, kann man<br />
leicht am folgenden Beispiel erkennen:<br />
Nehmen wir an, wir hätten e<strong>in</strong>e Klasse IntStore, die e<strong>in</strong>e Typumwandlung<br />
auf <strong>in</strong>t32 besitzt:<br />
1 class IntStore<br />
2 {<br />
3 // Constructor , Destructor , etc . . .<br />
4<br />
5 virtual operator <strong>in</strong>t32 ( ) const ;<br />
6 } ;<br />
Weiters hätten wir e<strong>in</strong>e Klasse CommonStore, die e<strong>in</strong>e Typumwandlung auf<br />
IntStore unterstützt:<br />
1 class CommonStore<br />
2 {<br />
3 // Constructor , Destructor , etc . . .<br />
4<br />
5 virtual operator IntStore ( ) const ;<br />
6 } ;<br />
Und nun schreiben wir Folgendes:<br />
CommonStore my_store;<br />
<strong>in</strong>t32 my_var = 10 + my_store;<br />
Manche Leser mögen nun glauben, dass die Berechnung funktionieren sollte,<br />
denn der Compiler kann ja zuerst die Umwandlung von my_store auf e<strong>in</strong><br />
Objekt vom Typ IntStore e<strong>in</strong>setzen. Da dieses dann e<strong>in</strong>e Umwandlung auf<br />
<strong>in</strong>t32 unterstützt, wäre der Fall erledigt.<br />
Der Compiler jedoch sieht das anders, denn er betrachtet nur die Umwandlungen,<br />
die von CommonStore selbst zur Verfügung gestellt werden. Da
12.3 Speicherverwaltung 363<br />
er dort ke<strong>in</strong>e passende Umwandlung f<strong>in</strong>det, endet der Versuch der Übersetzung<br />
des obigen Statements <strong>in</strong> e<strong>in</strong>em Fehler. Es gibt auch zwei sehr gute<br />
Gründe für dieses Verhalten:<br />
1. Würde der Compiler Umwandlungsketten suchen (und f<strong>in</strong>den), so kann<br />
es leicht zu bösen Überraschungen bezüglich Ambiguitäten oder auch<br />
bezüglich Verträglichkeit von Datentypen kommen, die sich eigentlich<br />
beabsichtigterweise nicht vertragen sollten.<br />
2. Müsste der Compiler auf die Suche nach Umwandlungsketten gehen,<br />
dann würde ihn dies nicht wirklich performanter machen, denn dafür<br />
wäre die Implementation von e<strong>in</strong>igen sehr komplexen graphentheoretischen<br />
Algorithmen notwendig. Und die durch Umwandlungen aufgespannten<br />
Graphen können groß werden...<br />
12.3 Speicherverwaltung<br />
Waren schon selbstdef<strong>in</strong>ierte Typumwandlungs-Operatoren mit Vorsicht anzuwenden,<br />
so kommen wir jetzt zu e<strong>in</strong>em Thema, das wirklich als for Experts<br />
only ausgewiesen werden muss: Overload<strong>in</strong>g der new und delete<br />
Operatoren, um gewisse Teile der Speicherverwaltung selbst <strong>in</strong> die Hand zu<br />
nehmen. Im Pr<strong>in</strong>zip kann man damit sehr s<strong>in</strong>nvolle Konstrukte implementieren,<br />
aber die Möglichkeiten, ungeahnte Zeitbomben <strong>in</strong> die eigene Software<br />
e<strong>in</strong>zubauen, s<strong>in</strong>d auch nicht zu verachten! Man muss wirklich ganz genau wissen,<br />
was man tut, wenn man e<strong>in</strong> Overload<strong>in</strong>g von new und delete s<strong>in</strong>nvoll<br />
und robust e<strong>in</strong>setzen will!<br />
Genug gewarnt – wenden wir uns also nun wirklich den Grundpr<strong>in</strong>zipien<br />
e<strong>in</strong>er selbstgebastelten Speicherverwaltung zu.<br />
12.3.1 E<strong>in</strong>faches new und delete<br />
Bisher haben wir damit gelebt, dass bei e<strong>in</strong>em Aufruf von new e<strong>in</strong>fach h<strong>in</strong>ter<br />
den Kulissen genügend Speicher reserviert wurde und wir ke<strong>in</strong>en E<strong>in</strong>fluss darauf<br />
hatten, wo dieser Speicherblock hergenommen wird und wo er endgültig<br />
zu liegen kommt. In den allermeisten Fällen ist das auch vollkommen ausreichend.<br />
Stellen wir uns aber nun z.B. vor, dass wir es mit e<strong>in</strong>em System zu tun<br />
hätten, <strong>in</strong> dem gewisse Objekte <strong>in</strong> e<strong>in</strong>em besonderen Speicherbereich liegen<br />
müssen, um e<strong>in</strong>e saubere Funktionalität zu gewährleisten. In embedded Systems<br />
kann es schon e<strong>in</strong>mal vorkommen, dass man es mit zwei verschiedenen<br />
Arten von Speichern zu tun hat. Die e<strong>in</strong>e Art von Speicher gestattet e<strong>in</strong>en<br />
schnellen Zugriff, ist aber flüchtig. Die andere Art von Speicher ist langsam,<br />
dafür aber nicht flüchtig und gestattet daher das Speichern von Objekten, die<br />
auch nach dem “Ausschalten” des Systems erhalten bleiben. Es g<strong>in</strong>ge jetzt
364 12. Operator Overload<strong>in</strong>g<br />
zu weit, solche Systeme e<strong>in</strong>er genaueren Analyse zu unterziehen, e<strong>in</strong>es ist allerd<strong>in</strong>gs<br />
offensichtlich: Objekte, auf die man oft zugreift, müssen im schnellen<br />
Speicher zu liegen kommen. Objekte, deren Zustand erhalten bleiben muss,<br />
müssen wohl oder übel im nicht flüchtigen, dafür aber langsamen Speicher zu<br />
liegen kommen. Daneben gibt es noch die typischen Hybridobjekte, auf die<br />
e<strong>in</strong>erseits oft zugegriffen wird, die andererseits aber trotzdem nicht flüchtig<br />
se<strong>in</strong> dürfen. Diese lassen wir im Augenblick außer Acht, werden aber später<br />
noch e<strong>in</strong>mal darauf zurückkommen.<br />
Was wollen wir also erreichen? Der Aufruf von new soll den Speicherblock<br />
für das entsprechende Objekt automatisch entweder im schnellen oder<br />
im langsamen Teil des Speichers ablegen, je nachdem wie es die Natur des<br />
Objekts verlangt. Damit e<strong>in</strong>e Klasse kundtun kann, <strong>in</strong> welchem Teil des Speichers<br />
sie zu liegen kommen will, schreiben wir zwei Basisklassen, von denen<br />
entsprechend abgeleitet werden muss: E<strong>in</strong>e Klasse FastAccessibleObject<br />
und e<strong>in</strong>e Klasse NonVolatileObject. Je nachdem, von welcher Klasse abgeleitet<br />
wird, kommt e<strong>in</strong> Objekt bei se<strong>in</strong>er dynamischen Erzeugung im e<strong>in</strong>en<br />
oder anderen Teil des Speichers zu liegen. Bevor das Ganze nun zu theoretisch<br />
wird, sehen wir uns das am besten gleich an e<strong>in</strong>em ersten kle<strong>in</strong>en<br />
Beispiel an (first_new_delete_overload<strong>in</strong>g_demo.cpp).<br />
In diesem Beispiel ist zur Demonstration e<strong>in</strong>e Klasse MemoryProvider<br />
implementiert, die nur als Platzhalter für e<strong>in</strong>e “richtige” Speicherverwaltung<br />
dient, die <strong>in</strong> solchen Systemen gebraucht wird. Diese Klasse sieht folgendermaßen<br />
aus:<br />
23 class MemoryProvider<br />
24 {<br />
25 private :<br />
26 MemoryProvider ( ) ; // f o r b i d i n s t a n t i a t i o n<br />
27 public :<br />
28 static const u<strong>in</strong>t32 FAST = 0x01 ;<br />
29 static const u<strong>in</strong>t32 NON VOLATILE = 0 x02 ;<br />
30<br />
31 static void ∗ allocMemBlock ( u<strong>in</strong>t32 mem type , s i z e t s i z e )<br />
32 throw( b a d a l l o c ) ;<br />
33 static void freeMemBlock ( void ∗ base ptr )<br />
34 throw ( ) ;<br />
35 } ;<br />
Wie sich leicht erkennen lässt, kann man über diese Klasse die entsprechenden<br />
(un<strong>in</strong>itialisierten!) Speicherblöcke anfordern und nicht mehr benötigte<br />
Speicherblöcke wieder freigeben lassen. Beim Anfordern gibt man den Typ<br />
des benötigten Blocks sowie se<strong>in</strong>e Größe <strong>in</strong> Bytes an und bekommt e<strong>in</strong>en<br />
entsprechenden Base-Po<strong>in</strong>ter geliefert. Beim Freigeben übergibt man e<strong>in</strong>fach<br />
den Base-Po<strong>in</strong>ter. Die Implementation der Methoden liest sich so:<br />
182 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
183 /∗ This i s j u s t a dummy implementation f o r demo purposes . . .<br />
184 ∗/<br />
185 void ∗ MemoryProvider : : allocMemBlock ( u<strong>in</strong>t32 mem type , s i z e t s i z e )<br />
186 throw( b a d a l l o c )
187 {<br />
188 switch (mem type)<br />
189 {<br />
190 case FAST:<br />
191 case NON VOLATILE:<br />
192 return ( malloc ( s i z e ) ) ;<br />
193 }<br />
194 return ( 0 ) ;<br />
195 }<br />
196<br />
12.3 Speicherverwaltung 365<br />
197 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
198 /∗ This i s j u s t a dummy implementation f o r demo purposes . . .<br />
199 ∗/<br />
200 void MemoryProvider : : freeMemBlock ( void ∗ base ptr )<br />
201 throw( )<br />
202 {<br />
203 i f ( base ptr )<br />
204 f r e e ( base ptr ) ;<br />
205 }<br />
So e<strong>in</strong>ige Leser werden sich jetzt fragen, ob ich vielleicht zu lange <strong>in</strong> der Sonne<br />
war, weil ich hier doch tatsächlich die Funktionen malloc und free verwende.<br />
In Abschnitt 6.2.2 habe ich noch ausdrücklich erklärt, dass man von diesen<br />
Funktionen tunlichst die F<strong>in</strong>ger lassen soll und stattdessen new und delete<br />
verwenden soll. Der Grund für diesen verme<strong>in</strong>tlichen Regelverstoß ist e<strong>in</strong>fach<br />
zu erklären: Wenn man schon new und delete implementiert, dann bewegt<br />
man sich ja bereits e<strong>in</strong>e Ebene tiefer unten und muss e<strong>in</strong>fach irgendwo her<br />
e<strong>in</strong>en nicht <strong>in</strong>itialisierten Speicherblock bekommen. Deshalb ist es <strong>in</strong> diesem<br />
Kontext (und ausschließlich <strong>in</strong> diesem!) erlaubt und zumeist notwendig, mit<br />
malloc und free zu arbeiten.<br />
Folgende von C übernommene Funktionen werden angeboten, wenn man<br />
<strong>in</strong>kludiert:<br />
void *malloc(size_t size): Allokiert Speicher der Größe size und liefert<br />
den Base-Po<strong>in</strong>ter darauf. Der Speicher, der allokiert wird, ist nicht<br />
<strong>in</strong>itialisiert.<br />
void *calloc(size_t num,size_t size): Allokiert Speicher der Größe<br />
num * size, <strong>in</strong>itialisiert diesen mit 0 und liefert den Base-Po<strong>in</strong>ter darauf.<br />
void free(void *base_ptr): Gibt Speicher, der entweder über Aufruf von<br />
malloc oder calloc allokiert wurde, wieder frei.<br />
void *realloc(void *base_ptr,size_t size): Ändert die Größe des<br />
durch base_ptr referenzierten Speicherblocks auf size. Da nicht garantiert<br />
werden kann, dass der Block nicht z.B. im Zuge e<strong>in</strong>es Vergrößerns<br />
verschoben werden muss, wird der neue Base-Po<strong>in</strong>ter des Blocks zurückgeliefert.<br />
Der neu h<strong>in</strong>zugekommene Speicher ist nicht <strong>in</strong>itialisiert.<br />
Vorsicht Falle: Man sieht, es gibt also auch tatsächlich e<strong>in</strong> realloc, obwohl<br />
ich es bisher konsequent unter den Tisch gekehrt habe. Allerd<strong>in</strong>gs wird auch<br />
auf dieser unteren Ebene dr<strong>in</strong>gendst von dessen Verwendung abgeraten!<br />
Das Problem bei realloc ist, dass der Block im Speicher verschoben
366 12. Operator Overload<strong>in</strong>g<br />
werden kann. Hat man nun z.B. e<strong>in</strong>en Po<strong>in</strong>ter, der auf e<strong>in</strong> Objekt zeigt, das<br />
<strong>in</strong> e<strong>in</strong>em solchen Speicherblock liegt, dann zeigt nach e<strong>in</strong>er Verschiebung des<br />
Blocks dieser Po<strong>in</strong>ter <strong>in</strong>s Nirvana. Welche lustigen Folgen das haben kann,<br />
lässt sich leicht vorstellen.<br />
Vorsicht Falle: Es wurde bereits implizit erwähnt, aber weil es derartig<br />
gefährlich ist, möchte ich es hier noch e<strong>in</strong>mal explizit anführen:<br />
• Die Funktion malloc allokiert nur Speicher. Sie weiß nichts von Objekten<br />
oder sonstigen C ++ Features. Es wird also bei Verwendung von malloc<br />
niemals e<strong>in</strong> Konstruktor aufgerufen.<br />
• Die Funktion free gibt nur Speicher frei. Auch sie weiß nichts von Objekten.<br />
Es wird also bei Verwendung von free niemals e<strong>in</strong> Destruktor<br />
aufgerufen.<br />
Das Schlimmste, was man machen kann (und ich habe so etwas bereits <strong>in</strong><br />
mehr als e<strong>in</strong>em C ++ Programm gesehen!), ist also Folgendes: Man legt e<strong>in</strong><br />
Objekt ordnungsgemäß mit new an. Anstatt allerd<strong>in</strong>gs das Objekt dann mit<br />
delete wieder freizugeben, ruft man free mit dem Base-Po<strong>in</strong>ter des Objekts<br />
auf. Dadurch wird ke<strong>in</strong> Destruktor aufgerufen und was das bedeutet, kann<br />
man sich ausmalen!<br />
Vorsicht Falle: Um den Blick nicht vom Wesentlichen abzulenken, habe<br />
ich im Beispiel den return-Value von malloc nicht überprüft. Die Funktion<br />
malloc ist allerd<strong>in</strong>gs gleich def<strong>in</strong>iert wie <strong>in</strong> C: Wenn der Speicher ausgeht,<br />
dann retourniert sie e<strong>in</strong>en 0-Po<strong>in</strong>ter! Diese Funktion weiß nichts von Exceptions<br />
und wirft dementsprechend selbsttätig ke<strong>in</strong>e bad_alloc Exception, wenn<br />
nicht mehr genug Speicher da ist.<br />
Aus diesem Grund muss bei e<strong>in</strong>er ernsthaften Implementation von new<br />
immer der return-Value von malloc abgefragt und entsprechend reagiert werden.<br />
Allerd<strong>in</strong>gs ist dies alle<strong>in</strong> bei e<strong>in</strong>er ernsthaften Implementation auch nicht<br />
genug! Per Konvention darf man nicht bl<strong>in</strong>dl<strong>in</strong>gs e<strong>in</strong>e bad_alloc Exception<br />
werfen, denn es gibt da noch e<strong>in</strong>e weitere Konvention <strong>in</strong> C ++, die man<br />
zu beachten hat. Wie man nun korrekt nach Standard die Behandlung von<br />
Speicherknappheit übernimmt, wird ausführlich <strong>in</strong> Abschnitt Verhalten bei<br />
“Ausgehen” des Speichers diskutiert.<br />
Der Vollständigkeit halber möchte ich an dieser Stelle auch noch erwähnen,<br />
dass bei Inkludieren von auch noch die aus C bekannten Funktionen<br />
memcpy, memmove, memchr, memcmp und memset zur Verfügung stehen.<br />
Und natürlich muss ich auch hier gleich vor deren unsachgemäßen Verwendung<br />
dr<strong>in</strong>gendst warnen.<br />
Nach diesem kurzen aber notwendigen Ausflug <strong>in</strong> die Welt der Low-Level<br />
Speicherverwaltung wird es Zeit zum eigentlichen Thema dieses Kapitels
12.3 Speicherverwaltung 367<br />
zurückzukehren: Overload<strong>in</strong>g von new und delete. Sehen wir uns dazu die<br />
Deklaration der Klasse FastAccessibleObject an:<br />
46 class FastAccessibleObject<br />
47 {<br />
48 public :<br />
49 void ∗ operator new( s i z e t s i z e )<br />
50 throw( b a d a l l o c ) ;<br />
51 void operator delete ( void ∗ base ptr )<br />
52 throw ( ) ;<br />
53<br />
54 FastAccessibleObject ( ) { }<br />
55<br />
56 virtual ˜ FastAccessibleObject ( ) { }<br />
57 } ;<br />
In den Zeilen 49–50 sehen wir, wie e<strong>in</strong> new Operator def<strong>in</strong>iert ist: Es wird e<strong>in</strong><br />
Parameter übergeben, der die Größe des benötigten Speicherblocks <strong>in</strong> Bytes<br />
angibt. Der erwartete return-Value ist dann der Base-Po<strong>in</strong>ter des allokierten<br />
Speicherblocks. Im Falle, dass etwas schief geht, muss e<strong>in</strong>e bad_alloc<br />
Exception geworfen werden. In den Zeilen 51–52 sehen wir, wie e<strong>in</strong> delete<br />
Operator def<strong>in</strong>iert ist: Es wird e<strong>in</strong>fach der Base-Po<strong>in</strong>ter des freizugebenden<br />
Speicherblocks übergeben. Die Implementation dieser beiden Operatoren<br />
liest sich dann folgendermaßen:<br />
138 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
139 /∗<br />
140 ∗/<br />
141 void ∗ FastAccessibleObject : : operator new( s i z e t s i z e )<br />
142 throw( b a d a l l o c )<br />
143 {<br />
144 cout
368 12. Operator Overload<strong>in</strong>g<br />
E<strong>in</strong>e Klasse, die e<strong>in</strong> FastAccessibleObject repräsentiert, wird entsprechend<br />
von dieser Basis abgeleitet und kommt damit automatisch im<br />
schnellen Teil des Speichers zu liegen. Als Beispiel hierfür dient die Klasse<br />
MyFastObject:<br />
89 class MyFastObject : public FastAccessibleObject<br />
90 {<br />
91 protected :<br />
92 <strong>in</strong>t32 some data ;<br />
93 u<strong>in</strong>t32 some other data ;<br />
94 public :<br />
95 MyFastObject ( )<br />
96 {<br />
97 cout
8 d e s t r u c t o r of MyNonVolatileObject<br />
9 d e l e t e o f NonVolatileObject c a l l e d<br />
10 c r e a t i n g with e x p l i c i t constructor c a l l s . . .<br />
11 constructor of MyFastObject<br />
12 constructor of MyNonVolatileObject<br />
13 s t a t i c a l l y a l l o c a t e d o b j e c t s . . .<br />
14 constructor of MyFastObject<br />
15 constructor of MyNonVolatileObject<br />
16 l e a v i n g ma<strong>in</strong><br />
17 d e s t r u c t o r of MyNonVolatileObject<br />
18 d e s t r u c t o r of MyFastObject<br />
19 d e s t r u c t o r of MyNonVolatileObject<br />
20 d e s t r u c t o r of MyFastObject<br />
12.3 Speicherverwaltung 369<br />
Die Zeilen 1–9 repräsentieren den Output, der durch die Zeilen 212–216 unseres<br />
Programms erzeugt wird. Im Falle des dynamischen Erzeugens e<strong>in</strong>es<br />
neuen Objekts, wie es z.B. <strong>in</strong> Zeile 213 unseres Programms gemacht wird,<br />
passiert Folgendes:<br />
1. Es wird <strong>in</strong>tern ausgerechnet, wie viel Speicherplatz das Objekt benötigt.<br />
Darauf haben wir <strong>in</strong> unserem Programm überhaupt ke<strong>in</strong>en E<strong>in</strong>fluss, denn<br />
dies wird e<strong>in</strong>zig und alle<strong>in</strong> vom Compiler erledigt.<br />
2. Danach wird unser selbstdef<strong>in</strong>ierter new Operator aufgerufen und ihm<br />
die benötigte Größe mitgeteilt (siehe Zeile 2 des Outputs). Im Operator<br />
selbst muss der Speicherblock bereitgestellt werden und e<strong>in</strong> Base-Po<strong>in</strong>ter<br />
darauf zurückgeliefert werden.<br />
3. Nachdem der Speicher zur Verfügung steht, wird <strong>in</strong>tern der entsprechende<br />
Konstruktor aufgerufen (siehe Zeile 3 des Outputs). Auch darauf haben<br />
wir als Entwickler natürlich ke<strong>in</strong>en E<strong>in</strong>fluss.<br />
Wird e<strong>in</strong> Objekt mittels delete <strong>in</strong> die ewigen Jagdgründe geschickt, dann<br />
passiert dies folgendermaßen:<br />
1. Zuerst wird <strong>in</strong>tern der entsprechende Destruktor aufgerufen. Darauf haben<br />
wir natürlich ke<strong>in</strong>en E<strong>in</strong>fluss.<br />
2. Danach wird der nun nicht mehr benötigte Speicherblock zur Freigabe<br />
an unseren selbstdef<strong>in</strong>ierten delete Operator übergeben, der für das<br />
Aufräumen zu sorgen hat.<br />
Man sieht also ganz deutlich, dass man durch das Overload<strong>in</strong>g von new und<br />
delete e<strong>in</strong>zig und alle<strong>in</strong> E<strong>in</strong>fluss auf die Beschaffung und Freigabe von Speicher<br />
nehmen kann. Es ist natürlich <strong>in</strong> ke<strong>in</strong>ster Weise festgelegt, woher man<br />
den Speicher beim Anfordern bekommt bzw. wie man ihn wieder freigibt.<br />
Das e<strong>in</strong>zig Wichtige ist, dass man beim Anfordern e<strong>in</strong>en entsprechenden<br />
Block liefern kann und den H<strong>in</strong>weis zur Freigabe korrekt behandelt. Es ist<br />
auch nicht festgelegt, dass der von new gelieferte Block genau die angeforderte<br />
Größe haben muss, wichtig ist nur, dass er m<strong>in</strong>destens die geforderte<br />
Speicherkapazität besitzt. Genau diese Def<strong>in</strong>ition wird von erfahrenen<br />
Entwicklern so ausgenützt, dass sie nicht jeden Block e<strong>in</strong>zeln mit malloc<br />
anfordern und dann mit free wieder dem System überantworten. Stattdessen<br />
wird für Objekte, die sehr oft erzeugt und wieder zerstört werden, e<strong>in</strong>
370 12. Operator Overload<strong>in</strong>g<br />
Pool von vorallokierten Blöcken verwaltet (bzw. auch nur e<strong>in</strong> e<strong>in</strong>ziger großer<br />
Block, <strong>in</strong> den viele kle<strong>in</strong>ere h<strong>in</strong>e<strong>in</strong>passen). E<strong>in</strong>zelne Blöcke aus diesem Pool<br />
werden dann nur als benutzt oder unbenutzt markiert. S<strong>in</strong>nvoll und richtig<br />
angewandt br<strong>in</strong>gt diese Art der Verwaltung <strong>in</strong> gewissen Fällen enorme<br />
Performancesteigerungen der Software mit sich.<br />
Noch e<strong>in</strong>e Eigenschaft der beiden Operatoren wird hier deutlich: beide<br />
s<strong>in</strong>d implizit static, obwohl sie explizit nicht so deklariert wurden. Das<br />
ist auch absolut lebensnotwendig, denn new beschafft erst den Speicher für<br />
e<strong>in</strong>e Instanz, kann also niemals selbst zu dieser Instanz gehören. Zum Zeitpunkt<br />
des Aufrufs existiert diese ja noch gar nicht. Ebenso verhält es sich<br />
mit delete, denn dieses wird ja erst nach dem Abarbeiten des Destruktors<br />
aufgerufen, die Instanz ist also nicht mehr wirklich lebendig.<br />
Welchen E<strong>in</strong>fluss haben unsere selbstdef<strong>in</strong>ierten new und delete Operatoren<br />
nun auf das Verhalten e<strong>in</strong>es nicht dynamisch angelegten Objekts? Die<br />
Zeilen 10–20 des Outputs, die von den Zeilen 218–227 des Programms her<br />
rühren, zeigen es ganz deutlich: überhaupt ke<strong>in</strong>en!<br />
Dies ist auch vollkommen verständlich, wenn man sich e<strong>in</strong>mal vor Augen<br />
führt, welche verschiedenen Speicherbereiche <strong>in</strong>nerhalb e<strong>in</strong>es laufenden<br />
Programms existieren und wie sie verwaltet werden:<br />
Static Memory: Dies ist der Bereich <strong>in</strong> dem Variablen mit globaler Lifetime<br />
abgelegt werden. Für diesen Bereich ist der L<strong>in</strong>ker zuständig.<br />
Automatic Memory: Dies ist der Bereich <strong>in</strong> dem die sogenannten auto-<br />
Variablen, also lokale Variablen, abgelegt werden. Jeder E<strong>in</strong>tritt <strong>in</strong> e<strong>in</strong>e<br />
Methode, Funktion oder e<strong>in</strong>fach <strong>in</strong> e<strong>in</strong>en Block bekommt e<strong>in</strong>en eigenen<br />
Bereich im automatic Memory. Für diesen Bereich ist der Compiler<br />
zuständig und der Bereich wird als Stack verwaltet und deshalb auch oft<br />
e<strong>in</strong>fach als Stack bezeichnet.<br />
Anm.: Es gibt <strong>in</strong> C ++ sogar das (redundante!) Keyword auto, wenn<br />
jemand Lust hat, es explizit zu schreiben. Das Keyword hat allerd<strong>in</strong>gs<br />
überhaupt ke<strong>in</strong>en E<strong>in</strong>fluss auf die Verwaltung, sondern ist nur als Eye-<br />
Catcher für die Entwickler gedacht.<br />
Free Memory: Dies ist der (ausschließlich) von Entwicklern selbst verwaltete<br />
Speicherbereich e<strong>in</strong>es Programms, der auch oft als Heap bezeichnet wird.<br />
Dieser Teil des Memories wird für die mittels new und delete explizit<br />
dynamisch verwalteten Daten herangezogen.<br />
Nachdem wir weder dem L<strong>in</strong>ker noch dem Compiler <strong>in</strong>s Handwerk pfuschen<br />
dürfen, denn dies hätte fatale Folgen, haben wir also sowohl auf das static<br />
als auch auf das automatic Memory ke<strong>in</strong>en E<strong>in</strong>fluss. Unsere new und delete<br />
Operatoren kommen ausschließlich dann zum Tragen, wenn wir Objekte am<br />
Heap ablegen, also dynamische Speicherverwaltung verwenden.<br />
Dass dies aber z.B. für unser Beispiel ke<strong>in</strong>e E<strong>in</strong>schränkung bedeutet, lässt<br />
sich auch schnell erkennen: Will man, dass e<strong>in</strong> Objekt im nicht flüchtigen<br />
Teil des Speichers zu liegen kommt, dann will man es ja offensichtlich über
12.3 Speicherverwaltung 371<br />
längere Zeit speichern. Das ist bei lokalen Variablen sowieso nicht der Fall,<br />
also betrifft uns dies nicht. Sehr wohl würde es uns betreffen, wenn wir<br />
e<strong>in</strong>fach e<strong>in</strong>e Variable mit globaler Lifetime anlegen wollten und diese im nicht<br />
flüchtigen Speicher zu liegen kommen sollte. In diesem Fall ist es dann so,<br />
dass entweder die Compiler/L<strong>in</strong>ker Komb<strong>in</strong>ation für unser System sowieso<br />
das static Memory im nicht flüchtigen Teil verewigt oder wir eben e<strong>in</strong>fach per<br />
Konvention nur mittels dynamischer Memory-Verwaltung arbeiten dürfen.<br />
12.3.2 Array new und delete<br />
Es ist bereits bekannt, dass es verschiedene new und delete Operatoren gibt,<br />
je nachdem, ob man e<strong>in</strong> e<strong>in</strong>zelnes Objekt oder e<strong>in</strong> Array von Objekten anlegen<br />
will. Genau diese Unterscheidung trifft uns auch beim Overload<strong>in</strong>g<br />
dieser Operatoren, denn für Arrays gibt es tatsächlich explizit die alternativen<br />
Operatoren new[] und delete[]. Man sieht also, dass der Aufruf<br />
von delete[] statt delete für Arrays nicht nur re<strong>in</strong>e Kosmetik ist, sondern<br />
dass sich dah<strong>in</strong>ter sehr wohl e<strong>in</strong> eigener, separater Operator mit besonderen<br />
Eigenschaften verbirgt. Ergänzen wir also unser Beispielprogramm um<br />
die entsprechenden Array-Operatoren um zu sehen, wie sich diese verhalten<br />
(second_new_delete_overload<strong>in</strong>g_demo.cpp). Die geänderte Klasse<br />
FastAccessibleObject sieht dann folgendermaßen aus:<br />
48 class FastAccessibleObject<br />
49 {<br />
50 public :<br />
51 void ∗ operator new( s i z e t s i z e )<br />
52 throw( b a d a l l o c ) ;<br />
53<br />
54 void ∗ operator new [ ] ( s i z e t s i z e )<br />
55 throw( b a d a l l o c ) ;<br />
56<br />
57 void operator delete ( void ∗ base ptr )<br />
58 throw ( ) ;<br />
59<br />
60 void operator delete [ ] ( void ∗ base ptr )<br />
61 throw ( ) ;<br />
62<br />
63 FastAccessibleObject ( ) { }<br />
64<br />
65 virtual ˜ FastAccessibleObject ( ) { }<br />
66 } ;<br />
Die Implementation der beiden dazugekommenen Operatoren birgt nichts<br />
wirklich Berauschendes, wie sich leicht erkennen lässt:<br />
169 void ∗ FastAccessibleObject : : operator new [ ] ( s i z e t s i z e )<br />
170 throw( b a d a l l o c )<br />
171 {<br />
172 cout
372 12. Operator Overload<strong>in</strong>g<br />
191 void FastAccessibleObject : : operator delete [ ] ( void ∗ base ptr )<br />
192 throw( )<br />
193 {<br />
194 cout
3 constructor of MyFastObject<br />
4 new o f NonVolatileObject c a l l e d with s i z e 16<br />
5 constructor of MyNonVolatileObject<br />
6 d e s t r u c t o r of MyFastObject<br />
7 d e l e t e o f FastAccessibleObject c a l l e d<br />
8 d e s t r u c t o r of MyNonVolatileObject<br />
9 d e l e t e o f NonVolatileObject c a l l e d<br />
10 c r e a t i n g arrays of o b j e c t s . . .<br />
11 new [ ] of FastAccessibleObject c a l l e d with s i z e 40<br />
12 constructor of MyFastObject<br />
13 constructor of MyFastObject<br />
14 constructor of MyFastObject<br />
15 new [ ] of NonVolatileObject c a l l e d with s i z e 52<br />
16 constructor of MyNonVolatileObject<br />
17 constructor of MyNonVolatileObject<br />
18 constructor of MyNonVolatileObject<br />
19 d e s t r u c t o r of MyFastObject<br />
20 d e s t r u c t o r of MyFastObject<br />
21 d e s t r u c t o r of MyFastObject<br />
22 d e l e t e [ ] of FastAccessibleObject c a l l e d<br />
23 d e s t r u c t o r of MyNonVolatileObject<br />
24 d e s t r u c t o r of MyNonVolatileObject<br />
25 d e s t r u c t o r of MyNonVolatileObject<br />
26 d e l e t e [ ] of NonVolatileObject c a l l e d<br />
27 ∗∗∗∗∗ and now the catastrophe ! ! ! ! ! ! ∗ ∗ ∗ ∗ ∗<br />
28 new [ ] of FastAccessibleObject c a l l e d with s i z e 40<br />
29 constructor of MyFastObject<br />
30 constructor of MyFastObject<br />
31 constructor of MyFastObject<br />
32 new [ ] of NonVolatileObject c a l l e d with s i z e 52<br />
33 constructor of MyNonVolatileObject<br />
34 constructor of MyNonVolatileObject<br />
35 constructor of MyNonVolatileObject<br />
36 d e s t r u c t o r of MyFastObject<br />
37 d e l e t e o f FastAccessibleObject c a l l e d<br />
38 d e s t r u c t o r of MyNonVolatileObject<br />
39 d e l e t e o f NonVolatileObject c a l l e d<br />
12.3 Speicherverwaltung 373<br />
Die e<strong>in</strong>zige wirkliche Besonderheit beim Anlegen von Arrays, die im Output<br />
augenfällig wird, ist die angeforderte Größe des Speicherblocks: In unserem<br />
Fall ist der angeforderte Speicherblock für e<strong>in</strong> Array nämlich <strong>in</strong> beiden Fällen<br />
um 4 Bytes größer als erwartet! Man sieht z.B., dass für e<strong>in</strong> MyFastObject<br />
12 Bytes angefordert werden, für 3 Objekte dieses Typs allerd<strong>in</strong>gs mitnichten<br />
36 Bytes, sondern 40! Ganz gleich verhält es sich bei MyNonVolatileObject.<br />
Das ist darauf zurückzuführen, dass im angeforderten Block auch adm<strong>in</strong>istrative<br />
Daten abgelegt werden, die allerd<strong>in</strong>gs für die verschiedenen Systeme<br />
weder <strong>in</strong> ihrer Größe noch <strong>in</strong> ihrem Inhalt standardisiert und daher den Entwicklern<br />
nicht explizit zugänglich s<strong>in</strong>d.<br />
Bewusst wurde <strong>in</strong> das Programm <strong>in</strong> den Zeilen 286–293 zu Demonstrationszwecken<br />
auch e<strong>in</strong>e absolute Katastrophe e<strong>in</strong>gebaut, die furchtbare Folgen<br />
hat. Es werden dort nämlich Arrays mittels new[] angelegt. Diese werden<br />
allerd<strong>in</strong>gs schlimmerweise nicht korrekt mit delete[], sondern nur mit<br />
delete wieder freigegeben. Das wahre Ausmaß dieses Harakiri-Konstrukts<br />
wird deutlich, wenn man sich die Zeilen 27–39 des Outputs ansieht: Es werden<br />
die beiden Arrays ordnungsgemäß angelegt, allerd<strong>in</strong>gs passiert beim Freigeben<br />
Grauenvolles: Erstens werden nicht alle Objekte ordnungsgemäß destruiert,<br />
sondern immer nur e<strong>in</strong>es von dreien! Zweitens wird der falsche Operator
374 12. Operator Overload<strong>in</strong>g<br />
aufgerufen, der sich z.B. auf e<strong>in</strong>en anderen Speicherbereich beziehen könnte –<br />
mit entsprechend fatalen Folgen. Wie lang man suchen kann, bis man e<strong>in</strong>en<br />
solchen Fehler <strong>in</strong> e<strong>in</strong>em verrückt gewordenen Programm f<strong>in</strong>det, kann man<br />
sich leicht ausmalen.<br />
12.3.3 Placement Operator new<br />
Neben den beiden new Operatoren für <strong>in</strong>dividuelle Objekte und für Arrays<br />
gibt es noch e<strong>in</strong>en dritten, den sogenannten Placement Operator new(). Bei<br />
diesem übergibt man noch zusätzlich als zweiten Parameter e<strong>in</strong>en Po<strong>in</strong>ter,<br />
der e<strong>in</strong>en H<strong>in</strong>weis darauf gibt, wo der Speicher allokiert werden soll.<br />
Nehmen wir am besten e<strong>in</strong>e kle<strong>in</strong>e Änderung an unserem Beispiel vor,<br />
um zu sehen, was dieser Operator bietet und wie man mit ihm umgeht<br />
(third_new_delete_overload<strong>in</strong>g_demo.cpp). Die erste Änderung betrifft<br />
den MemoryProvider: Anstatt nur statische Methoden zur Verfügung zu stellen,<br />
muss man nun e<strong>in</strong>e Instanz erzeugen. Die Verwendung ist so gedacht,<br />
dass man für jeden spezifischen Teil des Memories e<strong>in</strong>e eigene Instanz hat.<br />
Bei uns also e<strong>in</strong>e für den schnellen und e<strong>in</strong>e für den nicht flüchtigen Teil des<br />
Memories. Die umgeschriebene Klasse liest sich dann so:<br />
24 class MemoryProvider<br />
25 {<br />
26 public :<br />
27 // u s u a l l y the constructor has a parameter f o r the s t a r t<br />
28 // address of the managed pool , the s i z e , etc . . . but f o r<br />
29 // demo purposes I don ’ t want to write a ” r e a l ” memory<br />
30 // management c l a s s . So i t ’ s j u s t a dummy . . .<br />
31 MemoryProvider ( ) { } ;<br />
32<br />
33 void ∗ allocMemBlock ( s i z e t s i z e )<br />
34 throw( b a d a l l o c ) ;<br />
35<br />
36 void freeMemBlock ( void ∗ base ptr )<br />
37 throw ( ) ;<br />
38 } ;<br />
Die Implementationen der beiden Methoden zum Allokieren und Freigeben<br />
s<strong>in</strong>d <strong>in</strong> unserem Demoprogramm wieder re<strong>in</strong>e Dummies, die mit malloc und<br />
free geschrieben s<strong>in</strong>d. Deshalb erspare ich mir das Abdrucken derselben an<br />
dieser Stelle.<br />
Anstatt zwei verschiedene Klassen, e<strong>in</strong>e für das schnelle und e<strong>in</strong>e für das<br />
langsame Memory zu haben, brauchen wir hier nur noch e<strong>in</strong>e e<strong>in</strong>zige Klasse,<br />
denn beim Aufruf von new wird jetzt e<strong>in</strong> Po<strong>in</strong>ter auf die gewünschte Instanz<br />
des Memory Managers mitgegeben. Also haben wir es hier mit der Klasse<br />
SpecialMemoryManagedObject zu tun:<br />
49 class SpecialMemoryManagedObject<br />
50 {<br />
51 private :<br />
52 // f o r b i d standard i n s t a n t i a t i o n
53 void ∗ operator new( s i z e t s i z e )<br />
54 throw( b a d a l l o c ) { return ( ( void ∗ ) 0 ) ; }<br />
55<br />
12.3 Speicherverwaltung 375<br />
56 // f o r b i d array i n s t a n t i a t i o n<br />
57 void ∗ operator new [ ] ( s i z e t s i z e )<br />
58 throw( b a d a l l o c ) { return ( ( void ∗ ) 0 ) ; }<br />
59 public :<br />
60 void ∗ operator new( s i z e t s i z e , MemoryProvider ∗ mem provider )<br />
61 throw( b a d a l l o c ) ;<br />
62<br />
63 static void destroy ( SpecialMemoryManagedObject ∗ obj ,<br />
64 MemoryProvider ∗ mem provider )<br />
65 throw ( ) ;<br />
66<br />
67 SpecialMemoryManagedObject ( ) { }<br />
68<br />
69 virtual ˜ SpecialMemoryManagedObject ( ) { }<br />
70 } ;<br />
In dieser Klasse wurden bewusst die Standard-Operatoren new und new[]<br />
als private deklariert, um ihre Verwendung zu verh<strong>in</strong>dern. Dafür gibt es<br />
jetzt den speziellen Placement Operator new(), dessen Deklaration man <strong>in</strong><br />
den Zeilen 60–61 sieht. Wenn man schon mit Placement arbeitet, so sollte<br />
man (je nach Anwendung) auch das Standard delete nicht mehr verwenden.<br />
Deshalb wurde e<strong>in</strong>e static Methode destroy e<strong>in</strong>geführt, die diese Aufgabe<br />
übernimmt. Diese sieht man <strong>in</strong> den Zeilen 63–65.<br />
Die Implementation des Placement Operators birgt auch ke<strong>in</strong>e Überraschungen.<br />
Es wird e<strong>in</strong>fach der Speicher vom mitübergebenen Provider angefordert,<br />
wie man <strong>in</strong> der Folge sieht:<br />
105 void ∗ SpecialMemoryManagedObject : : operator new(<br />
106 s i z e t s i z e , MemoryProvider ∗ mem provider )<br />
107 throw( b a d a l l o c )<br />
108 {<br />
109 cout
376 12. Operator Overload<strong>in</strong>g<br />
120 throw( )<br />
121 {<br />
122 cout
93<br />
94 virtual ˜MyMemManagedObject( )<br />
95 {<br />
96 cout
378 12. Operator Overload<strong>in</strong>g<br />
Was s<strong>in</strong>d nun die Vor- und Nachteile dieser Art der Speicherverwaltung mittels<br />
Placement Operator?<br />
• Der große Vorteil liegt dar<strong>in</strong>, dass man nicht zur Compiletime durch<br />
entsprechende Ableitung festlegen muss, wo e<strong>in</strong> Objekt genau zu liegen<br />
kommt. Stattdessen kann man dies zur Laufzeit bestimmen. Vor allem<br />
für die Implementation von bestimmten Conta<strong>in</strong>er-Objekten ist dies e<strong>in</strong><br />
essentielles Feature.<br />
• Der erste Nachteil ist das Fehlen e<strong>in</strong>es speziellen Placement Operators für<br />
Arrays. Im Pr<strong>in</strong>zip kann man diesen Nachteil bis zu e<strong>in</strong>em gewissen Grad<br />
umgehen, denn man kann ja e<strong>in</strong>fach “genügend” Platz für e<strong>in</strong> Array anfordern<br />
und den Rest irgendwie <strong>in</strong>tern verwalten. Allerd<strong>in</strong>gs ist das dann<br />
wirklich nicht mehr sehr sauber und v.a. für Entwickler sehr ungewohnt,<br />
denn sie können nun nicht mehr e<strong>in</strong>fach e<strong>in</strong> Array anlegen, sondern müssen<br />
explizit zur Selbsthilfe greifen. Deshalb würde ich wirklich empfehlen, die<br />
Placement-Konstruktion niemals zu verwenden, wenn man Arrays brauchen<br />
könnte, um ke<strong>in</strong>e Zeitbomben zu basteln.<br />
• Der zweite Nachteil ist das Fehlen e<strong>in</strong>es speziellen Placement delete()<br />
Operators als Gegenstück zum Placement new() Operator. Dadurch ist<br />
man nämlich gezwungen, sich selbst zu merken, wo e<strong>in</strong> Objekt angelegt<br />
wurde, um dies auch durch e<strong>in</strong>en entsprechenden destroy Aufruf wieder<br />
ordnungsgemäß freizugeben. Was passieren kann, wenn man dem falschen<br />
aus e<strong>in</strong>er Menge von Pools sagt, dass er e<strong>in</strong> Objekt freigeben soll, kann<br />
man sich ja denken.<br />
• Der dritte Nachteil ist, dass man das <strong>in</strong> diesem Fall zumeist verbotene<br />
delete nicht wirklich als private deklarieren kann und damit gesichert<br />
dessen Aufruf verh<strong>in</strong>dern kann. E<strong>in</strong> solcher Versuch wird nämlich im Normalfall<br />
mit e<strong>in</strong>er Warn<strong>in</strong>g des Compilers quittiert.<br />
Obwohl ich e<strong>in</strong> sehr entschiedener Gegner von schmutzigen Tricks b<strong>in</strong>, möchte<br />
ich trotzdem hier e<strong>in</strong>e abgewandelte Implementation unseres Beispiels vorstellen,<br />
die zum<strong>in</strong>dest die leidigen delete Probleme ausschließt. Allerd<strong>in</strong>gs<br />
möchte ich gleich vorausschicken, dass das delete Problem durch e<strong>in</strong>en wirklich<br />
sehr schmutzigen Trick elim<strong>in</strong>iert wurde, der selbst wieder e<strong>in</strong>e Zeitbombe<br />
darstellen kann. Warum ich die Lösung trotzdem vorstelle hat e<strong>in</strong>en e<strong>in</strong>fachen<br />
Grund: Die hier e<strong>in</strong>gebaute Zeitbombe existiert genau an e<strong>in</strong>er Stelle im<br />
Programm und sie ist auf jeder Plattform für sich testbar. Funktioniert alles<br />
wie geplant, ist es o.k., wenn nicht, weiß man genau, wo man zu suchen hat.<br />
Die Zeitbombe, die entsteht, wenn man destroy falsch anwendet, verteilt<br />
sich wunderhübsch im gesamten Programm, ist also bei weitem gefährlicher.<br />
Die erste Änderung im Programm betrifft unsere Basisklasse für die selbst<br />
verwalteten Objekte. Diese wurde um e<strong>in</strong>e Variable mem_provider_ und um<br />
den Operator delete ergänzt. Auch wurde konsequenterweise der delete[]<br />
Operator verboten. Außerdem wurde plangemäß die Methode destroy elim<strong>in</strong>iert:
49 class SpecialMemoryManagedObject<br />
50 {<br />
51 private :<br />
52 // f o r b i d standard i n s t a n t i a t i o n<br />
53 void ∗ operator new( s i z e t s i z e )<br />
54 throw( b a d a l l o c ) { return ( ( void ∗ ) 0 ) ; }<br />
55<br />
56 // f o r b i d array i n s t a n t i a t i o n<br />
57 void ∗ operator new [ ] ( s i z e t s i z e )<br />
58 throw( b a d a l l o c ) { return ( ( void ∗ ) 0 ) ; }<br />
59<br />
60 // as a consequence a l s o f o r b i d array d e l e t i o n<br />
61 void operator delete [ ] ( void ∗ base ptr )<br />
62 throw( ) { }<br />
63<br />
64 protected :<br />
65 MemoryProvider ∗ mem provider ;<br />
66<br />
12.3 Speicherverwaltung 379<br />
67 public :<br />
68 void ∗ operator new( s i z e t s i z e , MemoryProvider ∗ mem provider )<br />
69 throw( b a d a l l o c ) ;<br />
70<br />
71 void operator delete ( void ∗ base ptr )<br />
72 throw ( ) ;<br />
73<br />
74 SpecialMemoryManagedObject ( ) { }<br />
75<br />
76 virtual ˜ SpecialMemoryManagedObject ( ) { }<br />
77 } ;<br />
Die Variable mem_provider_ dient dazu, sich <strong>in</strong>tern zu merken, von welchem<br />
Provider der Speicherblock angefordert wurde. Damit braucht sich die verwendende<br />
Applikation nicht mehr selbst bei der Freigabe darum zu kümmern.<br />
Die Implementation von new() wurde entsprechend abgeändert. Sie sorgt<br />
jetzt dafür, dass der übergebene Placement Parameter auch im erzeugten<br />
Objekt gespeichert wird:<br />
112 void ∗ SpecialMemoryManagedObject : : operator new(<br />
113 s i z e t s i z e , MemoryProvider ∗ mem provider )<br />
114 throw( b a d a l l o c )<br />
115 {<br />
116 cout
380 12. Operator Overload<strong>in</strong>g<br />
Sollte also nun jemand auf die fatale Idee kommen, im Konstruktor dieses<br />
Objekts die Variable mem_provider_ <strong>in</strong>itialisieren zu wollen, dann geht Gewaltiges<br />
schief. Allerd<strong>in</strong>gs muss man sagen, dass alle Entwickler, die sich<br />
an e<strong>in</strong>er existenten Klasse zur Speicherverwaltung vergreifen wollen, wirklich<br />
genau wissen müssen, was sie tun. Ansonsten sollten sie sowieso gefälligst<br />
die F<strong>in</strong>ger davon lassen. Insofern ist die Gefahr (hoffentlich) nur noch halb<br />
so groß :-).<br />
Der zweite Teil des schmutzigen Tricks f<strong>in</strong>det sich im delete Operator<br />
wieder:<br />
128 void SpecialMemoryManagedObject : : operator delete ( void ∗ base ptr )<br />
129 throw( )<br />
130 {<br />
131 cout <br />
135 mem provider ;<br />
136 cout
180 delete n o n v o l a t i l e o b j ;<br />
181<br />
182 return ( 0 ) ;<br />
183 }<br />
12.3 Speicherverwaltung 381<br />
Der Beweis, dass dieses Meisterwerk trotz aller schmutzigen Tricks auch wirklich<br />
funktioniert, f<strong>in</strong>det sich im folgenden Output. Man sieht deutlich, dass<br />
für die beiden Objekte, die von verschiedenen Providern erzeugt wurden,<br />
auch dieselben Provider zum Freigeben wieder angesprochen werden. Um<br />
bösen Zungen zuvorzukommen: Ich verspreche, dass dieser Output ke<strong>in</strong> Fake<br />
ist :-).<br />
c r e a t i n g object <strong>in</strong> f a s t memory . . .<br />
new o f SpecialMemoryManagedObject c a l l e d , s i z e : 1 6<br />
provider : 0 x804a6d0<br />
constructor of MyMemManagedObject<br />
c r e a t i n g object <strong>in</strong> non v o l a t i l e memory . . .<br />
new o f SpecialMemoryManagedObject c a l l e d , s i z e : 1 6<br />
provider : 0 x804a6e0<br />
constructor of MyMemManagedObject<br />
d e l e t i n g o bject from f a s t memory . . .<br />
d e s t r u c t o r of MyMemManagedObject<br />
d e l e t e o f SpecialMemoryManagedObject c a l l e d , provider : 0 x804a6d0<br />
d e l e t i n g o bject from non v o l a t i l e memory . . .<br />
d e s t r u c t o r of MyMemManagedObject<br />
d e l e t e o f SpecialMemoryManagedObject c a l l e d , provider : 0 x804a6e0<br />
12.3.4 delete mit zwei Parametern<br />
Bisher haben wir delete so kennen gelernt, dass e<strong>in</strong>fach der Base-Po<strong>in</strong>ter<br />
des freizugebenden Blocks als Parameter übergeben wurde. Für unsere<br />
Fälle war das auch ke<strong>in</strong> Problem, denn wir haben immer nur e<strong>in</strong>e Dummy-<br />
Speicherverwaltung verwendet, anstatt uns wirklich um den Speicher selbst<br />
zu kümmern. Schreibt man aber tatsächlich selbst e<strong>in</strong>e Speicherverwaltung,<br />
so hat man zwei Möglichkeiten:<br />
1. Man merkt sich zu jedem angeforderten Block <strong>in</strong>tern selbst die Größe,<br />
die angefordert wurde. Damit braucht man beim Freigeben ke<strong>in</strong>e weitere<br />
Information als den Base-Po<strong>in</strong>ter, um den Rest erledigen zu können, denn<br />
man weiß ja über den Block genau Bescheid.<br />
2. Man verlässt sich auf das System und fordert, dass man beim Freigeben<br />
gefälligst mitgeteilt bekommt, wie groß denn der Block ist, den man<br />
freigeben soll.<br />
Vorausschicken möchte ich, dass die erste Methode immer die Methode der<br />
Wahl se<strong>in</strong> sollte, denn diese ist die sichere Variante (es wird weiter unten<br />
noch besprochen, warum). Im Normalfall werden auch alle Memory<br />
Verwaltungen nach dieser ersten Methode implementiert. In den seltenen<br />
Ausnahmefällen, <strong>in</strong> denen man dies, aus welchen Gründen auch immer,<br />
nicht tun will und das Merken der Blockgröße dem System überlässt,
382 12. Operator Overload<strong>in</strong>g<br />
hilft e<strong>in</strong> spezieller delete Operator, der zwei Parameter übergeben bekommt,<br />
nämlich den Base-Po<strong>in</strong>ter und die Größe. Im folgenden Beispiel<br />
(fifth_new_delete_overload<strong>in</strong>g_demo.cpp) wurde e<strong>in</strong>e Abwandlung unsere<br />
selbstverwalteten Objekte implementiert. Wiederum gibt es e<strong>in</strong>e Basisklasse,<br />
die entsprechende new, new[], delete und delete[] Operatoren<br />
implementiert. Die Deklaration sieht folgendermaßen aus:<br />
23 class MemoryManagedObject<br />
24 {<br />
25 public :<br />
26 void ∗ operator new( s i z e t s i z e )<br />
27 throw( b a d a l l o c ) ;<br />
28<br />
29 void ∗ operator new [ ] ( s i z e t s i z e )<br />
30 throw( b a d a l l o c ) ;<br />
31<br />
32 void operator delete ( void ∗ base ptr , s i z e t s i z e )<br />
33 throw ( ) ;<br />
34<br />
35 void operator delete [ ] ( void ∗ base ptr , s i z e t s i z e )<br />
36 throw ( ) ;<br />
37<br />
38 virtual ˜ MemoryManagedObject ( ) { }<br />
39 } ;<br />
Bei den beiden delete Operatoren ist nun jeweils e<strong>in</strong> zweiter Parameter<br />
dabei, der Auskunft über die Größe des freizugebenden Blocks gibt. Die Implementation<br />
der Operatoren <strong>in</strong> unserem Beispiel birgt nichts wirklich Neues<br />
und liest sich so:<br />
70 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
71 /∗<br />
72 ∗/<br />
73 void ∗ MemoryManagedObject : : operator new( s i z e t s i z e )<br />
74 throw( b a d a l l o c )<br />
75 {<br />
76 cout
12.3 Speicherverwaltung 383<br />
99 cout
384 12. Operator Overload<strong>in</strong>g<br />
130<br />
131 cout
12.3 Speicherverwaltung 385<br />
Blocks übergeben wird. Niemand h<strong>in</strong>dert uns daran, aus <strong>in</strong>ternen Gründen<br />
e<strong>in</strong> wenig mehr Speicher für e<strong>in</strong>en Block zu allokieren. Davon weiß allerd<strong>in</strong>gs<br />
der Compiler nichts, wie sollte er auch? Das bedeutet aber, dass der bei<br />
delete bzw. delete[] übergebene Größenparameter wieder nur dem geforderten<br />
M<strong>in</strong>destwert entspricht, anstatt unsere tatsächlich allokierte Größe zu<br />
reflektieren! Wie man sich leicht vorstellen kann, erzeugt man durch dieses<br />
Verhalten wunderbar tief fliegende Programme, die e<strong>in</strong>em viele angenehme<br />
Nächte der Fehlersuche bescheren :-).<br />
12.3.5 Globale new und delete Operatoren<br />
Bisher haben wir uns nur damit beschäftigt, wie man auf Klassenbasis die<br />
Speicherverwaltung bee<strong>in</strong>flussen kann. Was aber, wenn man wirklich die<br />
komplette Speicherverwaltung <strong>in</strong> e<strong>in</strong>em Programm auf e<strong>in</strong>en Schlag austauschen<br />
will? Auch daran wurde <strong>in</strong> C ++ gedacht und aus diesem Grund gibt es<br />
die Operatoren auch <strong>in</strong> globaler Variante, also quasi als Funktionen anstatt<br />
als Members.<br />
Wie man im folgenden Beispiel sieht, funktionieren die globalen Operatoren<br />
im Pr<strong>in</strong>zip ganz gleich wie die Members, die nur für e<strong>in</strong>zelne Klassen<br />
gelten (global_new_delete_overload<strong>in</strong>g.cpp). Die globalen Operatoren<br />
lesen sich erwartungsgemäß so:<br />
14 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
15 /∗<br />
16 ∗/<br />
17 void ∗ operator new( s i z e t s i z e )<br />
18 throw( b a d a l l o c )<br />
19 {<br />
20 cout
386 12. Operator Overload<strong>in</strong>g<br />
46 /∗<br />
47 ∗/<br />
48 void operator delete [ ] ( void ∗ base ptr )<br />
49 throw( )<br />
50 {<br />
51 cout
101 throw ( ) ;<br />
102<br />
103 void operator delete [ ] ( void ∗ base ptr )<br />
104 throw ( ) ;<br />
105<br />
12.3 Speicherverwaltung 387<br />
106 MemoryManagedObject ( )<br />
107 {<br />
108 cout
388 12. Operator Overload<strong>in</strong>g<br />
208 cout
12.3 Speicherverwaltung 389<br />
bedeutet, dass wir durch Overload<strong>in</strong>g der globalen Operatoren ausschließlich<br />
die Verwaltung des Speichers für Objekte übernehmen können, deren Klassen<br />
selbst noch ke<strong>in</strong>e besondere Speicherverwaltung implementieren.<br />
Vorsicht Falle: Da die globalen Operatoren nur dann herangezogen werden,<br />
wenn e<strong>in</strong>e Klasse nicht bereits selbst die entsprechenden new und delete<br />
Operatoren implementiert, hat das Austauschen der Speicherverwaltung für<br />
e<strong>in</strong> gesamtes Programm nach dieser Methode mit äußerster Umsicht zu geschehen.<br />
Sonst kann es leicht zu sehr unangenehmen Interferenzen kommen,<br />
sollten die Pr<strong>in</strong>zipien der beiden Speicherverwaltungsalgorithmen nicht kompatibel<br />
zue<strong>in</strong>ander se<strong>in</strong>!<br />
Besonders bösartig wird dieser Fehler, wenn z.B. e<strong>in</strong>e Klasse, aus welchen<br />
Gründen auch immer, nur die new Operatoren implementiert, aber delete<br />
dem System überlässt. Dann passiert nämlich der Fall, dass das klassen<strong>in</strong>terne<br />
new und als Gegenstück dazu das globale delete aufgerufen wird.<br />
Plötzlich bekommt also das globale delete e<strong>in</strong>en Block zur Freigabe, den<br />
es selbst gar nicht bereitgestellt hat! Je nachdem, welcher Algorithmus für<br />
die globale Speicherverwaltung implementiert wurde, kann dies dazu führen,<br />
dass sich die Entwickler des Programms mit erheblichen Tiefflugversuchen<br />
desselben konfrontiert sehen, die sie sich beim besten Willen nicht erklären<br />
können.<br />
Daher möchte ich hier ganz e<strong>in</strong>dr<strong>in</strong>glich e<strong>in</strong>en Ratschlag anbr<strong>in</strong>gen:<br />
Wenn man e<strong>in</strong>e <strong>in</strong>dividuelle Speicherverwaltung für e<strong>in</strong>e Klasse<br />
schreibt, dann müssen unbed<strong>in</strong>gt immer die Operatoren paarweise<br />
implementiert werden. Niemals darf e<strong>in</strong> new ohne zugehöriges<br />
delete implementiert werden oder umgekehrt!<br />
Je nachdem, was man mit <strong>in</strong>dividueller Speicherverwaltung <strong>in</strong> Klassen<br />
bezweckt, kann man vorausschauend genug arbeiten, um dieses Problem zum<strong>in</strong>dest<br />
<strong>in</strong> e<strong>in</strong>igen Fällen gar nicht erst aufkommen zu lassen. Nehmen wir<br />
an, die new und delete Operatoren wurden deshalb <strong>in</strong> e<strong>in</strong>er Klasse <strong>in</strong>dividuell<br />
implementiert, weil zusätzlich zum Beschaffen und Freigeben von Speicher<br />
noch etwas anderes stattf<strong>in</strong>den soll (z.B. <strong>in</strong>krementieren bzw. dekrementieren<br />
e<strong>in</strong>es Reference Counters). Wo der Speicher herkommt, ist allerd<strong>in</strong>gs den<br />
Operatoren egal, sie würden sowieso e<strong>in</strong>fach nur malloc und free verwenden.<br />
Für diesen Fall kann man den zuvor erwähnten Stolperste<strong>in</strong> umgehen,<br />
<strong>in</strong>dem man nicht malloc und free aufruft, sondern stattdessen, mittels<br />
Scope-Operator, die globalen Operatoren new und delete bzw. new[] und<br />
delete[]. Solange diese nicht von uns besonders implementiert s<strong>in</strong>d, werden<br />
automatisch die system<strong>in</strong>ternen Operatoren herangezogen, sonst unsere<br />
besonderen Varianten. Schreiben wir also unsere Implementation der Operatoren<br />
für das MemoryManagedObject den neuen Erkenntnissen entsprechend<br />
um (global_new_delete_overload<strong>in</strong>g_version2.cpp):
390 12. Operator Overload<strong>in</strong>g<br />
146 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
147 /∗<br />
148 ∗/<br />
149 void ∗ MemoryManagedObject : : operator new( s i z e t s i z e )<br />
150 throw( b a d a l l o c )<br />
151 {<br />
152 cout
12.3 Speicherverwaltung 391<br />
d e s t r u c t o r of SimpleObject<br />
d e s t r u c t o r of SimpleObject<br />
g l o b a l d e l e t e [ ] c a l l e d with base : 0 x804b2f0<br />
c r e a t i n g MyMemoryManagedObject<br />
new o f MemoryManagedObject c a l l e d with s i z e 12<br />
g l o b a l new c a l l e d with s i z e 12<br />
constructor of MemoryManagedObject<br />
constructor of MyMemoryManagedObject<br />
d e l e t i n g MyMemoryManagedObject . . .<br />
d e s t r u c t o r of MyMemoryManagedObject<br />
d e s t r u c t o r of MemoryManagedObject<br />
d e l e t e o f MemoryManagedObject c a l l e d with base : 0 x804b2f0<br />
g l o b a l d e l e t e c a l l e d with base : 0 x804b2f0<br />
c r e a t i n g MyMemoryManagedObject array . . .<br />
new [ ] of MemoryManagedObject c a l l e d with s i z e 40<br />
g l o b a l new [ ] c a l l e d with s i z e 40<br />
constructor of MemoryManagedObject<br />
constructor of MyMemoryManagedObject<br />
constructor of MemoryManagedObject<br />
constructor of MyMemoryManagedObject<br />
constructor of MemoryManagedObject<br />
constructor of MyMemoryManagedObject<br />
d e l e t i n g MyMemoryManagedObject array . . .<br />
d e s t r u c t o r of MyMemoryManagedObject<br />
d e s t r u c t o r of MemoryManagedObject<br />
d e s t r u c t o r of MyMemoryManagedObject<br />
d e s t r u c t o r of MemoryManagedObject<br />
d e s t r u c t o r of MyMemoryManagedObject<br />
d e s t r u c t o r of MemoryManagedObject<br />
d e l e t e [ ] of MemoryManagedObject c a l l e d with base : 0 x804b2f0<br />
g l o b a l d e l e t e [ ] c a l l e d with base : 0 x804b2f0<br />
12.3.6 Weitere Aspekte der eigenen Speicherverwaltung<br />
Um die Abhandlung über die handgestrickte Speicherverwaltung abzurunden,<br />
möchte ich hier noch kurz auf e<strong>in</strong> paar kle<strong>in</strong>e Aspekte e<strong>in</strong>gehen, die <strong>in</strong> der<br />
Praxis Relevanz besitzen.<br />
Vererbung von new und delete. Es ist natürlich nicht nur möglich, die<br />
new und delete Operatoren e<strong>in</strong>fach e<strong>in</strong>mal mittels Overload<strong>in</strong>g für <strong>in</strong>dividuelle<br />
Klassen zu implementieren und diese Implementation zu vererben. Auch<br />
hier gelten die üblichen Regeln für das Overrid<strong>in</strong>g. Sollte man also von e<strong>in</strong>er<br />
Basisklasse ableiten, die <strong>in</strong>dividuelle Speicherverwaltung macht, und sollte<br />
man weiters mit diesem Verhalten nicht zufrieden se<strong>in</strong>, so kann man <strong>in</strong> der<br />
Ableitung selbst e<strong>in</strong> Overrid<strong>in</strong>g vornehmen und damit die Operatoren aus<br />
der Basisklasse außer Kraft setzen. Im folgenden Beispiel ist dies kurz demonstriert<br />
(new_delete_overrid<strong>in</strong>g.cpp):<br />
1 // n e w d e l e t e o v e r r i d i n g . cpp − a demo f o r o v e r r i d i n g<br />
2 // i n d i v i d u a l new and d e l e t e operators <strong>in</strong> s u b c l a s s e s<br />
3<br />
4 #<strong>in</strong>clude <br />
5 #<strong>in</strong>clude <br />
6 #<strong>in</strong>clude <br />
7 #<strong>in</strong>clude < c s t d l i b><br />
8 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
9
392 12. Operator Overload<strong>in</strong>g<br />
10 us<strong>in</strong>g std : : b a d a l l o c ;<br />
11 us<strong>in</strong>g std : : cout ;<br />
12 us<strong>in</strong>g std : : endl ;<br />
13<br />
14<br />
15 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
16 /∗<br />
17 ∗ MemoryManagedObject<br />
18 ∗<br />
19 ∗ j u s t an o bject with i t s own memory management<br />
20 ∗<br />
21 ∗/<br />
22<br />
23 class MemoryManagedObject<br />
24 {<br />
25 public :<br />
26 void ∗ operator new( s i z e t s i z e )<br />
27 throw( b a d a l l o c )<br />
28 {<br />
29 cout
76 ∗/<br />
77<br />
12.3 Speicherverwaltung 393<br />
78 class MyMemoryManagedObject : public MemoryManagedObject<br />
79 {<br />
80 protected :<br />
81 <strong>in</strong>t32 some data ;<br />
82 u<strong>in</strong>t32 some other data ;<br />
83 public :<br />
84 void ∗ operator new( s i z e t s i z e )<br />
85 throw( b a d a l l o c )<br />
86 {<br />
87 cout
394 12. Operator Overload<strong>in</strong>g<br />
Das MemoryManagedObject hat sich bis auf den Umbau zu <strong>in</strong>l<strong>in</strong>e Methoden<br />
<strong>in</strong> Bezug auf den bereits bekannten Vorgänger nicht verändert. Sehr<br />
wohl aber ist MyMemoryManagedObject e<strong>in</strong> wenig mutiert: Es s<strong>in</strong>d jetzt <strong>in</strong><br />
dieser Klasse die Operatoren new und delete für <strong>in</strong>dividuelle Objekte overridden.<br />
Die entsprechenden Array-Operatoren wurden beim Alten belassen.<br />
Der Output des Programms zeigt auch gleich, dass dieser Umbau den erwarteten<br />
Effekt hat:<br />
c r e a t i n g MyMemoryManagedObject<br />
overridden new ! ! ! ! ! s i z e = 12<br />
constructor of MemoryManagedObject<br />
constructor of MyMemoryManagedObject<br />
d e l e t i n g MyMemoryManagedObject . . .<br />
d e s t r u c t o r of MyMemoryManagedObject<br />
d e s t r u c t o r of MemoryManagedObject<br />
overridden d e l e t e ! ! ! ! ! base = 0 x804ab68<br />
c r e a t i n g MyMemoryManagedObject array . . .<br />
new [ ] of MemoryManagedObject c a l l e d with s i z e 40<br />
constructor of MemoryManagedObject<br />
constructor of MyMemoryManagedObject<br />
constructor of MemoryManagedObject<br />
constructor of MyMemoryManagedObject<br />
constructor of MemoryManagedObject<br />
constructor of MyMemoryManagedObject<br />
d e l e t i n g MyMemoryManagedObject array . . .<br />
d e s t r u c t o r of MyMemoryManagedObject<br />
d e s t r u c t o r of MemoryManagedObject<br />
d e s t r u c t o r of MyMemoryManagedObject<br />
d e s t r u c t o r of MemoryManagedObject<br />
d e s t r u c t o r of MyMemoryManagedObject<br />
d e s t r u c t o r of MemoryManagedObject<br />
d e l e t e [ ] of MemoryManagedObject c a l l e d with base : 0 x804ab68<br />
Dass natürlich beim Overrid<strong>in</strong>g auch wieder äußerste Vorsicht geboten ist, um<br />
nicht die Speicherverwaltung des Orig<strong>in</strong>als völlig durche<strong>in</strong>ander zu br<strong>in</strong>gen,<br />
versteht sich von selbst.<br />
Manchmal gibt es auch Fälle, <strong>in</strong> denen es absolut nicht erwünscht ist,<br />
dass e<strong>in</strong>e abgeleitete Klasse die Operatoren new und delete erbt, da sie<br />
aus irgendwelchen Gründen sehr speziell implementiert s<strong>in</strong>d und nur für die<br />
Basisklasse korrekt funktionieren würden. In solchen Fällen wird oft folgende<br />
Implementation gewählt (new_and_delete_only_for_base.cpp):<br />
1 // n e w a n d d e l e t e o n l y f o r b a s e . cpp − a demo , how to implement<br />
2 // new and d e l e t e <strong>in</strong> a way that they do not i n f l u e n c e memory<br />
3 // management of derived c l a s s e s<br />
4<br />
5 #<strong>in</strong>clude <br />
6 #<strong>in</strong>clude <br />
7 #<strong>in</strong>clude <br />
8 #<strong>in</strong>clude < c s t d l i b><br />
9 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
10<br />
11 us<strong>in</strong>g std : : b a d a l l o c ;<br />
12 us<strong>in</strong>g std : : cout ;<br />
13 us<strong>in</strong>g std : : endl ;<br />
14<br />
15<br />
16 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
17 /∗
12.3 Speicherverwaltung 395<br />
18 ∗ MemoryManagedObject<br />
19 ∗<br />
20 ∗ an o bject with i t s own memory management which checks f o r<br />
21 ∗ i n h e r i t a n c e s i t u a t i o n s<br />
22 ∗<br />
23 ∗/<br />
24<br />
25 class MemoryManagedObject<br />
26 {<br />
27 public :<br />
28 void ∗ operator new( s i z e t s i z e )<br />
29 throw( b a d a l l o c )<br />
30 {<br />
31 cout
396 12. Operator Overload<strong>in</strong>g<br />
84 } ;<br />
85<br />
86 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
87 /∗<br />
88 ∗/<br />
89 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
90 {<br />
91 cout
12.3 Speicherverwaltung 397<br />
Wenn <strong>in</strong> der Ableitung Member-Variablen def<strong>in</strong>iert werden, dann ist es<br />
be<strong>in</strong>ahe sicher, dass sich die Größe der Ableitung im Vergleich zur Basis<br />
ändert. Jedoch kann man es aus verschiedenen compiler<strong>in</strong>ternen Gründen<br />
heraus auch nicht wirklich garantieren.<br />
Im S<strong>in</strong>ne e<strong>in</strong>er robusten Entwicklung kann ich daher nur sagen, dass Probleme<br />
wie dieses per Konvention und nicht über e<strong>in</strong>en Trick gelöst werden<br />
sollten. Nur dann bewegt man sich immer auf der sicheren Seite.<br />
Verhalten bei “Ausgehen” des Speichers. In Abschnitt 12.3.1 wurde<br />
bereits darauf h<strong>in</strong>gewiesen, dass erstens malloc ke<strong>in</strong>e Exception wirft, falls<br />
nicht mehr genügend Speicher vorhanden ist, sondern e<strong>in</strong>en 0-Po<strong>in</strong>ter liefert.<br />
Zweitens dürfen selbstdef<strong>in</strong>ierte new Operatoren auch nicht e<strong>in</strong>fach bl<strong>in</strong>dl<strong>in</strong>gs<br />
e<strong>in</strong>e bad_alloc Exception werfen, sondern müssen per Konvention dem<br />
Entwickler zuvor noch e<strong>in</strong>e besondere Chance geben. Diese Chance ist der<br />
besondere new_handler.<br />
Sehen wir uns das ganze Spielchen e<strong>in</strong>mal aus Sicht der Applikationsentwickler<br />
an, die mit speziellen Implementationen von new und delete nichts<br />
am Hut haben. E<strong>in</strong>e Applikation hat per Konvention <strong>in</strong> C ++ die Möglichkeit,<br />
e<strong>in</strong>e Funktion set_new_handler aufzurufen, die als Parameter e<strong>in</strong>en Funktionspo<strong>in</strong>ter<br />
nimmt, der auf e<strong>in</strong>e Funktion zeigt, die ke<strong>in</strong>e Parameter nimmt<br />
und ke<strong>in</strong>en return-Value liefert. Dieser spezielle Handler kann z.B. dafür sorgen,<br />
dass auf irgende<strong>in</strong>em Weg Speicher freigemacht wird oder er kann auch<br />
e<strong>in</strong>fach e<strong>in</strong>e bad_alloc Exception werfen, wenn er ke<strong>in</strong>e Chance mehr sieht.<br />
Im Pr<strong>in</strong>zip muss er sogar e<strong>in</strong>e bad_alloc Exception werfen, wenn es ke<strong>in</strong>e<br />
weitere Chance mehr gibt, denn sonst kommt es zu e<strong>in</strong>er Endlosschleife,<br />
doch dazu später. Sehen wir uns dazu kurz e<strong>in</strong> Beispielprogrämmchen an<br />
(first_new_handler_demo.cpp):<br />
1 // first new handler demo . cpp − a demo f o r s e t t i n g a new handler<br />
2 // from with<strong>in</strong> an a p p l i c a t i o n<br />
3<br />
4 #<strong>in</strong>clude <br />
5 #<strong>in</strong>clude <br />
6 #<strong>in</strong>clude <br />
7 #<strong>in</strong>clude < c s t d l i b><br />
8 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
9<br />
10 us<strong>in</strong>g std : : b a d a l l o c ;<br />
11 us<strong>in</strong>g std : : cout ;<br />
12 us<strong>in</strong>g std : : c e r r ;<br />
13 us<strong>in</strong>g std : : endl ;<br />
14 us<strong>in</strong>g std : : set new handler ;<br />
15<br />
16 <strong>in</strong>t32 ∗ dummy memory consumer ;<br />
17<br />
18 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
19 /∗<br />
20 ∗/<br />
21 void memoryExhaustedHook ( )<br />
22 throw( b a d a l l o c )<br />
23 {<br />
24 c e r r
398 12. Operator Overload<strong>in</strong>g<br />
25 i f ( dummy memory consumer ) // there i s someth<strong>in</strong>g to get r i d o f<br />
26 {<br />
27 delete [ ] dummy memory consumer ;<br />
28 dummy memory consumer = 0;<br />
29 c e r r try aga<strong>in</strong> ” b a d a l l o c<br />
Per Def<strong>in</strong>ition liefert die Funktion set_new_handler den zuletzt aktiven<br />
Handler. Je nach Applikation sollte man sich diesen merken und wieder e<strong>in</strong>setzen,<br />
wenn man se<strong>in</strong>en eigenen Handler nicht mehr im Programm haben<br />
will.<br />
Nachdem wir jetzt das erwartete Verhalten bei Speicherknappheit kennen,<br />
wissen wir endlich auch, wie sich e<strong>in</strong> eigener new Operator korrekt zu verhalten<br />
hat, um dieser Konvention zu entsprechen. Zur Demonstration habe<br />
ich das obige Beispiel um e<strong>in</strong>e korrekt funktionierende Def<strong>in</strong>ition e<strong>in</strong>es globalen<br />
new[] Operators erweitert (correct_new_operator_demo.cpp). Bis<br />
auf den neu h<strong>in</strong>zugekommenen new[] Operator hat sich das Programm nicht<br />
verändert, also möchte ich auch nur diesen hier abdrucken:<br />
23 void ∗ operator new [ ] ( s i z e t s i z e )<br />
24 throw( b a d a l l o c )<br />
25 {<br />
26 cout
12.3 Speicherverwaltung 399<br />
32 // i s the standard place , where i t i s stored . However ,<br />
33 // t h i s i s <strong>C++</strong>−Compiler Implementation dependent and may<br />
34 // not work . Therefore there i s a more portable<br />
35 // t r i c k to obta<strong>in</strong> the new handler :<br />
36 new handler i n s t a l l e d h a n d l e r = set new handler ( 0 ) ;<br />
37 set new handler ( i n s t a l l e d h a n d l e r ) ;<br />
38<br />
39 i f ( ! i n s t a l l e d h a n d l e r )<br />
40 throw b a d a l l o c ( ) ; // no handler i n s t a l l e d , sorry . . .<br />
41 i n s t a l l e d h a n d l e r ( ) ; // handler e x i s t s − there i s hope :−)<br />
42 }<br />
43 }<br />
Man sieht hier ganz deutlich, warum ich zuvor geme<strong>in</strong>t habe, dass e<strong>in</strong> eigener<br />
Handler unbed<strong>in</strong>gt e<strong>in</strong>e bad_alloc Exception werfen muss, wenn er<br />
beschließt, dass gar nichts mehr geht: solange er dies nämlich nicht tut, signalisiert<br />
er, dass es möglich wäre, dass er vielleicht doch noch irgendwo e<strong>in</strong><br />
wenig Speicher auftreiben kann. Es ist also durchaus legitim, dass e<strong>in</strong> solcher<br />
Handler nicht auf e<strong>in</strong>en Schlag e<strong>in</strong>en riesigen Brocken Memory frei macht.<br />
Stattdessen kann er bei jedem Aufruf e<strong>in</strong> wenig mehr Speicher frei machen,<br />
bis new endlich zufrieden ist. Deshalb läuft die Speicherbeschaffung auch <strong>in</strong><br />
e<strong>in</strong>er Endlosschleife, wie man <strong>in</strong> den Zeilen 27–42 sehen kann. Diese wird nur<br />
dadurch beendet, dass entweder genug Speicher da ist oder e<strong>in</strong>e Exception<br />
das Gemetzel beendet.<br />
In den Zeilen 36–37 wird ganz bewusst e<strong>in</strong> kle<strong>in</strong>er Trick implementiert,<br />
um zum entsprechenden Handler zu kommen: Im Normalfall ist der Handler<br />
<strong>in</strong> der globalen Variable _new_handler gespeichert. Allerd<strong>in</strong>gs s<strong>in</strong>d Variablen,<br />
die mit e<strong>in</strong>em Underl<strong>in</strong>e beg<strong>in</strong>nen, per Konvention besondere Systemvariablen,<br />
die von Applikationsentwicklern gefälligst nicht verwendet werden<br />
sollen. Nebenbei haben solche Variablen auch die nicht gerade verwunderliche<br />
Eigenschaft, ohne vorherige Ankündigung ihren Namen oder Platz zu<br />
ändern. Alles das führt zu e<strong>in</strong>er nicht besonders portablen Implementation.<br />
Wir wissen aber, dass die Funktion set_new_handler als return-Value den<br />
zuvor <strong>in</strong>stallierten Handler liefert. Genau diese Eigenschaft nützen wir hier<br />
aus, <strong>in</strong>dem wir e<strong>in</strong>en neuen Handler <strong>in</strong>stallieren (e<strong>in</strong>fach e<strong>in</strong>en 0-Handler).<br />
Dadurch bekommen wir den tatsächlich <strong>in</strong>stallierten, den wir gleich <strong>in</strong> Zeile<br />
37 wieder e<strong>in</strong>setzen, damit alles beim Alten bleibt. Und schon haben wir<br />
durch diesen kle<strong>in</strong>en Trick unser Programm viel portabler gemacht, als es<br />
sonst der Fall wäre :-).<br />
Der Output des Programms zeigt, dass diese Implementation genau das<br />
tut, was wir von ihr erwarten. Je nach Rechner fällt die Anzahl der Zeilen,<br />
die vor dem ersten Fehlschlag von malloc ausgegeben wird ziemlich lang aus.<br />
Aufgrund der Wertlosigkeit und der gleichzeitigen hohen Anzahl dieser Zeilen<br />
wurde der Output entsprechend gekürzt :-).<br />
g l o b a l new [ ] c a l l e d with s i z e 800000<br />
g l o b a l new [ ] c a l l e d with s i z e 400000<br />
[ . . . l o t s o f equal l i n e s deleted . . . ]<br />
g l o b a l new [ ] c a l l e d with s i z e 400000
400 12. Operator Overload<strong>in</strong>g<br />
memoryExhaustedHook c a l l e d<br />
could f r e e some memory −> try aga<strong>in</strong><br />
g l o b a l new [ ] c a l l e d with s i z e 400000<br />
g l o b a l new [ ] c a l l e d with s i z e 400000<br />
memoryExhaustedHook c a l l e d<br />
no more memory a v a i l a b l e −> b a d a l l o c<br />
12.4 Abschließendes zu overloadable Operators<br />
Nachdem nun Operator Overload<strong>in</strong>g im Allgeme<strong>in</strong>en und Spezialitäten sowie<br />
Tücken der Typumwandlungs-Operatoren und der Speicherverwaltung mittels<br />
new und delete im Speziellen besprochen wurden, möchte ich an dieser<br />
Stelle noch schnell e<strong>in</strong> paar abschließende Worte zu diesem Thema verlieren.<br />
Vor allem gehört erwähnt, dass nicht wirklich alle Operatoren, die <strong>in</strong> C ++<br />
zur Verfügung stehen (siehe Tafel 3.1), auch wirklich selbst def<strong>in</strong>iert werden<br />
können.<br />
Die wenigen Ausnahmen hierbei bilden der Scope-Operator (::), der<br />
Member-Zugriffsoperator (.) und der Member-Zugriff über das Dereferenzieren<br />
e<strong>in</strong>es Po<strong>in</strong>ters auf e<strong>in</strong>en Member (.*).<br />
Um Missverständnisse zu vermeiden: Den Pfeil-Operator (->) kann man<br />
sehr wohl selbst def<strong>in</strong>ieren. Diese Möglichkeit kann man sehr gut zum<br />
E<strong>in</strong>führen e<strong>in</strong>er automatischen Indirektion auf e<strong>in</strong> anderes Objekt gebrauchen,<br />
wie es für verschiedene Konzepte der Delegation notwendig ist. E<strong>in</strong>e<br />
allzu genaue Abhandlung zu solchen Themen gehört allerd<strong>in</strong>gs <strong>in</strong> die weiterführende<br />
Literatur und würde den Rahmen dieses Buchs sprengen. Da<br />
jedoch e<strong>in</strong> Overload<strong>in</strong>g des -> Operators e<strong>in</strong> Verhalten zeigt, das für Neul<strong>in</strong>ge<br />
nicht auf den ersten Blick e<strong>in</strong>sichtig ist, möchte ich hierzu noch e<strong>in</strong> kurzes<br />
Beispiel besprechen (member_access_overload<strong>in</strong>g_demo.cpp):<br />
1 // member access overload<strong>in</strong>g demo . cpp − short demo f o r<br />
2 // overload<strong>in</strong>g of the −> operator<br />
3<br />
4 #<strong>in</strong>clude <br />
5 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
6<br />
7 us<strong>in</strong>g std : : cout ;<br />
8 us<strong>in</strong>g std : : endl ;<br />
9<br />
10 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
11 /∗<br />
12 ∗ MyObject<br />
13 ∗<br />
14 ∗ Just a dummy c l a s s<br />
15 ∗<br />
16 ∗/<br />
17<br />
18 class MyObject<br />
19 {<br />
20 protected :<br />
21 u<strong>in</strong>t32 value ;<br />
22 public :<br />
23 MyObject ( u<strong>in</strong>t32 value )
24 throw ( ) { value = value ; }<br />
25<br />
26 virtual ˜ MyObject ( )<br />
27 throw( ) { }<br />
28<br />
29 virtual u<strong>in</strong>t32 getValue ( )<br />
30 {<br />
31 cout
402 12. Operator Overload<strong>in</strong>g<br />
Interessant ist jetzt nur, was hier <strong>in</strong>tern passiert. Es ist nämlich -> e<strong>in</strong> unärer<br />
Operator, wie <strong>in</strong> Zeile 58 zu erkennen ist. Dieser Operator liefert allerd<strong>in</strong>gs<br />
wirklich e<strong>in</strong>en Po<strong>in</strong>ter auf das gewünschte Objekt. So weit, so gut, bloß damit<br />
ist doch eigentlich -> bereits abgearbeitet, also quasi “aufgebraucht”. Wieso<br />
kommt es dann tatsächlich zu e<strong>in</strong>em Aufruf von getValue? Das liegt daran,<br />
dass C ++ diesen Operator zweistufig behandelt:<br />
1. Zuerst wird unser def<strong>in</strong>ierter Operator aufgerufen.<br />
2. Auf dem return-Value wird dann der “echte” Memberzugriff gemäß -><br />
durchgeführt.<br />
Der Ausdruck obj_ptr->getValue() wird also vom Compiler so behandelt,<br />
als ob hier<br />
(obj_ptr.operator->())->getValue()<br />
stünde. Dieses Verhalten ist auch das e<strong>in</strong>zig s<strong>in</strong>nvolle, denn ansonsten würde<br />
e<strong>in</strong> Overload<strong>in</strong>g ke<strong>in</strong>en Wert haben. E<strong>in</strong> Objekt kann nur durch diese Behandlung<br />
des Operators nach außen h<strong>in</strong> so tun, als ob es e<strong>in</strong> Po<strong>in</strong>ter auf e<strong>in</strong><br />
anderes Objekt wäre.<br />
Vorsicht Falle: Drei Operatoren gibt es, bei denen ich allen Lesern den<br />
guten Tipp geben möchte, die F<strong>in</strong>ger von e<strong>in</strong>em Overload<strong>in</strong>g zu lassen, außer<br />
es geht gar nicht anders. Dies ist e<strong>in</strong>erseits der , (=Komma) Operator,<br />
andererseits && und ||. Das Problem dabei ist, dass hier nicht mehr gesagt<br />
ist, ob standardmäßig e<strong>in</strong>e short Evaluation des Ausdrucks stattf<strong>in</strong>det. Der<br />
Compiler kann sehr wohl entscheiden, dass hier auf jeden Fall den selbstgebastelten<br />
Operator aufruft, weil er diesem ja nicht <strong>in</strong>s Handwerk pfuschen<br />
will. Das kann alle möglichen Probleme, vom e<strong>in</strong>fachen Performancemord<br />
bis h<strong>in</strong> zur Segmentation Violation <strong>in</strong> gewissen Situationen nach sich ziehen.<br />
Sollte man also wirklich diese Operatoren selbst def<strong>in</strong>ieren wollen, so muss<br />
man auf allen verwendeten Plattformen testen, welche Art der Evaluierung<br />
vom Compiler herangezogen wird und entsprechend darauf reagieren.<br />
Vorsicht Falle: Neben den bereits erwähnten Fallen gibt es noch e<strong>in</strong>e ganz<br />
besondere, <strong>in</strong> die vor allem Neul<strong>in</strong>ge tappen, wenn sie beg<strong>in</strong>nen, sich beim<br />
Operator Overload<strong>in</strong>g wohl zu fühlen: Die übertriebene und damit oft nicht<br />
mehr <strong>in</strong>tuitive Anwendung des Operator Overload<strong>in</strong>gs!<br />
Operatoren s<strong>in</strong>d nur dann e<strong>in</strong>e Hilfe für Entwickler, wenn sie <strong>in</strong>tuitiv s<strong>in</strong>d.<br />
Zum Beispiel ist e<strong>in</strong> + Operator vollkommen <strong>in</strong>tuitiv, wenn er zur Addition<br />
von z.B. Vektoren oder Matrizen def<strong>in</strong>iert ist. Auch zum ane<strong>in</strong>ander Reihen<br />
von Str<strong>in</strong>gs (=concatenation) ist dessen E<strong>in</strong>satz noch s<strong>in</strong>nvoll. Gar nicht<br />
mehr <strong>in</strong>tuitiv ist dieser Operator, wenn er z.B. für e<strong>in</strong> Objekt Auto als Gas<br />
geben def<strong>in</strong>iert wird und vielleicht auch dann noch der Operator - als bremsen<br />
dazukommt. Klar wird man nach Lektüre der Dokumentation wissen, dass<br />
diese Operatoren so verwendet werden, aber trotzdem ist <strong>in</strong> diesem Fall die<br />
Implementation über Methoden bei weitem <strong>in</strong>tuitiver und leichter lesbar.
12.4 Abschließendes zu overloadable Operators 403<br />
Mir ist schon bewusst, dass die Grenze zwischen s<strong>in</strong>nvoller und schlechter<br />
Anwendung fließend ist. Es ist aber garantiert besser, im Fall des ger<strong>in</strong>gsten<br />
Zweifels an der Intuitivität e<strong>in</strong>es Operators, diesen nicht zu def<strong>in</strong>ieren,<br />
sondern stattdessen e<strong>in</strong>e entsprechende Methode zu implementieren.
13. Templates<br />
Bevor wir überhaupt <strong>in</strong> das Thema der Templates e<strong>in</strong>tauchen möchte ich hier<br />
aus gutem Grund etwas vorausschicken: Templates s<strong>in</strong>d e<strong>in</strong>es der mächtigsten<br />
Konzepte von C ++. Gleichzeitig s<strong>in</strong>d sie auch e<strong>in</strong>es der am schwierigsten zu<br />
begreifenden Konzepte, vor allem für Leute, die sich noch nicht so gut <strong>in</strong><br />
C ++ oder <strong>in</strong> der <strong>Softwareentwicklung</strong> im Allgeme<strong>in</strong>en auskennen. Folgende<br />
Steigerung der Schwierigkeitsgrade ist <strong>in</strong> der Diskussion der Templates <strong>in</strong> der<br />
Folge zu f<strong>in</strong>den<br />
• Abschnitt 13.1 ist sehr leicht zu verdauen.<br />
• Abschnitt 13.2 und Abschnitt 13.3 s<strong>in</strong>d auch noch ke<strong>in</strong>e allzu schwere Kost<br />
und deshalb für Neul<strong>in</strong>ge genießbar.<br />
• Bei Abschnitt 13.4 wird es schon etwas haariger und Abschnitt 13.5 würde<br />
ich als for Experts only bezeichnen. Me<strong>in</strong>e Empfehlung ist daher, dass<br />
sich E<strong>in</strong>steiger als Versuch noch an Abschnitt 13.4 versuchen sollten. Sollte<br />
es hierbei schon zu Verständnisproblemen kommen, kann man guten<br />
Gewissens dieses Kapitel auslassen und dessen Lektüre auf e<strong>in</strong>en späteren<br />
Zeitpunkt verlegen, zu dem man dann schon mehr Erfahrung im Umgang<br />
mit C ++ hat. Ke<strong>in</strong>esfalls würde ich Neul<strong>in</strong>gen anraten, Abschnitt 13.5 zu<br />
lesen, denn ohne größere Erfahrung mit Templates <strong>in</strong> der Praxis ist er wirklich<br />
praktisch unverdaulich. Mit Erfahrung zu e<strong>in</strong>em späteren Zeitpunkt<br />
ist dieser Abschnitt allerd<strong>in</strong>gs Goldes wert und dann ist der Augenblick<br />
gekommen, wo man dieses Buch noch e<strong>in</strong>mal aufschlagen sollte.<br />
So, jetzt aber wirklich – die Templates wollen behandelt werden.<br />
Als sauberes und klares Abstraktionskonzept haben wir bisher das Konzept<br />
von Klassen kennen gelernt, das es erlaubt Daten, Operatoren und Methoden<br />
zu e<strong>in</strong>em vollwertigen Typ zu vere<strong>in</strong>en. Nun gibt es aber Fälle, <strong>in</strong> denen selbst<br />
dies nicht genug ist und <strong>in</strong> denen wir am liebsten ke<strong>in</strong>e konkreten, sondern<br />
generische Datentypen zur Verfügung hätten. Was bedeutet das nun schon<br />
wieder?<br />
Wenden wir uns kurz e<strong>in</strong>em Beispiel aus der Welt der immer wieder gebrauchten<br />
Utilities zu und betrachten wir e<strong>in</strong>en Buffer: In e<strong>in</strong>en solchen<br />
wollen wir nach dem FIFO (=First In First Out) Konzept im e<strong>in</strong>fachsten<br />
Fall mittels e<strong>in</strong>er Methode put Daten h<strong>in</strong>e<strong>in</strong>stellen und mittels get diese <strong>in</strong><br />
derselben Reihenfolge wieder herausholen. Welchen Typ diese Daten haben,
406 13. Templates<br />
ist für das generelle Konzept e<strong>in</strong>es Buffers nicht wichtig, denn se<strong>in</strong> Verhalten<br />
ändert sich ja nicht, egal ob wir nun <strong>in</strong>t Werte oder char Werte h<strong>in</strong>e<strong>in</strong>stellen<br />
und wieder herausholen wollen.<br />
Nicht nur bei vollständigen Klassen f<strong>in</strong>det man diese Fälle, <strong>in</strong> denen der<br />
Datentyp eigentlich für die Funktionalität nicht so wichtig ist, denselben Fall<br />
gibt es auch bei simplen Funktionen. Nehmen wir e<strong>in</strong>fach nur an, dass es e<strong>in</strong>e<br />
Funktion geben soll, die e<strong>in</strong> Array von Daten sortiert. Egal, welchen tollen<br />
Sortieralgorithmus diese Funktion nun implementiert, e<strong>in</strong>es ist sicher: Solange<br />
der Datentyp, der <strong>in</strong> diesem Array gespeicherten Elemente die Operatoren<br />
>, und < (evtl. auch ==, je nach Algorithmus) und den Zuweisungsoperator<br />
= unterstützt, ist dieses sortierbar.<br />
Genau hier s<strong>in</strong>d wir also beim Pr<strong>in</strong>zip der generischen Programmierung<br />
gelandet: Wir schreiben e<strong>in</strong>e Klasse oder auch e<strong>in</strong>en Algorithmus, ohne <strong>in</strong><br />
der Implementierung auf konkrete Datentypen e<strong>in</strong>zugehen. Stattdessen parametrisieren<br />
wir unsere Implementierung bei deren Verwendung mit diesen<br />
Datentypen um vom generischen Fall zur konkreten Implementation zu kommen.<br />
Im Fall unseres Buffers von zuvor bedeutet das im Pr<strong>in</strong>zip Folgendes:<br />
1. Wir schreiben e<strong>in</strong>e generische Klasse Buffer, die so allgeme<strong>in</strong> formuliert<br />
ist, dass ihr der Datentyp egal ist.<br />
2. Bei Verwendung dieser Klasse wird über e<strong>in</strong>en konkreten Typ-Parameter<br />
(z.B. char) ihr E<strong>in</strong>satzgebiet festgelegt. In diesem Fall hätten wir also<br />
e<strong>in</strong>en Buffer, der nur mit Characters umgehen kann.<br />
Dasselbe passiert im Fall unserer Funktion zum Sortieren e<strong>in</strong>es Arrays. Auch<br />
hier wird der Algorithmus implementiert, ohne auf den echten Typ der zu<br />
sortierenden Elemente Rücksicht zu nehmen. Im speziellen Fall kann diese<br />
Funktion dann entweder e<strong>in</strong> <strong>in</strong>t32-Array, e<strong>in</strong> char-Array oder auch e<strong>in</strong> beliebiges<br />
Objekt-Array sortieren, wenn nur der Datentyp die entsprechenden<br />
Operatoren implementiert.<br />
In C ++ s<strong>in</strong>d die sogenannten Templates der Mechanismus, der es erlaubt,<br />
generische Datentypen zu schreiben und diese dann durch Typ-<br />
Parametrisierung <strong>in</strong> konkreten Varianten zum E<strong>in</strong>satz zu br<strong>in</strong>gen.<br />
Vorsicht Falle: Um e<strong>in</strong>igen Lesern komische Überraschungen zu ersparen<br />
möchte ich gleich e<strong>in</strong>e wichtige Warnung vorwegnehmen: Nicht alle Compiler<br />
können mit allen <strong>in</strong> der Folge vorgestellten Template-Aspekten umgehen. Es<br />
kann also passieren, dass das e<strong>in</strong>e oder andere Testprogrämmchen, das <strong>in</strong> der<br />
Folge vorgestellt wird, nicht compiliert.<br />
Üblicherweise ist es so, dass im Pr<strong>in</strong>zip ke<strong>in</strong> heutzutage verwendeter Compiler<br />
e<strong>in</strong> Problem mit Function Templates (siehe Abschnitt 13.1) und mit<br />
Class Templates (siehe Abschnitt 13.3) hat. So e<strong>in</strong>ige Compiler können allerd<strong>in</strong>gs<br />
gewisse Aspekte der Spezialisierung von Templates nicht (siehe Abschnitt<br />
13.5).
13.1 Function Templates<br />
13.1 Function Templates 407<br />
Um Lesern, die bisher noch ke<strong>in</strong>e Erfahrung mit generischer Programmierung<br />
sammeln konnten, e<strong>in</strong>en sanften E<strong>in</strong>stieg zu bieten, wenden wir uns zuerst<br />
dem leichter genießbaren Thema der generischen Funktionen zu, die <strong>in</strong> C ++<br />
auch als Function Templates bezeichnet werden. Ziehen wir zu Erklärungszwecken<br />
e<strong>in</strong> ganz e<strong>in</strong>faches Beispiel heran: Wir wollen e<strong>in</strong>e Funktion implementieren,<br />
die als Parameter e<strong>in</strong> beliebiges Array nimmt und als return-Value<br />
den Index des wertmäßig größten Elements <strong>in</strong> diesem Array liefert. Sollte der<br />
Maximalwert nicht e<strong>in</strong>deutig bestimmbar se<strong>in</strong>, weil mehrere Elemente gleich<br />
groß s<strong>in</strong>d, so wird der Index des ersten der möglichen Elemente geliefert.<br />
Das Template zu dieser Funktion, das der generischen Implementation<br />
entspricht, f<strong>in</strong>det sich <strong>in</strong> f<strong>in</strong>d_max_template_function.h und sieht so aus:<br />
1 // f<strong>in</strong>d max template function . h − implementation o f a template<br />
2 // function that f i n d s the g r e a t e s t element <strong>in</strong> an a r b i t r a r y array<br />
3<br />
4 #ifndef f i n d m a x t e m p l a t e f u n c t i o n h<br />
5 #def<strong>in</strong>e f i n d m a x t e m p l a t e f u n c t i o n h<br />
6<br />
7 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
8<br />
9 template u<strong>in</strong>t32 f<strong>in</strong>dMax (<br />
10 const ElementType ∗ elements , u<strong>in</strong>t32 num elements )<br />
11 {<br />
12 u<strong>in</strong>t32 current max = 0;<br />
13<br />
14 for ( u<strong>in</strong>t32 <strong>in</strong>dex = 0 ; <strong>in</strong>dex < num elements ; <strong>in</strong>dex++)<br />
15 {<br />
16 i f ( elements [ <strong>in</strong>dex ] > elements [ current max ] )<br />
17 current max = <strong>in</strong>dex ;<br />
18 }<br />
19 return ( current max ) ;<br />
20 }<br />
21<br />
22 #endif // f i n d m a x t e m p l a t e f u n c t i o n h<br />
Um zu verstehen, was hier passiert, werfen wir e<strong>in</strong>mal e<strong>in</strong>en Blick auf Zeile 9,<br />
denn dort ist der Kern dessen versteckt, was Templates <strong>in</strong> C ++ ausmacht: Die<br />
Deklaration des generischen Typs. Die Deklaration<br />
template <br />
bedeutet <strong>in</strong> diesem Kontext Folgendes: ElementType ist <strong>in</strong> diesem Kontext<br />
e<strong>in</strong> generischer Typ, der bei Verwendung durch e<strong>in</strong>en konkreten Typ ersetzt<br />
werden muss. Wo auch immer also im Rahmen der Deklaration und Def<strong>in</strong>ition<br />
der Funktion ElementType als Typ verwendet wird, steht dieser also als<br />
Platzhalter für den konkreten Typ. Das Keyword template und die spitzen<br />
Klammern, die den bzw. die generischen Typen e<strong>in</strong>fassen, s<strong>in</strong>d <strong>in</strong> der C ++<br />
Syntax so def<strong>in</strong>iert.<br />
Betrachten wir den Rest der Deklaration der Funktion <strong>in</strong> den Zeilen 9–<br />
10, dann sehen wir, dass ElementType gleich e<strong>in</strong>mal als generischer Typ <strong>in</strong><br />
der Parameterliste der Funktion verwendet wird. Dadurch ist die genaue
408 13. Templates<br />
Bedeutung der Deklaration e<strong>in</strong>mal folgendermaßen zu lesen: Es wird hier<br />
e<strong>in</strong>e Funktion f<strong>in</strong>dMax deklariert, die als return-Value e<strong>in</strong>en u<strong>in</strong>t32 liefert<br />
und die als Parameter e<strong>in</strong> Array beliebiger Elemente sowie die Anzahl der<br />
<strong>in</strong> diesem Array enthaltenen Elemente nimmt. Dazu sei noch anzumerken,<br />
dass hier der generische Parameter class ElementType nicht zwangsweise<br />
für “echte” Klassen steht, es s<strong>in</strong>d auch primitive Datentypen, wie z.B. <strong>in</strong>t,<br />
zulässig.<br />
In der Implementation der Funktion <strong>in</strong> den Zeilen 11–20 sieht man, dass<br />
der Parameter elements ganz genau so verwendet wird, als würde es sich<br />
hier um e<strong>in</strong>en konkreten Datentyp handeln. In Zeile 16 erkennt man, dass<br />
vom konkreten Datentyp nur e<strong>in</strong>es verlangt wird: Instanzen dieses Datentyps<br />
müssen mittels des > Operators mite<strong>in</strong>ander vergleichbar se<strong>in</strong>.<br />
Jetzt stellt sich nur noch die Frage, wie man mit e<strong>in</strong>em solchen Template<br />
arbeitet. Irgendwann muss ja wohl aus unserer generischen Deklaration<br />
und Def<strong>in</strong>ition auch e<strong>in</strong>mal e<strong>in</strong>e konkret aufrufbare Funktion werden. Wie<br />
man <strong>in</strong> der Folge erkennen kann, müssen wir uns nicht im Besonderen darum<br />
kümmern, sondern können das e<strong>in</strong>fach dem Compiler überlassen. Wir<br />
tun e<strong>in</strong>fach so, als wäre diese Funktion für e<strong>in</strong>en bestimmten Datentyp existent.<br />
Werfen wir also kurz e<strong>in</strong>en Blick auf unser Testprogramm, das dieses<br />
Verhalten demonstriert (f<strong>in</strong>d_max_test.cpp):<br />
1 // f<strong>in</strong>d max test . cpp − demo , how the template works<br />
2<br />
3 #<strong>in</strong>clude ” f<strong>in</strong>d max template function . h”<br />
4 #<strong>in</strong>clude <br />
5<br />
6 us<strong>in</strong>g std : : cout ;<br />
7 us<strong>in</strong>g std : : endl ;<br />
8<br />
9 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
10 {<br />
11 <strong>in</strong>t32 t e s t a r r a y 1 [ ] = { 4 , 1 , 2 , 5 , 3 } ;<br />
12 double t e s t a r r a y 2 [ ] = { 1 7 . 0 , 1 2 . 0 , 1 3 . 4 , − 7 . 5 , 1 1 . 3 } ;<br />
13<br />
14 cout
f<strong>in</strong>dMax f o r <strong>in</strong>t32 array . . . max value i s at <strong>in</strong>dex 3<br />
f<strong>in</strong>dMax f o r double array . . . max value i s at <strong>in</strong>dex 0<br />
13.1 Function Templates 409<br />
Was tut der Compiler also h<strong>in</strong>ter den Kulissen, dass wir e<strong>in</strong>e generische Funktion<br />
e<strong>in</strong>fach so aufrufen können und <strong>in</strong> Wirklichkeit den Aufruf e<strong>in</strong>er konkreten<br />
Funktion damit erreichen? Pr<strong>in</strong>zipiell geht er nach folgendem Schema<br />
vor:<br />
1. Beim Aufruf e<strong>in</strong>er Template Funktion wirft er e<strong>in</strong>en Blick auf deren Deklaration,<br />
um herauszuf<strong>in</strong>den, was denn die generischen Teile des Templates<br />
s<strong>in</strong>d (davon kann es natürlich mehrere geben...). In unserem Fall gibt<br />
es den Datentyp ElementType, der e<strong>in</strong>e konkrete Entsprechung braucht.<br />
2. Die Aufrufparameter werden mit den generischen Teilen des Templates<br />
verglichen. Dadurch erfährt der Compiler, welche Entsprechung er konkret<br />
nehmen soll. In Zeile 15 unseres Testprogramms f<strong>in</strong>det der Compiler<br />
heraus, dass ElementType wohl <strong>in</strong>t32 se<strong>in</strong> muss, denn die Funktion<br />
wird mit e<strong>in</strong>em <strong>in</strong>t32 * aufgerufen und im Template steht als Parameter<br />
ElementType *. Wenn ElementType also für <strong>in</strong>t32 steht, passt alles<br />
wunderbar zusammen.<br />
3. Jetzt, wo der Compiler herausgefunden hat, was ElementType ist, geht er<br />
daran, im gesamten Funktionsrumpf alle Vorkommen dieses generischen<br />
Typs durch den konkreten Typ <strong>in</strong>t32 zu ersetzen. Er bastelt also e<strong>in</strong>e<br />
konkrete Funktion für diesen Typ.<br />
4. Die resultierende konkrete Funktion wird danach vom Compiler übersetzt,<br />
als ob wir selbst e<strong>in</strong>e Funktion geschrieben hätten, die mit e<strong>in</strong>em<br />
<strong>in</strong>t32 Array arbeiten würde.<br />
5. Der Aufruf der konkreten Funktion wird nun endgültig an der Stelle<br />
e<strong>in</strong>gesetzt, an der wir mit f<strong>in</strong>dMax <strong>in</strong> dieser Ausprägung arbeiten.<br />
Ganz genau dasselbe passiert <strong>in</strong> Zeile 18, nur dass hier die Analyse der generischen<br />
Parameter ergibt, dass der konkrete Datentyp e<strong>in</strong> double ist. Der<br />
Compiler erzeugt also e<strong>in</strong>e neue konkrete Ausprägung der Funktion, die mit<br />
e<strong>in</strong>em double Array arbeiten kann und setzt diesen Aufruf e<strong>in</strong>. Natürlich<br />
wird nicht jedes Mal, wenn der Compiler z.B. auf e<strong>in</strong>en double stößt, wieder<br />
e<strong>in</strong>e neue Ausprägung der Funktion erzeugt, denn die besondere Ausprägung<br />
mit double existiert ja nach dem ersten Vorkommen schon. Es wird ab dem<br />
zweiten Vorkommen e<strong>in</strong>fach diese existente Ausprägung herangezogen. Genaueres<br />
dazu wird noch <strong>in</strong> Abschnitt 13.7 behandelt.<br />
Geht man dieses Schema, wie der Compiler mit Templates arbeitet, noch<br />
e<strong>in</strong>mal im Kopf durch, dann erkennt man schnell, warum ich hier die gesamte<br />
Def<strong>in</strong>ition der Funktion e<strong>in</strong>mal kurzerhand quasi als <strong>in</strong>l<strong>in</strong>e Code <strong>in</strong> das<br />
Header File geschrieben habe: Der Compiler muss die Def<strong>in</strong>ition der Funktion<br />
kennen, um aus dieser generischen Def<strong>in</strong>ition auch e<strong>in</strong>e konkrete Ausprägung<br />
durch Ersetzen des Typs bauen und danach übersetzen zu können. Ich möchte<br />
allerd<strong>in</strong>gs hier gleich vorausschicken, dass dies im Pr<strong>in</strong>zip notgedrungen so<br />
se<strong>in</strong> muss, dass es aber zum Glück trotzdem Möglichkeiten gibt, wie man die
410 13. Templates<br />
Deklaration und die Def<strong>in</strong>ition von Templates vone<strong>in</strong>ander trennen kann und<br />
damit wieder unsere saubere Aufteilung <strong>in</strong> cpp und h Files erreichen kann.<br />
Wie man dabei vorgeht, wird <strong>in</strong> Abschnitt 13.7 noch genauer behandelt. Um<br />
den Blick auf das Wesentliche nicht zu verlieren, begnügen wir uns derzeit<br />
e<strong>in</strong>fach e<strong>in</strong>mal mit dieser etwas unschönen Organisation des Source Codes<br />
und lassen Templates vollständig <strong>in</strong>klusive Def<strong>in</strong>itionen im Header stehen.<br />
Nur so als kle<strong>in</strong>es Gedankenexperiment möchte ich hier noch folgende<br />
Frage <strong>in</strong> den Raum stellen: Was muss man tun, wenn man bereits im generischen<br />
Typ selbst verankern will, dass es sich um e<strong>in</strong>en Po<strong>in</strong>ter handelt? Als<br />
Parameter will man also nicht mehr<br />
const ElementType *element<br />
stehen haben, sondern z.B.<br />
const ElementTypePtr element<br />
Nun, im Pr<strong>in</strong>zip ist das ganz e<strong>in</strong>fach, wie die folgende Modifikation beweist<br />
(f<strong>in</strong>d_max_template_function_v2.h):<br />
1 // f<strong>in</strong>d max template function v2 . h − a s l i g h t change o f the f i r s t<br />
2 // implementation that i n c l u d e s the po<strong>in</strong>ter <strong>in</strong> the g e n e r i c type<br />
3<br />
4 #ifndef f i n d m a x t e m p l a t e f u n c t i o n h<br />
5 #def<strong>in</strong>e f i n d m a x t e m p l a t e f u n c t i o n h<br />
6<br />
7 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
8<br />
9 template u<strong>in</strong>t32 f<strong>in</strong>dMax (<br />
10 const ElementTypePtr elements , u<strong>in</strong>t32 num elements )<br />
11 {<br />
12 u<strong>in</strong>t32 current max = 0;<br />
13<br />
14 for ( u<strong>in</strong>t32 <strong>in</strong>dex = 0 ; <strong>in</strong>dex < num elements ; <strong>in</strong>dex++)<br />
15 {<br />
16 i f ( elements [ <strong>in</strong>dex ] > elements [ current max ] )<br />
17 current max = <strong>in</strong>dex ;<br />
18 }<br />
19 return ( current max ) ;<br />
20 }<br />
21<br />
22 #endif // f i n d m a x t e m p l a t e f u n c t i o n h<br />
Es wurde ganz e<strong>in</strong>fach der Name des generischen Parameters geändert, um<br />
Entwicklern, die Zeile 9 lesen, zu signalisieren, dass hier e<strong>in</strong> Po<strong>in</strong>ter erwartet<br />
wird. Weiters wurde beim ersten Parameter <strong>in</strong> Zeile 10 der * weggelassen,<br />
denn wir wollen ja, dass der Po<strong>in</strong>ter bereits Teil des generischen Parameters<br />
ist. Beim Auflösen kommt der Compiler ja schon automatisch darauf, dass<br />
mit ElementTypePtr z.B. <strong>in</strong> unserem Testprogramm entweder e<strong>in</strong> <strong>in</strong>t32 *<br />
oder eben e<strong>in</strong> double * geme<strong>in</strong>t ist, denn nur so kann er e<strong>in</strong> korrektes Match<strong>in</strong>g<br />
vornehmen.<br />
Das Testprogramm dazu hat sich bis auf das Inkludieren des modifizierten<br />
Headers gar nicht verändert. Deshalb möchte ich es auch hier nicht abdrucken.<br />
Es ist unter dem Namen f<strong>in</strong>d_max_test_v2.cpp auf der beiliegenden
13.1 Function Templates 411<br />
CD-ROM verewigt. Auch den Output erspare ich hier den Lesern, denn auch<br />
dieser hat sich <strong>in</strong> ke<strong>in</strong>ster Weise verändert.<br />
Leider hat es sich sowohl im Alltag als auch <strong>in</strong> der Literatur e<strong>in</strong>gebürgert,<br />
die Typ-Parameter von Templates unseligerweise mit möglichst kurzen und<br />
nichts sagenden Bezeichnern, wie z.B T, zu versehen. Von dieser Praxis kann<br />
ich nur energisch abraten, denn dies führt zu sehr schwer lesbaren Programmen.<br />
Deshalb habe ich auch ganz bewusst den generischen Typnamen im<br />
abgeänderten Beispiel mitgeändert. Damit wird e<strong>in</strong>fach klar signalisiert, dass<br />
man hier mit e<strong>in</strong>em Po<strong>in</strong>ter auf irgendetwas zu tun hat. Würde hier e<strong>in</strong>fach<br />
T oder Type oder Ähnliches stehen, so müsste man erst die Implementation<br />
lesen um überhaupt e<strong>in</strong>e Idee zu bekommen, dass hier e<strong>in</strong> Array erwartet<br />
wird.<br />
Gehen wir <strong>in</strong> unseren Gedankenexperimenten zu generischen Typen noch<br />
e<strong>in</strong>en Schritt weiter <strong>in</strong>dem wir behaupten, dass der Parameter, der die Anzahl<br />
der Elemente angibt, eigentlich auch von se<strong>in</strong>em Typ her gar nicht so<br />
wirklich festgelegt ist. Es könnte sich hier, je nach Anwendung, um e<strong>in</strong>en<br />
u<strong>in</strong>t32, e<strong>in</strong>en <strong>in</strong>t32, e<strong>in</strong>en long oder sonst e<strong>in</strong>en ganzzahligen Datentyp<br />
handeln. Es könnte sich sogar um e<strong>in</strong> besonderes Objekt handeln, das nach<br />
außen e<strong>in</strong>e Zahl repräsentieren kann, das aber selbst ke<strong>in</strong>eswegs e<strong>in</strong> primitiver<br />
Datentyp ist. So e<strong>in</strong> Beispiel haben wir ja bereits <strong>in</strong> Abschnitt 12.2 <strong>in</strong><br />
Form unserer Range-Controlled Ganzzahl kennen gelernt. Was also, wenn wir<br />
auch diesen zweiten Parameter generisch def<strong>in</strong>ieren wollen? Nun, ganz e<strong>in</strong>fach:<br />
Es ist natürlich <strong>in</strong> C ++ erlaubt, beliebig viele generische Typen für e<strong>in</strong><br />
Template festzulegen. Also schreiten wir zur Tat und ändern unser Template<br />
entsprechend ab (f<strong>in</strong>d_max_template_function_v3.h):<br />
1 // f<strong>in</strong>d max template function v3 . h − a s l i g h t change that a l s o<br />
2 // makes the num elements parameter g e n e r i c<br />
3<br />
4 #ifndef f i n d m a x t e m p l a t e f u n c t i o n h<br />
5 #def<strong>in</strong>e f i n d m a x t e m p l a t e f u n c t i o n h<br />
6<br />
7 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
8<br />
9 template IntType f<strong>in</strong>dMax (<br />
10 const ElementType ∗ elements , IntType num elements )<br />
11 {<br />
12 IntType current max = 0;<br />
13<br />
14 for ( IntType <strong>in</strong>dex = 0 ; <strong>in</strong>dex < num elements ; <strong>in</strong>dex++)<br />
15 {<br />
16 i f ( elements [ <strong>in</strong>dex ] > elements [ current max ] )<br />
17 current max = <strong>in</strong>dex ;<br />
18 }<br />
19 return ( current max ) ;<br />
20 }<br />
21<br />
22 #endif // f i n d m a x t e m p l a t e f u n c t i o n h<br />
In den Zeilen 9–10 erkennt man, dass man <strong>in</strong>nerhalb der spitzen Klammern<br />
auch mehrere generische Typbezeichner angeben kann. In unserem Fall ha-
412 13. Templates<br />
ben wir es hier also mit dem bereits bekannten ElementType sowie auch mit<br />
e<strong>in</strong>em IntType zu tun. Wenn wir schon für die Anzahl der Elemente e<strong>in</strong>en<br />
generischen Typ verwenden, dann ist es natürlich auch nur recht und billig,<br />
für den return-Value der Funktion denselben generischen Typ zu verwenden.<br />
Also haben wir hier auch die Deklaration des return-Values über unseren<br />
generischen Typ vorgenommen. Das br<strong>in</strong>gt uns genau zur Eigenschaft von<br />
generischen Typen <strong>in</strong> Templates, die wir bisher implizit vorausgesetzt haben:<br />
Innerhalb e<strong>in</strong>er Template Deklaration und Def<strong>in</strong>ition wird e<strong>in</strong> generischer<br />
Typ ganz genau gleich verwendet, wie wir sonst e<strong>in</strong>en konkreten Typ verwenden<br />
würden. Das Ersetzen durch e<strong>in</strong>en konkreten Typ nimmt dann der<br />
Compiler für uns vor. Der Gültigkeitsbereich der generischen Typbezeichner<br />
ist selbstverständlich auf die Deklaration und Def<strong>in</strong>ition des Templates selbst<br />
limitiert, ansonsten würde es ja zu sehr bösen Überraschungen kommen.<br />
Außer im return-Value und im zweiten Parameter f<strong>in</strong>det sich der generische<br />
Typ IntType natürlich auch <strong>in</strong> der Def<strong>in</strong>ition der Funktion wieder,<br />
denn current_max muss natürlich auch diesen Typ haben, ebenso unsere<br />
Laufvariable <strong>in</strong>dex.<br />
Ändern wir nun wiederum unser Testprogramm so ab, dass es unsere modifizierte<br />
Version des Templates <strong>in</strong>kludiert, so zeigt sich, dass diese Version<br />
genau dasselbe Resultat liefert, wie die beiden Versionen zuvor. Der Compiler<br />
betrachtet hier e<strong>in</strong>fach nur IntType als zusätzlichen generischen Parameter,<br />
zu dem er e<strong>in</strong>e konkrete Entsprechung f<strong>in</strong>den muss. Im Fall unseres Testprogramms<br />
wird als Entsprechung <strong>in</strong>t gefunden, denn e<strong>in</strong>e Ganzzahlkonstante<br />
wird vom Compiler natürlich implizit als <strong>in</strong>t betrachtet.<br />
Das geänderte Testprogramm f<strong>in</strong>det sich auf der beiliegenden CD-ROM<br />
unter dem Namen f<strong>in</strong>d_max_test_v3.cpp und der Output, den dieses Programm<br />
liefert, ist erwartungsgemäß wieder derselbe, den auch die Vorgängervarianten<br />
produziert haben.<br />
Auf den ersten Blick sche<strong>in</strong>t diese Version des Templates also die Erfüllung<br />
aller Träume zu se<strong>in</strong>: Wir implementieren e<strong>in</strong>mal den Algorithmus und er<br />
funktioniert unabhängig vom Typ der Elemente und auch unabhängig vom<br />
Typ der Längenangabe wunderbar. Hätten wir ke<strong>in</strong>e Function Templates zur<br />
Verfügung, so müssten wir für jeden bestimmten Elementtyp e<strong>in</strong>e eigene overloaded<br />
Function schreiben. Außerdem müssten wir auch den num_elements<br />
Parameter immer an den Typ anpassen oder gleich den größtmöglichen Ganzzahldatentyp<br />
verwenden, um auf der sicheren Seite zu bleiben. Da wir im<br />
Vorh<strong>in</strong>e<strong>in</strong> nicht e<strong>in</strong>mal wissen, mit welchen Parametertypen und Komb<strong>in</strong>ationen<br />
derselben wir es zu tun bekommen, schreiben wir gleich e<strong>in</strong>e unendlich<br />
lange Latte von verschiedenen Overload<strong>in</strong>gs und hoffen, dass e<strong>in</strong>es davon<br />
schon auf die spätere Verwendung passen wird.<br />
Die hier angesprochene Erfüllung aller Träume birgt allerd<strong>in</strong>gs auch e<strong>in</strong><br />
paar Albträume, die man erst nach der anfänglichen Euphorie bemerkt. Um<br />
auch über diese Bescheid zu wissen, betrachten wir e<strong>in</strong>mal e<strong>in</strong> paar Möglich-
13.1 Function Templates 413<br />
keiten etwaiger Fehlverwendungen unseres schönen Templates etwas näher<br />
(template_usage_dangers.cpp):<br />
111 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
112 {<br />
113 <strong>in</strong>t32 t e s t a r r a y [ ] = { 4 , 1 , 2 , 5 , 3 } ;<br />
114 RangeControlledInt num with range ( 0 , 5 0 , 5 ) ;<br />
115<br />
116 // the f o l l o w i n g l i n e r e s u l t s <strong>in</strong> a compiler e r r o r , because<br />
117 // <strong>in</strong> the template the second parameter i s used f o r<br />
118 // array <strong>in</strong>dex<strong>in</strong>g<br />
119 f<strong>in</strong>dMax ( t e s t a r r a y , 5 . 5 ) ;<br />
120<br />
121 // the f o l l o w i n g l i n e r e s u l t s <strong>in</strong> a compiler e r r o r , because<br />
122 // of two reasons :<br />
123 // ( 1 ) no constructor with an i n t parameter i s def<strong>in</strong>ed<br />
124 // ( 2 ) the ++ operator i s not implemented<br />
125 f<strong>in</strong>dMax ( t e s t a r r a y , num with range ) ;<br />
126 return ( 0 ) ;<br />
127 }<br />
Im obigen Beispiel wurde bewusst nur ma<strong>in</strong> abgedruckt. Das Template entspricht<br />
der zuletzt betrachteten Variante, die beide Parameter generisch def<strong>in</strong>iert.<br />
Die Klasse RangeControlledInt wurde direkt aus dem entsprechenden<br />
Beispiel <strong>in</strong> Abschnitt 12.2 übernommen. Wenn wir versuchen, das obige<br />
Beispiel zu compilieren, dann kommen wir leider zu ke<strong>in</strong>em lauffähigen Programm,<br />
denn der Compiler hat hier e<strong>in</strong> paar D<strong>in</strong>ge zu bemängeln.<br />
Erstens passt ihm der Aufruf von f<strong>in</strong>dMax aus Zeile 119 ganz und gar<br />
nicht. Es wird nämlich hier die Funktion mit e<strong>in</strong>em double als Längenparameter<br />
aufgerufen. Dies veranlasst den Compiler, e<strong>in</strong>e entsprechende konkrete<br />
Implementation mit e<strong>in</strong>em double zu erzeugen. Dieser wird allerd<strong>in</strong>gs<br />
<strong>in</strong> unserem Template als Index für das Array verwendet, was natürlich nicht<br />
gestattet ist.<br />
Zweitens passt dem Compiler auch der Aufruf von f<strong>in</strong>dMax aus Zeile 125<br />
nicht, denn hier wird e<strong>in</strong> RangeControlledInt als Längenparameter verwendet.<br />
Das bedeutet, dass dieser Typ auch für den return-Value und ebenso<br />
für die Laufvariable <strong>in</strong> unserem Template verwendet wird. Leider aber fehlt<br />
für diese Verwendung Entscheidendes: Es gibt ke<strong>in</strong>en Konstruktor, der e<strong>in</strong>fach<br />
nur e<strong>in</strong>en e<strong>in</strong>zigen Parameter, nämlich den Initialwert, nehmen würde.<br />
Außerdem ist der ++ Operator nicht def<strong>in</strong>iert. Beides wird aber im Template<br />
beim Arbeiten mit der Laufvariable sowie mit dem return-Value gebraucht.<br />
Man kann nun sagen, dass der Aufruf mit dem double wirklich e<strong>in</strong> grober<br />
Unfug ist und dass man daher froh se<strong>in</strong> kann, dass der Compiler darauf h<strong>in</strong>weist.<br />
Weiters kann man behaupten, dass man beim RangeControlledInt<br />
selbst die Schuld trägt, weil man auf den entsprechenden Konstruktor und<br />
den ++ Operator hätte denken sollen. Also ergänzt man diese Klasse und<br />
kommt damit auch wirklich problemlos durch den Compiler. Damit hat man<br />
sich aber e<strong>in</strong> wunderbares verstecktes Problem e<strong>in</strong>gehandelt, das verhängnisvoll<br />
se<strong>in</strong> kann: Beim Schreiben des Templates hat niemand damit gerechnet,<br />
dass beim Über- bzw. Unterschreiten e<strong>in</strong>es vorgegebenen Wertes e<strong>in</strong>e Excep-
414 13. Templates<br />
tion geworfen wird (was ja die Eigenschaft unseres speziellen Typs ist)! Das<br />
kann, je nach Funktion, zu groben Inkonsistenzen führen. In unserem Fall<br />
passiert es “nur”, dass e<strong>in</strong>e Exception geworfen wird, die durch das Funktionstemplate<br />
nicht explizit deklariert wurde – schlimm genug! In anderen<br />
Fällen kann es natürlich auch zu e<strong>in</strong>er echten Katastrophe kommen.<br />
In unserem Fall der RangeControlledInt Klasse kann man sich auch<br />
noch auf e<strong>in</strong>e andere Art schnell und sicher die Probleme vom Hals schaffen.<br />
Man braucht nur Zeile 125 umschreiben auf:<br />
f<strong>in</strong>dMax(test_array,static_cast(num_with_range));<br />
Damit hat man die Verwendung unseres selbstdef<strong>in</strong>ierten Cast-Operators erzwungen,<br />
wodurch das Template vom Compiler mit e<strong>in</strong>em <strong>in</strong>t32 als zweiten<br />
Parameter erzeugt wird. Gleich vorausschicken möchte ich, dass so e<strong>in</strong>e<br />
Lösung nicht immer so leicht zu f<strong>in</strong>den ist, wie man sich ja ausmalen kann.<br />
Leider gibt es <strong>in</strong> C ++ ke<strong>in</strong>e Möglichkeit, e<strong>in</strong>en generischen Parameter auf<br />
e<strong>in</strong>en genau def<strong>in</strong>ierten Satz von Möglichkeiten e<strong>in</strong>zuschränken. Daher fehlt<br />
uns hier das programmiersprachliche Werkzeug, das notwendig wäre, um e<strong>in</strong>e<br />
Fehlverwendung zu verh<strong>in</strong>dern. Von der anderen Seite betrachtet ist es<br />
natürlich auch nicht immer möglich bzw. wünschenswert, alle möglichen<br />
Komb<strong>in</strong>ationen von Typen auf e<strong>in</strong>en erlaubten Satz e<strong>in</strong>zuschränken, denn<br />
damit hat man bereits im Vorfeld Typen ausgeschlossen, die man zum Zeitpunkt<br />
der Entwicklung e<strong>in</strong>es Templates noch nicht gekannt hat. Abhilfe dazu<br />
würde das Unterstützen von sogenannten Contracts <strong>in</strong> C ++ schaffen, aber e<strong>in</strong>e<br />
Diskussion darüber geht an dieser Stelle zu weit. Deshalb möchte ich es<br />
hier bei folgender Warnung belassen:<br />
Vorsicht Falle: Bevor man <strong>in</strong> se<strong>in</strong>er Software e<strong>in</strong> Template verwendet,<br />
muss man sich sehr genau ansehen, welche Anwendung die Entwickler des<br />
Templates im S<strong>in</strong>n hatten und auf welche Umstände sie Rücksicht genommen<br />
haben. Die Verwendung von Templates außerhalb dieser Grenzen kann<br />
ungeahnte Fehler zur Folge haben!<br />
Leider kann man sehr oft die Details, die helfen, diese Frage zu beantworten,<br />
nicht aus der Dokumentation entnehmen. In diesem Fall hilft wirklich<br />
nur e<strong>in</strong>e Lektüre des Template Source Codes um absolute Sicherheit zu haben,<br />
dass man nicht <strong>in</strong> e<strong>in</strong>e Falle tappt.<br />
13.2 Overload<strong>in</strong>g Aspekte von Function Templates<br />
Im Pr<strong>in</strong>zip kann man Function Templates auch so betrachten, dass sie durch<br />
ihren generischen Typmechanismus viele verschiedene Overload<strong>in</strong>gs für e<strong>in</strong>e<br />
Funktion automatisch bereitstellen. Was passiert nun, wenn man dazu noch<br />
e<strong>in</strong> “echtes” Overload<strong>in</strong>g implementiert, das e<strong>in</strong>e bestimmte Ausprägung e<strong>in</strong>es<br />
solchen Function Templates explizit implementiert? Ich kann gleich vorwegnehmen,<br />
was die meisten Leser vermuten werden: es funktioniert. Al-
13.2 Overload<strong>in</strong>g Aspekte von Function Templates 415<br />
lerd<strong>in</strong>gs erwartungsgemäß nicht ohne gewisse Fallen, die sich aufgrund von<br />
Ambiguitäten auftun, wie wir noch sehen werden. Werfen wir also am besten<br />
e<strong>in</strong>en Blick auf e<strong>in</strong> Beispiel(templates_and_overload<strong>in</strong>g.cpp):<br />
1 // templates and overload<strong>in</strong>g . cpp − overload<strong>in</strong>g function templates<br />
2 // with concrete f u n c t i o n s<br />
3<br />
4 #<strong>in</strong>clude <br />
5 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
6<br />
7 us<strong>in</strong>g std : : cout ;<br />
8 us<strong>in</strong>g std : : endl ;<br />
9<br />
10 template IntType f<strong>in</strong>dMax (<br />
11 const ElementType ∗ elements , IntType num elements )<br />
12 {<br />
13 cout
416 13. Templates<br />
In Zeile 48 wird f<strong>in</strong>dMax mit e<strong>in</strong>em <strong>in</strong>t32 Array und e<strong>in</strong>em <strong>in</strong>t Längenparameter<br />
aufgerufen, was natürlich den Compiler dazu bewegt, e<strong>in</strong>e konkrete<br />
Ausprägung aus dem Template zu generieren. Anders verhält es sich da<br />
schon mit Zeile 49, <strong>in</strong> der f<strong>in</strong>dMax mit e<strong>in</strong>em double Array und e<strong>in</strong>em u<strong>in</strong>t32<br />
Längenparameter aufgerufen wird. Da unsere konkrete Implementierung von<br />
f<strong>in</strong>dMax haarscharf diesen Parametersatz deklariert, sieht der Compiler überhaupt<br />
ke<strong>in</strong>e Veranlassung, aus dem Template e<strong>in</strong>e konkrete Ausprägung zu<br />
basteln, denn es gibt ja bereits e<strong>in</strong>e Implementation der Funktion, die alle<br />
Wünsche erfüllt. Der Beweis dieser Aussage f<strong>in</strong>det sich im Output des<br />
Programms:<br />
template implementation o f f<strong>in</strong>dMax<br />
concrete implementation of f<strong>in</strong>dMax<br />
Das Angenehme an dieser Tatsache ist, dass wir also offensichtlich e<strong>in</strong>en<br />
gewissen E<strong>in</strong>fluss auf bestimmte Ausprägungen e<strong>in</strong>es Templates nehmen<br />
können. Wenn z.B. e<strong>in</strong>e besondere Ausprägung e<strong>in</strong>es Templates mit e<strong>in</strong>em<br />
ganz bestimmten Datentyp, aus welchem Grund auch immer, e<strong>in</strong>e besondere<br />
Implementation benötigt, dann können wir diese explizit schreiben. Hiermit<br />
kann man z.B. sehr gut die Behandlung von Sonderfällen <strong>in</strong>nerhalb von<br />
Templates <strong>in</strong> Grenzen halten.<br />
Wo es e<strong>in</strong>e angenehme Seite gibt, ist leider oft die unangenehme Seite<br />
auch nicht weit. Natürlich gibt es bei dieser Vorgehensweise auch nette Stolperste<strong>in</strong>e:<br />
Vorsicht Falle: Sollte der Parametersatz beim Aufruf nicht vollkommen<br />
exakt auf die konkrete Implementation der Funktion passen, dann kann man<br />
den Compiler <strong>in</strong> e<strong>in</strong> Ambiguitätsproblem stürzen, das er nicht mehr automatisch<br />
auflösen kann. Nehmen wir nur e<strong>in</strong>mal an, dass <strong>in</strong> Zeile 49 der Aufruf<br />
folgendermaßen lauten würde:<br />
f<strong>in</strong>dMax(test_array2,5)<br />
dann bedeutet dies für den Compiler e<strong>in</strong>en Aufruf mit e<strong>in</strong>em double Array<br />
und mit e<strong>in</strong>em <strong>in</strong>t Parameter. Das passt nicht vollkommen exakt auf die<br />
konkrete Implementation, ist aber auch noch nicht so weit daneben, dass<br />
nicht mit e<strong>in</strong>er impliziten Typumwandlung noch etwas zu machen wäre.<br />
Je nach Compiler passiert es hierbei, dass er entweder e<strong>in</strong>e Warn<strong>in</strong>g von<br />
sich gibt, oder sich sogar vollständig weigern kann, die Entscheidung für e<strong>in</strong>e<br />
der beiden Varianten selbsttätig zu treffen. Es kann dann nur noch mit e<strong>in</strong>em<br />
expliziten Cast Abhilfe geschaffen werden. Wenn man allerd<strong>in</strong>gs mit e<strong>in</strong>er<br />
fremden Library arbeitet, kann das schon e<strong>in</strong>iges an Rätselraten zur Folge<br />
haben, bis man endlich durchschaut hat, warum sich der Compiler an e<strong>in</strong>er<br />
bestimmten Stelle schlicht und ergreifend weigert, se<strong>in</strong>e Arbeit zu erledigen.<br />
Bisher s<strong>in</strong>d wir immer davon ausgegangen, dass der Compiler durch Analyse<br />
der Aufrufparameter implizit e<strong>in</strong>e passende konkrete Ausprägung e<strong>in</strong>er<br />
Funktion erzeugt. Manchmal s<strong>in</strong>d wir allerd<strong>in</strong>gs <strong>in</strong>teressiert daran, dies selbst<br />
<strong>in</strong> die Hand zu nehmen. Betrachten wir nur e<strong>in</strong>fach den Fall, dass wir <strong>in</strong> ei-
13.2 Overload<strong>in</strong>g Aspekte von Function Templates 417<br />
nem Programm immer das Maximum aus e<strong>in</strong>em <strong>in</strong>t32 Array f<strong>in</strong>den wollen.<br />
Andererseits jedoch haben wir es <strong>in</strong> den verschiedenen Programmteilen e<strong>in</strong>mal<br />
mit e<strong>in</strong>em signed char, e<strong>in</strong> andermal mit e<strong>in</strong>em <strong>in</strong>t32 und wieder e<strong>in</strong><br />
andermal mit e<strong>in</strong>em long Parameter als Index zu tun. Der Compiler würde<br />
nun drei verschiedene konkrete Ausprägungen der Funktion erzeugen, was<br />
nicht immer wünschenswert ist. In diesem Fall wäre es genug, wenn e<strong>in</strong>fach<br />
e<strong>in</strong>e Version mit e<strong>in</strong>em long als zweiten Parameter erzeugt würde, denn die<br />
anderen verwendeten Typen s<strong>in</strong>d ja kompatibel dazu. Zu diesem Zweck gibt<br />
es die Möglichkeit der expliziten Angabe, mit welchen Typen man arbeiten<br />
will. Dies sieht dann folgendermaßen aus:<br />
1 // f<strong>in</strong>d max test v4 . cpp − adapted t e s t f o r V3 o f the template<br />
2<br />
3 #<strong>in</strong>clude ” f<strong>in</strong>d max template function v3 . h”<br />
4 #<strong>in</strong>clude <br />
5<br />
6 us<strong>in</strong>g std : : cout ;<br />
7 us<strong>in</strong>g std : : endl ;<br />
8<br />
9 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
10 {<br />
11 <strong>in</strong>t32 t e s t a r r a y [ ] = { 4 , 1 , 2 , 5 , 3 } ;<br />
12<br />
13 cout
418 13. Templates<br />
1 // f<strong>in</strong>d max template function v4 . h − a f u r t h e r modification<br />
2 // with two templates that overload each other<br />
3<br />
4 #ifndef f i n d m a x t e m p l a t e f u n c t i o n h<br />
5 #def<strong>in</strong>e f i n d m a x t e m p l a t e f u n c t i o n h<br />
6<br />
7 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
8<br />
9 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
10 template u<strong>in</strong>t32 f<strong>in</strong>dMax (<br />
11 const ElementType ∗ elements , u<strong>in</strong>t32 num elements )<br />
12 {<br />
13 u<strong>in</strong>t32 current max = 0;<br />
14<br />
15 for ( u<strong>in</strong>t32 <strong>in</strong>dex = 0 ; <strong>in</strong>dex < num elements ; <strong>in</strong>dex++)<br />
16 {<br />
17 i f ( elements [ <strong>in</strong>dex ] > elements [ current max ] )<br />
18 current max = <strong>in</strong>dex ;<br />
19 }<br />
20 return ( current max ) ;<br />
21 }<br />
22<br />
23 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
24 template IntType f<strong>in</strong>dMax (<br />
25 const ElementType ∗ elements , IntType num elements )<br />
26 {<br />
27 IntType current max = 0;<br />
28<br />
29 for ( IntType <strong>in</strong>dex = 0 ; <strong>in</strong>dex < num elements ; <strong>in</strong>dex++)<br />
30 {<br />
31 i f ( elements [ <strong>in</strong>dex ] > elements [ current max ] )<br />
32 current max = <strong>in</strong>dex ;<br />
33 }<br />
34 return ( current max ) ;<br />
35 }<br />
36<br />
37 #endif // f i n d m a x t e m p l a t e f u n c t i o n h<br />
Hier haben wir es mit e<strong>in</strong>em Overload<strong>in</strong>g von zwei Function Templates zu<br />
tun, wobei das e<strong>in</strong>e Template nur e<strong>in</strong>en, das andere zwei Typ-Parameter<br />
entgegennimmt. Auf diese Art kann man quasi e<strong>in</strong>en default Typ-Parameter<br />
realisieren. Über explizites Angeben der Ausprägung kann man dann jeweils<br />
die e<strong>in</strong>e oder die andere Variante verwenden, wie das folgende Beispiel zeigt<br />
(f<strong>in</strong>d_max_test_v5.cpp):<br />
1 // f<strong>in</strong>d max test v5 . cpp − adapted t e s t f o r V4 the template<br />
2<br />
3 #<strong>in</strong>clude ” f<strong>in</strong>d max template function v4 . h”<br />
4 #<strong>in</strong>clude <br />
5<br />
6 us<strong>in</strong>g std : : cout ;<br />
7 us<strong>in</strong>g std : : endl ;<br />
8<br />
9 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
10 {<br />
11 <strong>in</strong>t32 t e s t a r r a y [ ] = { 4 , 1 , 2 , 5 , 3 } ;<br />
12<br />
13 cout
16 cout
420 13. Templates<br />
27 template class Buffer<br />
28 {<br />
29 public :<br />
30 static const u<strong>in</strong>t32 MAX NUM ELEMENTS = 8;<br />
31 private :<br />
32 // f o r b i d assignment by mak<strong>in</strong>g operator p r i v a t e<br />
33 const Buffer& operator = ( const Buffer&)<br />
34 { return (∗ this ) ; }<br />
35 protected :<br />
36 u<strong>in</strong>t32 num elements ;<br />
37 u<strong>in</strong>t32 num elements allocated ;<br />
38 u<strong>in</strong>t32 read <strong>in</strong>dex ;<br />
39 u<strong>in</strong>t32 w r i t e i n d e x ;<br />
40 ContentType ∗ elements ;<br />
41 public :<br />
42 Buffer ( )<br />
43 throw( b a d a l l o c ) ;<br />
44<br />
45 Buffer ( const Buffer &s r c )<br />
46 throw( <strong>in</strong>valid argument , b a d a l l o c ) ;<br />
47<br />
48 virtual ˜ Buffer ( )<br />
49 throw ( ) ;<br />
50<br />
51 virtual void put ( ContentType const &element )<br />
52 throw( r a n g e e r r o r ) ;<br />
53<br />
54 virtual ContentType getNext ( )<br />
55 throw( r a n g e e r r o r ) ;<br />
56 } ;<br />
Wie bereits von den Function Templates her gewohnt, beg<strong>in</strong>nt man mit dem<br />
Keyword template, gefolgt von der Liste der generischen Typenparameter<br />
<strong>in</strong> spitzen Klammern (siehe Zeile 27). In unserem Fall geht es hier um den<br />
Typ der e<strong>in</strong>zelnen Elemente, die im Buffer zwischengespeichert werden sollen.<br />
Zeile 30 zeigt gleich sehr deutlich, dass dieses Template wirklich nur zu<br />
Demonstrationszwecken geschrieben wurde und dass es <strong>in</strong> dieser Form nicht<br />
s<strong>in</strong>nvoll <strong>in</strong> Programmen e<strong>in</strong>gesetzt werden kann. Die Anzahl der Elemente,<br />
die im Buffer zwischengelagert werden können, ist nämlich auf großartige<br />
acht beschränkt. Die Zuweisung e<strong>in</strong>es Buffers auf e<strong>in</strong>en anderen wurde <strong>in</strong><br />
den Zeilen 33–34 verh<strong>in</strong>dert, <strong>in</strong>dem der Zuweisungsoperator private gesetzt<br />
wurde. Das zugegebenermaßen schwachs<strong>in</strong>nige Statement <strong>in</strong> Zeile 34 existiert<br />
nur, um der Deklaration Genüge zu tun. Da der Operator sowieso nie<br />
aufgerufen wird, erwächst hierbei ke<strong>in</strong>e Gefahr.<br />
E<strong>in</strong>e Auffälligkeit erkennt man allerd<strong>in</strong>gs <strong>in</strong> den Zeilen 33–34: Sowohl der<br />
return-Type als auch der Parameter s<strong>in</strong>d besondere Buffers, nämlich Buffers<br />
vom selben Typ, wie diese Instanz auch. Die Schreibweise<br />
Classname<br />
ist nämlich genau die Art, wie man e<strong>in</strong>e konkrete Ausprägung e<strong>in</strong>es Class<br />
Templates erzeugt. Dadurch, dass hier <strong>in</strong>nerhalb der spitzen Klammern<br />
selbst wieder der generische Typ ContentType steht, erreicht man, dass<br />
“dieselbe” Ausprägung angenommen wird, wie sie auch der konkreten Aus-
13.3 Class Templates 421<br />
prägung entspricht, auf der e<strong>in</strong>e Methode oder e<strong>in</strong> Operator aufgerufen wurde.<br />
Ganz genau dasselbe f<strong>in</strong>det sich <strong>in</strong> Zeile 45 wieder: Der Copy Constructor<br />
ist natürlich nur für äquivalente konkrete Ausprägungen von Buffers def<strong>in</strong>iert,<br />
es hätte wirklich ke<strong>in</strong>en S<strong>in</strong>n z.B. e<strong>in</strong>en Copy Constructor zwischen verschiedenen<br />
Ausprägungen zuzulassen.<br />
Vorsicht Falle: Vor allem Entwicklern, die noch ungeübt im Erstellen<br />
von Templates s<strong>in</strong>d, passiert es öfters, dass sie bei Parametern bzw. bei<br />
return-Values vergessen, die konkrete Ausprägung des Templates anzugeben.<br />
Fälschlicherweise könnte man für unseren Copy Constructor aus Zeile 45 Folgendes<br />
schreiben:<br />
Buffer(const Buffer &src)<br />
Diese Deklaration hat jedoch e<strong>in</strong>e ganz andere Bedeutung: src ist nämlich<br />
irgende<strong>in</strong>e Ausprägung e<strong>in</strong>es Buffers, allerd<strong>in</strong>gs bestimmt das erste Vorkommen<br />
im Source-Code, welche konkrete Ausprägung geme<strong>in</strong>t ist. Von dort weg<br />
wird angenommen, dass src genau diese Ausprägung hat. Zum Glück führt<br />
das dann zumeist aus anderen Gründen zu Compilerfehlern beim Übersetzen<br />
des Templates. Jedoch s<strong>in</strong>d die entsprechenden Fehlermeldungen manchmal<br />
ziemlich kryptisch.<br />
Dazu muss auch noch gesagt werden, dass e<strong>in</strong>ige Compiler sich sowieso<br />
partout weigern, die obige Zeile zu übersetzen und stattdessen auf die Angabe<br />
der Ausprägung bestehen – me<strong>in</strong>er Me<strong>in</strong>ung nach das s<strong>in</strong>nvollste Verhalten.<br />
Andere Compiler setzen wiederum implizit voraus, dass sicher dieselbe Ausprägung<br />
geme<strong>in</strong>t ist und übersetzen das Template dementsprechend. Dieses<br />
Verhalten führt zwar zu ke<strong>in</strong>en Problemen, allerd<strong>in</strong>gs b<strong>in</strong> ich ke<strong>in</strong> besonderer<br />
Freund davon, dass Compiler die Fehler von Entwicklern stillschweigend<br />
korrigieren anstatt sie zu melden.<br />
Die Methoden, die hier <strong>in</strong> der Klasse deklariert waren, müssen natürlich<br />
auch noch def<strong>in</strong>iert werden. Aus bereits besprochenen Gründen beschränken<br />
wir uns auch hier im Augenblick darauf, dass die Def<strong>in</strong>ition ebenfalls im Header<br />
steht, um dem Compiler e<strong>in</strong>e faire Chance zu geben, e<strong>in</strong>e entsprechende<br />
konkrete Ausprägung der Klasse mit allen zugehörigen Methoden erzeugen zu<br />
können. Folgende Def<strong>in</strong>itionen s<strong>in</strong>d also <strong>in</strong> unserem Header noch enthalten:<br />
58 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
59 /∗<br />
60 ∗/<br />
61 template <br />
62 Buffer:: Buffer ( )<br />
63 throw( b a d a l l o c )<br />
64 {<br />
65 num elements = 0;<br />
66 read <strong>in</strong>dex = 0;<br />
67 w r i t e i n d e x = 0;<br />
68 num elements allocated = 0 ; // j u s t <strong>in</strong> case new f a i l s . . .<br />
69 elements = new ContentType [MAX NUM ELEMENTS] ;<br />
70 num elements allocated = MAX NUM ELEMENTS;
422 13. Templates<br />
71 }<br />
72<br />
73 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
74 /∗<br />
75 ∗/<br />
76 template <br />
77 Buffer:: Buffer ( const Buffer &s r c )<br />
78 throw( <strong>in</strong>valid argument , b a d a l l o c )<br />
79 {<br />
80 cout
13.3 Class Templates 423<br />
der die altbekannte template Deklaration und anstatt nur den<br />
Klassennamen als Scope für die Methode zu verwenden, verwendet man bei<br />
Templates e<strong>in</strong>e besondere Ausprägung des Templates. In unserem Fall ist<br />
es Buffer. Dies bewirkt, dass für jede Ausprägung entsprechend<br />
auch e<strong>in</strong>e neue Ausprägung der Methode erzeugt wird, was natürlich<br />
notwendig ist.<br />
Nur um e<strong>in</strong>em oft gemachten Denkfehler vorzubeugen: Manche Leser<br />
könnten jetzt anmerken, dass es ja Fälle geben kann, <strong>in</strong> denen e<strong>in</strong>e Methode<br />
von der Ausprägung des Templates unabhängig ist. E<strong>in</strong>e Methode, die bei<br />
unserem Buffer die Anzahl der gespeicherten Elemente liefert, wäre theoretisch<br />
e<strong>in</strong> solcher Fall, denn diese Anzahl ist immer e<strong>in</strong>fach <strong>in</strong> num_elements_<br />
gespeichert. Jedoch, wer garantiert uns, dass das <strong>in</strong>nere Layout der Klasse <strong>in</strong><br />
den verschiedenen Ausprägungen äquivalent ist? Die Offsets der verschiedenen<br />
Variablen können sich sehr wohl über verschiedene Ausprägungen h<strong>in</strong>weg<br />
verändern und damit ist es mit der Gleichheit von Methoden vorbei!<br />
Vorsicht Falle: In den Zeilen 89–90 sieht man im Copy Constructor unseres<br />
Buffers, dass <strong>in</strong> e<strong>in</strong>er Schleife alle Elemente e<strong>in</strong>zeln kopiert werden.<br />
Manche Leser mögen nun geneigt se<strong>in</strong>, zu sagen, dass dies doch nicht besonders<br />
performant ist und dass man durch Verwendung von memcpy hier<br />
e<strong>in</strong>e deutliche Performancesteigerung erreichen kann. Dies jedoch würde <strong>in</strong><br />
bestimmten Fällen zur Katastrophe führen, denn damit umgeht man den Copy<br />
Constructor der Elemente! In der “langsamen” Implementation, die hier<br />
geschrieben wurde, werden diese jedoch garantiert aufgerufen.<br />
Es wurde bereits vorweggenommen, wie man bestimmte Ausprägungen<br />
von Class Templates erzeugt: Man stellt e<strong>in</strong>fach den Typ bzw. die Typen<br />
der gewünschten Ausprägung <strong>in</strong> spitzen Klammern dem Klassennamen nach.<br />
Genau dies wird auch im folgenden Testprogramm gemacht, um e<strong>in</strong>erseits<br />
e<strong>in</strong>en Buffer für char Daten und andererseits e<strong>in</strong>en Buffer für float Daten<br />
zur Verfügung zu haben (buffer_class_test.cpp):<br />
1<br />
2 // b u f f e r c l a s s t e s t . cpp − demo how to work with c l a s s templates<br />
3<br />
4 #<strong>in</strong>clude <br />
5<br />
6 #<strong>in</strong>clude ” b u f f e r c l a s s t e m p l a t e . h”<br />
7<br />
8 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
9 {<br />
10 Buffer c h a r b u f f e r ;<br />
11 Buffer f l o a t b u f f e r ;<br />
12<br />
13 c h a r b u f f e r . put ( ’ a ’ ) ;<br />
14 c h a r b u f f e r . put ( ’ b ’ ) ;<br />
15 c h a r b u f f e r . put ( ’ c ’ ) ;<br />
16 c h a r b u f f e r . put ( ’ d ’ ) ;<br />
17<br />
18 f l o a t b u f f e r . put ( 1 . 0 f ) ;<br />
19 f l o a t b u f f e r . put ( 2 . 0 f ) ;<br />
20 f l o a t b u f f e r . put ( 3 . 0 f ) ;
424 13. Templates<br />
21 f l o a t b u f f e r . put ( 4 . 0 f ) ;<br />
22<br />
23 cout
13.3 Class Templates 425<br />
E<strong>in</strong> Blick auf Zeile 26 verrät, dass zwischen den spitzen Klammern bei e<strong>in</strong>er<br />
Template Deklaration nicht ausschließlich nur generische Typenbezeichnungen<br />
als Parameter stehen können. Es können auch ganz normale typisierte<br />
Parameter <strong>in</strong> dieser Liste enthalten se<strong>in</strong>, wie hier unser Parameter<br />
MAX_NUM_ELEMENTS, der vom Typ u<strong>in</strong>t32 ist. In unserem Fall wird damit die<br />
maximale Anzahl von Elementen festgelegt, die e<strong>in</strong> Buffer halten kann und<br />
dieser Parameter ist <strong>in</strong> der Implementation äquivalent zu e<strong>in</strong>er Konstante<br />
verwendbar. Stellvertretend für die Änderungen, die sich <strong>in</strong> der Def<strong>in</strong>ition<br />
der Methoden ergeben, möchte ich <strong>in</strong> der Folge den Copy-Constructor abdrucken:<br />
74 template <br />
75 Buffer:: Buffer (<br />
76 const Buffer &s r c )<br />
77 throw( <strong>in</strong>valid argument , b a d a l l o c )<br />
78 {<br />
79 cout
426 13. Templates<br />
5<br />
6 #<strong>in</strong>clude ” b u f f e r c l a s s t e m p l a t e v 2 . h”<br />
7<br />
8 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
9 {<br />
10 Buffer c h a r b u f f e r ;<br />
11 Buffer f l o a t b u f f e r ;<br />
12<br />
13 c h a r b u f f e r . put ( ’ a ’ ) ;<br />
14 c h a r b u f f e r . put ( ’ b ’ ) ;<br />
15 c h a r b u f f e r . put ( ’ c ’ ) ;<br />
16 c h a r b u f f e r . put ( ’ d ’ ) ;<br />
17<br />
18 f l o a t b u f f e r . put ( 1 . 0 f ) ;<br />
19 f l o a t b u f f e r . put ( 2 . 0 f ) ;<br />
20 f l o a t b u f f e r . put ( 3 . 0 f ) ;<br />
21 f l o a t b u f f e r . put ( 4 . 0 f ) ;<br />
22<br />
23 cout
13.3 Class Templates 427<br />
Wenn man sich überlegt, dass hier e<strong>in</strong> konkretes Fassungsvermögen zum<br />
Teil des Typs wird, so erkennt man sofort, dass natürlich dieses Fassungsvermögen<br />
unbed<strong>in</strong>gt über e<strong>in</strong>e Konstante angegeben werden muss. Es ist ja<br />
schließlich der Compiler, der die konkrete Ausprägung aus e<strong>in</strong>em Template erzeugt<br />
und dieser hat natürlich vom Inhalt e<strong>in</strong>er Variable ke<strong>in</strong>e Ahnung, denn<br />
dieser wird ja erst zur Laufzeit festgelegt. Diese logische Schlussfolgerung<br />
wird durch Zeile 44 untermauert, die bei Entfernen der Kommentarzeichen<br />
<strong>in</strong> e<strong>in</strong>em Compilerfehler resultieren würde. Führt man den Gedanken zu Ende,<br />
was sonst noch alles zur Compilezeit bekannt ist, dann erkennt man, dass<br />
auch bestimmte Adressen <strong>in</strong> diese Kategorie fallen: Es s<strong>in</strong>d dies die Adressen,<br />
die sich auf Daten mit sogenanntem external L<strong>in</strong>kage beziehen, also solche,<br />
die auch außerhalb ihres Files sichtbar s<strong>in</strong>d, <strong>in</strong> dem sie def<strong>in</strong>iert wurden.<br />
Im Klartext bedeutet das: Adressen von globalen Variablen und Funktionen<br />
können auch als Template Parameter Anwendung f<strong>in</strong>den. Obwohl ich hier<br />
erwähnen möchte, dass diese Art von Parametrisierung nur sehr selten s<strong>in</strong>nvoll<br />
ist und nur noch von wirklichen Template Experten vorgenommen werden<br />
soll, möchte ich zum<strong>in</strong>dest e<strong>in</strong>e s<strong>in</strong>nvolle Anwendung anführen: Templates,<br />
die über e<strong>in</strong>en Funktionspo<strong>in</strong>ter typisiert werden, der e<strong>in</strong>en bestimmten Algorithmus<br />
ausführt. Zum Beispiel könnte man e<strong>in</strong> Template SortedList<br />
erstellen, das als Parameter den Funktionspo<strong>in</strong>ter auf e<strong>in</strong>e bestimmte Sortierfunktion<br />
nimmt. Dadurch kann man beim Festlegen der Ausprägung die<br />
Reihenfolge der Sortierung und den gewünschten Algorithmus e<strong>in</strong>fach durch<br />
Angabe der richtigen Sortierfunktion festlegen. Das hat den Vorteil, dass<br />
e<strong>in</strong>e aufsteigend und e<strong>in</strong>e absteigend sortierte Liste mit Elementen desselben<br />
Datentyps nicht typkompatibel s<strong>in</strong>d und e<strong>in</strong> falscher Umgang mit solchen<br />
Listen hierbei schon vom Compiler bemerkt wird.<br />
Der Output des Testprogramms beweist, dass sich am Verhalten der Buffers<br />
<strong>in</strong> Bezug zur vorangegangenen Version nichts verändert hat und dass der<br />
Copy-Constructor auch wirklich aufgerufen wird:<br />
content of the char b u f f e r : a , b , c , d<br />
content of the f l o a t b u f f e r : 1 , 2 , 3 , 4<br />
copy constructor<br />
Dass man auch e<strong>in</strong>e bestimmte Ausprägung e<strong>in</strong>es Templates selbst wieder<br />
als Template Parameter verwenden kann, zeigt sich im folgenden Beispiel, <strong>in</strong><br />
dem wir e<strong>in</strong>en Buffer zum Zwischenspeichern von Elementen verwenden, die<br />
selbst konkrete Ausprägungen e<strong>in</strong>es Templates s<strong>in</strong>d. Das Class Template für<br />
den Buffer entspricht vollständig der obigen Version und wird deshalb hier<br />
nicht abgedruckt. Die Daten, die im Buffer gespeichert werden sollen, s<strong>in</strong>d<br />
über das Template GenericStorage realisiert, das aus E<strong>in</strong>fachheitsgründen<br />
gleich im Testprogramm verewigt wurde (buffer_class_test_v3.cpp):<br />
1 // b u f f e r c l a s s t e s t v 3 . cpp − another demo f o r c l a s s templates<br />
2<br />
3<br />
4 #<strong>in</strong>clude <br />
5
428 13. Templates<br />
6 #<strong>in</strong>clude ” b u f f e r c l a s s t e m p l a t e v 2 . h”<br />
7<br />
8 template class GenericStorage<br />
9 {<br />
10 protected :<br />
11 DataType data ;<br />
12 public :<br />
13 GenericStorage ( ) { }<br />
14<br />
15 GenericStorage ( const DataType &data ) { data = data ; }<br />
16<br />
17 GenericStorage ( const GenericStorage &s r c )<br />
18 {<br />
19 data = s r c . data ;<br />
20 }<br />
21<br />
22 virtual GenericStorage &operator = (<br />
23 const GenericStorage &s r c )<br />
24 {<br />
25 data = s r c . data ;<br />
26 return (∗ this ) ;<br />
27 }<br />
28<br />
29 virtual operator DataType ( ) { return ( data ) ; }<br />
30 } ;<br />
31<br />
32 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
33 {<br />
34 Buffer a b u f f e r ;<br />
35<br />
36 a b u f f e r . put ( GenericStorage(1));<br />
37 a b u f f e r . put ( GenericStorage(2));<br />
38 a b u f f e r . put ( GenericStorage(3));<br />
39 a b u f f e r . put ( GenericStorage(4));<br />
40<br />
41 cout
13.4 Ableiten von Class Templates 429<br />
ergibt. Genau so verwenden wir Templates auch, wenn wir von ihnen ableiten.<br />
Versuchen wir uns gleich e<strong>in</strong>mal an unserem Buffer und implementieren<br />
wir e<strong>in</strong>e Variante, die uns auch Auskunft über den <strong>in</strong>neren Zustand desselben<br />
geben kann, also wie viele Elemente im Augenblick zwischengespeichert werden<br />
und wie hoch die Gesamtkapazität ist (buffer_class_template_v3.h).<br />
In dieser Abwandlung wurde aus Gründen der E<strong>in</strong>fachheit auf die erste Variante<br />
des Buffers zurückgegriffen, die nur den Typ und nicht die Anzahl der<br />
Elemente als Parameter nimmt.<br />
66 template class QueryableBuffer :<br />
67 public Buffer<br />
68 {<br />
69 public :<br />
70 QueryableBuffer ( )<br />
71 throw( b a d a l l o c ) : Buffer() {}<br />
72<br />
73 QueryableBuffer ( const QueryableBuffer &s r c )<br />
74 throw( <strong>in</strong>valid argument , b a d a l l o c ) :<br />
75 Buffer(s r c ) {}<br />
76<br />
77 // a l s o support copy<strong>in</strong>g from a ” normal ” b u f f e r<br />
78 QueryableBuffer ( const Buffer &s r c )<br />
79 throw( <strong>in</strong>valid argument , b a d a l l o c ) :<br />
80 Buffer(s r c ) {}<br />
81<br />
82 virtual ˜ QueryableBuffer ( )<br />
83 throw( ) { }<br />
84<br />
85 virtual u<strong>in</strong>t32 getNumBufferedElements ( )<br />
86 {<br />
87 return ( num elements ) ;<br />
88 }<br />
89<br />
90 virtual u<strong>in</strong>t32 getOverallCapacity ( )<br />
91 {<br />
92 return ( num elements allocated ) ;<br />
93 }<br />
94 } ;<br />
In den Zeilen 66–67 sieht man, dass das Ableiten e<strong>in</strong>es Class Templates von<br />
e<strong>in</strong>em anderen ke<strong>in</strong>e Hexerei ist: Hier wird e<strong>in</strong>fach von e<strong>in</strong>em Buffer abgeleitet,<br />
der dieselbe konkrete Ausprägung hat, wie unser QueryableBuffer.<br />
In den Initialisierungen, z.B. <strong>in</strong> den Zeilen 70–71, wo der default Konstruktor<br />
der Basis explizit aufgerufen wird, wird ebenfalls diese Ausprägung des<br />
Buffers e<strong>in</strong>gesetzt. Neben dem Copy-Constructor <strong>in</strong> den Zeilen 73–75 wurde<br />
zu Demonstrationszwecken auch noch e<strong>in</strong> anderer Konstruktor <strong>in</strong> den Zeilen<br />
78–80 e<strong>in</strong>geführt, der es zulässt, dass e<strong>in</strong> QueryableBuffer auch mit<br />
e<strong>in</strong>em “normalen” Buffer als Parameter konstruiert werden kann.<br />
Um zu zeigen, dass das ganze Spielchen, das hier implementiert wurde,<br />
auch funktioniert, werfen wir noch e<strong>in</strong>en kurzen Blick auf das dazugehörige<br />
Testprogrämmchen (buffer_class_test_v4.cpp):
430 13. Templates<br />
1<br />
2 // b u f f e r c l a s s t e s t v 4 . cpp − demo f o r derived templates<br />
3<br />
4 #<strong>in</strong>clude <br />
5<br />
6 #<strong>in</strong>clude ” b u f f e r c l a s s t e m p l a t e v 3 . h”<br />
7<br />
8 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
9 {<br />
10 QueryableBuffer b u f f e r 1 ;<br />
11 QueryableBuffer b u f f e r 2 = b u f f e r 1 ;<br />
12<br />
13 // j u s t to show that the constructor with a<br />
14 // standard b u f f e r works . . .<br />
15 Buffer b u f f e r 3 ;<br />
16 QueryableBuffer b u f f e r 4 = b u f f e r 3 ;<br />
17<br />
18 b u f f e r 1 . put ( 1 ) ;<br />
19 b u f f e r 1 . put ( 2 ) ;<br />
20 b u f f e r 1 . put ( 3 ) ;<br />
21 b u f f e r 1 . put ( 4 ) ;<br />
22<br />
23 cout
1 class QueryableFloatBuffer :<br />
2 public Buffer<br />
3 {<br />
4 public :<br />
5 QueryableFloatBuffer ( )<br />
6 throw( b a d a l l o c ) : Buffer();<br />
7<br />
8 virtual ˜ QueryableFloatBuffer ( )<br />
9 throw ( ) ;<br />
10<br />
11 virtual u<strong>in</strong>t32 getNumBufferedElements ( ) ;<br />
12<br />
13 virtual u<strong>in</strong>t32 getOverallCapacity ( ) ;<br />
14 } ;<br />
13.5 Explizite Spezialisierungen 431<br />
Man sieht <strong>in</strong> den Zeilen 1–2, dass man e<strong>in</strong>e “ganz normale” Klasse auch<br />
von e<strong>in</strong>er konkreten Ausprägung e<strong>in</strong>es Templates ableiten kann, <strong>in</strong>dem man<br />
e<strong>in</strong>fach nur die gewünschte Ausprägung entsprechend e<strong>in</strong>setzt. In unserem<br />
Fall leiten wir von der konkreten Ausprägung e<strong>in</strong>es Buffers ab, die float<br />
Elemente speichern kann.<br />
Natürlich funktioniert auch der umgekehrte Weg, nämlich e<strong>in</strong> Class Template<br />
zu schreiben, das selbst von e<strong>in</strong>er “ganz normalen” Klasse abgeleitet ist.<br />
Ebenso kann man e<strong>in</strong> Class Template schreiben, dass von e<strong>in</strong>er bestimmten<br />
Ausprägung e<strong>in</strong>es anderen Class Templates abgeleitet ist. Ich denke, die Beispiele<br />
hierzu kann ich wirklich auslassen, denn der Mechanismus ist immer<br />
derselbe:<br />
• Bezieht man sich auf e<strong>in</strong>e “normale” Klasse, dann wird als Typ der Klassenname<br />
verwendet.<br />
• Bezieht man sich auf e<strong>in</strong>e Ausprägung e<strong>in</strong>es Templates, dann ist noch die<br />
Angabe dieser Ausprägung <strong>in</strong> spitzen Klammern h<strong>in</strong>ter dem Namen des<br />
Class Templates vonnöten.<br />
13.5 Explizite Spezialisierungen<br />
E<strong>in</strong> sehr <strong>in</strong>teressantes Thema bei Templates, von dem bisher nicht die Rede<br />
war, ist die Menge des erzeugten Codes. Um zu erkennen, wodurch man<br />
die Menge des vom Compiler für bestimmte Ausprägungen erzeugten Codes<br />
bee<strong>in</strong>flussen kann, führen wir e<strong>in</strong>mal e<strong>in</strong>e kurze Analyse durch. Nehmen<br />
wir der E<strong>in</strong>fachheit halber wieder unsere e<strong>in</strong>-Parameter Variante des Buffers<br />
und nehmen wir an, dass <strong>in</strong> unserem Code irgendwo die folgenden Zeilen<br />
vorkommen:<br />
Buffer char_ptr_buf;<br />
Buffer u<strong>in</strong>t32_ptr_buf;<br />
Buffer double_ptr_buf;<br />
Hierdurch werden drei verschiedene konkrete Ausprägungen von Buffers erzeugt,<br />
e<strong>in</strong>e die e<strong>in</strong> Array von char * hält, e<strong>in</strong>e die e<strong>in</strong> Array von u<strong>in</strong>t32 *
432 13. Templates<br />
hält und schließlich noch e<strong>in</strong>e für double *. Vom Standpunkt des Laufzeitverhaltens,<br />
der Typkompatibilität und der Typsicherheit ist ja alles <strong>in</strong><br />
Ordnung. Bei genauerem Nachdenken erkennt man allerd<strong>in</strong>gs schnell, dass<br />
die ganze Angelegenheit doch e<strong>in</strong>en gröberen Haken hat: In allen drei Fällen<br />
speichert man e<strong>in</strong>fach nur Po<strong>in</strong>ter. Worauf diese zeigen, ist unserem Buffer<br />
<strong>in</strong>tern völlig egal, denn dieser <strong>in</strong>teressiert sich nur für die <strong>in</strong>terne Organisation<br />
des Speichers zum Ablegen derselben. Mit den Werten, auf die sie zeigen,<br />
hat e<strong>in</strong> Buffer nichts am Hut. Po<strong>in</strong>ter s<strong>in</strong>d e<strong>in</strong>fach immer gleich groß, egal,<br />
worauf sie zeigen, denn Computer haben eben e<strong>in</strong>mal e<strong>in</strong>e fixe Adresslänge.<br />
Für den Compiler gibt es aber nicht e<strong>in</strong>fach nur Adressen, sondern Po<strong>in</strong>ter<br />
s<strong>in</strong>d auch typisiert. Deshalb wird er für jede der obigen Ausprägungen<br />
e<strong>in</strong>es Buffers e<strong>in</strong>e vollständige konkrete Ausprägung des Templates erzeugen.<br />
Dass das dazu führen kann, dass Unmengen an quasi gleichem Code erzeugt<br />
werden, ist leicht e<strong>in</strong>zusehen.<br />
Es wäre also schön, wenn wir e<strong>in</strong>e e<strong>in</strong>fache Möglichkeit hätten, alle Ausprägungen<br />
von Buffers, die irgendwelche Po<strong>in</strong>ter halten, unter e<strong>in</strong>er bestimmten<br />
Ausprägung des Templates zusammenzufassen. Alle anderen Ausprägungen<br />
sollen wie gewohnt e<strong>in</strong>zeln und getrennt erzeugt werden.<br />
Damit s<strong>in</strong>d wir endlich bei der Erklärung der Kapitelüberschrift: Für<br />
diese Art des Zusammenfassens mehrerer Template-Ausprägungen wird oft<br />
der Begriff der Spezialisierung verwendet. Bevor wir zur Lösung des gerade<br />
eben angerissenen Problems mit den Po<strong>in</strong>tern kommen, möchte ich e<strong>in</strong>mal<br />
kurz das Pr<strong>in</strong>zip zeigen, durch das man Spezialisierungen erzeugt. Um genau<br />
zu sehen, was nun aus dem orig<strong>in</strong>alen Template kommt und was aus der<br />
Spezialisierung, wurde das ursprüngliche Template leicht verändert und um<br />
entsprechende Outputs ergänzt. Jede Methode (<strong>in</strong>klusive Konstruktoren,<br />
etc.) meldet sich nun mit dem Str<strong>in</strong>g generic ... wenn sie aufgerufen<br />
wird. Ansonsten ist diese Version vollkommen identisch zur Urversion und<br />
dementsprechend ist es s<strong>in</strong>nlos, sie hier abzudrucken.<br />
Die Deklaration der besonderen Spezialisierung auf void * sieht folgendermaßen<br />
aus (buffer_class_void_ptr_specialization.h):<br />
7<br />
8<br />
9 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
10 /∗<br />
11 ∗ s p e c i a l i z a t i o n of the Buffer template f o r void p o i n t e r s<br />
12 ∗<br />
13 ∗/<br />
14<br />
15 template class Buffer<br />
16 {<br />
17 public :<br />
18 static const u<strong>in</strong>t32 MAX NUM ELEMENTS = 8;<br />
19 private :<br />
20 // f o r b i d assignment by mak<strong>in</strong>g operator p r i v a t e<br />
21 const Buffer& operator = ( const Buffer&)<br />
22 { return (∗ this ) ; }<br />
23 protected :<br />
24 u<strong>in</strong>t32 num elements ;
25 u<strong>in</strong>t32 num elements allocated ;<br />
26 u<strong>in</strong>t32 read <strong>in</strong>dex ;<br />
27 u<strong>in</strong>t32 w r i t e i n d e x ;<br />
28 void ∗∗ elements ;<br />
29 public :<br />
30 Buffer ( )<br />
31 throw( b a d a l l o c ) ;<br />
32<br />
33 Buffer ( const Buffer &s r c )<br />
34 throw( <strong>in</strong>valid argument , b a d a l l o c ) ;<br />
35<br />
36 virtual ˜ Buffer ( )<br />
37 throw ( ) ;<br />
38<br />
39 virtual void put ( void ∗ const &element )<br />
40 throw( r a n g e e r r o r ) ;<br />
41<br />
42 virtual void ∗ getNext ( )<br />
43 throw( r a n g e e r r o r ) ;<br />
44 } ;<br />
13.5 Explizite Spezialisierungen 433<br />
In Zeile 15 sieht man, dass das Keyword template nur noch von e<strong>in</strong>er öffnenden<br />
und e<strong>in</strong>er schließenden spitzen Klammer gefolgt ist und dass ke<strong>in</strong> generischer<br />
Typ mehr angegeben wird. Das kommt daher, dass man dem Compiler<br />
auf diese Art mitteilt, für welchen bzw. welche der Template Parameter die<br />
Spezialisierung stattf<strong>in</strong>det. Die Parameter, für die man spezialisiert, kommen<br />
<strong>in</strong> der Spezialisierung nicht mehr vor. Die anderen Parameter, die weiterh<strong>in</strong><br />
vom Compiler behandelt werden sollen, bleiben erhalten.<br />
Würde man also z.B. e<strong>in</strong>e Spezialisierung unserer Variation des Templates<br />
mit den zwei Parametern machen, jedoch nur den Typ, nicht aber die Anzahl<br />
der Elemente spezialisieren wollen, so müsste hier Folgendes stehen:<br />
template<br />
class Buffer<br />
In der Folge sieht man, dass <strong>in</strong> der gesamten Deklaration alle Vorkommen<br />
des generischen Parameters durch die spezielle Ausprägung void * ersetzt<br />
wurden.<br />
Was haben wir also bisher erreicht?<br />
• Wir haben dem Compiler mitgeteilt, dass es e<strong>in</strong>e handgeschriebene Spezialisierung<br />
des Buffers für den Typ void * gibt und dass er diese besondere<br />
Ausprägung nicht mehr zu generieren braucht.<br />
• Wir haben die Deklaration dieser Spezialisierung auch wirklich von Hand<br />
vorgenommen und entsprechend angepasst. Je nach Spezialisierung wird<br />
die Anpassung verschieden ausfallen. Bei uns hat sich diese darauf beschränkt,<br />
dass wir e<strong>in</strong>fach nur den entsprechenden Typ e<strong>in</strong>gesetzt haben,<br />
wo ansonsten der generische Typ stehen würde.<br />
Wenn wir schon dem Compiler mitteilen, dass er mit dieser Ausprägung unseres<br />
Templates nichts mehr zu tun hat, dann s<strong>in</strong>d wir auch wirklich vollständig<br />
dafür verantwortlich. Das bedeutet, dass wir natürlich auch die entsprechenden<br />
Ausprägungen der Methoden selbst schreiben müssen. Genau das passiert<br />
auch <strong>in</strong> der Folge. Stellvertretend für die ganzen Methoden, die für
434 13. Templates<br />
die Spezialisierung implementiert wurden, möchte ich hier e<strong>in</strong>fach nur den<br />
default Konstruktor abdrucken:<br />
52 Buffer:: Buffer ( )<br />
53 throw( b a d a l l o c )<br />
54 {<br />
55 cout
14<br />
15 <strong>in</strong>t32 i n t e g e r s [ ] = { 1 , 2 , 3 , 4 } ;<br />
16<br />
17 i n t 3 2 b u f f e r . put ( 1 ) ;<br />
18 i n t 3 2 b u f f e r . put ( 2 ) ;<br />
19 i n t 3 2 b u f f e r . put ( 3 ) ;<br />
20 i n t 3 2 b u f f e r . put ( 4 ) ;<br />
21<br />
13.5 Explizite Spezialisierungen 435<br />
22 v o i d p t r b u f f e r . put ( re<strong>in</strong>terpret cast( i n t e g e r s ) ) ;<br />
23 v o i d p t r b u f f e r . put ( re<strong>in</strong>terpret cast( i n t e g e r s + 1 ) ) ;<br />
24 v o i d p t r b u f f e r . put ( re<strong>in</strong>terpret cast( i n t e g e r s + 2 ) ) ;<br />
25 v o i d p t r b u f f e r . put ( re<strong>in</strong>terpret cast( i n t e g e r s + 3 ) ) ;<br />
26<br />
27 cout
436 13. Templates<br />
Alles, was mit dem Buffer für <strong>in</strong>t32 geschieht, kommt aus dem generischen<br />
Typ, für den der Compiler e<strong>in</strong>e Ausprägung erzeugt hat. Alles jedoch, was<br />
mit dem Buffer für void * geschieht, bezieht sich auf unsere handgestrickte<br />
Spezialisierung.<br />
Was sieht man also daran? Es ist möglich, verschiedene Spezialisierungen<br />
per Hand zu implementieren. Die Gründe hierfür s<strong>in</strong>d mannigfaltig. E<strong>in</strong><br />
Hauptgrund ist die Möglichkeit des F<strong>in</strong>etun<strong>in</strong>gs durch spezielle Implementationsmaßnahmen,<br />
die nur für bestimmte Datentypen Geltung haben, bei<br />
anderen Typen aber zu Problemen führen würden.<br />
Was sieht man an unserer speziellen Implementation für void *? Wir<br />
haben leider unser Traumziel, die verschiedenen Po<strong>in</strong>ter-Typen h<strong>in</strong>ter den<br />
Kulissen zu e<strong>in</strong>er geme<strong>in</strong>samen Ausprägung zusammenzufassen, nicht wirklich<br />
so erreicht, wie wir das gerne hätten. In den Zeilen 22–41 sieht man sehr<br />
deutlich, dass wir e<strong>in</strong>fach per Hand alle Po<strong>in</strong>ter “gleichmachen”, <strong>in</strong>dem wir<br />
sie auf void * und wieder zurück casten. Dazu hätten wir eigentlich im Pr<strong>in</strong>zip<br />
nicht e<strong>in</strong>mal e<strong>in</strong>e Spezialisierung gebraucht, denn unsere Spezialisierung<br />
macht ja nichts Besondereres als die vollkommen generische Version.<br />
Aber, Kopf hoch, es gibt e<strong>in</strong>e Lösung, die wirklich das macht, was wir<br />
gerne hätten. Es gibt nämlich e<strong>in</strong>e Möglichkeit, e<strong>in</strong>e e<strong>in</strong>zige Spezialisierung<br />
zu schreiben, die alle Po<strong>in</strong>ter-Typen geme<strong>in</strong>sam erfasst. Wir müssen nur<br />
die Spezialisierung e<strong>in</strong> wenig anders deklarieren als im obigen Beispiel und<br />
e<strong>in</strong>en kle<strong>in</strong>en weiteren Kunstgriff anwenden, um nicht alles komplett neu zu<br />
implementieren (buffer_class_ptr_specialization.h):<br />
17 template<br />
18 class Buffer : private Buffer<br />
19 {<br />
20 private :<br />
21 // f o r b i d assignment by mak<strong>in</strong>g operator p r i v a t e<br />
22 const Buffer& operator =<br />
23 ( const Buffer&)<br />
24 { return (∗ this ) ; }<br />
25 public :<br />
26 Buffer ( )<br />
27 throw( b a d a l l o c ) ;<br />
28<br />
29 Buffer ( const Buffer &s r c )<br />
30 throw( <strong>in</strong>valid argument , b a d a l l o c ) ;<br />
31<br />
32 virtual ˜ Buffer ( )<br />
33 throw ( ) ;<br />
34<br />
35 virtual void put ( ContentType ∗ const &element )<br />
36 throw( r a n g e e r r o r ) ;<br />
37<br />
38 virtual ContentType ∗ getNext ( )<br />
39 throw( r a n g e e r r o r ) ;<br />
40 } ;<br />
In Zeile 18 ist des Pudels Kern Teil e<strong>in</strong>s versteckt, wie wir e<strong>in</strong>e Spezialisierung<br />
schreiben können, die für alle Po<strong>in</strong>ter Typen gleichermaßen vom Compiler<br />
herangezogen wird: Es wird für die spezielle Ausprägung des Buffers der Typ
13.5 Explizite Spezialisierungen 437<br />
ContentType * gewählt. Das bedeutet, dass der Compiler bis zum * beim<br />
Aussuchen e<strong>in</strong>er Ausprägung e<strong>in</strong> Match<strong>in</strong>g vornehmen kann, wodurch alle<br />
Po<strong>in</strong>ter Typen geme<strong>in</strong>sam erfasst werden. E<strong>in</strong>em Blick auf Zeile 17 entnehmen<br />
wir allerd<strong>in</strong>gs, dass wir damit alle<strong>in</strong> noch nicht wirklich e<strong>in</strong>e Reduktion<br />
des erzeugten Source Codes erreicht haben. Die Parametrisierung auf e<strong>in</strong>zelne<br />
Typen (<strong>in</strong> unserem Fall Po<strong>in</strong>ter-Typen) existiert ja nach wie vor. Also<br />
wird der Compiler auch nach wie vor für jeden e<strong>in</strong>zelnen Po<strong>in</strong>ter-Typ e<strong>in</strong>e<br />
konkrete Ausprägung erzeugen. Was soll dann das Ganze nun überhaupt?<br />
Genau hier kommt des Pudels Kern Teil zwei <strong>in</strong>s Spiel: In Zeile 18 sieht<br />
man, dass unsere Spezialisierung von e<strong>in</strong>er konkreten Ausprägung des Buffers,<br />
nämlich von e<strong>in</strong>em Buffer, abgeleitet ist. Egal also, mit welchem<br />
Typ von Po<strong>in</strong>ter wir es zu tun haben, dieser Buffer ist schon<br />
konkret und damit gibt es zum<strong>in</strong>dest von ihm ke<strong>in</strong>e neuen Ausprägungen.<br />
Wir brauchen also nur noch unsere Methoden so zu implementieren, dass sie<br />
diejenigen des Buffer aufrufen und schon haben wir, was wir wollen.<br />
Also schreiten wir zur Tat und tun das. Das Ergebnis sieht dann so aus:<br />
48 template <br />
49 Buffer:: Buffer ( )<br />
50 throw( b a d a l l o c ) : Buffer()<br />
51 {<br />
52 cout
438 13. Templates<br />
89 ∗/<br />
90 template <br />
91 ContentType ∗ Buffer:: getNext ( )<br />
92 throw( r a n g e e r r o r )<br />
93 {<br />
94 cout
35 i n t 3 2 p t r b u f f e r . put ( i n t e g e r s + 2 ) ;<br />
36 i n t 3 2 p t r b u f f e r . put ( i n t e g e r s + 3 ) ;<br />
37<br />
38 f l o a t p t r b u f f e r . put ( f l o a t s ) ;<br />
39 f l o a t p t r b u f f e r . put ( f l o a t s + 1 ) ;<br />
40 f l o a t p t r b u f f e r . put ( f l o a t s + 2 ) ;<br />
41 f l o a t p t r b u f f e r . put ( f l o a t s + 3 ) ;<br />
42<br />
43 c h a r p t r b u f f e r . put ( chars ) ;<br />
44 c h a r p t r b u f f e r . put ( chars + 1 ) ;<br />
45 c h a r p t r b u f f e r . put ( chars + 2 ) ;<br />
46 c h a r p t r b u f f e r . put ( chars + 3 ) ;<br />
47<br />
48 cout
440 13. Templates<br />
g e n e r i c put method<br />
s p e c i a l i z e d put method<br />
s p e c i a l i z e d put method<br />
s p e c i a l i z e d put method<br />
s p e c i a l i z e d put method<br />
s p e c i a l i z e d put method<br />
s p e c i a l i z e d put method<br />
s p e c i a l i z e d put method<br />
s p e c i a l i z e d put method<br />
s p e c i a l i z e d put method<br />
s p e c i a l i z e d put method<br />
s p e c i a l i z e d put method<br />
s p e c i a l i z e d put method<br />
s p e c i a l i z e d put method<br />
s p e c i a l i z e d put method<br />
s p e c i a l i z e d put method<br />
s p e c i a l i z e d put method<br />
s p e c i a l i z e d put method<br />
s p e c i a l i z e d put method<br />
s p e c i a l i z e d put method<br />
s p e c i a l i z e d put method<br />
s p e c i a l i z e d put method<br />
s p e c i a l i z e d put method<br />
s p e c i a l i z e d put method<br />
s p e c i a l i z e d put method<br />
content of i n t 3 2 b u f f e r : g e n e r i c getNext method<br />
1 , g e n e r i c getNext method<br />
2 , g e n e r i c getNext method<br />
3 , g e n e r i c getNext method<br />
4<br />
content of f l o a t b u f f e r : g e n e r i c getNext method<br />
1 , g e n e r i c getNext method<br />
2 , g e n e r i c getNext method<br />
3 , g e n e r i c getNext method<br />
4<br />
content of i n t 3 2 p t r b u f f e r : s p e c i a l i z e d getNext method<br />
s p e c i a l i z e d getNext method<br />
1 , s p e c i a l i z e d getNext method<br />
s p e c i a l i z e d getNext method<br />
2 , s p e c i a l i z e d getNext method<br />
s p e c i a l i z e d getNext method<br />
3 , s p e c i a l i z e d getNext method<br />
s p e c i a l i z e d getNext method<br />
4<br />
content of f l o a t p t r b u f f e r : s p e c i a l i z e d getNext method<br />
s p e c i a l i z e d getNext method<br />
1 , s p e c i a l i z e d getNext method<br />
s p e c i a l i z e d getNext method<br />
2 , s p e c i a l i z e d getNext method<br />
s p e c i a l i z e d getNext method<br />
3 , s p e c i a l i z e d getNext method<br />
s p e c i a l i z e d getNext method<br />
4<br />
content of c h a r p t r b u f f e r : s p e c i a l i z e d getNext method<br />
s p e c i a l i z e d getNext method<br />
a , s p e c i a l i z e d getNext method<br />
s p e c i a l i z e d getNext method<br />
b , s p e c i a l i z e d getNext method<br />
s p e c i a l i z e d getNext method<br />
c , s p e c i a l i z e d getNext method<br />
s p e c i a l i z e d getNext method<br />
d<br />
s p e c i a l i z e d d e s t r u c t o r<br />
s p e c i a l i z e d d e s t r u c t o r<br />
s p e c i a l i z e d d e s t r u c t o r<br />
s p e c i a l i z e d d e s t r u c t o r
s p e c i a l i z e d d e s t r u c t o r<br />
s p e c i a l i z e d d e s t r u c t o r<br />
g e n e r i c d e s t r u c t o r<br />
g e n e r i c d e s t r u c t o r<br />
13.5 Explizite Spezialisierungen 441<br />
Es ist hier auch sehr schön am Output zu erkennen, dass durch das Inkludieren<br />
des Headers mit unserer ersten Spezialisierung des Buffers auf void *<br />
auch wirklich diese Spezialisierung bei der Ableitung herangezogen wird.<br />
Um zu zeigen, dass man unsere jetzige Spezialisierung nicht unbed<strong>in</strong>gt<br />
zweistufig vornehmen muss, sondern diese auch direkt aus dem generischen<br />
Code erzeugen kann, ändern wir unser Testprogramm e<strong>in</strong> wenig ab, sodass es<br />
die void * Spezialisierung nicht mehr <strong>in</strong>kludiert. Das bedeutet, dass unsere<br />
allgeme<strong>in</strong>e Po<strong>in</strong>ter-Spezialisierung nun direkt von e<strong>in</strong>er vom Compiler generierten<br />
Ausprägung des Buffers abgeleitet ist. Abgesehen von e<strong>in</strong>em kle<strong>in</strong>en<br />
Stolperste<strong>in</strong> funktioniert dies natürlich auch...<br />
Vorsicht Falle: Leitet man e<strong>in</strong>e Spezialisierung von e<strong>in</strong>er bestimmten Ausprägung<br />
e<strong>in</strong>es Templates ab, die vom Compiler generiert werden muss, so<br />
kann es je nach Compiler zu e<strong>in</strong>em auf den ersten Blick unerklärlichen Effekt<br />
kommen: Der Compiler weigert sich die Spezialisierung zu übersetzen mit<br />
dem H<strong>in</strong>weis darauf, dass z.B. <strong>in</strong> unserem Fall Buffer ke<strong>in</strong>e Basisklasse<br />
der Spezialisierung sei.<br />
Bei näherem Nachdenken wird dieser Effekt erklärbar: Wenn man von<br />
e<strong>in</strong>er Klasse ableitet, dann geht der Compiler davon aus, dass die Basisklasse<br />
auch existiert. Viele Compiler ziehen gar nicht <strong>in</strong> Betracht, dass sie vielleicht<br />
selbst dafür verantwortlich se<strong>in</strong> könnten, diese Basisklasse zuerst e<strong>in</strong>mal aus<br />
e<strong>in</strong>em Template selbst zu generieren, bevor sie weitermachen. Soll heißen,<br />
rekursives Generieren von Template-Ausprägungen wird nicht wirklich immer<br />
unterstützt.<br />
Zum Glück können wir im Notfall selbst dafür sorgen, dass der Compiler<br />
e<strong>in</strong>e existierende Instanz vorf<strong>in</strong>det: Wir brauchen ihn nur früh genug dazu<br />
zu zw<strong>in</strong>gen, e<strong>in</strong>e solche zu erzeugen und schon ist er zufrieden. Wie erzeugt<br />
man e<strong>in</strong>e solche? Ganz e<strong>in</strong>fach: Man legt e<strong>in</strong>e entsprechende Variable an.<br />
Im Fall unseres Programms würde dies z.B. folgendermaßen aussehen:<br />
6 #<strong>in</strong>clude ” b u f f e r c l a s s t e m p l a t e v 4 . h”<br />
7 static Buffer dummy void ptr buffer ;<br />
8 #<strong>in</strong>clude ” b u f f e r c l a s s p t r s p e c i a l i z a t i o n . h”<br />
In Zeile 7 wird e<strong>in</strong>e s<strong>in</strong>nlose Variable angelegt, der Compiler erzeugt dadurch<br />
den notwendigen Code und schon funktioniert die Ableitung klaglos.<br />
Vorsicht Falle: Ich gebe ja zu, dass es ziemlich dumm aussieht, wenn die<br />
Lösung zu e<strong>in</strong>er Falle gleich selbst wieder e<strong>in</strong>e andere Falle be<strong>in</strong>haltet, aber<br />
leider ist es <strong>in</strong> diesem Fall so:<br />
Manche Leser mögen sich denken, dass es wirklich Verschwendung ist,<br />
gleich e<strong>in</strong>e globale Variable mit e<strong>in</strong>em Buffer anzulegen, der ja doch nie gebraucht<br />
wird. Viel weniger Speicher braucht da schon e<strong>in</strong> Po<strong>in</strong>ter auf e<strong>in</strong>en
442 13. Templates<br />
solchen Buffer, also nimmt man e<strong>in</strong>en solchen zur Hand und def<strong>in</strong>iert ganz<br />
e<strong>in</strong>fach so etwas wie:<br />
Buffer *dummy_ptr__;<br />
Leider bleibt der Compiler dadurch unbee<strong>in</strong>druckt, denn ihm genügt das Wissen,<br />
dass es sich um e<strong>in</strong>en Po<strong>in</strong>ter handelt und dass er diesen irgendwann e<strong>in</strong>mal<br />
zu gegebener Zeit typsicher behandeln muss. Zu diesem Zweck braucht<br />
er allerd<strong>in</strong>gs jetzt noch ke<strong>in</strong>e konkrete Ausprägung des Buffers zu erzeugen,<br />
denn die gegebene Zeit ist noch nicht gekommen. Niemand will derzeit<br />
nämlich mit dem Po<strong>in</strong>ter wirklich arbeiten. Das bedeutet: Verschwendung<br />
oder nicht, wir brauchen e<strong>in</strong>e “echte” Variable!<br />
Jetzt aber zurück zum Thema: Wir haben die Hürde des Erzw<strong>in</strong>gens<br />
der Erzeugung e<strong>in</strong>er Ausprägung durch unsere s<strong>in</strong>nlose Variable genommen<br />
und den Compiler überzeugt, dass er das Programm nun wirklich übersetzen<br />
soll. Damit bekommen wir beim Starten unseres Meisterwerks nun folgenden<br />
Output:<br />
g e n e r i c d e f a u l t constructor<br />
g e n e r i c d e f a u l t constructor<br />
g e n e r i c d e f a u l t constructor<br />
g e n e r i c d e f a u l t constructor<br />
s p e c i a l i z e d d e f a u l t constructor<br />
g e n e r i c d e f a u l t constructor<br />
s p e c i a l i z e d d e f a u l t constructor<br />
g e n e r i c d e f a u l t constructor<br />
s p e c i a l i z e d d e f a u l t constructor<br />
g e n e r i c put method<br />
g e n e r i c put method<br />
g e n e r i c put method<br />
g e n e r i c put method<br />
g e n e r i c put method<br />
g e n e r i c put method<br />
g e n e r i c put method<br />
g e n e r i c put method<br />
s p e c i a l i z e d put method<br />
g e n e r i c put method<br />
s p e c i a l i z e d put method<br />
g e n e r i c put method<br />
s p e c i a l i z e d put method<br />
g e n e r i c put method<br />
s p e c i a l i z e d put method<br />
g e n e r i c put method<br />
s p e c i a l i z e d put method<br />
g e n e r i c put method<br />
s p e c i a l i z e d put method<br />
g e n e r i c put method<br />
s p e c i a l i z e d put method<br />
g e n e r i c put method<br />
s p e c i a l i z e d put method<br />
g e n e r i c put method<br />
s p e c i a l i z e d put method<br />
g e n e r i c put method<br />
s p e c i a l i z e d put method<br />
g e n e r i c put method<br />
s p e c i a l i z e d put method<br />
g e n e r i c put method<br />
s p e c i a l i z e d put method<br />
g e n e r i c put method<br />
content of i n t 3 2 b u f f e r : g e n e r i c getNext method<br />
1 , g e n e r i c getNext method
13.5 Explizite Spezialisierungen 443<br />
2 , g e n e r i c getNext method<br />
3 , g e n e r i c getNext method<br />
4<br />
content of f l o a t b u f f e r : g e n e r i c getNext method<br />
1 , g e n e r i c getNext method<br />
2 , g e n e r i c getNext method<br />
3 , g e n e r i c getNext method<br />
4<br />
content of i n t 3 2 p t r b u f f e r : s p e c i a l i z e d getNext method<br />
g e n e r i c getNext method<br />
1 , s p e c i a l i z e d getNext method<br />
g e n e r i c getNext method<br />
2 , s p e c i a l i z e d getNext method<br />
g e n e r i c getNext method<br />
3 , s p e c i a l i z e d getNext method<br />
g e n e r i c getNext method<br />
4<br />
content of f l o a t p t r b u f f e r : s p e c i a l i z e d getNext method<br />
g e n e r i c getNext method<br />
1 , s p e c i a l i z e d getNext method<br />
g e n e r i c getNext method<br />
2 , s p e c i a l i z e d getNext method<br />
g e n e r i c getNext method<br />
3 , s p e c i a l i z e d getNext method<br />
g e n e r i c getNext method<br />
4<br />
content of c h a r p t r b u f f e r : s p e c i a l i z e d getNext method<br />
g e n e r i c getNext method<br />
a , s p e c i a l i z e d getNext method<br />
g e n e r i c getNext method<br />
b , s p e c i a l i z e d getNext method<br />
g e n e r i c getNext method<br />
c , s p e c i a l i z e d getNext method<br />
g e n e r i c getNext method<br />
d<br />
s p e c i a l i z e d d e s t r u c t o r<br />
g e n e r i c d e s t r u c t o r<br />
s p e c i a l i z e d d e s t r u c t o r<br />
g e n e r i c d e s t r u c t o r<br />
s p e c i a l i z e d d e s t r u c t o r<br />
g e n e r i c d e s t r u c t o r<br />
g e n e r i c d e s t r u c t o r<br />
g e n e r i c d e s t r u c t o r<br />
g e n e r i c d e s t r u c t o r<br />
Vorsicht Falle: Neben allen Aspekten der Spezialisierung, die hier angerissen<br />
wurden, können vor allem Neul<strong>in</strong>ge auf dem Gebiet der Templates<br />
oft e<strong>in</strong>er Versuchung nicht widerstehen: Sie wollen e<strong>in</strong>e “normale” Klasse<br />
implementieren, die denselben Namen hat, wie e<strong>in</strong> Class Template. Dieses<br />
Unterfangen geht unweigerlich schief, denn Overload<strong>in</strong>g ist nur für Funktionen<br />
und Methoden def<strong>in</strong>iert, nicht aber für Klassen! Deshalb gilt folgende<br />
Grundregel:<br />
Reale Klassen und Class Templates dürfen niemals denselben<br />
Namen haben!<br />
Um das Thema der Spezialisierungen abzurunden möchte ich hier nur<br />
noch kurz anmerken, dass man solche Spezialisierungen <strong>in</strong> genau derselben<br />
Form auch mit Function-Templates durchführen kann und dass diese nicht<br />
auf Class Templates beschränkt s<strong>in</strong>d. E<strong>in</strong>e explizite Besprechung von Bei-
444 13. Templates<br />
spielen möchte ich allen Lesern an dieser Stelle ersparen, denn das Schema<br />
dah<strong>in</strong>ter ist wirklich genau dasselbe, wie wir es eben bei den Class Templates<br />
besprochen haben.<br />
13.6 Verschiedenes zu Templates<br />
Nach dieser langen Abhandlung zum Thema Templates gibt es, neben den<br />
Aspekten zur Organisation des Source Codes, die <strong>in</strong> Abschnitt 13.7 noch genauer<br />
beleuchtet werden, noch zwei Kle<strong>in</strong>igkeiten, die erwähnt werden sollten:<br />
1. E<strong>in</strong> generischer Typ-Parameter kann bereits <strong>in</strong>nerhalb des Parametrisierungsteils<br />
e<strong>in</strong>es Templates (also <strong>in</strong>nerhalb der spitzen Klammern) als<br />
existenter Typ verwendet werden, nachdem er dort dr<strong>in</strong>nen deklariert<br />
wurde.<br />
2. Es ist auch möglich, dass Methoden <strong>in</strong>nerhalb e<strong>in</strong>es Class-Templates<br />
selbst wieder Template-Methoden (=Function Templates) s<strong>in</strong>d.<br />
Zur Verwendung von Typ-Parametern <strong>in</strong>nerhalb des Parametrisierungsteils<br />
werfen wir am besten e<strong>in</strong>en kurzen Blick auf das folgende Programm<br />
(generic_param_<strong>in</strong>_decl_use.cpp):<br />
1 // g e n e r i c p a r a m i n d e c l u s e . cpp − short demo that a g e n e r i c type<br />
2 // can be used i n s i d e the type d e c l a r a t i o n part o f a template<br />
3<br />
4 #<strong>in</strong>clude <br />
5 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
6<br />
7 us<strong>in</strong>g std : : cout ;<br />
8 us<strong>in</strong>g std : : endl ;<br />
9<br />
10 template <br />
11 class GenericStorage<br />
12 {<br />
13 protected :<br />
14 DataType data ;<br />
15 public :<br />
16 GenericStorage ( )<br />
17 {<br />
18 data = i n i t i a l v a l u e ;<br />
19 }<br />
20<br />
21 GenericStorage ( const DataType &data )<br />
22 {<br />
23 data = data ;<br />
24 }<br />
25<br />
26 GenericStorage ( const GenericStorage &s r c )<br />
27 {<br />
28 data = s r c . data ;<br />
29 }<br />
30<br />
31 virtual GenericStorage &operator = (<br />
32 const GenericStorage &s r c )<br />
33 {<br />
34 data = s r c . data ;
35 return (∗ this ) ;<br />
36 }<br />
37<br />
38 virtual operator DataType ( )<br />
39 {<br />
40 return ( data ) ;<br />
41 }<br />
42 } ;<br />
43<br />
44<br />
45 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
46 {<br />
47 GenericStorage dummy;<br />
48<br />
49 cout
446 13. Templates<br />
32 throw ( ) : r e a l p a r t ( s r c . r e a l p a r t ) ,<br />
33 imag<strong>in</strong>ary part ( s r c . imag<strong>in</strong>ary part )<br />
34 {<br />
35 cout
13.7 Source Code Organisation 447<br />
Dass e<strong>in</strong> Konstruieren von e<strong>in</strong>er anderen Ausprägung von ComplexNumber<br />
nur möglich ist, wenn Real- und Imag<strong>in</strong>ärteil zue<strong>in</strong>ander kompatibel s<strong>in</strong>d,<br />
ist durch die Initialisierung der Members <strong>in</strong> den Zeilen 40–41 sichergestellt.<br />
Sollten diese nicht kompatibel se<strong>in</strong>, dann beschwert sich natürlich sofort der<br />
Compiler.<br />
Auffällig an diesem Beispiel ist jedoch der extra implementierte Copy-<br />
Constructor <strong>in</strong> den Zeilen 31–36. Wozu brauchen wir diesen, wo doch bereits<br />
e<strong>in</strong> Template existiert, von dem e<strong>in</strong>e konkrete Ausprägung der Copy-<br />
Constructor se<strong>in</strong> könnte? Die Antwort ist e<strong>in</strong>fach: Der Copy-Constructor ist<br />
für den Compiler etwas ganz Besonderes und er denkt nicht im Traum daran,<br />
diesen aus dem Template zu erzeugen. Entweder er existiert explizit, so wie<br />
hier, oder er wird vom Compiler implizit nach se<strong>in</strong>en eigenen Regeln erzeugt.<br />
13.7 Source Code Organisation<br />
Bisher war immer davon die Rede, dass der Compiler nur dann den Code für<br />
e<strong>in</strong>e konkrete Ausprägung e<strong>in</strong>es Templates generieren kann, wenn er ihn auch<br />
vollständig sieht. Deshalb waren Deklaration und Def<strong>in</strong>ition aller Templates<br />
bisher immer vollständig <strong>in</strong> unseren Header Files enthalten. Schöner wäre<br />
es allerd<strong>in</strong>gs, wenn wir wieder zu unserer guten alten Methode greifen und<br />
die Deklarationen <strong>in</strong> e<strong>in</strong>en Header, die Def<strong>in</strong>itionen jedoch <strong>in</strong> e<strong>in</strong> cpp File<br />
schreiben könnten.<br />
E<strong>in</strong>en weiteren Aspekt gibt es noch, der bei Templates zu beachten ist:<br />
Der Scope des Compilers ist immer auf e<strong>in</strong>e e<strong>in</strong>zige sogenannte Compilation<br />
Unit beschränkt, also salopp gesprochen auf e<strong>in</strong> cpp File. Das bedeutet<br />
bei der Verwendung von Templates Folgendes: Wenn e<strong>in</strong> Header mit Template<br />
Deklarationen und Def<strong>in</strong>itionen <strong>in</strong> mehreren Files <strong>in</strong>kludiert ist, dann<br />
erzeugt der Compiler bei jedem e<strong>in</strong>zelnen File, das er getrennt übersetzt, alle<br />
benötigten konkreten Ausprägungen der Templates von Neuem. Nehmen<br />
wir als Beispiel an, dass wir <strong>in</strong> e<strong>in</strong>em Programm vier verschiedene cpp Files<br />
haben. In jedem e<strong>in</strong>zelnen dieser Files würde unser Buffer Template Header<br />
<strong>in</strong>kludiert werden und <strong>in</strong> jedem e<strong>in</strong>zelnen File würde e<strong>in</strong> Buffer gebraucht<br />
werden. So, wie wir bisher mit Templates gearbeitet haben, würde<br />
der Compiler nun auch für jedes der e<strong>in</strong>zelnen Files e<strong>in</strong>e neue, identische<br />
konkrete Ausprägung e<strong>in</strong>- und desselben Buffers für <strong>in</strong>t32 erzeugen. Zum<br />
Glück ist von Seiten des Compilers dafür gesorgt, dass diese identischen Ausprägungen<br />
nur <strong>in</strong>nerhalb e<strong>in</strong>er Compilation Unit sichtbar s<strong>in</strong>d, sonst würde<br />
sich der L<strong>in</strong>ker beschweren. Nichts desto Trotz haben wir es hier mit s<strong>in</strong>nlos<br />
erzeugtem Code zu tun, denn es würde ja reichen, dass e<strong>in</strong>e Ausprägung<br />
e<strong>in</strong>mal erzeugt wird und dann e<strong>in</strong>fach <strong>in</strong> den anderen Compilation Units<br />
wiederverwendet wird, wenn sie schon existiert.<br />
Leider habe ich an dieser Stelle mehr schlechte als gute Nachrichten. Es<br />
ist nämlich im Pr<strong>in</strong>zip nicht möglich, die Def<strong>in</strong>ition e<strong>in</strong>es Templates e<strong>in</strong>fach<br />
<strong>in</strong> e<strong>in</strong> getrenntes File zu schreiben, das für sich alle<strong>in</strong> übersetzt und dann zum
448 13. Templates<br />
Executable dazugel<strong>in</strong>kt wird. Das Problem hierbei ist, dass sich der Compiler<br />
für jede e<strong>in</strong>zelne übersetzte Compilation Unit merken müsste, welche<br />
konkreten Ausprägungen e<strong>in</strong>es Templates er jetzt braucht. Nachdem er auf<br />
diese Art alle Files übersetzt hat, müsste er die Liste der gebrauchten Ausprägungen<br />
nehmen. Mit Hilfe dieser Liste müsste er die Compilation Unit<br />
übersetzen, welche die entsprechenden Def<strong>in</strong>itionen enthält, damit er auch<br />
wirklich den gesamten benötigten Code erzeugt. Leider tut das der Compiler<br />
nicht und es gibt auch ke<strong>in</strong>en vorgegebenen Weg im Standard, wie er<br />
das tun sollte. Das bedeutet, dass wir die Geschichte mit dem Aufteilen <strong>in</strong><br />
Deklarationen und Def<strong>in</strong>itionen im Pr<strong>in</strong>zip vergessen können.<br />
Es gibt jedoch zum Glück auch e<strong>in</strong>e gute Nachricht: Im C ++ Standard ist<br />
e<strong>in</strong> Weg vorgegeben, wie der Compiler dafür sorgen kann und soll, dass nicht<br />
e<strong>in</strong> und dieselbe Ausprägung e<strong>in</strong>es Templates für jede e<strong>in</strong>zelne Compilation<br />
Unit immer wieder von Neuem gebaut wird und damit den Code unnötig<br />
aufbläst: Man kann Def<strong>in</strong>itionen mit dem Keyword export versehen. Der<br />
Compiler reagiert laut Standard, wenn er auf e<strong>in</strong>e gewünschte Ausprägung<br />
e<strong>in</strong>es Templates stößt folgendermaßen:<br />
1. Er sieht nach, ob es schon e<strong>in</strong>e zuvor erzeugte, exportierte Ausprägung<br />
<strong>in</strong> der Form gibt, wie sie gerade gebraucht wird.<br />
2. Wenn ja, wird sie e<strong>in</strong>fach verwendet.<br />
3. Wenn ne<strong>in</strong>, wird e<strong>in</strong>e neue erzeugt und für eventuellen späteren Gebrauch<br />
exportiert.<br />
Leider hat auch das e<strong>in</strong>en Haken: Nur ausgesprochen wenige Compiler beherrschen<br />
den Export und die Wiederverwendung von speziellen Ausprägungen<br />
außerhalb der Compilation Unit, <strong>in</strong> der sie erzeugt wurden :-(.<br />
Zum<strong>in</strong>dest aber ist die Existenz e<strong>in</strong>es Standard-Vorgehens e<strong>in</strong> gutes Zeichen,<br />
dass sich die Situation diesbezüglich bald zum Guten ändern könnte.<br />
Deshalb möchte ich für Templates die folgende Vorgehensweise anregen:<br />
• Man schreibt e<strong>in</strong>en Header, <strong>in</strong> dem nur die Deklarationen enthalten s<strong>in</strong>d.<br />
Das ist im Pr<strong>in</strong>zip der e<strong>in</strong>zige Teil, den Benutzer von Templates wirklich<br />
zu sehen bekommen sollen.<br />
• Man schreibt e<strong>in</strong> cpp File, <strong>in</strong> dem alle benötigten Def<strong>in</strong>itionen enthalten<br />
s<strong>in</strong>d. Hierbei ist jede Def<strong>in</strong>ition mit export zu versehen, <strong>in</strong> der Hoffnung,<br />
dass <strong>in</strong> Kürze die Compiler damit wirklich etwas anfangen können :-).<br />
• Man <strong>in</strong>kludiert dieses cpp File am Ende des Headers. Mir ist schon bewusst,<br />
dass sich nun bei vielen Lesern die Zehennägel aufrollen, denn e<strong>in</strong><br />
cpp File sollte man beim besten Willen nicht <strong>in</strong>kludieren. Alle Leser, die<br />
dies wirklich außerordentlich stört, können auch gerne e<strong>in</strong>e andere Extension,<br />
wie z.B. tcpp (für Template-cpp) oder die auch gebräuchliche Extension<br />
tcc verwenden. In persönlich bevorzuge die Methode, alles, was<br />
mit Templates zu tun hat, getrennt vom Rest der Headers und Sources, <strong>in</strong><br />
e<strong>in</strong>em eigenen Subdirectory zu halten und cpp zu verwenden.
13.7 Source Code Organisation 449<br />
Dadurch ist zwar weiterh<strong>in</strong> alles, also auch die Def<strong>in</strong>ition, <strong>in</strong>kludiert, jedoch<br />
bekommen die User der Templates nicht mehr alles auf e<strong>in</strong>mal zu Gesicht,<br />
was sie im Pr<strong>in</strong>zip gar nicht sehen sollen. Sie sollen sich ja nur für die<br />
Deklarationen <strong>in</strong>teressieren, nicht für die Def<strong>in</strong>itionen.<br />
• Der so <strong>in</strong> zwei Teile zerlegte Header wird wie gewohnt von den benutzenden<br />
Teilen des Templates e<strong>in</strong>gebunden.<br />
Am Beispiel betrachtet sieht dies also folgendermaßen aus: Wir schreiben<br />
e<strong>in</strong>e Template Deklaration (generic_storage.h).<br />
1 // g e n e r i c s t o r a g e . h − j u s t the d e c l a r a t i o n o f the g e n e r i c<br />
2 // storage template<br />
3<br />
4 #ifndef g e n e r i c s t o r a g e h<br />
5 #def<strong>in</strong>e g e n e r i c s t o r a g e h<br />
6<br />
7 template <br />
8 class GenericStorage<br />
9 {<br />
10 protected :<br />
11 DataType data ;<br />
12 public :<br />
13 GenericStorage ( )<br />
14 throw ( ) ;<br />
15<br />
16 GenericStorage ( const DataType &data )<br />
17 throw ( ) ;<br />
18<br />
19 GenericStorage ( const GenericStorage &s r c )<br />
20 throw ( ) ;<br />
21<br />
22 virtual GenericStorage &operator = (<br />
23 const GenericStorage &s r c )<br />
24 throw ( ) ;<br />
25<br />
26 virtual operator DataType ( )<br />
27 throw ( ) ;<br />
28<br />
29 } ;<br />
30<br />
31 // the f o l l o w i n g i n c l u s i o n i s unfortunately not avoidable<br />
32 #<strong>in</strong>clude ” g e n e r i c s t o r a g e . cpp”<br />
33<br />
34 #endif // g e n e r i c s t o r a g e h<br />
Zu dieser Deklaration schreiben wir die entsprechenden Def<strong>in</strong>itionen <strong>in</strong> das<br />
File generic_storage.cpp:<br />
1 // g e n e r i c s t o r a g e . cpp − d e f i n i t i o n s o f the template methods<br />
2<br />
3 #<strong>in</strong>clude ” g e n e r i c s t o r a g e . h”<br />
4<br />
5 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
6 /∗<br />
7 ∗/<br />
8 export template <br />
9 GenericStorage:: GenericStorage ( )<br />
10 throw( )<br />
11 {
450 13. Templates<br />
12 }<br />
13<br />
14 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
15 /∗<br />
16 ∗/<br />
17 export template <br />
18 GenericStorage:: GenericStorage ( const DataType &data )<br />
19 throw( )<br />
20 {<br />
21 data = data ;<br />
22 }<br />
23<br />
24 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
25 /∗<br />
26 ∗/<br />
27 export template <br />
28 GenericStorage:: GenericStorage ( const GenericStorage &s r c )<br />
29 throw( )<br />
30 {<br />
31 data = s r c . data ;<br />
32 }<br />
33<br />
34 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
35 /∗<br />
36 ∗/<br />
37 export template <br />
38 GenericStorage &GenericStorage:: operator = (<br />
39 const GenericStorage &s r c )<br />
40 throw( )<br />
41 {<br />
42 data = s r c . data ;<br />
43 return (∗ this ) ;<br />
44 }<br />
45<br />
46 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
47 /∗<br />
48 ∗/<br />
49 export template <br />
50 GenericStorage:: operator DataType ( )<br />
51 throw( )<br />
52 {<br />
53 return ( data ) ;<br />
54 }<br />
Das Programm, das mit diesem Template arbeiten will, <strong>in</strong>kludiert wie gewohnt<br />
e<strong>in</strong>fach unseren Header (generic_storage_test.cpp):<br />
1 // g e n e r i c s t o r a g e t e s t . cpp − t e s t program f o r the g e n e r i c storage<br />
2 // template that has been separated i n t o d e c l a r a t i o n and<br />
3 // d e f i n i t i o n f i l e s<br />
4<br />
5 #<strong>in</strong>clude <br />
6 #<strong>in</strong>clude <br />
7<br />
8 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
9 #<strong>in</strong>clude ” g e n e r i c s t o r a g e . h”<br />
10<br />
11 us<strong>in</strong>g std : : cout ;<br />
12 us<strong>in</strong>g std : : endl ;<br />
13<br />
14 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
15 {<br />
16 GenericStorage i n t 3 2 s t o r a g e ;<br />
17 i n t 3 2 s t o r a g e = 0;
18 GenericStorage double storage ( 1 2 . 0 ) ;<br />
19<br />
13.7 Source Code Organisation 451<br />
20 GenericStorage i n t 3 2 s t o r a g e 2 = i n t 3 2 s t o r a g e ;<br />
21 GenericStorage d o u b l e s t o r a g e 2 ;<br />
22 d o u b l e s t o r a g e 2 = double storage ;<br />
23<br />
24 cout
14. Namespaces<br />
Als letztes Feature im C ++ Sprachstandard kommen wir zu den sogenannten<br />
Namespaces, die man sehr s<strong>in</strong>nvoll zur Modularisierung von Software e<strong>in</strong>setzen<br />
kann. Mit Hilfe von Namespaces kann man logische Gruppierungen<br />
def<strong>in</strong>ieren und gegene<strong>in</strong>ander abgrenzen. Nehmen wir e<strong>in</strong>fach an, wir hätten<br />
e<strong>in</strong>e Sammlung von Klassen, Templates und Funktionen, die sich alle um das<br />
Thema Datenstrukturen drehen. Dann wäre es nur logisch, diese Sammlung<br />
nach außen h<strong>in</strong> als Gruppierung z.B. unter dem Namen Datastructures_<br />
anzubieten. Grundsätzlich geht das sehr e<strong>in</strong>fach, wie man am folgenden<br />
Beispiel sehen kann. Als Demonstration, wie man Funktionen <strong>in</strong> e<strong>in</strong>en Namespace<br />
verpacken kann, schreiben wir e<strong>in</strong>fach e<strong>in</strong>e beliebige <strong>in</strong>l<strong>in</strong>e Funktion<br />
und stecken sie <strong>in</strong> e<strong>in</strong>en Header (someth<strong>in</strong>g.h):<br />
1 // someth<strong>in</strong>g . h − j u s t a dummy function<br />
2<br />
3 #ifndef someth<strong>in</strong>g h<br />
4 #def<strong>in</strong>e someth<strong>in</strong>g h<br />
5<br />
6 #<strong>in</strong>clude <br />
7<br />
8 us<strong>in</strong>g std : : cout ;<br />
9 us<strong>in</strong>g std : : endl ;<br />
10<br />
11 namespace Datastructures<br />
12 {<br />
13<br />
14 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
15 /∗<br />
16 ∗/<br />
17 <strong>in</strong>l<strong>in</strong>e void doSometh<strong>in</strong>g ( )<br />
18 {<br />
19 cout
454 14. Namespaces<br />
geme<strong>in</strong>samen Namespace haben wollen, dann fällt auf, dass diese e<strong>in</strong>fache<br />
Klammerung eigentlich noch nicht der Weisheit letzter Schluss se<strong>in</strong> kann.<br />
Denn damit müsste ja alles, was zu e<strong>in</strong>em Namespace gehört auch geme<strong>in</strong>sam<br />
<strong>in</strong> e<strong>in</strong>em File stehen, was sicher nicht im S<strong>in</strong>ne des Erf<strong>in</strong>ders ist. Zum Glück<br />
ist dem auch nicht so, denn Namespaces s<strong>in</strong>d offen. Das bedeutet, dass an<br />
beliebig vielen verschiedenen Orten Teile e<strong>in</strong>es Namespaces existieren können.<br />
Solange der Bezeichner des Namespaces derselbe ist, werden sie auch vom<br />
Compiler demselben Namespace zugerechnet. Mit jeder neuen Def<strong>in</strong>ition,<br />
die zu e<strong>in</strong>em Namespace gehört, wird dieser also erweitert. Genau diese<br />
Eigenschaft nützen wir <strong>in</strong> e<strong>in</strong>em kurzen Beispiel aus. Werfen wir e<strong>in</strong>en Blick<br />
auf e<strong>in</strong> Template e<strong>in</strong>es primitiven Stacks (simple_stack.h):<br />
1 // s i m ple stack . h − a simple stack template<br />
2<br />
3 #ifndef s i m p l e s t a c k h<br />
4 #def<strong>in</strong>e s i m p l e s t a c k h<br />
5<br />
6 #<strong>in</strong>clude <br />
7 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
8<br />
9 namespace Datastructures<br />
10 {<br />
11 us<strong>in</strong>g std : : r a n g e e r r o r ;<br />
12 us<strong>in</strong>g std : : b a d a l l o c ;<br />
13<br />
14 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
15 /∗<br />
16 ∗ Stack<br />
17 ∗<br />
18 ∗ j u s t a simple stack template<br />
19 ∗<br />
20 ∗/<br />
21<br />
22 template <br />
23 class Stack<br />
24 {<br />
25 public :<br />
26 static const u<strong>in</strong>t32 MAX NUM ELEMENTS = 8;<br />
27 private :<br />
28 Stack ( const Stack&) {}<br />
29 const Stack& operator = (<br />
30 const Stack&) { return (∗ this ) ; }<br />
31 protected :<br />
32 ElementType ∗ elements ;<br />
33 u<strong>in</strong>t32 num elements ;<br />
34 public :<br />
35 Stack ( )<br />
36 throw( b a d a l l o c ) : num elements (0)<br />
37 {<br />
38 elements = new ElementType [MAX NUM ELEMENTS] ;<br />
39 }<br />
40<br />
41 virtual ˜ Stack ( )<br />
42 throw( )<br />
43 {<br />
44 delete [ ] elements ;<br />
45 }<br />
46<br />
47 virtual void push ( ElementType const &element )<br />
48 throw( r a n g e e r r o r )
49 {<br />
50 i f ( num elements >= MAX NUM ELEMENTS)<br />
51 throw r a n g e e r r o r ( ” stack overflow ” ) ;<br />
52 elements [ num elements ++] = element ;<br />
53 }<br />
54<br />
55 virtual ElementType pop ( )<br />
56 throw( r a n g e e r r o r )<br />
57 {<br />
58 i f ( num elements
456 14. Namespaces<br />
33 u<strong>in</strong>t32 num elements allocated ;<br />
34 u<strong>in</strong>t32 read <strong>in</strong>dex ;<br />
35 u<strong>in</strong>t32 w r i t e i n d e x ;<br />
36 char ∗ elements ;<br />
37 public :<br />
38 CharBuffer ( )<br />
39 throw( b a d a l l o c ) ;<br />
40<br />
41<br />
42 virtual ˜ CharBuffer ( )<br />
43 throw( )<br />
44 {<br />
45 delete [ ] elements ;<br />
46 }<br />
47<br />
48 virtual void put ( char element )<br />
49 throw( r a n g e e r r o r ) ;<br />
50<br />
51 virtual char getNext ( )<br />
52 throw( r a n g e e r r o r ) ;<br />
53 } ;<br />
54<br />
55 } // end namespace Datastructures<br />
56<br />
57 #endif // c h a r b u f f e r h<br />
Die Def<strong>in</strong>ition der Methoden ist erwartungsgemäß <strong>in</strong> char_buffer.cpp zu<br />
f<strong>in</strong>den, was sich dann so liest:<br />
1 #<strong>in</strong>clude ” c h a r b u f f e r . h”<br />
2<br />
3 namespace Datastructures<br />
4 {<br />
5 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
6 /∗<br />
7 ∗/<br />
8 CharBuffer : : CharBuffer ( )<br />
9 throw( b a d a l l o c )<br />
10 {<br />
11 num elements = 0;<br />
12 read <strong>in</strong>dex = 0;<br />
13 w r i t e i n d e x = 0;<br />
14 num elements allocated = 0 ; // j u s t <strong>in</strong> case new f a i l s . . .<br />
15 elements = new char [MAX NUM ELEMENTS] ;<br />
16 num elements allocated = MAX NUM ELEMENTS;<br />
17 }<br />
18<br />
19 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
20 /∗<br />
21 ∗/<br />
22 void CharBuffer : : put ( char element )<br />
23 throw( r a n g e e r r o r )<br />
24 {<br />
25 i f ( num elements >= num elements allocated )<br />
26 throw r a n g e e r r o r ( ” b u f f e r overflow ” ) ;<br />
27 num elements ++;<br />
28 elements [ w r i t e i n d e x ++] = element ;<br />
29 i f ( w r i t e i n d e x >= num elements allocated )<br />
30 w r i t e i n d e x = 0;<br />
31 }<br />
32<br />
33 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
34 /∗<br />
35 ∗/
36 char CharBuffer : : getNext ( )<br />
37 throw( r a n g e e r r o r )<br />
38 {<br />
39 i f ( num elements = num elements allocated )<br />
44 read <strong>in</strong>dex = 0;<br />
45 return ( element ) ;<br />
46 }<br />
47<br />
48 } // end namespace Datastructures<br />
14. Namespaces 457<br />
Wie zu erwarten war, muss man auch die Def<strong>in</strong>itionen <strong>in</strong> denselben Namespace<br />
verpacken, <strong>in</strong> dem schon die Deklarationen zu f<strong>in</strong>den waren. Verabsäumt<br />
man das, so ist der Compiler recht unglücklich damit, weil er nicht<br />
erkennen kann, zu welcher Deklaration die Def<strong>in</strong>ition gehört.<br />
Wir haben also nun e<strong>in</strong>en Namespace Datastructures_ def<strong>in</strong>iert, <strong>in</strong> dem<br />
e<strong>in</strong> Buffer, e<strong>in</strong> Stack Template und e<strong>in</strong>e (zugegeben recht s<strong>in</strong>nlose) Funktion<br />
enthalten s<strong>in</strong>d. Wie man nun pr<strong>in</strong>zipiell mit diesem Namespace umgeht, zeigt<br />
unser Demoprogramm (first_namespace_demo.cpp):<br />
1 // first namespace demo . cpp − demo how to use namespaces<br />
2<br />
3 #<strong>in</strong>clude <br />
4<br />
5 #<strong>in</strong>clude ” simple stack . h”<br />
6 #<strong>in</strong>clude ” c h a r b u f f e r . h”<br />
7 #<strong>in</strong>clude ”someth<strong>in</strong>g . h”<br />
8 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
9<br />
10 us<strong>in</strong>g std : : cout ;<br />
11 us<strong>in</strong>g std : : endl ;<br />
12<br />
13 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
14 {<br />
15 Datastructures : : Stack a stack ;<br />
16 Datastructures : : CharBuffer a b u f f e r ;<br />
17<br />
18 Datastructures : : doSometh<strong>in</strong>g ( ) ;<br />
19<br />
20 a stack . push ( 1 ) ;<br />
21 a stack . push ( 2 ) ;<br />
22 a stack . push ( 3 ) ;<br />
23 a stack . push ( 4 ) ;<br />
24<br />
25 cout
458 14. Namespaces<br />
39 Datastructures : : CharBuffer : :MAX NUM ELEMENTS
14. Namespaces 459<br />
tion stehen, also <strong>in</strong> unserem Fall z.B. <strong>in</strong> Zeile 15, dann würde sich die Verwendung<br />
von cout ohne Scope Operator auf die ma<strong>in</strong> Funktion beschränken.<br />
Sp<strong>in</strong>nt man diesen Gedanken mit der Sichtbarkeit weiter, so kann man<br />
sich leicht vorstellen, dass e<strong>in</strong>e us<strong>in</strong>g Direktive, die <strong>in</strong>nerhalb e<strong>in</strong>es Namespaces<br />
steht, für den gesamten Namespace gilt. Dieses Feature wird gerne<br />
verwendet, wenn e<strong>in</strong> großes Modul mit mehreren kle<strong>in</strong>en Submodulen arbeitet.<br />
Alle Teile des Namespaces können hierbei auf die Submodule ohne<br />
besonderen Scope zugreifen, die Programmteile, die außerhalb des Namespaces<br />
stehen, aber nicht. Durch diesen Mechanismus kann man sehr gut<br />
gewisse Implementationsdetails vor den Benutzern e<strong>in</strong>er Library verstecken.<br />
Neben der Verwendung von us<strong>in</strong>g für e<strong>in</strong>zelne Teile e<strong>in</strong>es Namespaces<br />
gibt es auch noch die Möglichkeit mit e<strong>in</strong>er e<strong>in</strong>zigen Anweisung gleich den<br />
gesamten Namespace sichtbar zu machen. Wollten wir unseren gesamten<br />
Namespace Datastructures_ sichtbar machen, würden wir Folgendes schreiben:<br />
us<strong>in</strong>g namespace Datastructures_;<br />
Damit wären alle Elemente aus unserem Namespace ohne expliziten Scope<br />
ansprechbar. Ich möchte jedoch darauf h<strong>in</strong>weisen, dass diese Vorgehensweise<br />
wirklich nur <strong>in</strong> besonderen Fällen s<strong>in</strong>nvoll ist. Im Normalfall ist es besser, e<strong>in</strong>zelne<br />
Elemente aus e<strong>in</strong>zelnen Namespaces e<strong>in</strong>zub<strong>in</strong>den um Überraschungen<br />
zu verh<strong>in</strong>dern. Es könnte ja se<strong>in</strong>, dass <strong>in</strong> e<strong>in</strong>em Namespace gewisse Elemente<br />
enthalten s<strong>in</strong>d, die eigentlich nur für den privaten Gebrauch <strong>in</strong>nerhalb der<br />
Implementation dieses Namespaces gedacht s<strong>in</strong>d. Mir ist schon klar, dass<br />
das eigentlich nur aufgrund von eher mangelhaftem Design von Namespaces<br />
passieren kann, aber man soll niemanden <strong>in</strong> Versuchung führen :-).<br />
Es gibt auch noch e<strong>in</strong>en anderen Grund, warum man nicht gleich ganze<br />
Namespaces automatisch sichtbar machen soll: Was passiert, wenn <strong>in</strong> zwei<br />
Namespaces zufällig Elemente unter demselben Namen existieren? Dann<br />
kann sich der Compiler beim Aufruf ohne Scope nicht wirklich entscheiden,<br />
welches Element denn nun geme<strong>in</strong>t ist. In e<strong>in</strong>em solchen Fall wird man also<br />
dann erst recht wieder gezwungen, mit e<strong>in</strong>em expliziten Scope zu arbeiten,<br />
um den Compiler zu beruhigen.<br />
E<strong>in</strong>en Fall allerd<strong>in</strong>gs gibt es, <strong>in</strong> dem der Compiler e<strong>in</strong>en Name-Clash selbst<br />
auflöst: Nehmen wir an, wir hätten <strong>in</strong> unserem Namespace Datastructures_<br />
die Direktive<br />
us<strong>in</strong>g namespace SomeStacks_;<br />
und im Namespace SomeStacks_ hätten wir, so wie <strong>in</strong> Datastructures_, e<strong>in</strong>e<br />
Funktion doSometh<strong>in</strong>g mit denselben Parametern. Ruft man <strong>in</strong> e<strong>in</strong>em Codestück<br />
<strong>in</strong>nerhalb des Namespaces Datastructures_ nun doSometh<strong>in</strong>g auf,<br />
so wird automatisch die Implementation aus dem eigenen Namespace genommen<br />
und nicht die sichtbar gemachte Implementation aus SomeStacks_.<br />
Aus gutem Grund möchte ich allen Lesern raten, die Bezeichner von<br />
Namespaces immer sprechend und nicht zu kurz zu machen. Namespaces<br />
mit Bezeichnern wie z.B. DS (für Datastructures_) s<strong>in</strong>d um jeden Preis
460 14. Namespaces<br />
zu vermeiden. Durch solche Bezeichner besteht die große Gefahr, dass man<br />
irrtümlich Namespaces, die mite<strong>in</strong>ander nichts zu tun haben, zu e<strong>in</strong>em geme<strong>in</strong>samen<br />
größeren Namespace vere<strong>in</strong>t, bloß weil die Namen zufällig dieselben<br />
s<strong>in</strong>d. Das ist sicher nicht im S<strong>in</strong>ne des Erf<strong>in</strong>ders und vor allem s<strong>in</strong>d<br />
dadurch Name-Clashes praktisch vorprogrammiert. Nun gibt es aber Fälle,<br />
<strong>in</strong> denen man ke<strong>in</strong> explizites us<strong>in</strong>g für viele verschiedene Elemente aus e<strong>in</strong>em<br />
Namespace schreiben will, weil man jedes e<strong>in</strong>zelne für sich eigentlich nicht<br />
oft genug braucht, um das zu rechtfertigen. Andererseits aber braucht man<br />
trotzdem den Scope auf diesen Namespace ziemlich oft, weil man es eben<br />
mit vielen verschiedenen Namespaces zu tun hat. Für solche Fälle gibt es<br />
die Möglichkeit, e<strong>in</strong>em Namespace e<strong>in</strong> Alias zu geben. Wollte man z.B. das<br />
Alias DS für Datastructures_ im Code verwenden, so kann man folgendes<br />
Statement schreiben:<br />
namespace DS = Datastructures_;<br />
Ich würde jedoch wirklich zur Vorsicht mahnen, denn man kann Code dadurch<br />
ziemlich verwirrend und damit unlesbar machen.<br />
Zu guter Letzt möchte ich noch auf e<strong>in</strong>e Möglichkeit h<strong>in</strong>weisen, die man<br />
mit Namespaces hat und die eigentlich wie e<strong>in</strong> Paradoxon kl<strong>in</strong>gt: Es gibt<br />
auch sogenannte unnamed Namespaces. Man kann z.B. die folgenden Zeilen<br />
schreiben:<br />
1 namespace<br />
2 {<br />
3 void f ( )<br />
4 {<br />
5 // whatever code<br />
6 }<br />
7 // some code<br />
8 f ( ) ; // c a l l s the function f def<strong>in</strong>ed above<br />
9 }<br />
Hierdurch hat man e<strong>in</strong>en unnamed Namespace verwendet, der e<strong>in</strong>zig und<br />
alle<strong>in</strong> die Aufgabe hat, die Funktion mit dem glorreichen Namen f davor<br />
zu schützen, e<strong>in</strong>er anderen Funktion f <strong>in</strong> die Quere zu kommen, die an e<strong>in</strong>em<br />
anderen Ort def<strong>in</strong>iert se<strong>in</strong> könnte. Der Aufruf von f() <strong>in</strong> Zeile 8 f<strong>in</strong>det<br />
aufgrund der zuvor genannten Auflösungsstrategie sicher zuerst die im Namespace<br />
def<strong>in</strong>ierte Funktion f.<br />
Allerd<strong>in</strong>gs kann ich zu dieser Vorgehensweise wirklich nur noch sagen,<br />
dass es wohl ke<strong>in</strong>en schlimmeren Hack gibt als solche s<strong>in</strong>nlosen Funktionsnamen.<br />
Sollte man jemals <strong>in</strong> die Situation kommen, e<strong>in</strong>en unnamed Namespace<br />
zu brauchen, dann sollte man lieber e<strong>in</strong>mal stark darüber nachdenken, ob<br />
nicht e<strong>in</strong> Umschreiben des Codes dr<strong>in</strong>gend vonnöten wäre. Bei sauber geschriebenem<br />
Code ist die Wahrsche<strong>in</strong>lichkeit praktisch Null, dass man jemals<br />
unnamed Namespaces brauchen würde. Natürlich kann man jetzt auch das<br />
Argument br<strong>in</strong>gen, dass es ja z.B. alten Code gibt, den man wiederverwenden<br />
möchte und der eben leider e<strong>in</strong>mal nicht so toll ist. Aber auch dann würde<br />
ich sehr dr<strong>in</strong>gend (!) zu e<strong>in</strong>er Überarbeitung raten. Durch das Kaschieren<br />
von schlimmen Hacks hat man das Problem ja doch nur verschoben und nicht<br />
aus der Welt geschafft!
14. Namespaces 461<br />
E<strong>in</strong> gutes Haar muss ich allerd<strong>in</strong>gs trotzdem an den unnamed Namespaces<br />
lassen: In C ++ wird zwar die Verwendung des Keywords static bei globalen<br />
Variablen zum E<strong>in</strong>schränken der Sichtbarkeit auf die Compilation Unit<br />
weiterh<strong>in</strong> aus Kompatibilitätsgründen unterstützt. Jedoch wird angeraten,<br />
dieses Feature nicht mehr zu verwenden und stattdessen unnamed Namespaces<br />
zum E<strong>in</strong>satz zu br<strong>in</strong>gen. Wenn also ke<strong>in</strong> schlimmer Hack vorliegt, wie<br />
im Beispiel zuvor, man sich aber trotzdem vor eventuellen Name-Clashes aus<br />
Vorsichtsgründen schützen will, dann s<strong>in</strong>d unnamed Namespaces e<strong>in</strong> sauberes<br />
Mittel zum Zweck.
15. Verschiedenes<br />
In diesem Kapitel werden Aspekte von C ++ behandelt, die aus verschiedenen<br />
Gründen ke<strong>in</strong>en guten Platz im bisherigen Text bekommen konnten. Sie<br />
hätten den Aufbau entweder durch starke Vorgriffe oder durch starke Nebenläufigkeit<br />
zu sehr verletzt.<br />
15.1 mutable Member Variablen<br />
Es wurde <strong>in</strong> Abschnitt 6.1 als auch <strong>in</strong> Abschnitt 9.4.1 bereits erwähnt, dass<br />
es e<strong>in</strong>e saubere Alternative zum const_cast gibt. Es ist nämlich <strong>in</strong> C ++<br />
möglich, bestimmte Member-Variablen als mutable zu deklarieren. Damit<br />
dürfen sie auch im Fall e<strong>in</strong>es mit const vor Veränderung geschützten Objekts<br />
modifiziert werden, ohne dass sich der Compiler beschwert. Durch<br />
mutable steht also e<strong>in</strong> sauberer Mechanismus zur Verfügung, logische von<br />
physikalischer Konstanz zu trennen. Am Beispiel sieht das folgendermaßen<br />
aus (mutable_demo.cpp):<br />
1 // mutable demo . cpp − demo how mutable o b j e c t s help to avoid<br />
2 // the nasty c o n s t c a s t<br />
3<br />
4 #<strong>in</strong>clude <br />
5 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
6<br />
7 us<strong>in</strong>g std : : cout ;<br />
8 us<strong>in</strong>g std : : endl ;<br />
9<br />
10 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
11 /∗<br />
12 ∗ ClassWithCache<br />
13 ∗<br />
14 ∗ j u s t a dummy c l a s s with a cache<br />
15 ∗<br />
16 ∗/<br />
17<br />
18 class ClassWithCache<br />
19 {<br />
20 protected :<br />
21 mutable u<strong>in</strong>t32 c a c h e d r e s u l t ;<br />
22 mutable bool d i r t y ;<br />
23 u<strong>in</strong>t32 o r i g i n a l ;<br />
24 public :<br />
25 ClassWithCache ( )<br />
26 throw ( ) : c a c h e d r e s u l t ( 0 ) ,
464 15. Verschiedenes<br />
27 d i r t y ( true ) ,<br />
28 o r i g i n a l ( 0 ) { }<br />
29<br />
30 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
31 virtual ˜ ClassWithCache ( )<br />
32 throw( ) { }<br />
33<br />
34 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
35 virtual void s e t O r i g i n a l ( u<strong>in</strong>t32 o r i g i n a l )<br />
36 {<br />
37 o r i g i n a l = o r i g i n a l ;<br />
38 d i r t y = true ;<br />
39 }<br />
40<br />
41 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
42 virtual u<strong>in</strong>t32 g e t O r i g i n a l ( ) const<br />
43 {<br />
44 return ( o r i g i n a l ) ;<br />
45 }<br />
46<br />
47 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
48 virtual u<strong>in</strong>t32 getResult ( ) const<br />
49 {<br />
50 i f ( d i r t y )<br />
51 {<br />
52 cout
15.2 Unions im OO Kontext 465<br />
an object s t o r e s the o r i g i n a l 17<br />
a f t e r the c a l c u l a t i o n the r e s u l t i s : c a l c u l a t i n g r e s u l t . . .<br />
34 a l l f u r t h e r r e q u e s t s come from the cache :<br />
j u s t tak<strong>in</strong>g cached r e s u l t . . .<br />
34<br />
j u s t tak<strong>in</strong>g cached r e s u l t . . .<br />
34<br />
Diese Art des Cach<strong>in</strong>gs ist <strong>in</strong> vielen verschiedenen Situationen sehr brauchbar,<br />
vor allem auch, wenn es um Netzwerk- und Plattenzugriffe geht. Dort liegt<br />
dann auch e<strong>in</strong>er der Haupte<strong>in</strong>satzbereiche von mutable Member Variablen<br />
<strong>in</strong> der Praxis.<br />
Vorsicht Falle: Man kann gar nicht oft genug davor warnen: Sowohl e<strong>in</strong><br />
const_cast als auch mutable Members s<strong>in</strong>d ausschließlich dann e<strong>in</strong>zusetzen,<br />
wenn auch wirklich die logische Konstanz e<strong>in</strong>es Objekts gewahrt bleibt! Fehler,<br />
die dadurch entstehen, dass sich e<strong>in</strong> Objekt nicht mehr logisch konstant<br />
verhält, s<strong>in</strong>d unglaublich schwer zu lokalisieren und ziehen zumeist lange<br />
Nächte mit hohem Kaffeeverbrauch im Zuge e<strong>in</strong>er verzweifelten Debugg<strong>in</strong>g<br />
Session nach sich.<br />
15.2 Unions im OO Kontext<br />
In Abschnitt 2.4.3 wurden bereits die Grundeigenschaften von Unions und<br />
auch die Gefahren bei ihrer Nutzung erklärt. Da zu diesem Zeitpunkt aber die<br />
OO Konzepte von C ++ noch nicht bekannt waren, musste ich e<strong>in</strong>ige Aspekte<br />
schuldig bleiben, die ich <strong>in</strong> der Folge nachholen werde. Wenden wir uns zu<br />
diesem Zweck also am besten e<strong>in</strong>em e<strong>in</strong>fachen Beispiel zu (union_demo.cpp):<br />
1 // union demo . cpp − demo f o r unions <strong>in</strong> the OO context<br />
2<br />
3<br />
4 #<strong>in</strong>clude <br />
5 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
6<br />
7 us<strong>in</strong>g std : : cout ;<br />
8 us<strong>in</strong>g std : : c e r r ;<br />
9 us<strong>in</strong>g std : : endl ;<br />
10 us<strong>in</strong>g std : : bad cast ;<br />
11<br />
12 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
13 /∗<br />
14 ∗ DummyUnion<br />
15 ∗<br />
16 ∗ j u s t a dummy union f o r demo purposes<br />
17 ∗<br />
18 ∗/<br />
19<br />
20 union DummyUnion<br />
21 {<br />
22 private :<br />
23 DummyUnion( const DummyUnion&) {}<br />
24 const DummyUnion& operator = ( const DummyUnion&) { return (∗ this ) ; }<br />
25 public :
466 15. Verschiedenes<br />
26 u<strong>in</strong>t32 u i n t 3 2 v a l u e ;<br />
27 double double value ;<br />
28<br />
29 DummyUnion( )<br />
30 throw( )<br />
31 {<br />
32 cout
92 }<br />
93<br />
15.2 Unions im OO Kontext 467<br />
94 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
95 virtual operator u<strong>in</strong>t32 ( )<br />
96 throw( bad cast )<br />
97 {<br />
98 i f ( c u r r e n t t y p e ! = UINT32 TYPE)<br />
99 throw bad cast ( ) ;<br />
100 return ( the union . u i n t 3 2 v a l u e ) ;<br />
101 }<br />
102<br />
103 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
104 virtual operator double ( )<br />
105 throw( bad cast )<br />
106 {<br />
107 i f ( c u r r e n t t y p e ! = DOUBLE TYPE)<br />
108 throw bad cast ( ) ;<br />
109 return ( the union . double value ) ;<br />
110 }<br />
111<br />
112 } ;<br />
113<br />
114 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
115 UnionEncapsulation : : UnionEncapsulation (<br />
116 const UnionEncapsulation & s r c )<br />
117 throw ( ) : c u r r e n t t y p e ( s r c . c u r r e n t t y p e )<br />
118 {<br />
119 switch ( c u r r e n t t y p e )<br />
120 {<br />
121 case UINT32 TYPE:<br />
122 the union . u i n t 3 2 v a l u e = s r c . the union . u i n t 3 2 v a l u e ;<br />
123 break ;<br />
124 case DOUBLE TYPE:<br />
125 the union . double value = s r c . the union . double value ;<br />
126 break ;<br />
127 default :<br />
128 c u r r e n t t y p e = NO TYPE; // a l s o an exception would be ok here<br />
129 case NO TYPE:<br />
130 break ;<br />
131 }<br />
132 }<br />
133<br />
134 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
135 UnionEncapsulation& UnionEncapsulation : : operator = (<br />
136 const UnionEncapsulation & s r c )<br />
137 throw( )<br />
138 {<br />
139 c u r r e n t t y p e = s r c . c u r r e n t t y p e ;<br />
140 switch ( c u r r e n t t y p e )<br />
141 {<br />
142 case UINT32 TYPE:<br />
143 the union . u i n t 3 2 v a l u e = s r c . the union . u i n t 3 2 v a l u e ;<br />
144 break ;<br />
145 case DOUBLE TYPE:<br />
146 the union . double value = s r c . the union . double value ;<br />
147 break ;<br />
148 default :<br />
149 c u r r e n t t y p e = NO TYPE; // a l s o an exception would be ok here<br />
150 case NO TYPE:<br />
151 break ;<br />
152 }<br />
153 return (∗ this ) ;<br />
154 }<br />
155<br />
156 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
157 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )
468 15. Verschiedenes<br />
158 {<br />
159 UnionEncapsulation encap ;<br />
160<br />
161 encap . setValue ( ( u<strong>in</strong>t32 ) 1 0 ) ;<br />
162<br />
163 try<br />
164 {<br />
165 cout
15.2 Unions im OO Kontext 469<br />
durch die entsprechenden Typumwandlungs-Operatoren <strong>in</strong> den Zeilen 95–110<br />
ersetzt.<br />
Die Implementationen des Copy Constructors und des Zuweisungsoperators<br />
bergen ke<strong>in</strong>e Neuigkeiten, also sehen wir uns nur noch kurz die Verwendung<br />
unserer gekapselten Union an:<br />
• In Zeile 161 wird setValue mit e<strong>in</strong>em u<strong>in</strong>t32 Parameter aufgerufen. Der<br />
explizite Cast ist notwendig, da ja 10 e<strong>in</strong>fach als <strong>in</strong>t <strong>in</strong>terpretiert wird und<br />
somit der Compiler vor e<strong>in</strong>em Ambiguitätsproblem stehen würde.<br />
• In den Zeilen 165–166 wird korrekt auf die Union zugegriffen und dementsprechend<br />
funktioniert alles wie geplant.<br />
• In den Zeilen 167–168 jedoch wird versucht e<strong>in</strong>en double Wert zu lesen,<br />
der hier ungültig ist. Zum Glück gibt’s das try ... catch Konstrukt :-).<br />
Anzumerken wäre hier noch, dass Fehlermeldungen wie <strong>in</strong> den Zeilen 174<br />
und 189 im Normalfall immer auf cerr und nicht auf cout geschrieben<br />
werden müssen. Im Demoprogramm wurde nur deshalb auf cout geschrieben,<br />
um die Reihenfolge der e<strong>in</strong>zelnen Ausgaben nicht durche<strong>in</strong>ander zu<br />
br<strong>in</strong>gen.<br />
• In den Zeilen 177–190 f<strong>in</strong>det dasselbe Spielchen noch e<strong>in</strong>mal statt, nur eben<br />
hier mit e<strong>in</strong>em double Wert.<br />
Dass me<strong>in</strong>e Beschreibung der Vorgänge nicht frei erfunden ist, sieht man am<br />
folgenden Output, den das Programm liefert:<br />
DummyUnion constructor<br />
t r y i n g u<strong>in</strong>t32 value : 1 0<br />
t r y i n g double value : oops − t r i e d to a c c e s s wrong type . . .<br />
t r y i n g double value : 2 0 . 5<br />
t r y i n g u<strong>in</strong>t32 value : oops − t r i e d to a c c e s s wrong type . . .<br />
DummyUnion d e s t r u c t o r<br />
Obwohl wir hier also offensichtlich e<strong>in</strong>en Weg gefunden haben, Unions e<strong>in</strong>igermaßen<br />
sicher zu machen, sollte man sich e<strong>in</strong>ige Gedanken um deren<br />
Verwendung bzw. um die Vermeidung derselben machen:<br />
Wenn wir hier e<strong>in</strong>mal ganz bestimmte und besondere Vorgaben durch die<br />
Hardware oder durch Fremdsysteme außen vor lassen, ist der e<strong>in</strong>zige Grund<br />
für die Verwendung von Unions die damit erreichbare Speicherersparnis.<br />
Überlegt man allerd<strong>in</strong>gs, dass der von e<strong>in</strong>er Union benötigte Speicher<br />
immer ihrem größten Member entspricht, dann kommt man zu folgendem<br />
Schluss: Sollten alle Members e<strong>in</strong>igermaßen gleich groß se<strong>in</strong>, dann erreicht<br />
man e<strong>in</strong>e s<strong>in</strong>nvolle Ersparnis. Sollte es jedoch zum<strong>in</strong>dest e<strong>in</strong>en Member geben,<br />
der im Vergleich zu den anderen sehr groß ist, dann ist die Ersparnis<br />
nicht mehr wirklich gegeben. Ganz im Gegenteil! Man kann hierbei nämlich<br />
je nach Anwendung sogar e<strong>in</strong>e große Speicherverschwendung bewirken, denn<br />
auch für die kle<strong>in</strong>en Members wird immer der Platz des bzw. der großen<br />
belegt!<br />
Aus dieser Überlegung heraus kommt man schnell zu dem Schluss, dass es<br />
<strong>in</strong> solchen Fällen besser wäre, mit e<strong>in</strong>er vernünftigen Klassenhierarchie und<br />
unter s<strong>in</strong>nvoller Ausnützung der Polymorphismus-Eigenschaften zu arbeiten.
470 15. Verschiedenes<br />
15.3 Funktionspo<strong>in</strong>ter<br />
Wie bereits <strong>in</strong> Abschnitt 6.2.4 erwähnt, gibt es auch <strong>in</strong> C ++ die bereits<br />
von C her bekannten Funktionspo<strong>in</strong>ter. Man kann e<strong>in</strong>fach e<strong>in</strong>e entsprechende<br />
Po<strong>in</strong>tervariable def<strong>in</strong>ieren und ihr als Adresse die E<strong>in</strong>sprungadresse<br />
e<strong>in</strong>er Funktion zuweisen. In C ++ kann man Funktionspo<strong>in</strong>ter allerd<strong>in</strong>gs<br />
nicht nur auf Funktionen zeigen lassen, sondern unter anderem auch auf<br />
Member-Methoden. Sehen wir uns hierzu e<strong>in</strong>fach e<strong>in</strong> kle<strong>in</strong>es Beispiel an<br />
(function_po<strong>in</strong>ter_demo1.cpp):<br />
1 // function po<strong>in</strong>ter demo1 . cpp − f i r s t demo f o r function p o i n t e r s<br />
2<br />
3 #<strong>in</strong>clude <br />
4 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
5<br />
6 us<strong>in</strong>g std : : cout ;<br />
7 us<strong>in</strong>g std : : endl ;<br />
8<br />
9 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
10 u<strong>in</strong>t32 aCalculationFunction ( u<strong>in</strong>t32 val )<br />
11 {<br />
12 return ( val ∗ 5 ) ;<br />
13 }<br />
14<br />
15 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
16 u<strong>in</strong>t32 anotherCalculationFunction ( u<strong>in</strong>t32 val )<br />
17 {<br />
18 return ( val + 17);<br />
19 }<br />
20<br />
21 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
22 /∗<br />
23 ∗ CalcMethodCollection<br />
24 ∗<br />
25 ∗ j u s t a dummy c o l l e c t i o n of c a l c u l a t i o n methods<br />
26 ∗<br />
27 ∗/<br />
28<br />
29 class CalcMethodCollection<br />
30 {<br />
31 private :<br />
32 CalcMethodCollection ( ) ;<br />
33 CalcMethodCollection ( const CalcMethodCollection &) {}<br />
34 const CalcMethodCollection& operator = ( const CalcMethodCollection&)<br />
35 { return (∗ this ) ; }<br />
36 public :<br />
37 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
38 static u<strong>in</strong>t32 aSpecialCalculationFunction ( u<strong>in</strong>t32 val )<br />
39 {<br />
40 return ( val ∗ 2 ) ;<br />
41 }<br />
42<br />
43 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
44 static u<strong>in</strong>t32 anotherSpecialCalculationFunction ( u<strong>in</strong>t32 val )<br />
45 {<br />
46 return ( val + 12);<br />
47 }<br />
48 } ;<br />
49<br />
50<br />
51 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
52 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )
53 {<br />
54 u<strong>in</strong>t32 ( ∗ calcFunction ) ( u<strong>in</strong>t32 ) = aCalculationFunction ;<br />
55<br />
15.3 Funktionspo<strong>in</strong>ter 471<br />
56 cout
472 15. Verschiedenes<br />
so ganz genau sagen und ist vom Compiler abhängig. Entweder er lässt das<br />
Inl<strong>in</strong><strong>in</strong>g der Methode überhaupt bleiben oder er generiert zwei verschiedene<br />
Ausprägungen derselben Methode, e<strong>in</strong>e als <strong>in</strong>l<strong>in</strong>e Methode und e<strong>in</strong>e andere,<br />
die über den Funktionspo<strong>in</strong>ter aufrufbar ist. Auch andere Alternativen s<strong>in</strong>d<br />
möglich, aber e<strong>in</strong>e Diskussion darüber überlasse ich am besten den Compilerbauern<br />
:-).<br />
Im obigen Beispiel habe ich bewusst nur static Members demonstriert.<br />
Wie verhält es sich aber nun mit Funktionspo<strong>in</strong>tern auf Members, die nicht<br />
static s<strong>in</strong>d? Wenn man sich überlegt, dass bei solchen Methoden immer<br />
auch e<strong>in</strong> Scope auf die aktuelle Instanz mit im Spiel ist und dass entsprechend<br />
der this Po<strong>in</strong>ter quasi als versteckter Parameter beim Aufruf mitgegeben<br />
wird, dann kann man sich leicht vorstellen, dass die Situation hierbei nicht<br />
mehr ganz so e<strong>in</strong>fach ist. Wenn man e<strong>in</strong>en Po<strong>in</strong>ter auf e<strong>in</strong>e Methode hat, muss<br />
man also beim Aufruf dafür sorgen, dass irgendwie der Kontext zu e<strong>in</strong>er Instanz<br />
der Klasse, aus der die Methode kommt, erhalten bleibt. Wie das funktioniert,<br />
sieht man am folgenden Beispiel (function_po<strong>in</strong>ter_demo2.cpp):<br />
1 // function po<strong>in</strong>ter demo2 . cpp − demo o f ( someth<strong>in</strong>g l i k e ) function<br />
2 // p o i n t e r s to non−s t a t i c methods<br />
3<br />
4 #<strong>in</strong>clude <br />
5 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
6<br />
7 us<strong>in</strong>g std : : cout ;<br />
8 us<strong>in</strong>g std : : endl ;<br />
9<br />
10<br />
11 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
12 /∗<br />
13 ∗ CalcMethodCollection<br />
14 ∗<br />
15 ∗ j u s t a dummy c o l l e c t i o n of c a l c u l a t i o n methods<br />
16 ∗<br />
17 ∗/<br />
18<br />
19 class CalcMethodCollection<br />
20 {<br />
21 private :<br />
22 CalcMethodCollection ( const CalcMethodCollection &) {}<br />
23 const CalcMethodCollection& operator = ( const CalcMethodCollection&)<br />
24 { return (∗ this ) ; }<br />
25 protected :<br />
26 u<strong>in</strong>t32 m u l t i p l i c a t o r ;<br />
27 u<strong>in</strong>t32 o f f s e t ;<br />
28 public :<br />
29 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
30 CalcMethodCollection ( u<strong>in</strong>t32 m u l t i p l i c a t o r , u<strong>in</strong>t32 o f f s e t )<br />
31 throw ( ) : m u l t i p l i c a t o r ( m u l t i p l i c a t o r ) ,<br />
32 o f f s e t ( o f f s e t ) {}<br />
33<br />
34 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
35 virtual ˜ CalcMethodCollection ( ) { }<br />
36<br />
37 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
38 u<strong>in</strong>t32 aCalculatorMethod ( u<strong>in</strong>t32 val )<br />
39 {<br />
40 return ( val ∗ m u l t i p l i c a t o r ) ;<br />
41 }
42<br />
15.3 Funktionspo<strong>in</strong>ter 473<br />
43 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
44 virtual u<strong>in</strong>t32 anotherCalculatorMethod ( u<strong>in</strong>t32 val )<br />
45 {<br />
46 return ( val + o f f s e t ) ;<br />
47 }<br />
48 } ;<br />
49<br />
50 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
51 /∗<br />
52 ∗ AnotherCalcMethodCollection<br />
53 ∗<br />
54 ∗ j u s t a demo f o r polymorphism<br />
55 ∗<br />
56 ∗/<br />
57<br />
58 class AnotherCalcMethodCollection : public CalcMethodCollection<br />
59 {<br />
60 public :<br />
61 AnotherCalcMethodCollection ( u<strong>in</strong>t32 m u l t i p l i c a t o r , u<strong>in</strong>t32 o f f s e t )<br />
62 throw ( ) : CalcMethodCollection ( m u l t i p l i c a t o r , o f f s e t ) {}<br />
63<br />
64 virtual ˜ AnotherCalcMethodCollection ( )<br />
65 throw( ) { }<br />
66<br />
67 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
68 virtual u<strong>in</strong>t32 anotherCalculatorMethod ( u<strong>in</strong>t32 val )<br />
69 {<br />
70 return ( val + ( 2 ∗ o f f s e t ) ) ;<br />
71 }<br />
72 } ;<br />
73<br />
74<br />
75 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
76 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
77 {<br />
78 u<strong>in</strong>t32 ( CalcMethodCollection : : ∗ calcFunction ) ( u<strong>in</strong>t32 ) ;<br />
79<br />
80 CalcMethodCollection method collection1 ( 2 , 7 ) ;<br />
81 AnotherCalcMethodCollection method collection2 ( 2 , 7 ) ;<br />
82<br />
83 calcFunction = &CalcMethodCollection : : aCalculatorMethod ;<br />
84 cout
474 15. Verschiedenes<br />
Pr<strong>in</strong>zip gleich, wie wir es schon gewohnt s<strong>in</strong>d, nur dass auch der Scope auf<br />
die Klasse mit angegeben werden muss, <strong>in</strong> der die Methode zu f<strong>in</strong>den ist.<br />
Zeile 78 ist also folgendermaßen zu lesen: Die Variable calcFunction ist<br />
e<strong>in</strong> Po<strong>in</strong>ter auf e<strong>in</strong>e beliebige Methode der Klasse CalcMethodCollection,<br />
die e<strong>in</strong>en u<strong>in</strong>t32 als Parameter nimmt und e<strong>in</strong>en u<strong>in</strong>t32 als return-Value<br />
liefert.<br />
Das bedeutet also, dass man dieser Variable alle Methoden zuweisen kann,<br />
die diese Bed<strong>in</strong>gung erfüllen. In Zeile 80 erzeugen wir e<strong>in</strong>e Instanz unserer<br />
CalcMethodCollection, die den Kontext für e<strong>in</strong>en Aufruf bietet. In Zeile 83<br />
sieht man, wie man die Zuweisung an unsere Variable bewerkstelligt: Man<br />
weist e<strong>in</strong>en Po<strong>in</strong>ter auf die Methode zu. Hierbei braucht man, im Gegensatz<br />
zu Funktionen und static Methoden, tatsächlich den & Operator um den<br />
Compiler davon zu überzeugen, dass man wirklich die Adresse der Methode<br />
zuweisen will.<br />
Jetzt habe ich allerd<strong>in</strong>gs zuvor noch groß davon gesprochen, dass man<br />
ja e<strong>in</strong>e Methode nur im Kontext der Instanz e<strong>in</strong>es Objekts aufrufen kann.<br />
Bisher ist aber von e<strong>in</strong>er solchen Instanz noch ke<strong>in</strong>e Rede, obwohl man sie<br />
bei der Zuweisung eigentlich erwartet hätte. Des Rätsels Lösung zeigt sich <strong>in</strong><br />
den Zeilen 84–85: Der Kontext wird beim Aufruf der Methode hergestellt!<br />
Die Zeilen 87–89 zeigen, dass man auch virtual Methoden an e<strong>in</strong>en solchen<br />
Funktionspo<strong>in</strong>ter zuweisen kann. Dass dadurch auch im Fall des Aufrufs<br />
über e<strong>in</strong>en Funktionspo<strong>in</strong>ter die Eigenschaft des Polymorphismus erhalten<br />
bleibt, zeigen die Zeilen 92–93: Hier wird nur die zuvor zugewiesene Methode<br />
<strong>in</strong> e<strong>in</strong>em anderen Kontext aufgerufen, nämlich im Kontext e<strong>in</strong>er Instanz<br />
e<strong>in</strong>es von CalcMethodCollection abgeleiteten Objekts. Dass hierbei erwartungsgemäß<br />
die entsprechende Methode <strong>in</strong> der abgeleiteten Klasse aufgerufen<br />
wird, zeigt der Output des Programms:<br />
c a l l i n g calcFunction ( 3 ) : 6<br />
c a l l i n g calcFunction ( 3 ) : 1 0<br />
c a l l i n g calcFunction ( 3 ) : 1 7<br />
Dadurch bed<strong>in</strong>gt, dass man bei Po<strong>in</strong>tern auf nicht static Members immer<br />
beim Aufruf die Verb<strong>in</strong>dung zu e<strong>in</strong>em Objekt hergestellt werden muss, hat<br />
man nicht mehr ganz die Flexibilität, die man bei Po<strong>in</strong>tern auf Funktionen<br />
und static Members hatte. Man kann nicht e<strong>in</strong>fach “irgende<strong>in</strong>e” Methode<br />
aufrufen, die jemand e<strong>in</strong>mal zugewiesen hat, ohne die Klasse und sogar das<br />
Objekt dazu zu kennen. Aus diesem Grund stehe ich auf dem Standpunkt,<br />
dass man Po<strong>in</strong>ter auf Methoden eigentlich auch wirklich nicht Funktionspo<strong>in</strong>ter<br />
nennen sollte, sondern Methodenpo<strong>in</strong>ter. Dadurch ist nämlich auch<br />
bereits im Namen ausgedrückt, dass sie e<strong>in</strong>e andere Natur haben und anders<br />
zu behandeln s<strong>in</strong>d.<br />
Vorsicht Falle: Neben allen Stolperste<strong>in</strong>en, die Funktionspo<strong>in</strong>ter von Natur<br />
aus zu bieten haben (siehe Abschnitt 10.7 aus <strong>Softwareentwicklung</strong> <strong>in</strong><br />
C ), möchte ich auf e<strong>in</strong>e Falle h<strong>in</strong>weisen, <strong>in</strong> die gerade begeisterte Neul<strong>in</strong>ge<br />
sehr gerne tappen: Funktions- und Methodenpo<strong>in</strong>ter können vorsichtig, sel-
15.4 Besondere Keywords, Diagraphs und Trigraphs 475<br />
ten und sauber angewandt Code flexibler und auch übersichtlicher machen.<br />
E<strong>in</strong> Übermaß an solchen Po<strong>in</strong>tern allerd<strong>in</strong>gs macht Code absolut undurchschaubar<br />
und unwartbar. Vor allem ist dieses Übermaß sehr schnell erreicht!<br />
Deshalb möchte ich folgenden Tipp geben: Man sollte immer zuerst sehr<br />
e<strong>in</strong>gehend darüber nachdenken, ob das Ziel, das man erreichen will, nicht<br />
auch mit sauberen OO Mitteln erreichbar ist. Nur, wenn wirklich Klassen,<br />
Objekte und Ableitungshierarchien mit allen ihren Eigenschaften, nicht zum<br />
gewünschten Erfolg führen, sollte man über den E<strong>in</strong>satz von Funktions- und<br />
Methodenpo<strong>in</strong>tern nachdenken!<br />
15.4 Besondere Keywords, Diagraphs und Trigraphs<br />
Aus Gründen der Vollständigkeit möchte ich auch auf das Thema der sogenannten<br />
Diagraphs und Trigraphs e<strong>in</strong>gehen, sowie auf besondere Keywords,<br />
die <strong>in</strong> C ++ für den Fall existieren, dass gewisse Zeichen auf den Entwicklungsmasch<strong>in</strong>en<br />
der Entwickler nicht zur Verfügung stehen. Das Problem, über<br />
das wir hier sprechen, hat se<strong>in</strong>en Ursprung dar<strong>in</strong>, dass die Characters [, ],<br />
{, }, \ und | <strong>in</strong> manchen europäischen Zeichensätzen schlicht und ergreifend<br />
nicht vorhanden s<strong>in</strong>d. Stattdessen s<strong>in</strong>d ihre Positionen an besondere,<br />
sprachabhängige Zeichen vergeben, wie es <strong>in</strong> e<strong>in</strong>igen der ISO-646 Character<br />
Sets der Fall ist. Hat man aber diese Zeichen nicht zur Verfügung, wie soll<br />
man dann e<strong>in</strong> C ++ Programm schreiben? Um hier Abhilfe zu schaffen, gibt<br />
es zwei Möglichkeiten, von denen <strong>in</strong> jedem Fall die erste zu bevorzugen ist:<br />
1. Man <strong>in</strong>stalliert zum Entwickeln zum<strong>in</strong>dest e<strong>in</strong>en Zeichensatz, <strong>in</strong> dem diese<br />
Characters zur Verfügung stehen und konfiguriert sowohl den Editor<br />
als auch das Keyboard entsprechend.<br />
2. Wenn das aus irgendwelchen Gründen wirklich überhaupt nicht geht,<br />
bietet C ++ e<strong>in</strong>e Alternative an, die <strong>in</strong> der Folge besprochen wird.<br />
Kommen wir zuerst zum Teil der <strong>in</strong> C ++ lebensnotwendigen Character-<br />
Sequenzen, die über Keywords zur Verfügung stehen. Verwendung dieser<br />
Keywords resultiert immerh<strong>in</strong> noch <strong>in</strong> lesbarem Code und deshalb sehe ich<br />
die Verwendung derselben noch als akzeptabel an. Die Rede ist hier von den<br />
logischen und den Bitoperatoren, die man auch wie folgt schreiben kann:
476 15. Verschiedenes<br />
Operator alternatives Keyword<br />
&& and<br />
|| or<br />
! not<br />
!= not_eq<br />
& bitand<br />
&= and_eq<br />
| bitor<br />
|= or_eq<br />
~ compl<br />
^ xor<br />
^= xor_eq<br />
Das bedeutet also, dass z.B. der Ausdruck<br />
my_var && your_var<br />
auch als<br />
my_var and your_var<br />
geschrieben werden kann. Wenn es vermeidbar ist, sollte man es nicht tun,<br />
aber immerh<strong>in</strong> ist es lesbar. Es gibt Entwickler, denen die Keywords im<br />
laufenden Code lieber s<strong>in</strong>d als die Orig<strong>in</strong>aloperatoren und die sie deswegen<br />
auch ohne besondere Notwendigkeit verwenden. Ob man dies zulässt ist<br />
Geschmackssache und eventuell im Cod<strong>in</strong>g Standard festzuschreiben.<br />
Vorsicht Falle: Manche Entwickler s<strong>in</strong>d schon <strong>in</strong> die Falle getappt, Variablennamen<br />
wie z.B. and zu vergeben. Abgesehen davon, dass e<strong>in</strong> solcher<br />
Name mit an Sicherheit grenzender Wahrsche<strong>in</strong>lichkeit schwachs<strong>in</strong>nig ist, ist<br />
auch der Compiler gar nicht so glücklich darüber, denn es handelt sich ja<br />
hierbei um e<strong>in</strong> reserved Keyword mit besonderer Bedeutung.<br />
Weniger lesbar s<strong>in</strong>d da schon die sogenannten Diagraphs, also Komb<strong>in</strong>ationen<br />
aus zwei Characters, die für e<strong>in</strong> besonderes Zeichen stehen und die im<br />
laufenden Programmcode Verwendung f<strong>in</strong>den:<br />
Zeichen alternativer Diagraph<br />
{ <br />
[ <br />
# %:<br />
Als ob Diagraphs nicht schon schlimm genug wären, stellt sich auch e<strong>in</strong><br />
anderes Problem: Was tut man, wenn man <strong>in</strong> Str<strong>in</strong>gs bzw. <strong>in</strong> Character<br />
Konstanten diese Zeichen benötigt? Hierzu kann man die Diagraphs nicht<br />
verwenden, sondern dazu werden die sogenannten Trigraphs, also Komb<strong>in</strong>ationen<br />
aus drei Characters, herangezogen:
15.5 volatile Objekte<br />
15.6 RTTI und dynamic cast im OO Kontext 477<br />
Zeichen alternativer Trigraph<br />
{ ??<<br />
} ??><br />
[ ??(<br />
] ??)<br />
\ ??/<br />
~ ??-<br />
| ??!<br />
^ ??’<br />
# ??=<br />
Es grenzt zwar schon an e<strong>in</strong>en Overkill, für das Keyword volatile e<strong>in</strong>en eigenen<br />
Abschnitt zu schreiben, aber für manche, sehr seltene (!) Anwendungen<br />
ist volatile e<strong>in</strong>fach notwendig und e<strong>in</strong>e Erklärung fand im laufenden Text<br />
e<strong>in</strong>fach beim besten Willen ke<strong>in</strong>en vernünftigen Platz.<br />
Manchmal muss man den Compiler daran h<strong>in</strong>dern, besondere Optimierungen<br />
vorzunehmen, die auf Annahmen des Optimizers über die Datenhaltung<br />
h<strong>in</strong>ter e<strong>in</strong>er Variable beruhen. Das Standardbeispiel für e<strong>in</strong>en solchen Fall<br />
ist das Auslesen e<strong>in</strong>es Zeitgebers über e<strong>in</strong>e Pseudo-Konstante. Nehmen wir<br />
e<strong>in</strong>fach an, wir hätten folgende Deklaration im Programm:<br />
extern const long timer_clock_;<br />
Beim Optimieren kann der Compiler im Normalfall zu Recht annehmen,<br />
dass sich am Inhalt von timer_clock_ nichts ändert, denn es handelt sich ja<br />
hier um e<strong>in</strong>e Konstante. Dies wäre <strong>in</strong> unserem Fall aber fatal, denn hier war<br />
etwas Anderes beabsichtigt: Es sollte nur verh<strong>in</strong>dert werden, dass jemand<br />
im Programm irrtümlich versucht, an timer_clock_ e<strong>in</strong>en Wert zuzuweisen.<br />
H<strong>in</strong>ter dieser Konstanten verbirgt sich jedoch der Zeitgeber und die Variable<br />
timer_clock_ verändert ihren Wert sehr wohl im Lauf der Zeit, denn sie<br />
repräsentiert die laufende Uhrzeit.<br />
Schreibt man die Deklaration um <strong>in</strong><br />
extern const volatile long timer_clock_;<br />
so hat man damit dem Compiler mitgeteilt, dass e<strong>in</strong>e Optimierung, die auf die<br />
Eigenschaft, dass der Wert von timer_clock_ konstant bleibt, nicht zulässig<br />
ist. Damit wird der Compiler von solchen Schritten Abstand nehmen und<br />
das Auslesen des Zeitgebers funktioniert wie erwartet.<br />
15.6 RTTI und dynamic cast im OO Kontext<br />
In Abschnitt 3.6.1 wurden bereits die Grundpr<strong>in</strong>zipien von RTTI besprochen.<br />
Da zu diesem Zeitpunkt die OO-Mechanismen von C ++ noch nicht bekannt
478 15. Verschiedenes<br />
waren, musste ich <strong>in</strong> dieser Besprechung allen Lesern Teile der Information<br />
vorenthalten. Diese fehlenden Teile möchte ich nun hier nachliefern, geme<strong>in</strong>sam<br />
mit <strong>in</strong>teressanten Eigenheiten von dynamic_cast.<br />
Bisher bekannt ist, dass man mittels typeid den Typ e<strong>in</strong>er Variable erfahren<br />
kann. Nicht wirklich erklärt wurde, was typeid nun genau liefert<br />
und was man alles damit tun kann. Hier wenden wir uns am besten e<strong>in</strong>em<br />
Beispiel zu (rtti_demo.cpp):<br />
1 // rtti demo . cpp − demo , what can be done with r t t i<br />
2<br />
3 #<strong>in</strong>clude <br />
4 #<strong>in</strong>clude < t y pe<strong>in</strong>fo><br />
5 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
6<br />
7 us<strong>in</strong>g std : : cout ;<br />
8 us<strong>in</strong>g std : : endl ;<br />
9 us<strong>in</strong>g std : : type <strong>in</strong>fo ;<br />
10 us<strong>in</strong>g std : : bad typeid ;<br />
11 us<strong>in</strong>g std : : bad cast ;<br />
12<br />
13<br />
14<br />
15 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
16 class DummyBase<br />
17 {<br />
18 public :<br />
19 virtual void doSometh<strong>in</strong>g ( )<br />
20 {<br />
21 cout
15.6 RTTI und dynamic cast im OO Kontext 479<br />
55 const type <strong>in</strong>fo &<strong>in</strong>fo dummy1 = typeid (dummy1) ;<br />
56 const type <strong>in</strong>fo &<strong>in</strong>fo dummy2 = typeid (dummy2) ;<br />
57<br />
58 cout ”
480 15. Verschiedenes<br />
Wir haben es hier mit den Deklarationen von drei verschiedenen Klassen<br />
zu tun, die sicherlich ke<strong>in</strong>e nähere Erklärung brauchen. Interessanter s<strong>in</strong>d<br />
hier schon die Zeilen 54–56, <strong>in</strong> denen man sieht, dass typeid als return-<br />
Value e<strong>in</strong>e const Reference auf e<strong>in</strong> type_<strong>in</strong>fo Objekt liefert. Was daran so<br />
besonders <strong>in</strong>teressant ist, wird gleich noch besprochen, wenden wir uns aber<br />
zuerst e<strong>in</strong>mal den Zeilen 58–59 zu. In diesen wird überprüft, ob <strong>in</strong>fo_base<br />
vom Typ DummyBase ist. Dies geschieht durch Vergleich der beiden entsprechenden<br />
type_<strong>in</strong>fo Objekte mittels ==. E<strong>in</strong> entsprechendes Overload<strong>in</strong>g von<br />
== ist Teil von type_<strong>in</strong>fo.<br />
Auf dieselbe Art stellen wir auch <strong>in</strong> den Zeilen 62–63 fest, wie es sich<br />
mit dem Typ von dummy1 verhält. In den Zeilen 66-67 jedoch passiert im<br />
Programm etwas ganz Böses: Es werden hier die Po<strong>in</strong>ter auf zwei type_<strong>in</strong>fo<br />
Objekte verglichen um die Gleichheit der Typen festzustellen. Genau das ist<br />
auch der Punkt, warum zuvor erwähnt wurde, dass es <strong>in</strong>teressant ist, dass<br />
typeid e<strong>in</strong>e Referenz liefert: Es ist nicht garantiert, dass zu e<strong>in</strong> und demselben<br />
Typ immer nur e<strong>in</strong> e<strong>in</strong>ziges type_<strong>in</strong>fo Objekt existiert. Das bedeutet,<br />
dass die Referenzen, die man erhält nicht unbed<strong>in</strong>gt auf dasselbe Objekt zeigen<br />
müssen. Im Fall von dynamic Libraries wäre e<strong>in</strong>e solche Garantie auch<br />
unmöglich abzugeben. In unserem Programm aber wird auf diesen Umstand<br />
ke<strong>in</strong>e Rücksicht genommen und es werden e<strong>in</strong>fach die Po<strong>in</strong>ter mite<strong>in</strong>ander<br />
verglichen. Der Output weiter unten zeigt, dass das genau hier funktioniert,<br />
es kann allerd<strong>in</strong>gs auch fehlschlagen!<br />
Mittels type<strong>in</strong>fo kann man ganz e<strong>in</strong>fach feststellen, ob zwei Objekte genau<br />
denselben Typ haben, wie man <strong>in</strong> den Zeilen 75–76 sehen kann. Man<br />
kann typeid natürlich auf verschiedenste D<strong>in</strong>ge anwenden, z.B. kann man<br />
auch den Typ des Resultats e<strong>in</strong>er Operation überprüfen, wie sich <strong>in</strong> Zeile 96<br />
erkennen lässt. Auch Funktionspo<strong>in</strong>ter besitzen e<strong>in</strong>en Typ. Dies zeigt sich<br />
an Zeile 97. Dass type<strong>in</strong>fo auch e<strong>in</strong>e Exception werfen kann, zeigt sich allerd<strong>in</strong>gs<br />
<strong>in</strong> den Zeilen 102–103: Hier wird versucht, den Typ e<strong>in</strong>es Objekts<br />
herauszuf<strong>in</strong>den, <strong>in</strong>dem der Po<strong>in</strong>ter auf das Objekt dereferenziert wird. Das<br />
wäre auch pr<strong>in</strong>zipiell noch ke<strong>in</strong> Problem und <strong>in</strong> der Praxis vollkommen legitim<br />
und üblich. Leider aber handelt es sich genau <strong>in</strong> diesem speziellen Fall<br />
um e<strong>in</strong>en 0 Po<strong>in</strong>ter. Wenn dieser Po<strong>in</strong>ter also auf “nichts” zeigt, dann kann<br />
dieses “Nichts” auch ke<strong>in</strong>en Typ haben. Dementsprechend wirft typeid <strong>in</strong><br />
diesem Fall e<strong>in</strong>e Exception. Würden wir nicht den Typ des Objekts erfahren<br />
wollen, das sich h<strong>in</strong>ter base_ptr verbirgt, wofür wir den Po<strong>in</strong>ter dereferenzieren<br />
müssen, sondern den Typ von base_ptr selbst, dann würde es hier<br />
ke<strong>in</strong> Problem geben. E<strong>in</strong> Aufruf von<br />
typeid(base_ptr)<br />
würde ganz e<strong>in</strong>fach das entsprechende type_<strong>in</strong>fo Objekt liefern, dass e<strong>in</strong>em<br />
Po<strong>in</strong>ter auf e<strong>in</strong> DummyBase Objekt entspricht. Was kann man also aus diesem<br />
Programm bisher lernen?
15.6 RTTI und dynamic cast im OO Kontext 481<br />
• Die type_<strong>in</strong>fo Objekte unterstützen sowohl den == als auch den != Operator.<br />
Aussagen über Typen können nur gesichert getroffen werden, wenn<br />
diese beiden verwendet werden.<br />
• Ke<strong>in</strong>esfalls dürfen Po<strong>in</strong>ter auf type_<strong>in</strong>fo Objekte mite<strong>in</strong>ander verglichen<br />
werden, denn es ist nicht garantiert, dass es zu e<strong>in</strong>en bestimmten Typ nur<br />
e<strong>in</strong> e<strong>in</strong>ziges type_<strong>in</strong>fo Objekt gibt.<br />
• Bisher noch nicht erwähnt, aber trotzdem <strong>in</strong>teressant ist, dass type_<strong>in</strong>fo<br />
Objekte die Methode<br />
bool before(const type_<strong>in</strong>fo&)<br />
unterstützen, die e<strong>in</strong>e Sortierung zwischen solchen Objekten möglich macht.<br />
Vorsicht Falle: Was sich auch immer bei verschiedenen Implementationen<br />
als Reihenfolge durch Verwendung von before ergibt sagt überhaupt<br />
nichts über Ableitungshierarchien aus! Manche Entwickler treffen leider<br />
Fehlannahmen wie z.B. “e<strong>in</strong> type_<strong>in</strong>fo Objekt der Basisklasse wird genau<br />
vor dem der davon abgeleiteten Klassen gereiht”. Dies ist falsch,<br />
auch wenn es manchmal tatsächlich so aussehen könnte, als ob es zutreffen<br />
würde.<br />
• Die Methode name dient dem Herausf<strong>in</strong>den des (<strong>in</strong>ternen!) Typnamens<br />
e<strong>in</strong>es Objekts.<br />
• E<strong>in</strong> Aufruf von type_<strong>in</strong>fo kann e<strong>in</strong>e bad_typeid Exception werfen, wenn<br />
ke<strong>in</strong>e Chance besteht, den gefragten Typ herauszuf<strong>in</strong>den. Dies ist bei e<strong>in</strong>em<br />
dereferenzierten 0 Po<strong>in</strong>ter der Fall.<br />
So nett die Anwendungsmöglichkeiten von typeid auch s<strong>in</strong>d, e<strong>in</strong>es kann man<br />
damit nicht tun, nämlich Typkompatibilitäten feststellen, die sich durch<br />
die Ableitungshierarchie ergeben. Dazu müssen wir unseren bekannten<br />
dynamic_cast Operator heranziehen und e<strong>in</strong>e Eigenheit desselben ausnützen,<br />
die bisher noch nicht bekannt ist: In Abschnitt 9.4.4 wurde bereits erwähnt,<br />
dass dynamic_cast e<strong>in</strong>e bad_cast Exception wirft, wenn e<strong>in</strong> Cast nicht<br />
zulässig ist. Das ist allerd<strong>in</strong>gs nur die halbe Wahrheit. In Wirklichkeit ist<br />
dynamic_cast vollständig so def<strong>in</strong>iert:<br />
• Mittels dynamic_cast können (ausschließlich) Casts zwischen Po<strong>in</strong>tern auf<br />
Objekte oder zwischen Referenzen auf Objekte durchgeführt werden.<br />
• Wendet man dynamic_cast auf Po<strong>in</strong>ter an, dann können folgende zwei<br />
Fälle passieren:<br />
1. Wenn der Cast zulässig ist, so wird e<strong>in</strong> Po<strong>in</strong>ter auf das gewandelte Objekt<br />
geliefert.<br />
2. Wenn der Cast nicht zulässig ist, so wird e<strong>in</strong> 0 Po<strong>in</strong>ter geliefert. Es wird<br />
<strong>in</strong> diesem Fall ke<strong>in</strong>e Exception geworfen!<br />
Dieses Verhalten ist sehr s<strong>in</strong>nvoll, denn damit kann man dynamic_cast dazu<br />
verwenden, Kompatibilitäten festzustellen, ohne e<strong>in</strong>e Exception befürchten<br />
zu müssen, wie z.B. <strong>in</strong> Zeile 70 zu sehen ist. Für alle Leser, die mit
482 15. Verschiedenes<br />
Java zu tun haben: Dieses Verhalten entspricht im Pr<strong>in</strong>zip dem, was man<br />
dort mit <strong>in</strong>stanceof erreicht.<br />
• Wendet man e<strong>in</strong>en dynamic_cast auf Referenzen an, dann sieht die Welt<br />
e<strong>in</strong> wenig anders aus:<br />
1. Wenn der Cast zulässig ist, so wird e<strong>in</strong>e Referenz auf das gewandelte<br />
Objekt geliefert.<br />
2. Wenn der Cast nicht zulässig ist, so wird e<strong>in</strong>e bad_cast Exception geworfen,<br />
wie man z.B. <strong>in</strong> Zeile 83 sieht.<br />
Der Output des Programms bestätigt, dass es sich bei den soeben besprochenen<br />
D<strong>in</strong>gen nicht nur um Theorien handelt:<br />
i s base of type DummyBase? −> 1<br />
i s dummy1 o f type DummyBase? −> 0<br />
i s dummy1 o f type DummyClassOne ( bad v e r s i o n )? −> 1<br />
i s dummy1 compatible with DummyBase? −> 1<br />
i s dummy1 o f type DummyClassTwo? −> 0<br />
i s dummy1 o f the same type as dummy2? −> 0<br />
i s dummy1 compatible with dummy2? −> 0<br />
oops − dynamic cast f a i l e d . . .<br />
typeid o f expr : i<br />
typeid o f ma<strong>in</strong> : FiiPPcE<br />
typeid o f 0 po<strong>in</strong>ter : oops − typeid f a i l e d . . .<br />
15.7 Weiterführendes zu Exceptions<br />
In Kapitel 11 wurde alles besprochen, was man im normalen Programmieralltag<br />
wissen muss, um vernünftig mit Exceptions umgehen zu können. Hier<br />
möchte ich noch e<strong>in</strong> wenig weiterführende Information liefern, die manchmal<br />
sehr nützlich se<strong>in</strong> kann. Vor allem geht es um Situationen, bei denen man<br />
<strong>in</strong> der Entwicklungsphase von Software beim Debugg<strong>in</strong>g verzweifelt, denn<br />
e<strong>in</strong> Programm wird partout immer zwangsweise vom System beendet, obwohl<br />
man sich doch eigentlich ke<strong>in</strong>es Fehlers bewusst ist. Trotzdem man<br />
alle nur vorstellbaren try ... catch Konstrukte geschrieben hat, werden<br />
gewisse Exceptions beim besten Willen nicht ihrer angedachten Behandlung<br />
zugeführt, sondern stattdessen geht das Programm ansatzlos <strong>in</strong> den Tiefflug<br />
über.<br />
Betrachten wir zuerst das e<strong>in</strong>fachste Problem, das uns über den Weg<br />
laufen kann: Wir wissen aus verschiedenen Gründen nicht ganz genau, welche<br />
Exceptions aus e<strong>in</strong>em Funktions- oder Methodenaufruf überhaupt geworfen<br />
werden können. Dies kann mannigfaltige Gründe haben: Wir können es mit<br />
Code zu tun haben, der ke<strong>in</strong>en Gebrauch von throw Deklarationen macht<br />
und zu dem auch ke<strong>in</strong>e ausreichende Dokumentation vorhanden ist. Wir<br />
können es aber auch mit Template- oder Funktionspo<strong>in</strong>ter Implementationen<br />
zu tun haben, bei denen im Vorh<strong>in</strong>e<strong>in</strong> gar nicht bekannt se<strong>in</strong> kann, welche<br />
Exceptions geworfen werden können, denn das hängt immer vom speziellen<br />
Fall ab. Wie dem auch immer sei, wir suchen nach e<strong>in</strong>er Möglichkeit, alle
15.7 Weiterführendes zu Exceptions 483<br />
Exceptions zu fangen und aufgrund dessen, was wir gefangen haben, über die<br />
Behandlung zu entscheiden. Im Pr<strong>in</strong>zip geht das <strong>in</strong> C ++ sehr leicht, wie man<br />
<strong>in</strong> der Folge sehen kann (catch_all_demo.cpp):<br />
1 // catch all demo . cpp − demo , how to catch a r b i t r a r y exceptions<br />
2<br />
3 #<strong>in</strong>clude <br />
4 #<strong>in</strong>clude < t y pe<strong>in</strong>fo><br />
5 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
6<br />
7 us<strong>in</strong>g std : : cout ;<br />
8 us<strong>in</strong>g std : : endl ;<br />
9 us<strong>in</strong>g std : : type <strong>in</strong>fo ;<br />
10 us<strong>in</strong>g std : : bad typeid ;<br />
11 us<strong>in</strong>g std : : bad cast ;<br />
12<br />
13 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
14 class OneException<br />
15 {<br />
16 } ;<br />
17<br />
18 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
19 class AnotherException<br />
20 {<br />
21 } ;<br />
22<br />
23 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
24 void doSometh<strong>in</strong>g ( )<br />
25 {<br />
26 static u<strong>in</strong>t32 c a l l c o u n t e r = 0;<br />
27 i f ( c a l l c o u n t e r ++ % 2)<br />
28 {<br />
29 cout
484 15. Verschiedenes<br />
61 cout
15.7 Weiterführendes zu Exceptions 485<br />
E<strong>in</strong>fluss nehmen. Die erste Möglichkeit ist, bei e<strong>in</strong>er Methode bzw. Funktion<br />
die Exception bad_exception durch e<strong>in</strong>e throw Angabe als erlaubte Exception<br />
zu deklarieren. Darauf reagiert der Compiler und setzt nicht mehr Code<br />
e<strong>in</strong>, der das Programm beendet. Stattdessen setzt er Code e<strong>in</strong>, der im Fall e<strong>in</strong>er<br />
nicht deklarierten Exception e<strong>in</strong>e bad_exception wirft. Diese wiederum<br />
kann man nach altbekannter Manier fangen.<br />
Vorsicht Falle: Nicht alle Compiler folgen dieser Spezifikation, was natürlich<br />
wieder zu netten Überraschungen führen kann. Deshalb ist es sicherer, gleich<br />
zur <strong>in</strong> der Folge vorgestellten Methodik zu greifen, mit der man genau dieses<br />
Verhalten explizit implementieren kann.<br />
Die andere Möglichkeit, E<strong>in</strong>fluss zu nehmen, ist der unexpected_handler,<br />
der im Pr<strong>in</strong>zip sehr ähnlich funktioniert, wie der new_handler, den wir bereits<br />
kennen gelernt haben. Sehen wir uns diese beiden Möglichkeiten am<br />
besten gleich am Beispiel an (unexpected_exc_demo.cpp):<br />
1 // unexpected exc demo . cpp − demo , how to deal with unexpected<br />
2 // exceptions<br />
3<br />
4 #<strong>in</strong>clude <br />
5 #<strong>in</strong>clude < t y pe<strong>in</strong>fo><br />
6 #<strong>in</strong>clude <br />
7 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
8<br />
9 us<strong>in</strong>g std : : cout ;<br />
10 us<strong>in</strong>g std : : endl ;<br />
11 us<strong>in</strong>g std : : bad exception ;<br />
12 us<strong>in</strong>g std : : set unexpected ;<br />
13<br />
14 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
15 class OneException<br />
16 {<br />
17 } ;<br />
18<br />
19 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
20 class AnotherException<br />
21 {<br />
22 } ;<br />
23<br />
24 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
25 void doSometh<strong>in</strong>g ( )<br />
26 throw( OneException , bad exception )<br />
27 {<br />
28 static u<strong>in</strong>t32 c a l l c o u n t e r = 0;<br />
29 i f ( c a l l c o u n t e r ++ % 2)<br />
30 {<br />
31 cout
486 15. Verschiedenes<br />
43 throw bad exception ( ) ;<br />
44 }<br />
45<br />
46 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
47 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
48 {<br />
49 u<strong>in</strong>t32 count = 0;<br />
50<br />
51 void ( ∗ old unexpected handler )() =<br />
52 set unexpected ( makeBadFromUnexpectedException ) ;<br />
53<br />
54 for ( count = 0 ; count < 2 ; count++)<br />
55 {<br />
56 try<br />
57 {<br />
58 doSometh<strong>in</strong>g ( ) ;<br />
59 }<br />
60 catch ( OneException &exc )<br />
61 {<br />
62 cout
15.7 Weiterführendes zu Exceptions 487<br />
zum<strong>in</strong>dest kann man noch Handlungen setzen, bevor das Programm über den<br />
Jordan geschickt wird. Warum man den Abbruch nicht verh<strong>in</strong>dern kann, ist<br />
e<strong>in</strong>leuchtend: Wird e<strong>in</strong>e Exception geworfen, dann wird das Stack Unw<strong>in</strong>d<strong>in</strong>g<br />
so lange durchgeführt, bis man bei e<strong>in</strong>em entsprechenden catch angelangt ist.<br />
Sollte ke<strong>in</strong>es gefunden werden, so ist dadurch auch ma<strong>in</strong> dem Unw<strong>in</strong>d<strong>in</strong>g zum<br />
Opfer gefallen. Wie soll man also dann noch im Programm weitermachen? Es<br />
gibt e<strong>in</strong>fach ke<strong>in</strong>en Punkt mehr, an dem man neu aufsetzen könnte. An e<strong>in</strong>em<br />
kurzen Beispiel betrachtet, sieht das dann so aus (uncaught_exc_demo.cpp):<br />
1 // uncaught exc demo . cpp − demo f o r uncaught exceptions<br />
2<br />
3 #<strong>in</strong>clude <br />
4 #<strong>in</strong>clude < t y pe<strong>in</strong>fo><br />
5 #<strong>in</strong>clude <br />
6 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
7<br />
8 us<strong>in</strong>g std : : cout ;<br />
9 us<strong>in</strong>g std : : endl ;<br />
10 us<strong>in</strong>g std : : s e t t e r m i n a t e ;<br />
11<br />
12 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
13 class OneException<br />
14 {<br />
15 } ;<br />
16<br />
17 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
18 void doSometh<strong>in</strong>g ( )<br />
19 throw( OneException )<br />
20 {<br />
21 cout
488 15. Verschiedenes<br />
zurückkehrt. Man hat nur noch die Chance, selbst so def<strong>in</strong>iert wie möglich<br />
auszusteigen, bevor das System das von sich aus übernimmt. In Zeile 32 passiert<br />
genau das. Würde man hier nicht mittels exit aussteigen, und wider<br />
Erwarten aus der Funktion wieder herauskommen, dann würde automatisch<br />
abort aufgerufen werden. Den Handler setzt man durch Aufruf der Methode<br />
set_term<strong>in</strong>ate <strong>in</strong> Kraft. Der weltbewegende Output dieses sterbenden<br />
Meisterwerks liest sich dann so:<br />
throw<strong>in</strong>g OneException<br />
preMortemExceptionProblemFunction c a l l e d<br />
time to say good bye . . .<br />
In Kapitel 11 wurde bereits e<strong>in</strong>dr<strong>in</strong>glichst gesagt, dass man unbed<strong>in</strong>gt dafür<br />
sorgen muss, dass aus e<strong>in</strong>em Destruktor ke<strong>in</strong>e Exceptions geworfen werden.<br />
Durch das catch(...) Konstrukt haben wir auch jetzt e<strong>in</strong>e schöne Möglichkeit,<br />
<strong>in</strong>nerhalb e<strong>in</strong>es Destruktors auf alle Eventualitäten zu reagieren und<br />
heimtückische Exceptions, die aus aufgerufenen Teilen stammen, daran zu<br />
h<strong>in</strong>dern, aus dem Destruktor h<strong>in</strong>aus zu fallen. Wie bereits erklärt kann e<strong>in</strong><br />
Destruktor eben im Zuge e<strong>in</strong>es Stack Unw<strong>in</strong>d<strong>in</strong>gs aufgerufen werden und<br />
dann würden bösartigerweise plötzlich zwei Exceptions gleichzeitig auftreten.<br />
Wenn man darüber genau nachdenkt, dann kommt man allerd<strong>in</strong>gs zu<br />
der Erkenntnis, dass man Exceptions aus e<strong>in</strong>em Destruktor theoretisch schon<br />
werfen könnte, wenn man nur sicherstellt, dass man nicht gerade im Zuge e<strong>in</strong>es<br />
Stack Unw<strong>in</strong>d<strong>in</strong>gs destruiert wird. Genau das ist auch wirklich der Fall,<br />
wie im folgenden Beispiel zu sehen ist (destructor_exc_demo.cpp):<br />
1 // destructor exc demo . cpp − demo f o r exceptions <strong>in</strong> d e s t r u c t o r s<br />
2<br />
3 #<strong>in</strong>clude <br />
4 #<strong>in</strong>clude < t y pe<strong>in</strong>fo><br />
5 #<strong>in</strong>clude <br />
6 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
7<br />
8 us<strong>in</strong>g std : : cout ;<br />
9 us<strong>in</strong>g std : : endl ;<br />
10 us<strong>in</strong>g std : : s e t t e r m i n a t e ;<br />
11 us<strong>in</strong>g std : : uncaught exception ;<br />
12<br />
13 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
14 class OneException<br />
15 {<br />
16 } ;<br />
17<br />
18 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
19 /∗<br />
20 ∗ VeryBadClass<br />
21 ∗<br />
22 ∗ throws an exception <strong>in</strong> the d e s t r u c t o r . . .<br />
23 ∗<br />
24 ∗/<br />
25<br />
26 class VeryBadClass<br />
27 {<br />
28 public :<br />
29 virtual ˜ VeryBadClass ( )<br />
30 {<br />
31 cout
32 throw OneException ( ) ;<br />
33 }<br />
34 } ;<br />
35<br />
15.7 Weiterführendes zu Exceptions 489<br />
36 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
37 /∗<br />
38 ∗ BadClass<br />
39 ∗<br />
40 ∗ throws an exception <strong>in</strong> the d e s t r u c t o r . . .<br />
41 ∗<br />
42 ∗/<br />
43<br />
44 class BadClass<br />
45 {<br />
46 public :<br />
47 virtual ˜ BadClass ( )<br />
48 {<br />
49 cout
490 15. Verschiedenes<br />
98 }<br />
99<br />
100 s e t t e r m i n a t e ( old term<strong>in</strong>ate handler ) ;<br />
101<br />
102 return ( 0 ) ;<br />
103 }<br />
Wir haben es hier mit e<strong>in</strong>er BadClass und mit e<strong>in</strong>er VeryBadClass zu tun.<br />
Die VeryBadClass zeichnet sich dadurch aus, dass sie ohne Rücksicht auf<br />
Verluste aus dem Destruktor e<strong>in</strong>e Exception wirft. Die BadClass ist e<strong>in</strong> wenig<br />
rücksichtsvoller und wirft nur dann e<strong>in</strong>e Exception, wenn nicht gerade e<strong>in</strong><br />
Stack Unw<strong>in</strong>d<strong>in</strong>g im Gange ist. Ob das der Fall ist, f<strong>in</strong>det man durch Aufruf<br />
der Funktion uncaught_exception <strong>in</strong> Zeile 50 heraus. Wenn diese Funktion<br />
true liefert, dann ist gerade e<strong>in</strong> Stack Unw<strong>in</strong>d<strong>in</strong>g im Gang, ansonsten nicht.<br />
In unserem ma<strong>in</strong> legen wir <strong>in</strong> den Zeilen 82–83 zwei Variablen vom Typ<br />
BadClass an. Sobald die erste der beiden im Zuge des Verlassens des umschließenden<br />
Blocks ihr Leben verwirkt hat, wirft sie aus dem Destruktor<br />
e<strong>in</strong>e Exception. Dadurch wird das Stack Unw<strong>in</strong>d<strong>in</strong>g ausgelöst und im Zuge<br />
dessen wird auch die zweite Variable destruiert. Diese erkennt im Destruktor,<br />
dass sie sich mit dem Werfen von Exceptions gefälligst zurückhalten soll und<br />
verhält sich entsprechend brav. Dadurch gel<strong>in</strong>gt es dem Programm, sich bis<br />
zum entsprechenden catch <strong>in</strong> den Zeilen 85–88 durchzukämpfen.<br />
Anders ist die Situation schon bei unserer VeryBadClass. Auch hier wird<br />
beim ersten Destruktoraufruf e<strong>in</strong>e Exception geworfen, das e<strong>in</strong> Stack Unw<strong>in</strong>d<strong>in</strong>g<br />
zur Folge hat. Im Zuge dessen wirft aber auch noch die zweite Instanz<br />
dieser Klasse schwachs<strong>in</strong>nigerweise aus dem Destruktor e<strong>in</strong>e Exception. Dadurch<br />
bleibt gar nichts Anderes mehr übrig, als das Programm zu beenden.<br />
Wie man am folgenden Output sieht, wird auch dafür wieder der entsprechende<br />
term<strong>in</strong>ate Handler herangezogen, den wir schon von den uncaught<br />
Exceptions her kennen.<br />
BadClass d e s t r u c t o r<br />
stack unw<strong>in</strong>d<strong>in</strong>g not <strong>in</strong> progress . . . throw<strong>in</strong>g OneException<br />
BadClass d e s t r u c t o r<br />
stack unw<strong>in</strong>d<strong>in</strong>g <strong>in</strong> progress . . . not throw<strong>in</strong>g an exception<br />
caught OneException<br />
VeryBadClass d e s t r u c t o r<br />
VeryBadClass d e s t r u c t o r<br />
preMortemExceptionProblemFunction c a l l e d<br />
time to say good bye . . .<br />
Vorsicht Falle: Auch wenn man im Destruktor nach entsprechender Absicherung<br />
Exceptions werfen kann, muss ich zu äußerster Vorsicht mahnen!<br />
Destruktoren müssen so entworfen werden, dass diese Maßnahme nicht nötig<br />
ist, sonst landet man irgendwann <strong>in</strong> Teufels Küche. Die Problematik ist, dass<br />
man <strong>in</strong> gewissen Situationen Exceptions nicht werfen kann, weil gerade e<strong>in</strong><br />
Stack Unw<strong>in</strong>d<strong>in</strong>g passiert. Was tut man aber <strong>in</strong> so e<strong>in</strong>er Situation mit Exceptions,<br />
die e<strong>in</strong>em im Destruktor von dar<strong>in</strong> aufgerufenen Methoden um die<br />
Ohren geworfen werden? E<strong>in</strong> silent Catch führt garantiert zur Katastrophe!
Teil III<br />
Ausgesuchte Teile aus der <strong>C++</strong> Standard Library
16. Die <strong>C++</strong> Standard Library<br />
Dieser dritte Abschnitt des Buchs ist e<strong>in</strong>er überblicksmäßigen Besprechung<br />
der C ++ Standard Library gewidmet. Wie zu Beg<strong>in</strong>n des Buchs erwähnt<br />
wurde, würde e<strong>in</strong>e detaillierte Beschreibung der gesamten Funktionsweise<br />
und der Designentscheidungen, die zur derzeitigen Form der Library geführt<br />
haben, bei weitem den Rahmen sprengen. Deshalb beschränke ich mich hier<br />
darauf, kurz zu erklären, welchen Umfang die Standard Library hat und was<br />
man pr<strong>in</strong>zipiell damit tun kann. Das Ziel dieses Kapitels ist es also, den<br />
Lesern e<strong>in</strong> Gefühl dafür zu vermitteln, bei welchen Problemstellungen sie<br />
selbst Hand anlegen müssen und wofür bereits wiederverwendbare Lösungen<br />
existieren.<br />
Die C ++ Standard Library ist üblicherweise bei Entwicklern unter dem<br />
Namen STL (=Standard Template Library) bekannt und dementsprechend<br />
wird auch hier im Buch <strong>in</strong> der Folge die Bezeichnung STL dafür verwendet.<br />
Diese Bezeichnung verrät auch bereits sehr viel über die Natur der Library:<br />
Sie enthält e<strong>in</strong>e größere Menge an Templates zum Lösen von Standardproblemen,<br />
die sich Entwicklern im Alltag immer wieder stellen.<br />
16.1 Übersicht<br />
Es war gerade zuvor die Rede von Standardproblemen, die zum Entwicklungsalltag<br />
gehören, nun stellt sich also die Frage, welche dieser Probleme es<br />
wert s<strong>in</strong>d, <strong>in</strong> e<strong>in</strong>e Standard Library aufgenommen zu werden. Die Entwickler<br />
der STL sche<strong>in</strong>en die folgende, sehr logische und lobenswerte Sicht vertreten<br />
zu haben, als sie entschieden, welchen Umfang die STL haben soll:<br />
Was s<strong>in</strong>d die pr<strong>in</strong>zipiellen Low-Level Datenstrukturen und Algorithmen,<br />
die man bei be<strong>in</strong>ahe jeder Problemlösung benötigt? Diese gehören e<strong>in</strong> für<br />
alle Mal befriedigend und möglichst allgeme<strong>in</strong>gültig implementiert, sodass sie<br />
nicht noch unzählige Male von neuem implementiert werden müssen!<br />
Welche Entwickler kennen nicht die Situation, immer wieder bestimmte<br />
Datenstrukturen, wie z.B. Listen, Stacks, etc., von neuem implementieren<br />
zu müssen, da alle ihre anderen Implementationen so spezialisiert für e<strong>in</strong>e<br />
bestimmte Anwendung s<strong>in</strong>d, dass sie außerhalb dieser Anwendung wieder<br />
nicht vernünftig verwendet werden können. Nach der x-ten Implementation
494 16. Die <strong>C++</strong> Standard Library<br />
beschließen die Entwickler, dass es nun wirklich an der Zeit wäre, e<strong>in</strong>e allgeme<strong>in</strong>gültige<br />
Version zu schreiben, die auch den anderen Leuten im Team<br />
oder <strong>in</strong> der ganzen Firma zur Verfügung stehen soll. Auf diese Art kam und<br />
kommt es ständig dazu, dass D<strong>in</strong>ge, die zum Teil bereits seit der tiefsten<br />
Ste<strong>in</strong>zeit der <strong>Softwareentwicklung</strong> bekannt und gelöst s<strong>in</strong>d, wieder und wieder<br />
von neuem geschrieben werden. Das bedeutet auch, dass gleichermaßen<br />
immer und immer wieder dieselben Fehler gemacht werden und unter großem<br />
Arbeits- und Zeitaufwand korrigiert werden müssen. Teilweise Schuld an diesem<br />
Phänomen hat auch das sogenannte not <strong>in</strong>vented here Syndrom: “Diese<br />
Implementation kann bei me<strong>in</strong>em Problem nicht gut funktionieren, weil die<br />
Entwickler dies und das nicht bedacht haben / anders gemacht haben / etc.”<br />
Dass solche Kritik nicht so selten auch ihre Berechtigung hat, soll hier gar<br />
nicht <strong>in</strong> Frage gestellt werden.<br />
Die Entwickler der STL hatten also folgende Anforderungen zu erfüllen,<br />
um die Library auch wirklich für e<strong>in</strong>en möglichst breiten Anwenderkreis e<strong>in</strong>setzbar<br />
zu machen:<br />
• Die STL soll allgeme<strong>in</strong>, verständlich und s<strong>in</strong>nvoll genug gehalten se<strong>in</strong>, dass<br />
sie für E<strong>in</strong>steiger und erfahrene Entwickler gleichermaßen e<strong>in</strong>setzbar ist.<br />
• Die STL soll nicht nur leicht und s<strong>in</strong>nvoll bei der Entwicklung von Applikationen<br />
e<strong>in</strong>zusetzen se<strong>in</strong>, sondern sie soll auch e<strong>in</strong>fach und s<strong>in</strong>nvoll als<br />
Basis-Library für andere Library-Entwicklungen dienen können.<br />
• Die allgeme<strong>in</strong>en Implementationen <strong>in</strong> der STL müssen effizient genug se<strong>in</strong>,<br />
dass sie gute Alternativen zu speziellen, sehr problembezogenen Implementationen<br />
darstellen.<br />
• Alle Algorithmen, die <strong>in</strong> der STL Anwendung f<strong>in</strong>den, sollen entweder<br />
Policy-frei se<strong>in</strong>, oder e<strong>in</strong>e Policy als Argument nehmen. Das bedeutet, dass<br />
es z.B. ke<strong>in</strong>en Sortieralgorithmus <strong>in</strong> der STL geben darf, der auf Gedeih<br />
und Verderb aufsteigend sortiert und auch zwangsweise die Implementation<br />
der > < und = Operatoren von den e<strong>in</strong>zelnen Elementen erwartet. Es<br />
kann ja auch se<strong>in</strong>, dass man z.B. absteigend sortieren will und dass man e<strong>in</strong>e<br />
eigene Komparatorfunktion zum Vergleich von Elementen bereitstellen<br />
will. Dies muss dann über entsprechende selbst implementierbare Policies<br />
e<strong>in</strong>stellbar se<strong>in</strong>.<br />
• Alle Teile der STL müssen so designed se<strong>in</strong>, dass sie immer nur genau e<strong>in</strong>e<br />
e<strong>in</strong>zige Aufgabe erfüllen, diese aber so gut wie möglich. Niemals darf e<strong>in</strong>e<br />
Komponente zwei mite<strong>in</strong>ander gekoppelte Rollen erfüllen, denn dies geht<br />
erstens zu Lasten der Allgeme<strong>in</strong>heit und zweitens erlangt man durch explizites<br />
Verknüpfen zweier spezialisierter Komponenten üblicherweise e<strong>in</strong>e<br />
bessere Lösung.<br />
• Komponenten der STL müssen sicher <strong>in</strong> ihrer Verwendung se<strong>in</strong>. Das bedeutet,<br />
dass es nicht vorkommen darf, dass man besondere Interna e<strong>in</strong>er<br />
Komponente kennen muss, um mit ihr s<strong>in</strong>nvoll arbeiten zu können und<br />
z.B. ke<strong>in</strong> Speicherloch zu verursachen.<br />
• Komponenten der STL müssen typsicher se<strong>in</strong>.
16.1 Übersicht 495<br />
• Komponenten der STL müssen sowohl mit Standard-Datentypen als auch<br />
mit benutzerdef<strong>in</strong>ierten Datentypen gleichermaßen verwendbar se<strong>in</strong>.<br />
• Komponenten der STL müssen D<strong>in</strong>ge so vollständig wie möglich implementieren.<br />
Das bedeutet, dass sie e<strong>in</strong>e Aufgabe, die sie übernehmen, so<br />
vollständig implementieren müssen, dass es nicht notwendig ist, für e<strong>in</strong>e<br />
Standardanwendung e<strong>in</strong>e Erweiterung selbst schreiben zu müssen. Es<br />
hätte z.B. ke<strong>in</strong>en S<strong>in</strong>n, e<strong>in</strong> Template für e<strong>in</strong>e Liste zu schreiben, <strong>in</strong> die<br />
man zwar Elemente e<strong>in</strong>fügen kann, aber aus der man ke<strong>in</strong>e Elemente mehr<br />
entfernen kann. Wenn schon Liste, dann ordentlich :-).<br />
In kurzen Schlagworten umrissen bedeutet das also, dass das Ziel der STL<br />
ist, so allgeme<strong>in</strong> wie möglich, so vollständig wie möglich und so robust wie<br />
möglich zu se<strong>in</strong> bei gleichzeitiger Wahrung der Intuitivität bei der Verwendung<br />
und der Effizienz der e<strong>in</strong>zelnen Lösungen. Dass man die STL nicht<br />
vor fahrlässigem Missbrauch schützen kann, ist sonnenklar. Wie sollte man<br />
auch verh<strong>in</strong>dern, dass jemand e<strong>in</strong>e falsche Datenstruktur zur Lösung e<strong>in</strong>es<br />
Problems <strong>in</strong> e<strong>in</strong>er Applikation verwendet? In jedem Fall muss man der STL<br />
attestieren, dass sie bei korrekter und s<strong>in</strong>nvoller Verwendung auch wirklich<br />
sehr effizient und fehlerfrei arbeitet und Entwicklern sehr viel Zeit erspart!<br />
So e<strong>in</strong>ige Entwickler, die der Me<strong>in</strong>ung waren, dass man die e<strong>in</strong>e oder andere<br />
Datenstruktur auch besser implementieren kann, wurden sehr schnell e<strong>in</strong>es<br />
Besseren belehrt. Deshalb kann ich an dieser Stelle allen Lesern nur den Rat<br />
geben, die STL auch wirklich zu benutzen anstatt selbst das Rad neu zu<br />
erf<strong>in</strong>den!<br />
Sehen wir uns also ganz kurz an, für welche E<strong>in</strong>satzgebiete die STL bereits<br />
vorgefertigte Lösungen anbietet:<br />
Conta<strong>in</strong>er: Hierzu werden Templates zum Arbeiten mit Vektoren, Listen,<br />
Mengen, Queues und Stacks angeboten. Im Pr<strong>in</strong>zip also alles, was man<br />
im Programmieralltag an verschiedenen Arten von Conta<strong>in</strong>ern braucht,<br />
sofern man es nicht gleich mit Datenmengen zu tun hat, die nach e<strong>in</strong>er<br />
größeren Datenbank verlangen.<br />
Iterators: Zu den verschiedenen Conta<strong>in</strong>ern werden, wo s<strong>in</strong>nvoll, Iterators angeboten,<br />
mit denen man den Inhalt e<strong>in</strong>es Conta<strong>in</strong>ers Element für Element<br />
<strong>in</strong> wohldef<strong>in</strong>ierter Ordnung (also z.B. vorwärts und rückwärts) durchgehen<br />
kann.<br />
Allocators: Aus Gründen der Portierbarkeit will man Memory-Management<br />
Details, wie z.B. das Wissen über bestimmte Po<strong>in</strong>ter-Typen, Objektgrößen<br />
und Speicher-Anlege- und Freigabe-Details, sauber kapseln. Alle<br />
Conta<strong>in</strong>er der STL s<strong>in</strong>d <strong>in</strong> Bezug auf die Allocators parametrisierbar, das<br />
bedeutet, dass sie z.B. nicht selbsttätig nach Gutdünken Objekte anlegen,<br />
sondern dass sie das Anlegen von e<strong>in</strong>em entsprechenden Allocator<br />
anfordern. Auf diese Art werden Conta<strong>in</strong>er unabhängig von speziellen<br />
Memory Modellen der verwendenden Applikationen.<br />
Str<strong>in</strong>gs: Zwei große Probleme treten bei der Verwendung von simplen char *<br />
als Str<strong>in</strong>gs <strong>in</strong> Programmen auf:
496 16. Die <strong>C++</strong> Standard Library<br />
1. Die Gefahr von Memory Leaks und wild gewordenen Po<strong>in</strong>tern ist<br />
extrem groß.<br />
2. Zeichentabellen, wie z.B. Unicode, die mehr als 8 Bits pro Zeichen<br />
verlangen, s<strong>in</strong>d nachträglich nur extrem schwer bis überhaupt nicht<br />
<strong>in</strong> e<strong>in</strong> existentes Programm e<strong>in</strong>zubauen.<br />
Die e<strong>in</strong>zig s<strong>in</strong>nvolle Lösung zum sauberen Umgang mit Str<strong>in</strong>gs <strong>in</strong> Programmen<br />
ist daher ihre Kapselung <strong>in</strong> e<strong>in</strong>e eigene Klasse bzw., aus<br />
Gründen der verschiedenartigen Zeichensatztabellen, <strong>in</strong> e<strong>in</strong> Template.<br />
Streams: Ob es sich nun um Input vom Keyboard, von e<strong>in</strong>em File oder auch<br />
von e<strong>in</strong>em Str<strong>in</strong>g handelt und ob der Output auf den Bildschirm oder<br />
sonstwoh<strong>in</strong> gehen soll, es handelt sich immer um e<strong>in</strong>en Datenstrom. Dieser<br />
ist <strong>in</strong> se<strong>in</strong>er Länge pr<strong>in</strong>zipiell nicht im Vorh<strong>in</strong>e<strong>in</strong> def<strong>in</strong>ierbar und auch<br />
das Zeitverhalten (z.B. wann wird etwas geschrieben und wie viel) ist<br />
nicht limitiert. Genau um dieses Verhalten zu modellieren, gibt es die<br />
sogenannten Streams.<br />
Numerik: Verschiedene aus dem Bereich der Numerik kommende Datenstrukturen<br />
(z.B. verschiedene Array-Typen, komplexe Zahlen) und Algorithmen<br />
zum Rechnen mit diesen werden oft genug <strong>in</strong> der Praxis verwendet,<br />
dass sie <strong>in</strong> die STL E<strong>in</strong>gang fanden. In diesem Bereich der STL<br />
s<strong>in</strong>d unter anderem die typischen Funktionen für den S<strong>in</strong>us, Cos<strong>in</strong>us,<br />
Logarithmus, etc. und natürlich auch die Zufallszahlen enthalten.<br />
Algorithmen und Funktionsobjekte: In diesem Bereich der Library f<strong>in</strong>den<br />
sich die typischen Algorithmen, die immer wieder auf Datenstrukturen<br />
angewandt werden. Vertreter hierfür s<strong>in</strong>d z.B. Transformationen, das<br />
Vertauschen von Inhalten, etc.<br />
Wie es auch <strong>in</strong> den Anforderungen def<strong>in</strong>iert ist, kann man natürlich auf Basis<br />
der Teile, die <strong>in</strong> der STL enthalten s<strong>in</strong>d, eigene Komponenten schreiben,<br />
wie z.B. sehr spezielle Conta<strong>in</strong>er etc. Um e<strong>in</strong> Gefühl für die mit der<br />
STL ausgelieferten Komponenten zu bekommen, ist den verschiedenen oben<br />
erwähnten Gruppen <strong>in</strong> der Folge jeweils e<strong>in</strong> kurzer Abschnitt gewidmet.<br />
E<strong>in</strong>e tiefer gehende Beschreibung vieler Design- und Anwendungsaspekte<br />
können <strong>in</strong>teressierte Leser <strong>in</strong> [Stroustrup 1997] nachlesen. E<strong>in</strong> sehr gutes<br />
und ausführliches Buch, das sich e<strong>in</strong>zig und alle<strong>in</strong> mit der STL beschäftigt,<br />
stellt [Musser et al. 2001] dar. Außerdem gibt es im Internet e<strong>in</strong>ige onl<strong>in</strong>e-<br />
Manuals und Tutorials zur STL zum Download. Zu [Stroustrup 1997] möchte<br />
ich noch erwähnen, dass ich ganz absichtlich auf die Special Edition se<strong>in</strong>es<br />
Buchs verweise und nicht auf die Standard Edition, da die Special Edition<br />
gerade durch die sehr tief gehende Diskussion der STL so “special” wird :-).<br />
Im Pr<strong>in</strong>zip muss man zur STL sagen, dass ihr Inhalt sehr <strong>in</strong>tuitiv verwendbar<br />
ist, sofern man sich e<strong>in</strong>mal an die doch nicht ganz standardgemäße Namensgebung<br />
gewisser Methoden gewöhnt hat (z.B. implementiert e<strong>in</strong>e Queue<br />
Methoden namens push und pop anstatt die sonst üblichen Bezeichnungen<br />
put und get zu verwenden). Ist man e<strong>in</strong>mal über diese kle<strong>in</strong>e Hürde h<strong>in</strong>weg,<br />
kann man den Umgang mit den Komponenten der STL auch e<strong>in</strong>fach durch
16.2 Conta<strong>in</strong>er 497<br />
Lesen der entsprechenden Header-Files und durch Ausprobieren sehr leicht<br />
<strong>in</strong> den Grundzügen erlernen. Lesern, die den Umgang mit fremdem Code<br />
noch nicht gewohnt s<strong>in</strong>d, möchte ich diese Vorgangsweise sogar sehr ans Herz<br />
legen, um e<strong>in</strong> wenig Übung zu bekommen.<br />
16.2 Conta<strong>in</strong>er<br />
Als Conta<strong>in</strong>er werden <strong>in</strong> der STL e<strong>in</strong>fache und assoziative Typen angeboten.<br />
Der Unterschied zwischen diesen beiden ist, dass die assoziativen Conta<strong>in</strong>er<br />
e<strong>in</strong>e Assoziation zwischen e<strong>in</strong>em Key und e<strong>in</strong>em Value quasi zum Nachschlagen<br />
implementieren. Die e<strong>in</strong>fachen Conta<strong>in</strong>er erlauben “nur” e<strong>in</strong>er Speicherung<br />
und das Durchgehen bzw. auch, je nach Conta<strong>in</strong>er, den <strong>in</strong>dizierten<br />
Zugriff auf e<strong>in</strong>zelne Elemente.<br />
16.2.1 Vektoren<br />
Beg<strong>in</strong>nen wir mit den e<strong>in</strong>fachen Conta<strong>in</strong>ern, die auch oftmalig als Sequences<br />
bezeichnet werden. E<strong>in</strong> sehr brauchbares Template ist der Vektor (Template<br />
vector), den wir uns e<strong>in</strong>mal ganz kurz am Beispiel ansehen wollen<br />
(simple_vector_demo.cpp):<br />
1 // simple vector demo . cpp − j u s t a simple demo , how to<br />
2 // use the vector template<br />
3<br />
4 #<strong>in</strong>clude <br />
5 #<strong>in</strong>clude <br />
6<br />
7 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
8<br />
9 us<strong>in</strong>g std : : vector ;<br />
10<br />
11 us<strong>in</strong>g std : : cout ;<br />
12 us<strong>in</strong>g std : : endl ;<br />
13<br />
14 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
15 {<br />
16 vector a v e c t o r ;<br />
17 vector another vector ( 4 ) ; // vector with 4 elements<br />
18<br />
19 // never f o r g e t to make sure that there i s enough space ! ! !<br />
20 a v e c t o r . r e s i z e ( 4 ) ;<br />
21<br />
22 a v e c t o r [ 0 ] = 1 0 ;<br />
23 a v e c t o r [ 1 ] = 2 0 ;<br />
24 a v e c t o r [ 2 ] = 3 0 ;<br />
25 a v e c t o r [ 3 ] = 4 0 ;<br />
26<br />
27 another vector [ 0 ] = 1 0 0 ;<br />
28 another vector [ 1 ] = 2 0 0 ;<br />
29 another vector [ 2 ] = 3 0 0 ;<br />
30 another vector [ 3 ] = 4 0 0 ;<br />
31<br />
32 for ( <strong>in</strong>t count = 0; count < 4 ; count ++)<br />
33 {
498 16. Die <strong>C++</strong> Standard Library<br />
34 cout
16.2 Conta<strong>in</strong>er 499<br />
Zum Index-Operator möchte ich noch e<strong>in</strong> Wort verlieren: Dieser nimmt aus<br />
Gründen der Performance ke<strong>in</strong>e Überprüfung vor, ob der Index im erlaubten<br />
Bereich liegt. Sollte e<strong>in</strong>e solche Überprüfung beim Zugriff aus Sicherheitsgründen<br />
gewünscht se<strong>in</strong>, so steht auch die Methode at(...) zur Verfügung,<br />
die als Parameter erwartungsgemäß e<strong>in</strong>e Ganzzahl entgegennimmt.<br />
Die Elemente e<strong>in</strong>es Vektors s<strong>in</strong>d nicht nur direkt <strong>in</strong>diziert ansprechbar,<br />
sondern man kann auch entsprechende Iterators anfordern um die Elemente<br />
der Reihe nach durchzugehen (siehe auch Abschnitt 16.3).<br />
16.2.2 Listen<br />
Oft brauchen wir <strong>in</strong> unseren Programmen aber nicht e<strong>in</strong>fach nur e<strong>in</strong>en sauber<br />
gekapselten Ersatz für e<strong>in</strong> Array, sondern e<strong>in</strong>en Conta<strong>in</strong>er, der folgende<br />
besonderen Eigenschaften besitzt:<br />
• Wir wollen ohne Laufzeite<strong>in</strong>bußen an beliebigen Stellen Elemente e<strong>in</strong>fügen<br />
können.<br />
• Wir wollen ohne Laufzeite<strong>in</strong>bußen Elemente von beliebigen Stellen wieder<br />
entfernen können und die verbliebenen Elemente sollen “nachrücken”.<br />
Dass wir bei diesen Forderungen mit e<strong>in</strong>em Vektor nicht mehr weit kommen,<br />
ist leicht e<strong>in</strong>zusehen. Wir brauchen hier e<strong>in</strong>e verkettete Liste, denn<br />
nur diese Datenstruktur kann die notwendigen Eigenschaften bieten. Erwartungsgemäß<br />
f<strong>in</strong>det sich e<strong>in</strong>e solche <strong>in</strong> der STL als list Template. Diese Liste<br />
stellt die Methoden <strong>in</strong>sert, erase und clear zur Verfügung um Elemente<br />
an e<strong>in</strong>er bestimmten Stelle e<strong>in</strong>zufügen, zu entfernen und die gesamte Liste<br />
zu leeren. Erwartungsgemäß weiß auch e<strong>in</strong>e Liste wieder über die Anzahl<br />
der <strong>in</strong> ihr gespeicherten Elemente Bescheid und gibt dieses Wissen bei Aufruf<br />
von size auch preis. E<strong>in</strong>es ist allerd<strong>in</strong>gs bei der Liste bewusst nicht<br />
implementiert: Der Index-Operator, der direkten Zugriff auf e<strong>in</strong> bestimmtes<br />
Element erlauben würde. Dies ist auch e<strong>in</strong>sichtig, denn um e<strong>in</strong>en <strong>in</strong>dizierten<br />
Zugriff zu erreichen, müsste jedes Mal die Liste von e<strong>in</strong>em Ende her bis<br />
zum gewünschten Element durchgegangen werden, was mit e<strong>in</strong>er Komplexität<br />
von O(n) e<strong>in</strong>er ziemlichen Laufzeitkatastrophe gleichkommen würde.<br />
Deshalb wurde nach dem Motto “...und führe uns nicht <strong>in</strong> Versuchung...”<br />
der Index Operator gleich gar nicht implementiert. In e<strong>in</strong>em kurzen Beispiel<br />
sieht die Verwendung der Basisfeatures e<strong>in</strong>er Liste dann folgendermaßen aus<br />
(simple_list_demo.cpp):<br />
1 // s i m p l e l i s t d e m o . cpp − j u s t a simple demo , how to<br />
2 // use the l i s t template<br />
3<br />
4 #<strong>in</strong>clude < l i s t><br />
5 #<strong>in</strong>clude <br />
6<br />
7 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
8<br />
9 us<strong>in</strong>g std : : l i s t ;
500 16. Die <strong>C++</strong> Standard Library<br />
10<br />
11 us<strong>in</strong>g std : : cout ;<br />
12 us<strong>in</strong>g std : : endl ;<br />
13<br />
14 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
15 {<br />
16 l i s t a l i s t ;<br />
17<br />
18 a l i s t . push front ( 1 . 0 ) ;<br />
19 a l i s t . push back ( 5 . 0 ) ;<br />
20<br />
21 l i s t:: i t e r a t o r i n s e r t i o n i t e r a t o r = a l i s t . end ( ) ;<br />
22 i n s e r t i o n i t e r a t o r −−;<br />
23 a l i s t . i n s e r t ( i n s e r t i o n i t e r a t o r , 1 , 3 . 0 ) ;<br />
24 i n s e r t i o n i t e r a t o r −−;<br />
25 a l i s t . i n s e r t ( i n s e r t i o n i t e r a t o r , 1 , 2 . 0 ) ;<br />
26<br />
27 cout
16.2 Conta<strong>in</strong>er 501<br />
Element immer vor dem Element e<strong>in</strong>fügt auf das der Iterator verweist.<br />
Das Ändern der Position des Iterators um e<strong>in</strong>s <strong>in</strong> Richtung Listenanfang<br />
bewerkstelligen wir durch Aufruf des -- Operators, der für den Iterator<br />
speziell def<strong>in</strong>iert ist, wie man <strong>in</strong> Zeile 22 sieht.<br />
• Nun, da der Iterator auf das Element zeigt, vor dem wir e<strong>in</strong> Element<br />
e<strong>in</strong>fügen wollen, rufen wir <strong>in</strong>sert auf, wobei die Aufrufparameter folgendermaßen<br />
zu <strong>in</strong>terpretieren s<strong>in</strong>d: Der erste Parameter gibt an, vor welchem<br />
Element e<strong>in</strong>gefügt werden soll. Der zweite Parameter gibt an, wie viele Elemente<br />
e<strong>in</strong>gefügt werden sollen. Das bedeutet, dass man e<strong>in</strong>- und dasselbe<br />
Element auch 20 Mal e<strong>in</strong>fügen kann, wenn man hier e<strong>in</strong>fach 20 angibt. Der<br />
dritte Parameter ist das Element selbst.<br />
In den Zeilen 24–25 wiederholt sich dasselbe Spiel. Will man die Liste nun<br />
von Anfang bis Ende durchgehen, so holt man sich e<strong>in</strong>en Iterator, der auf<br />
das erste Element zeigt, wie <strong>in</strong> Zeile 29 zu sehen ist. Die Schleife <strong>in</strong> den<br />
Zeilen 30–31 zeigt, dass der Iterator erstens kompatibel zu e<strong>in</strong>em Po<strong>in</strong>ter auf<br />
e<strong>in</strong> Element ist und dass man ihn mit dem abschließenden Element der Liste<br />
vergleichen kann, um zu wissen, wann es ke<strong>in</strong> weiteres Element mehr gibt.<br />
Dass das ganze Spielchen auch so funktioniert, wie hier beschrieben, sieht<br />
man am Output, den das Programm liefert:<br />
i t e r a t i n g l i s t from the beg<strong>in</strong>n<strong>in</strong>g :<br />
1 2 3 5<br />
Die Möglichkeiten, die e<strong>in</strong>e Liste bietet, gehen weit über das E<strong>in</strong>fügen, Entfernen<br />
und Durchgehen von Elementen h<strong>in</strong>aus: Man kann Listen auch teilen,<br />
sortieren und mehrere Listen zu e<strong>in</strong>er zusammenfügen. Dies geschieht über<br />
die Methoden splice, sort und merge.<br />
16.2.3 Double-Ended Queues<br />
Bisher kennen wir e<strong>in</strong>erseits Vektoren, die effizienten <strong>in</strong>dizierten Zugriff gestatten<br />
und Listen, die effizientes E<strong>in</strong>fügen und Löschen von Elementen erlauben,<br />
jedoch ke<strong>in</strong>en <strong>in</strong>dizierten Zugriff unterstützen. Relativ oft stoßen<br />
wir <strong>in</strong> Anwendungen auf e<strong>in</strong>en recht speziellen Fall, der so häufig ist, dass<br />
ihm e<strong>in</strong>e eigene Datenstruktur gewidmet ist: Wir benötigen e<strong>in</strong>en Conta<strong>in</strong>er,<br />
der es erlaubt am Anfang und am Ende e<strong>in</strong>er Sequenz Elemente effizient e<strong>in</strong>zufügen<br />
(nicht <strong>in</strong> der Mitte), der jedoch auch den schnellen <strong>in</strong>dizierten Zugriff<br />
auf die e<strong>in</strong>zelnen Elemente gestattet. Diese Datenstruktur nennt sich Deque<br />
(ausgesprochen: dek), was für double-ended-Queue steht. Das entsprechende<br />
Template nennt sich bezeichnenderweise deque, wie wir am kurzen Beispiel<br />
sehen können (simple_deque_demo.cpp):<br />
1 // simple deque demo . cpp − j u s t a simple demo , how to<br />
2 // use the deque template<br />
3<br />
4 #<strong>in</strong>clude <br />
5 #<strong>in</strong>clude
502 16. Die <strong>C++</strong> Standard Library<br />
6<br />
7 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
8<br />
9 us<strong>in</strong>g std : : deque ;<br />
10<br />
11 us<strong>in</strong>g std : : cout ;<br />
12 us<strong>in</strong>g std : : endl ;<br />
13<br />
14 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
15 {<br />
16 deque a deque ;<br />
17<br />
18 // push elements at the f r o n t and the back<br />
19 a deque . push front ( 2 0 ) ;<br />
20 a deque . push back ( 3 0 ) ;<br />
21 a deque . push front ( 1 0 ) ;<br />
22 a deque . push back ( 4 0 ) ;<br />
23<br />
24 // u t i l i z e <strong>in</strong>dexed a c c e s s<br />
25 for ( <strong>in</strong>t count = 0; count < 4 ; count ++)<br />
26 cout
6<br />
7 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
8<br />
9 us<strong>in</strong>g std : : queue ;<br />
10<br />
11 us<strong>in</strong>g std : : cout ;<br />
12 us<strong>in</strong>g std : : endl ;<br />
13<br />
14 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
15 {<br />
16 queue a queue ;<br />
17<br />
18 // push elements ( they are appended at the back )<br />
19 a queue . push ( 1 0 ) ;<br />
20 a queue . push ( 2 0 ) ;<br />
21 a queue . push ( 3 0 ) ;<br />
22 a queue . push ( 4 0 ) ;<br />
23<br />
24 cout
504 16. Die <strong>C++</strong> Standard Library<br />
1 // simple priority queue demo . cpp − j u s t a simple demo , how to<br />
2 // use the p r i o r i t y q u e u e template<br />
3<br />
4 #<strong>in</strong>clude <br />
5 #<strong>in</strong>clude <br />
6<br />
7 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
8<br />
9 us<strong>in</strong>g std : : p r i o r i t y q u e u e ;<br />
10<br />
11 us<strong>in</strong>g std : : cout ;<br />
12 us<strong>in</strong>g std : : endl ;<br />
13<br />
14 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
15 /∗<br />
16 ∗ ElementWithPriority<br />
17 ∗<br />
18 ∗ j u s t a dummy Element with a p r i o r i t y f i e l d<br />
19 ∗<br />
20 ∗/<br />
21<br />
22 class ElementWithPriority<br />
23 {<br />
24 protected :<br />
25 u<strong>in</strong>t32 p r i o r i t y ;<br />
26 u<strong>in</strong>t32 data ;<br />
27 public :<br />
28 ElementWithPriority ( )<br />
29 throw ( ) : p r i o r i t y ( 0 ) , data ( 0 ) { }<br />
30<br />
31 ElementWithPriority ( u<strong>in</strong>t32 p r i o r i t y , u<strong>in</strong>t32 data )<br />
32 throw ( ) : p r i o r i t y ( p r i o r i t y ) , data ( data ) {}<br />
33<br />
34 ElementWithPriority ( const ElementWithPriority & s r c )<br />
35 throw( )<br />
36 {<br />
37 p r i o r i t y = s r c . p r i o r i t y ;<br />
38 data = s r c . data ;<br />
39 }<br />
40<br />
41 virtual ElementWithPriority& operator = ( const ElementWithPriority & s r c )<br />
42 {<br />
43 p r i o r i t y = s r c . p r i o r i t y ;<br />
44 data = s r c . data ;<br />
45 return (∗ this ) ;<br />
46 }<br />
47<br />
48 virtual bool operator < ( const ElementWithPriority &cmp with ) const<br />
49 {<br />
50 return ( p r i o r i t y < cmp with . p r i o r i t y ) ;<br />
51 }<br />
52<br />
53 virtual operator u<strong>in</strong>t32 ( ) const<br />
54 {<br />
55 return ( data ) ;<br />
56 }<br />
57<br />
58 virtual ˜ ElementWithPriority ( )<br />
59 throw( ) { }<br />
60 } ;<br />
61<br />
62<br />
63 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
64 {<br />
65 p r i o r i t y q u e u e a p r i o r i t y q u e u e ;
66<br />
67 // push elements ( they are appended at the back )<br />
68 a p r i o r i t y q u e u e . push ( ElementWithPriority ( 1 0 , 1 0 0 ) ) ;<br />
69 a p r i o r i t y q u e u e . push ( ElementWithPriority ( 4 0 , 2 0 0 ) ) ;<br />
70 a p r i o r i t y q u e u e . push ( ElementWithPriority ( 2 0 , 3 0 0 ) ) ;<br />
71 a p r i o r i t y q u e u e . push ( ElementWithPriority ( 2 0 , 4 0 0 ) ) ;<br />
72<br />
73 cout
506 16. Die <strong>C++</strong> Standard Library<br />
als stack <strong>in</strong> der STL zur Verfügung. Dieses wird verwendet, wie im folgenden<br />
Beispiel gezeigt (simple_stack_demo.cpp):<br />
1 // simple stack demo . cpp − j u s t a simple demo , how to<br />
2 // use the queue template<br />
3<br />
4 #<strong>in</strong>clude <br />
5 #<strong>in</strong>clude <br />
6<br />
7 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
8<br />
9 us<strong>in</strong>g std : : stack ;<br />
10<br />
11 us<strong>in</strong>g std : : cout ;<br />
12 us<strong>in</strong>g std : : endl ;<br />
13<br />
14 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
15 {<br />
16 stack a stack ;<br />
17<br />
18 // push elements ( they are appended at the back )<br />
19 a stack . push ( 1 0 ) ;<br />
20 a stack . push ( 2 0 ) ;<br />
21 a stack . push ( 3 0 ) ;<br />
22 a stack . push ( 4 0 ) ;<br />
23<br />
24 cout
16.2 Conta<strong>in</strong>er 507<br />
Elemente über bestimmte Schlüssel, sogenannte Keys, ansprechen zu wollen.<br />
E<strong>in</strong> solcher Conta<strong>in</strong>er hat also ke<strong>in</strong>e sequentielle Ordnung mehr, sondern<br />
speichert Key-Value Pairs. Auch das ist am e<strong>in</strong>fachsten wieder an e<strong>in</strong>em<br />
Beispiel zu zeigen (simple_map_demo.cpp):<br />
1 // simple map demo . cpp − j u s t a simple demo , how to<br />
2 // use a map<br />
3<br />
4 #<strong>in</strong>clude <br />
5 #<strong>in</strong>clude <br />
6<br />
7 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
8<br />
9 us<strong>in</strong>g std : : map;<br />
10<br />
11 us<strong>in</strong>g std : : cout ;<br />
12 us<strong>in</strong>g std : : endl ;<br />
13<br />
14 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
15 {<br />
16 map a map ;<br />
17<br />
18 a map [ ’ a ’ ] = 1 1 . 0 ;<br />
19 a map [ ’ x ’ ] = 1 . 3 ;<br />
20 a map [ ’ u ’ ] = 1 7 . 8 ;<br />
21 a map [ ’ b ’ ] = 0 . 7 ;<br />
22<br />
23 cout ”
508 16. Die <strong>C++</strong> Standard Library<br />
das set Template zur Verfügung. In e<strong>in</strong>em Set speichert man Elemente,<br />
die selbst Key-Funktionalität besitzen. Am Beispiel sieht dies so aus<br />
(simple_set_demo.cpp):<br />
1 // simple set demo . cpp − j u s t a simple demo , how to<br />
2 // use a s e t<br />
3<br />
4 #<strong>in</strong>clude < s e t><br />
5 #<strong>in</strong>clude <br />
6<br />
7 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
8<br />
9 us<strong>in</strong>g std : : s e t ;<br />
10<br />
11 us<strong>in</strong>g std : : cout ;<br />
12 us<strong>in</strong>g std : : endl ;<br />
13<br />
14 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
15 {<br />
16 s e t a s e t ;<br />
17 s e t a n o t h e r s e t ;<br />
18<br />
19 a s e t . i n s e r t ( 1 0 ) ;<br />
20 a s e t . i n s e r t ( 2 0 ) ;<br />
21 a s e t . i n s e r t ( 3 0 ) ;<br />
22 a s e t . i n s e r t ( 4 0 ) ;<br />
23<br />
24 a n o t h e r s e t . i n s e r t ( 1 0 ) ;<br />
25 a n o t h e r s e t . i n s e r t ( 4 0 ) ;<br />
26 a n o t h e r s e t . i n s e r t ( 3 0 ) ;<br />
27 a n o t h e r s e t . i n s e r t ( 2 0 ) ;<br />
28<br />
29 cout ”
16.2 Conta<strong>in</strong>er 509<br />
Wie bei map ist auch bei set e<strong>in</strong> duplicate Key nicht zulässig und analog<br />
zur multimap gibt es auch für Fälle, <strong>in</strong> denen dieses Verhalten benötigt wird<br />
e<strong>in</strong> multiset. Neben diesem gibt es noch e<strong>in</strong>e weitere besondere Art e<strong>in</strong>es<br />
Sets, das sich <strong>in</strong> der Praxis als sehr brauchbar erweist: Das Bit-Set, das als<br />
bitset Template zur Verfügung steht. Oft hat man, aus welchen Gründen<br />
auch immer, e<strong>in</strong>e gewisse Anzahl von boolschen Variablen, die verschiedene<br />
Teilzustände repräsentieren. Jede dieser Variablen speichert im Pr<strong>in</strong>zip<br />
nur e<strong>in</strong> e<strong>in</strong>ziges Bit an Information, jedoch wird e<strong>in</strong> x-faches an Speicherplatz<br />
dafür verbraucht (je nach Compiler und Architektur zwischen 8 und<br />
32 Bits). Bei Objekten, die vielfach im Programm vorkommen, kann das<br />
schon zur groben Speicherverschwendung entarten. Arbeitet man jedoch mit<br />
e<strong>in</strong>em bitset, so kann man dies leicht <strong>in</strong> den Griff bekommen, ohne auf den<br />
Gebrauch von Bitmasken per Hand zurückgreifen zu müssen. Wie dies <strong>in</strong> der<br />
Praxis aussehen könnte, sieht man an folgendem Beispiel:<br />
1 // simple bitset demo . cpp − j u s t a simple demo , how to<br />
2 // use a b i t s e t<br />
3<br />
4 #<strong>in</strong>clude < b i t s e t><br />
5 #<strong>in</strong>clude <br />
6<br />
7 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
8<br />
9 us<strong>in</strong>g std : : b i t s e t ;<br />
10<br />
11 us<strong>in</strong>g std : : cout ;<br />
12 us<strong>in</strong>g std : : endl ;<br />
13<br />
14 static const u<strong>in</strong>t32 INITIALIZED BIT = 0;<br />
15 static const u<strong>in</strong>t32 BROKEN BIT = 1;<br />
16 static const u<strong>in</strong>t32 OPEN HANDSHAKE BIT = 2;<br />
17 static const u<strong>in</strong>t32 ALIVE BIT = 3;<br />
18 static const u<strong>in</strong>t32 CLOSE HANDSHAKE BIT = 4;<br />
19 static const u<strong>in</strong>t32 CLOSED BIT = 5;<br />
20 static const u<strong>in</strong>t32 NUM BITS = 6;<br />
21<br />
22 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
23 {<br />
24 b i t s e t c o n n e c t i o n s t a t u s = 0x0 ;<br />
25<br />
26 c o n n e c t i o n s t a t u s [ INITIALIZED BIT ] = 1 ;<br />
27 c o n n e c t i o n s t a t u s [ ALIVE BIT ] = 1 ;<br />
28<br />
29 cout ”
510 16. Die <strong>C++</strong> Standard Library<br />
zelnen Bits <strong>in</strong>tuitiverweise über den Index Operator. Natürlich ist der Index<br />
des ersten verfügbaren Bits 0, wie wir es bei Indizes generell gewohnt s<strong>in</strong>d.<br />
Der Output des Programms sieht dann so aus:<br />
i s the connection i n i t i a l i z e d ? −> 1<br />
i s the connection a l i v e ? −> 1<br />
i s c l o s e handshake <strong>in</strong> progress ? −> 0<br />
Noch zu erwähnen wäre, dass s<strong>in</strong>nigerweise für Bit-Sets alle Bitmanipulations-<br />
Operatoren entsprechend def<strong>in</strong>iert s<strong>in</strong>d, so dass man mit e<strong>in</strong>em Bit-Set ganz<br />
gleich arbeiten kann wie mit Ganzzahlen.<br />
16.2.9 Zusammenfassung der Conta<strong>in</strong>er-Operationen<br />
Da ich bei den verschiedenen Conta<strong>in</strong>ern immer nur e<strong>in</strong>en ger<strong>in</strong>gen Teil ihrer<br />
Möglichkeiten gezeigt und besprochen habe, möchte ich an dieser Stelle<br />
e<strong>in</strong>e kurze tabellarische Zusammenfassung geben, welche Operationen pr<strong>in</strong>zipiell<br />
für die verschiedenen Conta<strong>in</strong>er def<strong>in</strong>iert s<strong>in</strong>d. Die Tabellen enthalten<br />
die Operationen (Methoden und Operatoren), die im Pr<strong>in</strong>zip für alle verschiedenen<br />
Standard-Conta<strong>in</strong>er Gültigkeit haben. Je nach Spezialisierung<br />
bestimmter Conta<strong>in</strong>er s<strong>in</strong>d noch weitere Operationen def<strong>in</strong>iert, die hier nicht<br />
angeführt s<strong>in</strong>d (z.B. Bitmanipulationen beim bitset). Die Absicht h<strong>in</strong>ter<br />
den folgenden Tabellen ist es, Lesern e<strong>in</strong>en zusätzlichen Startpunkt zum Spielen<br />
mit den Standard Conta<strong>in</strong>ern zu geben. Für e<strong>in</strong>e detailliertere Diskussion<br />
möchte ich auf die entsprechende Spezialliteratur (z.B. [Musser et al. 2001]<br />
und [Stroustrup 1997]) verweisen.<br />
Elementzugriff<br />
front Erstes Element<br />
back Letztes Element<br />
[] Ungeprüfter Zugriff mit Index<br />
at Geprüfter Zugriff mit Index (nur vector und deque)
16.2 Conta<strong>in</strong>er 511<br />
Generelle Operationen<br />
size Anzahl der Elemente<br />
empty Ist der Conta<strong>in</strong>er leer?<br />
max_size Größe des größtmöglichen Conta<strong>in</strong>ers<br />
capacity Für wie viele Elemente ist Platz reserviert?<br />
(nur vector)<br />
reserve Reserviere Platz für x Elemente (nur vector)<br />
resize Conta<strong>in</strong>ergröße ändern (vector, list, deque)<br />
swap Elemente zweier Conta<strong>in</strong>er austauschen<br />
get_allocator Kopie des Allocators anfordern<br />
== Ist der Content zweier Conta<strong>in</strong>er identisch?<br />
!= Ist der Content zweier Conta<strong>in</strong>er verschieden?<br />
< Ist e<strong>in</strong> Conta<strong>in</strong>er lexikographisch kle<strong>in</strong>er?<br />
Stack und Queue Operationen<br />
push Element anfügen<br />
pop Element entfernen<br />
push_back Element am Ende anfügen<br />
pop_back Element vom Ende entfernen<br />
push_front Element als erstes anfügen (nur deque)<br />
pop_front Erstes Element entfernen (nur deque)<br />
Anm.: Die Operationen push_front und pop_front s<strong>in</strong>d bewusst <strong>in</strong> der<br />
folgenden Tabelle noch e<strong>in</strong>mal angeführt, da sie auch für Listen gelten, e<strong>in</strong>e<br />
Liste aber weder e<strong>in</strong> Stack, noch e<strong>in</strong>e Queue ist.<br />
Listenoperationen<br />
<strong>in</strong>sert Element e<strong>in</strong>fügen<br />
erase Element entfernen<br />
clear Conta<strong>in</strong>er entleeren<br />
push_front Element als erstes anfügen<br />
pop_front Erstes Element entfernen<br />
Die folgende Tabelle wurde mit Assoziative Operationen betitelt, da die dar<strong>in</strong><br />
angeführten Operationen sowohl für Maps als auch für Sets gültig s<strong>in</strong>d.
512 16. Die <strong>C++</strong> Standard Library<br />
Assoziative Operationen<br />
[] Quasi-<strong>in</strong>dizierter Zugriff mittels Key<br />
f<strong>in</strong>d Suche nach Key<br />
lower_bound Suche nach erstem Element mit Key<br />
upper_bound Suche nach erstem Element mit Key<br />
größer als gegebener Key<br />
equal_range Suche nach allen Elementen mit Key<br />
key_comp Liefert e<strong>in</strong>e Kopie des Key-Komparator Objekts<br />
value_comp Liefert e<strong>in</strong>e Kopie des Value-Komparator Objekts<br />
In der folgenden Tabelle schlägt leider zum wiederholten Mal das “Henne-Ei-<br />
Problem” zu: E<strong>in</strong>erseits wurden Iteratoren noch nicht erklärt, andererseits<br />
s<strong>in</strong>d die entsprechenden Operationen Teil der Conta<strong>in</strong>er. Deshalb wurden<br />
auch bereits Vorgriffe gemacht. Würde man die Reihenfolge der Behandlung<br />
der beiden Themen umstellen, so steht man vor demselben Problem: Es<br />
werden die Iterators erklärt, allerd<strong>in</strong>gs fehlt das Wissen um die Conta<strong>in</strong>er,<br />
auf die sie sich beziehen. Nach kurzem Überfliegen von Abschnitt 16.3 sollte<br />
sich diese Diskrepanz aber <strong>in</strong> Wohlgefallen aufgelöst haben.<br />
Anfordern von Iteratoren<br />
beg<strong>in</strong> Iterator, der auf das erste Element zeigt<br />
end Iterator, der um e<strong>in</strong> Element h<strong>in</strong>ter das letzte Element zeigt<br />
rbeg<strong>in</strong> Iterator, der auf das erste Element der reverse Sequence<br />
rend Iterator, der um e<strong>in</strong> Element vor das letzte Element der<br />
reverse Sequence zeigt<br />
In den Beispielen, die zur kurzen Demonstration verwendet wurden, wurde<br />
bereits von der Möglichkeit Gebrauch gemacht, Datentypen vom entsprechenden<br />
Conta<strong>in</strong>er-Template zu erfragen. Die folgende Tabelle enthält die<br />
Zusammenfassung aller verfügbaren Datentypen, die man von e<strong>in</strong>em Conta<strong>in</strong>er<br />
erfahren kann.
16.3 Iterators 513<br />
Abfragbare Datentypen<br />
key_type Typ des Key-Objekts<br />
value_type Typ des Value-Objekts<br />
allocator_type Typ des Allocators<br />
size_type Typ für <strong>in</strong>dizierten Zugriff<br />
difference_type Typ für Unterschied zwischen Iteratoren<br />
iterator Typ des Iterators, der sich wie<br />
value_type* verhält<br />
const_iterator Typ des Iterators, der sich wie<br />
const value_type* verhält<br />
reverse_iterator analog zu iterator<br />
(umgekehrte Reihenfolge)<br />
const_reverse_iterator analog zu reverse_iterator<br />
(umgekehrte Reihenfolge)<br />
reference Typ des Iterators, der sich wie<br />
value_type& verhält<br />
const_reference analog zu reference, nur const<br />
mapped_type Typ von mapped_value<br />
(nur assoziative Conta<strong>in</strong>er)<br />
key_compare Typ des Key Komparators<br />
(nur assoziative Conta<strong>in</strong>er)<br />
16.3 Iterators<br />
Bisher wurden Iterators zwar bereits im Zuge von kurzen Vorgriffen erwähnt<br />
und auch verwendet, aber e<strong>in</strong>e genaue Erklärung dazu b<strong>in</strong> ich noch schuldig<br />
geblieben. Die sogenannten Iterators lösen e<strong>in</strong> immer wieder auftretendes<br />
Problem, über das man bei der Verwendung von Conta<strong>in</strong>ern stolpern kann:<br />
Man stelle sich vor, es gäbe e<strong>in</strong>e Implementation e<strong>in</strong>er Liste, die man ganz<br />
e<strong>in</strong>fach durch Aufruf der Methoden first...next...next...etc. Element für<br />
Element durchlaufen kann. Die Information, welches Element nun das aktuelle<br />
ist, würde im Conta<strong>in</strong>er selbst stecken. Nun stelle man sich weiters vor,<br />
was passiert, wenn aus mehreren Programmteilen heraus zugleich e<strong>in</strong> und<br />
dieselbe Liste durchlaufen würde. Das funktioniert schlicht und ergreifend<br />
nicht, denn sobald aus e<strong>in</strong>em Teil heraus next aufgerufen würde, nähme man<br />
dem anderen Teil ja damit e<strong>in</strong> Element weg. Diese Programmteile würden<br />
sich also gegenseitig durche<strong>in</strong>ander br<strong>in</strong>gen und das ist sicher nicht im S<strong>in</strong>ne<br />
des Erf<strong>in</strong>ders.<br />
Ganz anders verhält es sich da schon, wenn die Information über das aktuelle<br />
Element außerhalb des eigentlichen Conta<strong>in</strong>ers gespeichert wird und<br />
genau das ist die Natur des Iterators: E<strong>in</strong> Iterator bezieht sich immer auf<br />
e<strong>in</strong> aktuelles Element, das sich aufgrund irgende<strong>in</strong>er Art e<strong>in</strong>en Conta<strong>in</strong>er zu<br />
durchwandern ergeben hat. Egal also, ob e<strong>in</strong> Programmteil mit e<strong>in</strong>em Iterator<br />
vor, zurück, h<strong>in</strong> oder her wandert, es wird dadurch niemals e<strong>in</strong> anderer
514 16. Die <strong>C++</strong> Standard Library<br />
Programmteil <strong>in</strong> se<strong>in</strong>er Arbeit des Durchwanderns bee<strong>in</strong>flusst, solange dieser<br />
e<strong>in</strong>en eigenen Iterator besitzt.<br />
E<strong>in</strong> Iterator bezieht sich also auf das aktuelle Element und über ihn f<strong>in</strong>det<br />
auch die “Navigation” durch den Conta<strong>in</strong>er statt. Navigation über Iterators<br />
bedeutet, dass man die Elemente e<strong>in</strong>es Conta<strong>in</strong>ers immer <strong>in</strong> e<strong>in</strong>er bestimmten<br />
Ordnung zu Gesicht bekommt, die man vorwärts oder rückwärts durchlaufen<br />
kann. Man bewegt sich dabei immer um e<strong>in</strong> Element <strong>in</strong> e<strong>in</strong>e Richtung. Salopp<br />
gesagt bedeutet dies, dass sich die Elemente e<strong>in</strong>es Conta<strong>in</strong>ers durch den<br />
Iterator so präsentieren, als wären sie <strong>in</strong> e<strong>in</strong>er verketteten Liste gespeichert.<br />
Jede Navigationsoperation zum nächsten, vorherigen oder welchem anderen<br />
Element auch immer, verändert das aktuelle Element, auf das der Iterator<br />
zeigt. Je nachdem, was man nun tun will, gibt es e<strong>in</strong>e Reihe verschiedener<br />
Typen von Iterators, die neben der Navigation die e<strong>in</strong>e oder andere besondere<br />
Zusatzaufgabe zu erledigen haben:<br />
• Es gibt solche, die nur zum lesenden Zugriff auf Elemente verwendet werden.<br />
Wenn e<strong>in</strong>fach nur von Iterator die Rede ist, s<strong>in</strong>d <strong>in</strong> der Regel genau<br />
diese geme<strong>in</strong>t.<br />
• Es gibt solche, die dazu verwendet werden, <strong>in</strong> e<strong>in</strong>e Datenstruktur an e<strong>in</strong>er<br />
bestimmten Stelle, die man vorher heraussucht, Elemente e<strong>in</strong>zufügen. Solche<br />
werden im allgeme<strong>in</strong>en Sprachgebrauch von Softwareentwicklern als<br />
Inserters bezeichnet. Man kann sich leicht vorstellen, dass beim Erlauben<br />
von schreibenden Zugriffen e<strong>in</strong> erheblicher Synchronisationsaufwand<br />
vonnöten ist, um nicht Inkonsistenzen <strong>in</strong> anderen Programmteilen zu verursachen,<br />
die gleichzeitig e<strong>in</strong>en Conta<strong>in</strong>er lesender- oder ebenfalls schreibenderweise<br />
iterieren.<br />
• Es gibt solche, die man ganz bewusst zum verkehrten Durchlaufen e<strong>in</strong>er<br />
Datenstruktur verwendet. Diese werden als reverse Iterators bezeichnet,<br />
wenn sie nur für den lesenden Zugriff gedacht s<strong>in</strong>d, als reverse Inserters,<br />
wenn sie auch zum E<strong>in</strong>fügen von Elementen gedacht s<strong>in</strong>d. Warum die<br />
Bezeichnung reverse so besonders herausgestrichen wird, wird <strong>in</strong> Abbildung<br />
16.1 skizziert.<br />
• Es gibt dann auch noch den Typ der Checked Iterators, die sich dadurch<br />
auszeichnen, dass bei Navigationsoperationen überprüft wird, ob diese<br />
Operation beim aktuellen Inhalt des Conta<strong>in</strong>ers überhaupt noch möglich<br />
ist (z.B. nach dem Ende e<strong>in</strong>er Sequence kann man nicht noch e<strong>in</strong>mal um<br />
e<strong>in</strong> Element weiter rücken).<br />
In der STL s<strong>in</strong>d für alle verschiedenen Conta<strong>in</strong>er entsprechende Iterators<br />
def<strong>in</strong>iert. Wenn man e<strong>in</strong>en Iterator von e<strong>in</strong>em Conta<strong>in</strong>er anfordert, so kann<br />
man dies pr<strong>in</strong>zipiell auf e<strong>in</strong>e der folgenden vier Arten tun, die den Startpunkt<br />
der folgenden Navigationsoperationen def<strong>in</strong>ieren:<br />
beg<strong>in</strong> fordert e<strong>in</strong>en Iterator an, der auf das erste Element im Conta<strong>in</strong>er<br />
zeigt. E<strong>in</strong> Anfordern des Elements vom Iterator liefert also das erste<br />
Element.
16.3 Iterators 515<br />
end fordert e<strong>in</strong>en Iterator an, der um e<strong>in</strong> Element h<strong>in</strong>ter das letzte Element<br />
zeigt. E<strong>in</strong> Anfordern des Elements vom Iterator liefert also gar ke<strong>in</strong><br />
Element, sondern end! Diese Vere<strong>in</strong>barung wurde deshalb getroffen, da<br />
alle E<strong>in</strong>fügeoperationen, die über Iterators angeboten werden, so def<strong>in</strong>iert<br />
s<strong>in</strong>d, dass sie das neu dazugekommene Element immer vor dem aktuellen<br />
Element e<strong>in</strong>fügen. Würde der Iterator also direkt auf das letzte Element<br />
zeigen, so könnte niemals e<strong>in</strong> Element h<strong>in</strong>ter dem letzten Element der<br />
Liste e<strong>in</strong>gefügt werden.<br />
rbeg<strong>in</strong> fordert e<strong>in</strong>en Iterator an, der auf die Elemente des Conta<strong>in</strong>ers <strong>in</strong><br />
verkehrter Reihenfolge zeigt. E<strong>in</strong> Anfordern des Elements vom Iterator<br />
liefert also nun das letzte Element.<br />
rend fordert e<strong>in</strong>en Iterator an, der um e<strong>in</strong> Element h<strong>in</strong>ter das letzte Element<br />
<strong>in</strong> verkehrter Reihenfolge zeigt.<br />
Iterators <strong>in</strong> der STL gehen also von folgender logischer Sicht aus: Wenn man<br />
“verkehrt” im Conta<strong>in</strong>er navigiert, dann ist der Beg<strong>in</strong>n trotzdem “vorne”,<br />
nur die Elemente präsentieren sich <strong>in</strong> umgekehrter Reihenfolge. Das ist auch<br />
der Grund, warum es für e<strong>in</strong>en reverse_iterator e<strong>in</strong>en eigenen Datentyp<br />
gibt.<br />
Verwirrend? Ke<strong>in</strong>e Sorge, mit e<strong>in</strong>em kle<strong>in</strong>en Beispielchen wird das Ganze<br />
klarer (simple_iterator_demo.cpp):<br />
1 // simple iterator demo . cpp − j u s t a simple demo , how to<br />
2 // use the i t e r a t o r s<br />
3<br />
4 #<strong>in</strong>clude <br />
5 #<strong>in</strong>clude <br />
6<br />
7 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
8<br />
9 us<strong>in</strong>g std : : vector ;<br />
10<br />
11 us<strong>in</strong>g std : : cout ;<br />
12 us<strong>in</strong>g std : : endl ;<br />
13<br />
14 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
15 {<br />
16 vector a v e c t o r ( 1 0 ) ;<br />
17<br />
18 for ( <strong>in</strong>t count = 0 ; count < 1 0 ; count++)<br />
19 a v e c t o r [ count ] = count ;<br />
20<br />
21 vector:: i t e r a t o r f o r w a r d i t e r a t o r = a v e c t o r . beg<strong>in</strong> ( ) ;<br />
22 vector:: r e v e r s e i t e r a t o r backward iterator =<br />
23 a v e c t o r . rbeg<strong>in</strong> ( ) ;<br />
24<br />
25 cout
516 16. Die <strong>C++</strong> Standard Library<br />
35 cout
e v e r s e−i t e r a t i n g vector :<br />
9 8 7 6 5 4 3 2 1 0<br />
. . . and back to the s t a r t :<br />
0 1 2 3 4 5 6 7 8 9<br />
16.4 Allocators 517<br />
In Abbildung 16.1 f<strong>in</strong>det sich e<strong>in</strong>e schematische Darstellung des soeben Beschriebenen.<br />
Die Kästchen stellen die e<strong>in</strong>zelnen Elemente dar, die Pfeile<br />
zwischen den Kästchen stehen für e<strong>in</strong>e Navigationsrichtung und s<strong>in</strong>d jeweils<br />
mit ++ bzw. -- gekennzeichnet, um zu zeigen, welcher Operator für e<strong>in</strong>en Iterator<br />
nun <strong>in</strong> welche Richtung navigiert. Zusätzlich ist noch zu sehen, worauf<br />
die Iteratoren zeigen, die man mittels beg<strong>in</strong>, end, rbeg<strong>in</strong> und rend anfordert.<br />
In der Graphik ist auch zu erkennen, warum e<strong>in</strong> Iterator und e<strong>in</strong> reverse<br />
Iterator verschiedene Datentypen se<strong>in</strong> müssen, denn sie zeigen <strong>in</strong> Bezug auf<br />
die Navigation ja völlig unterschiedliches Verhalten.<br />
Iterator:<br />
beg<strong>in</strong>() end()<br />
reverse Iterator:<br />
rbeg<strong>in</strong>()<br />
++ ++<br />
++ ++<br />
0 1<br />
...<br />
8 9<br />
−− −−<br />
−− −−<br />
++ ++<br />
++ ++<br />
9 8<br />
...<br />
1 0<br />
−− −−<br />
−− −−<br />
++<br />
−−<br />
++<br />
−−<br />
rend()<br />
Abbildung 16.1: Iterator und reverse Iterator schematisch dargestellt<br />
16.4 Allocators<br />
Allocators werden an dieser Stelle nur der Vollständigkeit halber erwähnt,<br />
um allen Lesern bewusst zu machen, wie die STL beim Anfordern von Memory<br />
vorgeht. Die STL geht davon aus, dass <strong>in</strong>tern ke<strong>in</strong>e fixen Annahmen<br />
getroffen werden dürfen, auf welche Art für welche Objekte Memory angefordert<br />
werden soll (wie schon erwähnt, die STL ist Policy-frei!). Würden die<br />
<strong>in</strong> der STL implementierten Datenstrukturen grundsätzlich Objekte hardcodiert<br />
mittels new und delete anfordern, so könnte es se<strong>in</strong>, dass sie damit <strong>in</strong><br />
speziellen Fällen gegen die Intention der Applikation verstoßen, denn sowohl<br />
für Elemente <strong>in</strong> Conta<strong>in</strong>ern als auch für <strong>in</strong>terne Objekte der Datenstrukturen<br />
könnte e<strong>in</strong> spezielles Memory Management erwünscht se<strong>in</strong>.
518 16. Die <strong>C++</strong> Standard Library<br />
Aus diesem Grund gibt es <strong>in</strong> der STL das Pr<strong>in</strong>zip von Allocators, die<br />
<strong>in</strong> Belangen des Memory Managements angesprochen werden. E<strong>in</strong> Allocator<br />
ist nichts anderes als e<strong>in</strong> vorgeschriebenes Interface, <strong>in</strong> dem die Methoden<br />
deklariert s<strong>in</strong>d, die von der STL für die Memory Verwaltung verwendet werden.<br />
Im Normalfall s<strong>in</strong>d diese Allocators für Benutzer der STL unsichtbar,<br />
denn es gibt natürlich entsprechende Standard-Implementationen davon (die<br />
ganz e<strong>in</strong>fach new und delete verwenden). Sollte man aber <strong>in</strong> das Memory<br />
Management e<strong>in</strong>greifen wollen, so kann man selbst besondere Allocators<br />
implementieren und den verschiedenen Datenstrukturen der STL diese zur<br />
Verwendung übergeben.<br />
Das bedeutet also, dass Benutzer der STL <strong>in</strong> den allermeisten Fällen<br />
nichts mit Allocators zu tun haben werden. Jedoch sollten sie im H<strong>in</strong>terkopf<br />
behalten, dass es <strong>in</strong> besonderen Fällen nicht notwendig ist, Datenstrukturen<br />
per Hand nachzuprogrammieren, bloß weil z.B. Garbage-Collection<br />
erwünscht ist. Man kann e<strong>in</strong>fach weiter die Komponenten der STL verwenden<br />
und implementiert e<strong>in</strong>fach entsprechende Allocators, die Garbage-Collection<br />
unterstützen. Mehr ist nicht notwendig. Um nähere Details über Allocators<br />
zu erfahren möchte ich an dieser Stelle auf die entsprechende Spezialliteratur<br />
(z.B. [Musser et al. 2001] und [Stroustrup 1997]) verweisen.<br />
16.5 Str<strong>in</strong>gs<br />
Diskussionen über Str<strong>in</strong>gs haben sowohl <strong>in</strong> C als auch <strong>in</strong> C ++ e<strong>in</strong>e sehr lange<br />
Tradition. Die Möglichkeit, die gefährlichen char * <strong>in</strong> e<strong>in</strong>e Klasse zu<br />
kapseln, wurde von den Entwicklern bereits zu Urzeiten von C ++ dankend<br />
angenommen und auf diese Art entstand e<strong>in</strong>e nicht mehr überschaubare Menge<br />
von mehr oder weniger guten Str<strong>in</strong>g Implementationen. Leider konnten<br />
und können Entwickler immer wieder der Versuchung nicht widerstehen, im<br />
Applikationscode direkt mit den Low-Level char * zu arbeiten. Die Begründungen<br />
dafür s<strong>in</strong>d mannigfaltig, jedoch bis auf sehr wenige und sehr<br />
spezielle Ausnahmen nicht aufrecht zu halten, denn die Nachteile, die man<br />
sich damit erkauft, gew<strong>in</strong>nen im Normalfall deutlich Überhand gegenüber<br />
den ger<strong>in</strong>gen Vorteilen <strong>in</strong> sehr speziellen Situationen.<br />
Um zu verstehen, warum ausgerechnet Str<strong>in</strong>gs e<strong>in</strong> sehr heikles Thema<br />
s<strong>in</strong>d, das gehörig zur Qualität von Software beiträgt, führen wir uns e<strong>in</strong>fach<br />
e<strong>in</strong>mal folgende Punkte vor Augen:<br />
• Str<strong>in</strong>gs werden im Regelfall nicht e<strong>in</strong>fach als Konstanten verwendet, sondern<br />
es f<strong>in</strong>den <strong>in</strong> allen Programmen Unmengen von Str<strong>in</strong>g Manipulationen<br />
statt. Str<strong>in</strong>gs werden mite<strong>in</strong>ander verknüpft, durchsucht, <strong>in</strong> Tokens<br />
zerlegt, neu zusammengesetzt und noch vieles mehr. Alle diese Operationen<br />
s<strong>in</strong>d potentielle Fehlerquellen, denn im Normalfall wird für Str<strong>in</strong>gs<br />
praktisch ausschließlich mit dynamischer Memory Verwaltung gearbeitet.<br />
Mit statischen Arrays kommt man hier nicht mehr sehr weit. Außerdem
16.5 Str<strong>in</strong>gs 519<br />
kann man davon ausgehen, dass sehr viel Kreativität vonnöten ist, die entsprechenden<br />
Manipulationen performant genug zu implementieren, dass sie<br />
nicht zur Laufzeitkatastrophe werden.<br />
• Verschiedene Systeme unterstützen verschiedenste Zeichensätze. Es s<strong>in</strong>d<br />
daher bei plattformübergreifenden Programmen auch verschiedenste Umwandlungsverfahren<br />
zu implementieren. Wenn man im gesamten Programm<br />
verstreute Low-Level char * zusammensuchen müsste, um die Umwandlungen<br />
korrekt e<strong>in</strong>zubauen, würde dies zum undurchführbaren Unterfangen.<br />
• E<strong>in</strong> Character, der <strong>in</strong> e<strong>in</strong>em Str<strong>in</strong>g verwendet wird, muss nicht unbed<strong>in</strong>gt<br />
e<strong>in</strong> char se<strong>in</strong>! Leider ist genau diese Tatsache vielen Entwicklern nicht<br />
bewusst, aber aus guten Gründen werden immer häufiger Zeichensätze<br />
verwendet, die 16 oder sogar 32 Bit Characters voraussetzen (z.B. Unicode).<br />
Was es bedeutet, alle Vorkommen von char * (und auch char,<br />
die von gewissen Manipulationsrout<strong>in</strong>en geliefert werden) <strong>in</strong> e<strong>in</strong>em großen<br />
Programm zu f<strong>in</strong>den und entsprechend zu ändern, kann man sich leicht<br />
vorstellen.<br />
Neben diesen Betrachtungen gibt es noch andere, wie z.B. Mehrsprachigkeit,<br />
die <strong>in</strong> vielen Programmen e<strong>in</strong>e Rolle spielen. Die Anforderungen an Str<strong>in</strong>gs<br />
s<strong>in</strong>d auf jeden Fall deutlich zu sehen:<br />
• E<strong>in</strong> Str<strong>in</strong>g muss unbed<strong>in</strong>gt <strong>in</strong> e<strong>in</strong>e Klasse gekapselt werden. Die Verwendung<br />
von Low-Level char * <strong>in</strong> Programmen ist absolut unzulässig.<br />
• E<strong>in</strong>e Str<strong>in</strong>g-Klasse darf sich ke<strong>in</strong>esfalls auf die Verwendung von char für<br />
e<strong>in</strong>en Character kaprizieren. Auch die implementierten Methoden zur Manipulation<br />
müssen entsprechend allgeme<strong>in</strong> geschrieben se<strong>in</strong>, dass sie für<br />
verschiedenste Datentypen zu brauchen s<strong>in</strong>d.<br />
• Genauso wenig wie die Str<strong>in</strong>g-Klasse selbst dürfen sich spezielle (externe)<br />
Funktionen zur Manipulation auf char für e<strong>in</strong>en Character beschränken.<br />
• Da es immer noch viele althergebrachte Klassen, Funktionen und Methoden<br />
gibt, die aus historischen Gründen <strong>in</strong> gewissen Situationen mit char *<br />
arbeiten, muss e<strong>in</strong>e Str<strong>in</strong>g-Klasse e<strong>in</strong>e entsprechende Typumwandlung unterstützen,<br />
wenn diese beim verwendeten Zeichensatz überhaupt s<strong>in</strong>nvoll<br />
ist. Fehlverwendung (z.B. Cast auf char *, obwohl der Basis Character<br />
des Str<strong>in</strong>gs e<strong>in</strong> unsigned short ist) muss zu e<strong>in</strong>em Compilerfehler führen.<br />
Dass all diese Anforderungen (und noch e<strong>in</strong>ige zusätzliche, die man sich<br />
überlegen kann) geradezu nach der Implementation e<strong>in</strong>es Str<strong>in</strong>gs als Template<br />
schreien, ist sonnenklar. Genau das wurde auch <strong>in</strong> der STL gemacht<br />
und steht <strong>in</strong> Form des basic_str<strong>in</strong>g Templates zur Verfügung. Das Template<br />
basic_str<strong>in</strong>g ist mit dem Datentyp parametrisierbar, der für e<strong>in</strong>en<br />
Character verwendet werden soll. Um e<strong>in</strong> Maximum an Performance zu erreichen<br />
und um I/O Operationen mit Str<strong>in</strong>gs e<strong>in</strong>fach und effizient gestalten<br />
zu können, setzt basic_str<strong>in</strong>g noch voraus, dass der Typ, der für e<strong>in</strong>en
520 16. Die <strong>C++</strong> Standard Library<br />
Character verwendet wird, ke<strong>in</strong>e benutzerdef<strong>in</strong>ierten Copy-Operationen besitzt,<br />
sondern dass e<strong>in</strong>e e<strong>in</strong>fache Speicherkopie ausreichend ist. Es ist leicht<br />
nachzuvollziehen, dass es e<strong>in</strong>en riesigen Unterschied macht, ob man e<strong>in</strong>en<br />
Str<strong>in</strong>g Zeichen für Zeichen über Copy-Constructor Aufrufe kopiert oder ob<br />
man e<strong>in</strong>fach e<strong>in</strong>en gesamten Memory-Block ohne besondere Behandlung kopiert.<br />
Man kann nun diskutieren, ob dies dem Requirement widerspricht, dass<br />
STL Komponenten Policy-frei se<strong>in</strong> müssen, jedoch sollte man sich vor Augen<br />
halten, dass ohne diese E<strong>in</strong>schränkung das basic_str<strong>in</strong>g Template aufgrund<br />
der ungleich schlechteren Performance im Vergleich zur e<strong>in</strong>geschränkten<br />
Lösung schlicht und ergreifend im Alltag ke<strong>in</strong>e Verwendung f<strong>in</strong>den würde.<br />
Vor allem muss man sich auch vor Augen führen, dass e<strong>in</strong> Character <strong>in</strong> e<strong>in</strong>em<br />
Str<strong>in</strong>g mit praktisch 100%iger Wahrsche<strong>in</strong>lichkeit e<strong>in</strong>er der primitiven<br />
Ganzzahlendatentypen se<strong>in</strong> wird und ke<strong>in</strong>e spezielle Klasse.<br />
Um die Eigenschaften von bestimmten Character Datentypen für e<strong>in</strong>en<br />
Str<strong>in</strong>g zugänglich und handhabbar zu machen, gibt es <strong>in</strong> der STL das<br />
char_traits Template und die entsprechenden Spezialisierungen für die<br />
normalerweise verwendeten Ganzzahldatentypen. Wenn man nicht selbst<br />
e<strong>in</strong>en ganz besonderen Str<strong>in</strong>g mit e<strong>in</strong>em ganz besonderen Character Datentyp<br />
braucht, muss man sich um die char_traits ke<strong>in</strong>e besonderen Gedanken<br />
machen.<br />
Neben dem Template wurden auch gleich zwei konkrete Ausprägungen<br />
über typedef vorweggenommen:<br />
1. str<strong>in</strong>g für e<strong>in</strong>en Str<strong>in</strong>g mit char als Character-Datentyp.<br />
2. wstr<strong>in</strong>g für e<strong>in</strong>en “wide” Str<strong>in</strong>g mit wchar_t als Character-Datentyp.<br />
Leider ist die Anzahl der Bits für wchar_t nicht festgelegt und beträgt,<br />
je nach Plattform, entweder 16 oder 32. Mir ist ke<strong>in</strong>e Plattform (und<br />
auch ke<strong>in</strong> Zeichensatz) mit 64 Bit Characters bekannt, aber wer weiß... :-<br />
). Deshalb kann ich zur Verwendung von wstr<strong>in</strong>g nur den Tipp geben,<br />
davon auszugehen, dass damit Str<strong>in</strong>gs mit Unicode Characters auf den<br />
verschiedenen Plattformen zur Verfügung stehen und dass die Compilerbauer<br />
sich um die Interna (hoffentlich :-)) schon gekümmert haben.<br />
Um e<strong>in</strong> wenig Gefühl zu bekommen, wie man mit e<strong>in</strong>em str<strong>in</strong>g umgeht,<br />
werfen e<strong>in</strong>en Blick auf e<strong>in</strong> kurzes Beispiel (simple_str<strong>in</strong>g_demo.cpp):<br />
1 // simple str<strong>in</strong>g demo . cpp − j u s t a simple demo , how to<br />
2 // use a s t r i n g<br />
3<br />
4 #<strong>in</strong>clude < s t r i n g><br />
5 #<strong>in</strong>clude <br />
6<br />
7 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
8<br />
9 us<strong>in</strong>g std : : s t r i n g ;<br />
10 us<strong>in</strong>g std : : wstr<strong>in</strong>g ;<br />
11<br />
12 us<strong>in</strong>g std : : cout ;<br />
13 us<strong>in</strong>g std : : endl ;<br />
14
15 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
16 {<br />
17 s t r i n g s t r i n g 1 ;<br />
18 s t r i n g s t r i n g 2 ( ” j u s t i n i t i a l i z i n g ” ) ;<br />
19 s t r i n g s t r i n g 3 ( ” another s t r i n g ” ) ;<br />
20<br />
21 s t r i n g 1 = s t r i n g 2 + ” ” + s t r i n g 3 ;<br />
22 cout
522 16. Die <strong>C++</strong> Standard Library<br />
tation umwandeln muss, um sie am Bildschirm ausgeben zu können. In e<strong>in</strong>em<br />
Programm hat man es aber e<strong>in</strong>erseits mit vielen Objekten als Instanzen<br />
vieler verschiedener Klassen zu tun, andererseits mit vielen verschiedenen<br />
Input- und Output-Devices. Den Begriff Device möchte ich hier nicht als<br />
Hardware-E<strong>in</strong>heit, wie z.B. Bildschirm oder Festplatte, verstanden wissen,<br />
sondern vielmehr als e<strong>in</strong>e beliebige Datensenke, auf die man Daten schreiben<br />
kann bzw. auch als e<strong>in</strong>e beliebige Datenquelle, von der man Daten lesen<br />
kann.<br />
Auch hier wurde die traditionelle Sicht der D<strong>in</strong>ge von der Entwicklung<br />
überholt, denn wer behauptet, dass man es immer mit byteorientierten Operationen<br />
zu tun haben muss? Je nach Datenquelle bzw. -senke kann e<strong>in</strong><br />
Datenwort beliebig lang se<strong>in</strong>.<br />
Setzen wir also die E<strong>in</strong>zelteile e<strong>in</strong>mal zu e<strong>in</strong>er modernen Sicht der D<strong>in</strong>ge<br />
zusammen: Es gibt Devices, die auf e<strong>in</strong>e bestimmte Art bedient werden wollen<br />
und die mit verschiedenartigen Datenströmen umgehen können. Es gibt<br />
<strong>in</strong> e<strong>in</strong>em Programm Objekte, die mit diesen Devices zu tun bekommen, weil<br />
sie entweder auf diese geschrieben oder von diesen gelesen werden wollen.<br />
Die sogenannten Streams s<strong>in</strong>d nun die Teile e<strong>in</strong>es Programms, die e<strong>in</strong>e Verb<strong>in</strong>dung<br />
zwischen beiden Welten herstellen, sodass Entwicklern das Leben so<br />
leicht wie möglich gemacht wird: Sie führen die Transformation zwischen der<br />
Welt der Devices und der Welt der Objekte durch. Dadurch s<strong>in</strong>d Entwickler<br />
z.B. nicht mehr gezwungen, bei jeder Schreiboperation per Hand e<strong>in</strong>e Byterepräsentation<br />
e<strong>in</strong>es Objekts zu generieren und diese dann als Datenblock <strong>in</strong><br />
entsprechendem Format an das Device zu senden. Selbiges gilt für das Lesen<br />
von Objekten: Wenn e<strong>in</strong> Programm die E<strong>in</strong>gabe e<strong>in</strong>er Zahl erwartet, dann<br />
wollen die Entwickler beim besten Willen ke<strong>in</strong>en irgendwie gearteten Str<strong>in</strong>g<br />
<strong>in</strong> die Hand bekommen und diesen von Hand umwandeln. Es sollte ja wohl<br />
genügen, für spezielle Klassen e<strong>in</strong>mal e<strong>in</strong>e Umwandlung zu implementieren<br />
und diese wird dann automatisch verwendet.<br />
Außerdem, und das ist e<strong>in</strong>er der ganz wichtigen Punkte bei Streams, will<br />
man e<strong>in</strong>fach e<strong>in</strong>en virtuellen Datenstrom benutzen, ohne z.B. Rücksicht auf<br />
Blockgrößen und andere Details zu nehmen.<br />
Wir hatten es bisher bereits öfters mit drei speziellen Streams zu tun:<br />
c<strong>in</strong>, cout und cerr, die Standard-Input, Standard-Output und Standard-<br />
Error e<strong>in</strong>es Programms bezeichnen. Diese speziellen Streams wurden immer<br />
<strong>in</strong> Zusammenhang mit den Operatoren >> für den Input und
16.6 Streams 523<br />
diese Objekte werden auch genau <strong>in</strong> dieser Reihenfolge zum Device gesandt.<br />
Das haben wir bisher auch immer mit cout, als e<strong>in</strong>en Vertreter<br />
der Output Streams gemacht.<br />
2. E<strong>in</strong> Input Stream ist e<strong>in</strong> Mechanismus, der die genaue Umkehrung zum<br />
Output Stream darstellt. Er holt sich Low-Level Daten vom Device und<br />
füllt entsprechende High-Level Objekte mit diesen.<br />
Ich möchte hier nicht allzu genau auf die Interna der Implementation der<br />
verschiedenen Streams e<strong>in</strong>gehen. Pr<strong>in</strong>zipiell genügt es zu wissen, dass es<br />
e<strong>in</strong> Template basic_ostream gibt, über das die pr<strong>in</strong>zipiellen Output Operationen<br />
def<strong>in</strong>iert s<strong>in</strong>d. Weiters werden zwei spezielle Standard-Ausprägungen<br />
def<strong>in</strong>iert, nämlich ostream und wostream. Diese stellen konkrete Ausprägungen<br />
für die Ausgabe von char bzw. wchar_t Datenströmen dar. Ganz gleich<br />
verhält es sich bei den Input Streams: Auch hier gibt es e<strong>in</strong> basic_istream<br />
Template und die zwei Ausprägungen istream und wistream.<br />
Der Output von primitiven Datentypen mittels des <br />
5 #<strong>in</strong>clude <br />
6<br />
7 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
8<br />
9 us<strong>in</strong>g std : : ostream ;<br />
10 us<strong>in</strong>g std : : s t r i n g ;<br />
11 us<strong>in</strong>g std : : cout ;<br />
12 us<strong>in</strong>g std : : endl ;<br />
13<br />
14 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
15 /∗<br />
16 ∗ FullName<br />
17 ∗<br />
18 ∗ Just a dummy demo c l a s s<br />
19 ∗<br />
20 ∗/<br />
21<br />
22 class FullName<br />
23 {<br />
24 friend<br />
25 ostream &operator
524 16. Die <strong>C++</strong> Standard Library<br />
37<br />
38 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
39 /∗<br />
40 ∗/<br />
41 ostream &operator
8<br />
9 us<strong>in</strong>g std : : ostream ;<br />
10 us<strong>in</strong>g std : : s t r i n g ;<br />
11 us<strong>in</strong>g std : : cout ;<br />
12 us<strong>in</strong>g std : : endl ;<br />
13<br />
16.6 Streams 525<br />
14 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
15 /∗<br />
16 ∗ FullName<br />
17 ∗<br />
18 ∗ Just a dummy demo c l a s s<br />
19 ∗<br />
20 ∗/<br />
21<br />
22 class FullName<br />
23 {<br />
24 friend<br />
25 ostream &operator
526 16. Die <strong>C++</strong> Standard Library<br />
74 /∗<br />
75 ∗/<br />
76 ostream &operator > (istream &stream,const Whatever &obj)<br />
Naiv betrachtet ist das alles also ke<strong>in</strong>e große Hexerei, weniger naiv betrachtet<br />
allerd<strong>in</strong>gs ist die Sache viel haariger: Woher will man denn wissen,<br />
dass der Input, der von irgendwo kommt, auch so gestaltet ist, wie man ihn<br />
erwartet? Nehmen wir e<strong>in</strong>fach nur unsere FullName Klasse her und nehmen<br />
wir weiters an, dass wir e<strong>in</strong>en solchen Namen von c<strong>in</strong> lesen wollen. Wer<br />
sagt uns, dass e<strong>in</strong> Benutzer wirklich e<strong>in</strong>en Vornamen und e<strong>in</strong>en Nachnamen<br />
e<strong>in</strong>tippt? Oder nehmen wir an, dass wir von e<strong>in</strong>em File lesen (ja, auch dafür<br />
gibt es entsprechende Streams). Wer sagt uns, dass nicht z.B. das Ende des
16.6 Streams 527<br />
Files bereits nach dem Vornamen erreicht ist? In das Lesen und Analysieren<br />
von Input-Daten kann man unglaublich viel Intelligenz und Kreativität stecken,<br />
bis man wirklich e<strong>in</strong>igermaßen sauber und sicher mit allen möglichen<br />
Eventualitäten umgehen kann. Alle<strong>in</strong> das wäre e<strong>in</strong> größeres Kapitel wert,<br />
jedoch würde dieses den Rahmen des Buchs sprengen. Deshalb belasse ich es<br />
hier bei e<strong>in</strong>er kurzen Erwähnung der Methoden, die man zur Verfügung hat,<br />
um e<strong>in</strong>e Implementation sauber zu gestalten.<br />
Wenden wir uns zuerst e<strong>in</strong>mal den allgeme<strong>in</strong>en Methoden zu, die es erlauben,<br />
den Status e<strong>in</strong>es Streams abzufragen:<br />
bool good(): Liefert Auskunft darüber, ob die bisherigen Operationen so<br />
weit gut gelaufen s<strong>in</strong>d, dass auch e<strong>in</strong>e weitere Lese- oder Schreiboperation<br />
funktionieren könnte. Sollte man als return-Value false bekommen,<br />
dann funktioniert sicher ke<strong>in</strong>e weitere Operation. Sollte man true<br />
bekommen, dann wird allerd<strong>in</strong>gs ke<strong>in</strong>e Garantie übernommen, dass die<br />
darauf folgende Operation auch erfolgreich ist, denn e<strong>in</strong> Problem lässt<br />
sich ja erst erkennen, wenn es bereits aufgetreten ist.<br />
bool eof(): Liefert bei e<strong>in</strong>em istream true, wenn das Ende des Streams<br />
erreicht wurde und daher ke<strong>in</strong>e weiteren Daten mehr zum Lesen zur<br />
Verfügung stehen.<br />
bool fail(): Liefert true, wenn jegliche folgende Operation sicher fehlschlagen<br />
würde.<br />
bool bad(): Liefert true, wenn der Stream korrupt ist. Ist quasi die Inverse<br />
zu bool good().<br />
iostate rdstate(): Liefert als return-Value die I/O Status Flags.<br />
void clear(iostate flags = goodbit): Löscht bestimmte (oder ohne<br />
expliziten Parameter alle) I/O Status Flags.<br />
void setstate(iostate flags): Setzt bestimmte I/O Status Flags.<br />
bool operator !() const: Kurzform für !my_stream.fail().<br />
Um den Input etwas gezielter als nur mit dem Operator
528 16. Die <strong>C++</strong> Standard Library<br />
basic_istream &get(CharType *buffer, streamsize max,<br />
CharType delim): Analog zur vorhergehenden Methode, nur wird der<br />
Delimiter über den Parameter delim bestimmt.<br />
getl<strong>in</strong>e: Mit denselben Parametersätzen, die auch die vorigen beiden get<br />
Methoden akzeptieren, gibt es analoge getl<strong>in</strong>e Methoden, die genau<br />
dasselbe tun. Der Unterschied zwischen den beiden Methoden ist, dass<br />
get den Newl<strong>in</strong>e-Delimiter im Stream stehen lässt, während getl<strong>in</strong>e ihn<br />
für sich beansprucht und entfernt.<br />
basic_istream &read(CharType *buffer, streamsize num): Liest<br />
höchstens num Characters. Nimmt ke<strong>in</strong>en Delimiter, aber wenn das Ende<br />
des Streams erreicht wird, dann kann es se<strong>in</strong>, dass weniger als num<br />
Characters gelesen wurden.<br />
basic_istream &ignore(streamsize num = 1,<br />
<strong>in</strong>t_type delim = char_traits::eof): Überspr<strong>in</strong>gt num<br />
Characters im Stream bzw. so viele Zeichen, bis es auf den Delimiter<br />
stößt, der durch delim vorgegeben ist. Per Default ist der Delimiter das<br />
Ende des Streams.<br />
Man sieht, durch die Low-Level Methoden e<strong>in</strong>es Input Streams steht die Welt<br />
offen für saubere Analysen und robustes Lesen. Allerd<strong>in</strong>gs kann man auch<br />
leider nicht verleugnen, dass das Erstellen e<strong>in</strong>er sauberen Lese-Operation,<br />
die auf alle Eventualitäten reagieren kann, wirklich sehr mühsam ist. Das ist<br />
leider der Preis, den man für robuste Programme zahlen muss.<br />
Wie bereits erwähnt, kann man mit Streams nicht nur Keyboard Input<br />
lesen und auf den Bildschirm ausgeben. Ebenso gibt es entsprechende<br />
vorgefertigte File Streams und auch Str<strong>in</strong>g Streams, die es erlauben,<br />
e<strong>in</strong>en Str<strong>in</strong>g quasi als Device für e<strong>in</strong>en Stream (egal ob Input oder Output)<br />
zu verwenden. Dadurch ist es leicht, z.B. formatierten Output e<strong>in</strong>es<br />
Objekts <strong>in</strong> e<strong>in</strong>en Str<strong>in</strong>g zu schreiben, ohne dies explizit per Hand machen zu<br />
müssen. Wenden wir uns zuerst <strong>in</strong> e<strong>in</strong>em kurzen Beispiel den File Streams<br />
zu (simple_filestream_demo.cpp):<br />
1 // simple filestream demo . cpp − j u s t a simple<br />
2 // demo , how to work with f i l e streams<br />
3<br />
4 #<strong>in</strong>clude < s t r i n g><br />
5 #<strong>in</strong>clude <br />
6 #<strong>in</strong>clude <br />
7<br />
8 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
9<br />
10 us<strong>in</strong>g std : : i f s t r e a m ;<br />
11 us<strong>in</strong>g std : : ofstream ;<br />
12 us<strong>in</strong>g std : : s t r i n g ;<br />
13 us<strong>in</strong>g std : : cout ;<br />
14 us<strong>in</strong>g std : : endl ;<br />
15<br />
16<br />
17 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
18 /∗<br />
19 ∗/
20 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
21 {<br />
22 ofstream j u s t a f i l e ( ” a f i l e . txt ” ) ;<br />
23<br />
16.6 Streams 529<br />
24 j u s t a f i l e
530 16. Die <strong>C++</strong> Standard Library<br />
1 // simple str<strong>in</strong>gstream demo . cpp − j u s t a simple<br />
2 // demo , how to work with s t r i n g streams<br />
3<br />
4 #<strong>in</strong>clude < s t r i n g><br />
5 #<strong>in</strong>clude <br />
6 #<strong>in</strong>clude <br />
7<br />
8 #<strong>in</strong>clude ” u s e r t y p e s . h”<br />
9<br />
10 us<strong>in</strong>g std : : i s t r i n g s t r e a m ;<br />
11 us<strong>in</strong>g std : : ostr<strong>in</strong>gstream ;<br />
12 us<strong>in</strong>g std : : s t r i n g ;<br />
13 us<strong>in</strong>g std : : cout ;<br />
14 us<strong>in</strong>g std : : endl ;<br />
15<br />
16 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
17 /∗<br />
18 ∗/<br />
19 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
20 {<br />
21 s t r i n g a s t r i n g ;<br />
22 ostr<strong>in</strong>gstream t h e s t r i n g s t r e a m ;<br />
23 u<strong>in</strong>t32 a number = 17;<br />
24 double a double = 1 3 . 2 8 ;<br />
25<br />
26 t h e s t r i n g s t r e a m a double ;<br />
38<br />
39 cout
16.7 Numerik 531<br />
den muss, werden diese Art des Extrahierens sehr zu schätzen wissen. Der<br />
folgende Output beweist, dass alles wirklich so e<strong>in</strong>fach funktioniert, wie hier<br />
beschrieben :-).<br />
I ’ ve got a pair 1 7 , 1 3 . 2 8<br />
extracted values :<br />
a s t r i n g = Pair :<br />
a number = 5<br />
a double = 12<br />
Bisher haben wir uns damit zufrieden gegeben, dass wir z.B. bei Basisdatentypen<br />
ke<strong>in</strong>en E<strong>in</strong>fluss auf die Formatierung des Outputs genommen haben.<br />
Manchmal ist es allerd<strong>in</strong>gs notwendig, hierbei e<strong>in</strong>zugreifen. Man möchte<br />
z.B. e<strong>in</strong>mal e<strong>in</strong>e Ganzzahl als Hex-Darstellung ausgeben oder die Anzahl der<br />
Kommastellen bei e<strong>in</strong>er Gleitkommazahl bestimmen, etc. Das notwendige<br />
Werkzeug hierzu f<strong>in</strong>det sich <strong>in</strong> basic_ios und dessen Basis ios_base. E<strong>in</strong>e<br />
genaue Besprechung dieser Features würde jedoch wieder e<strong>in</strong>mal entschieden<br />
zu weit führen, deswegen belasse ich es hier bei dem H<strong>in</strong>weis, <strong>in</strong> welchen<br />
Templates man nachsehen und se<strong>in</strong> Glück versuchen soll.<br />
16.7 Numerik<br />
Die meisten Programme brauchen außer e<strong>in</strong>facher Arithmetik nicht wirklich<br />
besondere Verfahren. Wenn man jedoch zu technischen Simulationen, Mustererkennungsverfahren<br />
oder Ähnlichem kommt, dann ändert sich die Situation<br />
drastisch. In diesen Bereichen wimmelt es nur so von mathematischen Spezialitäten,<br />
vor allem von Vektor- und Matrizenrechnungen. Ich nehme nun<br />
nicht an, dass sich e<strong>in</strong> Großteil der Leser dieses Buchs ausgerechnet <strong>in</strong> solchen<br />
Bereichen bewegt :-).<br />
Aus diesem Grund möchte ich mich hier auf die Teile beschränken, die<br />
für alle Leser e<strong>in</strong>igermaßen <strong>in</strong>teressant s<strong>in</strong>d. Der allerwichtigste Teil <strong>in</strong> diesem<br />
Bereich handelt von den Limits, die Zahlen durch die masch<strong>in</strong>en<strong>in</strong>terne<br />
Repräsentation auferlegt s<strong>in</strong>d. Zu diesem Zweck gibt es das Template<br />
numeric_limits (zu f<strong>in</strong>den im Header ), das darüber sehr detaillierte<br />
Auskunft gibt. Die folgenden Spezialisierungen dieses Templates stehen<br />
zur Verfügung:<br />
numeric_limits<br />
numeric_limits<br />
numeric_limits<br />
numeric_limits<br />
numeric_limits<br />
numeric_limits<br />
numeric_limits<br />
numeric_limits<br />
numeric_limits<br />
numeric_limits
532 16. Die <strong>C++</strong> Standard Library<br />
numeric_limits<br />
numeric_limits<br />
numeric_limits<br />
numeric_limits<br />
Was man nun über diese Zahlen-Datentypen erfahren kann, eröffnet<br />
uns e<strong>in</strong> Blick auf die Members, die numeric_limits zur Verfügung stellt<br />
(DataType steht wieder für den konkreten Typ):<br />
static DataType denorm_m<strong>in</strong>(): Liefert den m<strong>in</strong>imalen denormalisierten<br />
Wert e<strong>in</strong>er Gleitkommazahl, falls denormalisierte Werte von dieser unterstützt<br />
werden. Ansonsten wird der m<strong>in</strong>imale normalisierte Wert geliefert.<br />
Hat für Ganzzahlen ke<strong>in</strong>e Bedeutung.<br />
static const <strong>in</strong>t digits: Enthält die Bitbreite der Zahl. Für vorzeichenbehaftete<br />
Ganzzahlenwerte ist diese Zahl die Anzahl der Bits m<strong>in</strong>us<br />
1, denn das Vorzeichenbit wird hier nicht gezählt. Für unsigned Typen<br />
ist dies die Anzahl der Bits und für Gleitkommatypen ist dies die Zahl<br />
der Bits <strong>in</strong> der Mantisse. Der Wert ist nur aussagekräftig für Zahlen, die<br />
is_bounded als true deklariert haben.<br />
static const <strong>in</strong>t digits10: Enthält die maximale Anzahl der Stellen der<br />
Zahl, wenn sie zur Basis 10 dargestellt wird. Auch dieser Wert ist nur<br />
aussagekräftig für Zahlen, die is_bounded als true deklariert haben.<br />
static DataType epsilon(): Liefert die erreichbare Genauigkeit als Unterschied<br />
zwischen der Zahl 1 und dem kle<strong>in</strong>sten darstellbaren Wert, der<br />
größer als 1 ist. Gilt nur für Gleitkommatypen.<br />
static const bool has_denorm: Enthält true, wenn denormalisierte Werte<br />
erlaubt s<strong>in</strong>d, sonst false. Als denormalisiert wird e<strong>in</strong> Datentyp bezeichnet,<br />
der e<strong>in</strong>e variable Anzahl von Bits für den Exponenten besitzt.<br />
Gilt nur für Gleitkommatypen.<br />
static const bool has_<strong>in</strong>f<strong>in</strong>ity: Enthält true, wenn der Typ e<strong>in</strong>e Repräsentation<br />
für (positives!) Unendlich besitzt. Gilt nur für Gleitkommatypen.<br />
static const bool has_quiet_NaN: Enthält true, wenn der Typ e<strong>in</strong>e Repräsentation<br />
für e<strong>in</strong> quiet not-a-number besitzt, wie sie z.B. bei e<strong>in</strong>er<br />
Division durch 0 als Ergebnis entsteht. Gilt nur für Gleitkommatypen.<br />
static const bool has_signal<strong>in</strong>g_NaN: Enthält true, wenn der Typ e<strong>in</strong>e<br />
Repräsentation für e<strong>in</strong> signal<strong>in</strong>g not-a-number besitzt, wie sie z.B. bei<br />
e<strong>in</strong>er Division durch 0 als Ergebnis entsteht. Gilt nur für Gleitkommatypen.<br />
static DataType <strong>in</strong>f<strong>in</strong>ity(): Liefert die Repräsentation für (positives)<br />
Unendlich. Die Abfrage ist natürlich nur s<strong>in</strong>nvoll, wenn beim Datentyp<br />
has_<strong>in</strong>f<strong>in</strong>ity == true. Gilt nur für Gleitkommatypen.<br />
static const bool is_bounded: Enthält true, wenn die Menge der Werte,<br />
die durch diesen Typ repräsentiert werden können, endlich ist. Dies<br />
ist für alle e<strong>in</strong>gebauten Datentypen aus C ++ der Fall.
16.7 Numerik 533<br />
static const bool is_exact: Enthält true, wenn der Datentyp e<strong>in</strong>e exakte<br />
Repräsentation e<strong>in</strong>es Wertes hält. Dies ist natürlich für alle Ganzzahldatentypen<br />
der Fall, ebenso für Fixkommazahlen (mit denen wir <strong>in</strong><br />
C ++ üblicherweise nichts zu tun haben) und für rationale Zahlen (mit denen<br />
wir im Normalfall ebenso wenig zu tun haben). Gleitkommazahlen<br />
s<strong>in</strong>d nicht exakt.<br />
static const bool is_iec559: Enthält true, wenn sich der Datentyp an<br />
den IEC 559 Standard hält. Gilt nur für Gleitkommazahlen.<br />
static const bool is_<strong>in</strong>teger: Enthält true, wenn es sich um e<strong>in</strong>en<br />
Ganzzahldatentyp handelt.<br />
static const bool is_modulo: Enthält true, wenn der Datentyp modulo<br />
ist. E<strong>in</strong> Datentyp ist dann modulo, wenn es möglich ist, zwei positive<br />
Zahlen zu addieren und als Ergebnis e<strong>in</strong>es Überlaufs e<strong>in</strong>e dritte Zahl zu<br />
erhalten, die kle<strong>in</strong>er ist.<br />
static const bool is_signed: Enthält true, wenn der Typ vorzeichenbehaftet<br />
ist.<br />
static const bool is_specialized: Gibt Information darüber, ob das<br />
numeric_limits Template für den aktuellen Datentyp <strong>in</strong> Form e<strong>in</strong>er<br />
besonderen Template-Spezialisierung vorliegt.<br />
static DataType max(): Liefert den maximalen endlichen Zahlenwert.<br />
Nur gültig für Datentypen bei denen is_bounded == true.<br />
static const <strong>in</strong>t max_exponent: Enthält den maximalen positiven Exponenten,<br />
so dass 2 max exponent noch im gültigen Bereich liegt. Gilt nur<br />
für Gleitkommazahlen.<br />
static const <strong>in</strong>t max_exponent10: Enthält den maximalen positiven<br />
Exponenten, so dass 10 max exponent10 noch im gültigen Bereich liegt. Gilt<br />
nur für Gleitkommazahlen.<br />
static DataType m<strong>in</strong>(): Liefert den m<strong>in</strong>imalen endlichen Wert. Bei Gleitkommazahlen,<br />
die Denormalisierung unterstützen, muss m<strong>in</strong> den m<strong>in</strong>imalen<br />
normalisierten Wert liefern. Gilt nur für Datentypen, bei denen<br />
is_bounded == true.<br />
static const <strong>in</strong>t m<strong>in</strong>_exponent: Enthält den m<strong>in</strong>imalen negativen Exponenten,<br />
so dass 2 m<strong>in</strong> exponent noch im gültigen Bereich liegt. Gilt nur<br />
für Gleitkommazahlen.<br />
static const <strong>in</strong>t m<strong>in</strong>_exponent10: Enthält den m<strong>in</strong>imalen negativen<br />
Exponenten, so dass 10 m<strong>in</strong> exponent noch im gültigen Bereich liegt. Gilt<br />
nur für Gleitkommazahlen.<br />
static DataType quiet_NaN(): Liefert die Repräsentation für e<strong>in</strong> quiet<br />
not-a-number. Nur gültig, wenn has_quiet_nan == true.<br />
static const <strong>in</strong>t radix: Enthält die Basis (oder auch den Radix) der<br />
Repräsentation des Exponenten. Im Normalfall ist diese Basis 2, deshalb<br />
wurde auch bei max_exponent und bei m<strong>in</strong>_exponent 2 <strong>in</strong> die Formel<br />
aufgenommen. Sollte die Basis ungleich 2 se<strong>in</strong> (sehr selten), dann ist die<br />
Formel entsprechend abzuändern zu radix ... exponent . Im Pr<strong>in</strong>zip könnte
534 16. Die <strong>C++</strong> Standard Library<br />
es auch vorkommen, dass die Basis für Ganzzahlentypen ungleich 2 ist, <strong>in</strong><br />
der Praxis hat diese Betrachtung allerd<strong>in</strong>gs im Normalfall ke<strong>in</strong>e Relevanz.<br />
static DataType round_error(): Liefert das Maß für den maximal möglichen<br />
Rundungsfehler. Gilt nur für Gleitkommatypen.<br />
static const float_round_style round_style: Enthält die Rundungsmethode<br />
für den Datentyp. Folgende Werte können angenommen werden:<br />
round_toward_zero, round_<strong>in</strong>determ<strong>in</strong>ate, round_to_nearest,<br />
round_toward_<strong>in</strong>f<strong>in</strong>ity oder round_toward_neg_<strong>in</strong>f<strong>in</strong>ity. Ist nur<br />
s<strong>in</strong>nvoll für Gleitkommatypen, denn Ganzzahlentypen unterstützen immer<br />
round_toward_zero, also Kommastellen “abschneiden”.<br />
static DataType signal<strong>in</strong>g_NaN(): Liefert die Repräsentation für e<strong>in</strong> signal<strong>in</strong>g<br />
not-a-number. Nur gültig, wenn has_signal<strong>in</strong>g_nan == true.<br />
static const bool t<strong>in</strong>yness_before: Enthält true, wenn vor dem Runden<br />
e<strong>in</strong> t<strong>in</strong>yness-Check stattf<strong>in</strong>det (soll heißen, e<strong>in</strong>e Zahl, die w<strong>in</strong>zig kle<strong>in</strong><br />
ist, kann nicht irrtümlich durch Rundung zu e<strong>in</strong>er “echten” 0 mutieren).<br />
Gilt nur für Gleitkommazahlen.<br />
static const bool traps: Enthält true, wenn Trapp<strong>in</strong>g für diesen Typ<br />
implementiert ist.<br />
Man sieht, es lässt sich für jeden Zahlentyp praktisch alles herausf<strong>in</strong>den, was<br />
man für Berechnungen wissen muss. Jetzt stellt sich nur noch die Frage,<br />
welche Berechnungen man überhaupt außer der e<strong>in</strong>fachen Arithmetik von<br />
Seiten der Standard Library zur Verfügung gestellt bekommt. Hier gibt es<br />
e<strong>in</strong>mal die “normalen” mathematischen Funktionen, die <strong>in</strong> zu f<strong>in</strong>den<br />
s<strong>in</strong>d. Dazu zählen s<strong>in</strong>, cos, log und viele andere. Ich überlasse hier den<br />
Lesern das Stöbern <strong>in</strong> den Headers bzw. <strong>in</strong> den Manuals. Aus historischen<br />
Gründen s<strong>in</strong>d auch noch e<strong>in</strong>ige mathematische Funktionen <strong>in</strong> zu<br />
f<strong>in</strong>den, wie z.B. abs. Es lohnt sich also, auch <strong>in</strong> diesem Header zu suchen :-).<br />
Mathematische Vektoren, Slices und entsprechende Operationen, die ich<br />
hier nicht genauer behandeln möchte, s<strong>in</strong>d im Header angesiedelt.<br />
Komplexe Zahlen und zugehörige Operationen f<strong>in</strong>den sich im Header<br />
. Wer auf der Suche nach e<strong>in</strong>em Zufallszahlengenerator ist, wird<br />
aus historischen Gründen <strong>in</strong> den Headers fündig werden.<br />
16.8 Algorithmen und Funktionsobjekte<br />
Zu guter Letzt möchte ich noch den kurzen H<strong>in</strong>weis liefern, dass die STL<br />
auch e<strong>in</strong>e ganze Reihe von Algorithmen zum Suchen, Sortieren, Permutieren,<br />
Ersetzen, etc. von Elementen <strong>in</strong> Sequences und anderen Datenstrukturen<br />
enthält. Auch existieren Basis-Templates für sogenannte Funktionsobjekte,<br />
die <strong>in</strong> Algorithmen und <strong>in</strong> Komb<strong>in</strong>ation mit Algorithmen Verwendung f<strong>in</strong>den<br />
und natürlich auch um eigene Implementationen ergänzt werden können.<br />
E<strong>in</strong>e genaue Darstellung derselben überlasse ich allerd<strong>in</strong>gs hier der Spezialliteratur,<br />
denn e<strong>in</strong>e naive Anwendung von vorgefertigten Algorithmen kann
16.8 Algorithmen und Funktionsobjekte 535<br />
sehr negative Auswirkungen auf e<strong>in</strong> Programm haben (vor allem auf se<strong>in</strong>e<br />
Laufzeit :-)). E<strong>in</strong>e detaillierte Beschreibung der Algorithmen und s<strong>in</strong>nvoller<br />
Anwendungen derselben würde wiederum am Ziel dieses Buchs vorbei gehen.
A. Cod<strong>in</strong>g-Standard<br />
Der hier angeführte Cod<strong>in</strong>g-Standard ist e<strong>in</strong> Vorschlag, wie e<strong>in</strong> solcher aussehen<br />
könnte. Zu erwähnen wäre, dass die hier abgedruckte Version e<strong>in</strong>e<br />
s<strong>in</strong>nvolle M<strong>in</strong>imalversion für kle<strong>in</strong>e Projekte darstellt. Für große Projekte<br />
s<strong>in</strong>d entsprechend weitere Regeln zu ergänzen.<br />
A.1 Generelle Regeln<br />
Die folgenden Pr<strong>in</strong>zipien s<strong>in</strong>d essentiell für die Lesbarkeit, Wartbarkeit und<br />
Erweiterbarkeit e<strong>in</strong>es Programms:<br />
E<strong>in</strong>fachheit: Das Pr<strong>in</strong>zip der E<strong>in</strong>fachheit ist auch als das KISS-Pr<strong>in</strong>zip (Keep<br />
It Small and Simple) bekannt. Kurz gesagt sollen Methoden, Funktionen<br />
und natürlich auch Operatoren genau e<strong>in</strong>e, für ihren Abstraktionslevel<br />
adäquate, atomare Aufgabe erfüllen. Niemals sollen mehrere Aufgaben<br />
auf e<strong>in</strong>mal erledigt werden, genauso wenig, wie Aufgaben erledigt werden<br />
sollen, die <strong>in</strong> e<strong>in</strong>en anderen Abstraktionslevel gehören (=Durchgriff nach<br />
unten oder oben). Parameterlisten sollen so kurz und übersichtlich wie<br />
möglich gehalten werden. Methoden, Operatoren und Funktionen sollen<br />
als Faustregel niemals mehr als 60 Zeilen lang se<strong>in</strong>. E<strong>in</strong>e durchschnittliche<br />
Länge von ca. 30 Zeilen ist zu bevorzugen. Seiteneffekte s<strong>in</strong>d absolut zu<br />
vermeiden!<br />
Das Pr<strong>in</strong>zip der E<strong>in</strong>fachheit bed<strong>in</strong>gt auch, dass Klassen ke<strong>in</strong>e fetten Interfaces<br />
haben dürfen.<br />
Intuitivität: Das Pr<strong>in</strong>zip der Intuitivität bedeutet, dass man den geschriebenen<br />
Source-Code “wie e<strong>in</strong> Buch” lesen und verstehen können muss,<br />
und zwar ohne Kommentare im Source-Code und ohne Erklärungen des<br />
Programmierers! Damit ist impliziert, dass Variablen-, Methoden- und<br />
Funktionsnamen sprechend (=selbsterklärend) und genau ihrer Funktionalität<br />
entsprechend benannt se<strong>in</strong> müssen. E<strong>in</strong>buchstabenvariablen,<br />
wie z.B. i, s<strong>in</strong>d nicht erlaubt. Unnötige Kommentare werden als störend<br />
erachtet und sollen dementsprechend weggelassen werden. E<strong>in</strong> typisches<br />
Beispiel für solche unnötigen Kommentare wäre:<br />
count++; // and here the counter is <strong>in</strong>cremented
538 A. Cod<strong>in</strong>g-Standard<br />
E<strong>in</strong>heitlichkeit: Verwandte Teile im Source-Code müssen denselben Pr<strong>in</strong>zipien<br />
folgen. Wenn z.B. e<strong>in</strong>e Funktion copy als ersten Parameter die<br />
Dest<strong>in</strong>ation und als zweiten Parameter den Source nimmt, dann müssen<br />
verwandte Funktionen, wie z.B. move, sich an dieselben Konventionen<br />
halten. Genauso gilt dies auch für Klassen und ihre Methoden. Nehmen<br />
wir z.B. e<strong>in</strong>e Klasse die e<strong>in</strong>en Knoten für z.B. e<strong>in</strong>e e<strong>in</strong>fach verkettete<br />
Liste repräsentiert. Wenn <strong>in</strong> dieser Klasse der nachfolgende Knoten über<br />
Aufruf von next() erreichbar ist, dann darf er nicht <strong>in</strong> e<strong>in</strong>em Knoten<br />
für e<strong>in</strong>e doppelt verkettete Liste auf e<strong>in</strong>mal über Aufruf von successor<br />
erreichbar se<strong>in</strong>.<br />
A.2 Cod<strong>in</strong>g-Rules<br />
Die hier angeführten Regeln helfen, den Source-Code so weit wie möglich zu<br />
vere<strong>in</strong>heitlichen und damit die Arbeit <strong>in</strong> e<strong>in</strong>em Team zu erleichtern:<br />
• Die Sprache für Source-Code ist Englisch. Dies gilt für alle Teile e<strong>in</strong>es<br />
Programms, von Variablennamen über Methoden- und Funktionsnamen<br />
bis h<strong>in</strong> zu Kommentaren im Source-Code.<br />
• Der Gebrauch von Block-Kommentaren (/* comment */) ist zu vermeiden.<br />
Stattdessen müssen Zeilenkommentare (// comment) e<strong>in</strong>gesetzt werden.<br />
Dies macht Source-Code robuster gegen Änderungen und erleichtert<br />
das Debugg<strong>in</strong>g.<br />
E<strong>in</strong>e Ausnahme zu dieser Regel bilden die Dokumentations-Headers, die<br />
zur Beschreibung von Variablen, Funktionen und Methoden dienen. Diese<br />
s<strong>in</strong>d <strong>in</strong> Block-Kommentaren zu schreiben. Der Grund ist, dass verschiedene<br />
Programme, die diese Dokumentation automatisch extrahieren können,<br />
mit solchen Block-Kommentaren besser umgehen können als mit Zeilenkommentaren.<br />
• Wenn es sich nicht vermeiden lässt, z.B. algorithmische Details <strong>in</strong> Form<br />
von Kommentaren <strong>in</strong> den Source-Code e<strong>in</strong>zubr<strong>in</strong>gen, dann ist e<strong>in</strong> Block<br />
mit e<strong>in</strong>er vollständigen Erklärung des Algorithmus vor der Implementation<br />
des Algorithmus selbst zu schreiben. Es darf die Kommentierung des<br />
Algorithmus nicht im laufenden Source-Code Zeile für Zeile erfolgen, denn<br />
sonst wird der Code durch die Kommentare unleserlich. Natürlich s<strong>in</strong>d wenige,<br />
kurze Kommentare <strong>in</strong> Form von Cross-References zur vollständigen<br />
Erklärung manchmal s<strong>in</strong>nvoll und deshalb erlaubt.<br />
• Sollten jemals Codezeilen <strong>in</strong> das Programm E<strong>in</strong>gang f<strong>in</strong>den, bei denen beim<br />
Lesen der Zeile nicht sofort klar ist, was sie tut, dann muss dies <strong>in</strong> e<strong>in</strong>em<br />
kurzen Kommentar dort festgehalten werden. Jedoch sollte man sich immer<br />
sehr gut überlegen, ob es nicht eigentlich e<strong>in</strong> besseres, leichter lesbares<br />
Konstrukt gibt, das ke<strong>in</strong>en Kommentar benötigt, um verstanden zu werden.<br />
• Globale Variablen s<strong>in</strong>d nach allen Möglichkeiten zu vermeiden!!!!
A.2 Cod<strong>in</strong>g-Rules 539<br />
• C-Style Casts s<strong>in</strong>d zu vermeiden. Stattdessen müssen die “echten” C ++<br />
Casts static_cast, dynamic_cast, etc. Anwendung f<strong>in</strong>den.<br />
• Wenn aus irgendwelchen Gründen böse Hacks im Source-Code temporär<br />
nicht vermeidbar s<strong>in</strong>d (z.B. Zeitdruck), so s<strong>in</strong>d diese unbed<strong>in</strong>gt <strong>in</strong> hackstart<br />
und hack-end Kommentare zu fassen, damit man sie e<strong>in</strong>fach wieder<br />
f<strong>in</strong>den und ausbessern kann. Die hack-... Kommentare haben die folgende<br />
Form:<br />
// FIXXME () -> <br />
[..... the code with the hack .....]<br />
// END FIXXME ()<br />
Hier gehört das Keyword FIXXME immer mit zum<strong>in</strong>dest zwei ’X’ geschrieben,<br />
denn damit kann man leicht nach ihm suchen. Je nachdem, wie<br />
schlimm der Hack ist, können auch mehrere ’X’ vorkommen. Als Faustregel<br />
für die Abstufung gilt, dass der SVH (=Schlimmste Vorstellbare Hack)<br />
mit 5 ’X’ geschrieben wird.<br />
• Klassendeklarationen müssen von ihren Def<strong>in</strong>itionen getrennt und <strong>in</strong> e<strong>in</strong>em<br />
separaten Header File gespeichert werden.<br />
• Alle Klassen müssen e<strong>in</strong>en virtual Destruktor besitzen.<br />
• Alle Klassen müssen sowohl den Copy-Constructor als auch den Zuweisungsoperator<br />
explizit implementieren. Wenn ke<strong>in</strong>e Implementation der<br />
beiden gewünscht ist, dann müssen sie als Dummies implementiert und<br />
auf private gesetzt werden.<br />
• Alle Headers müssen durch Def<strong>in</strong>ition und entsprechende Abfragen von<br />
Preprocessor Macros vor Mehrfach<strong>in</strong>klusion geschützt werden. Das entsprechende<br />
#def<strong>in</strong>e muss der folgenden Namenskonvention entsprechen:<br />
file_name_h___<br />
wobei file_name_h natürlich dem Namen des Headers entspricht und dieser<br />
von genau 3 Underl<strong>in</strong>es gefolgt se<strong>in</strong> muss. Die Verwendung von führenden<br />
Underl<strong>in</strong>es wird deshalb nicht empfohlen, da führende Underl<strong>in</strong>es den<br />
Compilerherstellern vorbehalten s<strong>in</strong>d.<br />
Zu dieser Regel gibt es noch e<strong>in</strong>e Ausnahme, die allerd<strong>in</strong>gs <strong>in</strong> der Praxis<br />
sehr selten zum Tragen kommt: Hat man mehrere Headers, die verschiedenen<br />
Versionen e<strong>in</strong> und derselben Klasse entsprechen und hat man diese<br />
z.B. als my_class_v1.h, my_class_v2.h, etc. benannt, so kann man auch<br />
e<strong>in</strong>en “geme<strong>in</strong>samen” Schutz vor mehrfacher Inklusion erreichen, <strong>in</strong>dem<br />
man <strong>in</strong> allen Headers e<strong>in</strong>fach die Versionsnummer weglässt. Es würde also<br />
<strong>in</strong> diesem Fall überall das Macro my_class_h___ verwendet werden.<br />
• Namen von Bezeichnern müssen den folgenden Konventionen genügen:<br />
Klassen:<br />
AlleWorteCapitalizedOhneUnderl<strong>in</strong>es
540 A. Cod<strong>in</strong>g-Standard<br />
Structures:<br />
GleichWieKlassen<br />
Unions:<br />
GleichWieKlassen<br />
Exceptions:<br />
KlassennameEndetMitException<br />
Methoden und Funktionen:<br />
erstesWortKle<strong>in</strong>RestCapitalizedOhneUnderl<strong>in</strong>es<br />
Konstanten:<br />
GROSS_MIT_UNDERLINES<br />
Member Variablen:<br />
kle<strong>in</strong>_mit_underl<strong>in</strong>es_und_underl<strong>in</strong>e_am_ende_<br />
Lokale Variablen:<br />
kle<strong>in</strong>_mit_underl<strong>in</strong>es<br />
Globale Variablen:<br />
gleich_wie_member_variablen_<br />
• Neben diesen generellen Konventionen gibt es auch e<strong>in</strong>e spezielle Konvention,<br />
die sich auf die Semantik von Methoden bezieht:<br />
Schreibende Zugriffsmethoden auf Member Variablen müssen setXXX und<br />
ihre lesenden Äquivalente getXXX benannt werden (z.B. setMaxUsers,<br />
getMaxUsers). E<strong>in</strong>e Ausnahme dabei bilden Zugriffe auf boolsche Variablen:<br />
Hier wird die lesende Methode semantisch s<strong>in</strong>nvoll mit isXXX,<br />
hasXXX, allowsXXX o.ä. benannt (z.B. setReadOnly, isReadOnly).<br />
• Die Struktur des Source-Codes muss den folgenden Pr<strong>in</strong>zipien genügen:<br />
– Jedes File muss e<strong>in</strong>en Header besitzen, <strong>in</strong> dem zum<strong>in</strong>dest der Filename,<br />
und e<strong>in</strong>e kurze Beschreibung des Inhalts zu f<strong>in</strong>den s<strong>in</strong>d. In der Praxis<br />
hat es sich e<strong>in</strong>gebürgert, dass weiters der Name des Autors, das Erstellungsdatum,<br />
das letzte Änderungsdatum, e<strong>in</strong>e Versionsnummer und e<strong>in</strong><br />
Copyright-Statement im Header stehen.<br />
– Geschwungene Klammern für Code-Blöcke müssen <strong>in</strong> e<strong>in</strong>er eigenen Zeile<br />
stehen. Die E<strong>in</strong>rückung der Klammern entspricht genau dem umschließenden<br />
Block. Der e<strong>in</strong>geschlossene Block selbst muss genau um<br />
2 Spaces e<strong>in</strong>gerückt se<strong>in</strong> (hier s<strong>in</strong>d <strong>in</strong> der Praxis Werte zwischen 2–4<br />
gängig). E<strong>in</strong>rückungen dürfen ausschließlich mit Spaces gemacht werden,<br />
Tabs s<strong>in</strong>d verboten, denn sonst kann es mit verschiedenen Editor-<br />
E<strong>in</strong>stellungen und beim Drucken Probleme geben.<br />
– Vor jeder Operator-, Methoden- oder Funktionsdef<strong>in</strong>ition muss e<strong>in</strong>e L<strong>in</strong>ie<br />
der folgenden Form stehen, damit e<strong>in</strong>e Funktion beim Lesen des Source-<br />
Codes visuell hervorsticht:<br />
//--------------------------------------------------<br />
Diese L<strong>in</strong>ie soll <strong>in</strong> der Regel mit 60–70 ’-’ geschrieben werden.<br />
– Zwischen dieser L<strong>in</strong>ie und dem Header der Funktion muss <strong>in</strong> kurzen<br />
Worten beschrieben werden, wozu diese Funktion dient. Bei größeren<br />
Projekten muss auch beschrieben se<strong>in</strong>, was bei den e<strong>in</strong>zelnen Parametern
A.3 Design Guidel<strong>in</strong>es 541<br />
erwartet wird, welche Randbed<strong>in</strong>gungen gelten, welche Return-Values zu<br />
erwarten s<strong>in</strong>d und wie diese zu <strong>in</strong>terpretieren s<strong>in</strong>d.<br />
A.3 Design Guidel<strong>in</strong>es<br />
Dieser Abschnitt enthält e<strong>in</strong>ige generelle Richtl<strong>in</strong>ien, die helfen sollen, sauberen<br />
und robusten Code zu schreiben.<br />
• Membervariablen dürfen niemals public zugreifbar se<strong>in</strong>.<br />
• Interne und externe Zugriffsmethoden dürfen niemals vermischt werden.<br />
Wenn Methoden zum Zugriff für die Außenwelt gedacht s<strong>in</strong>d und entsprechend<br />
public zugreifbar s<strong>in</strong>d, so dürfen diese klassen<strong>in</strong>tern nicht verwendet<br />
werden. Z.B., wenn man e<strong>in</strong> Paar von Methoden setVariable,<br />
getVariable als public zur Verfügung stellt, dann dürfen diese <strong>in</strong>tern<br />
nicht für den Zugriff verwendet werden. Stattdessen muss der direkte Zugriff<br />
auf die Membervariable erfolgen. Der Grund dafür ist e<strong>in</strong>fach zu erkennen:<br />
In public Methoden werden alle möglichen Schutzmechanismen<br />
e<strong>in</strong>gebaut, die e<strong>in</strong>e Fehlverwendung ausschließen sollen. Man will aber e<strong>in</strong>e<br />
Klasse nicht vor sich selbst schützen :-).<br />
Sollten tatsächlich auch <strong>in</strong>tern Methoden für gewisse Zugriffe benötigt werden,<br />
so s<strong>in</strong>d diese getrennt und entweder protected oder <strong>in</strong> seltenen Fällen<br />
sogar private zu implementieren.<br />
Durch diese Maßnahme erreicht man e<strong>in</strong>e saubere Trennung zwischen dem<br />
public Interface und der <strong>in</strong>ternen Implementation e<strong>in</strong>er Klasse.<br />
• Pr<strong>in</strong>zipiell ist protected e<strong>in</strong>er Deklaration von private vorzuziehen. Außer,<br />
wenn man e<strong>in</strong>en ganz besonders guten Grund hat, Teile e<strong>in</strong>er Klasse <strong>in</strong><br />
Ste<strong>in</strong> zu meißeln, ist dies deshalb s<strong>in</strong>nvoll, da man hierdurch Anpassungen<br />
durch Ableitung vornehmen kann, was bei private nicht mehr gegeben<br />
ist.<br />
• Seiteneffekte und unerwartetes (d.h. nicht aus dem Namen ersichtliches)<br />
Verhalten von Methoden, Operatoren und Funktionen s<strong>in</strong>d um jeden Preis<br />
zu vermeiden!!!!<br />
• Niemals dürfen sogenannte silent Catches implementiert werden, also Catches<br />
von Exceptions, die ganz e<strong>in</strong>fach sang- und klanglos ignoriert werden.<br />
• Exceptions dürfen niemals zu Standard Flow-Control Zwecken verwendet<br />
werden.<br />
• Abstrakte Methoden <strong>in</strong> Basisklassen s<strong>in</strong>d unbed<strong>in</strong>gt solchen mit s<strong>in</strong>nlosen<br />
dummy Implementationen vorzuziehen.
B. Vollständige Implementation des Memory<br />
Spiels<br />
In Kapitel 10 wurde das Design des Spiels besprochen und es wurden e<strong>in</strong><br />
paar kurze Auszüge aus der Implementation vorgestellt. In der Folge f<strong>in</strong>den<br />
<strong>in</strong>teressierte Leser den gesamten Source Code des Spiels. Dieser ist natürlich<br />
auch auf der beiliegenden CD-ROM zu f<strong>in</strong>den.<br />
Um Platz zu sparen habe ich mir bei den e<strong>in</strong>zelnen Methoden <strong>in</strong> der Implementation<br />
alle Beschreibungen erspart, denn diese s<strong>in</strong>d bereits vollständig<br />
<strong>in</strong> den Headers <strong>in</strong> Kapitel 10 enthalten. Dies ist auch vollkommen übliche<br />
Praxis, denn e<strong>in</strong>e Kopie der Beschreibung führt nur zu unnötiger Redundanz<br />
und zu erheblichem Wartungsaufwand. Nach e<strong>in</strong>igen Änderungen ist es vor<br />
allem mit ziemlicher Sicherheit zu erwarten, dass die Beschreibungen <strong>in</strong> den<br />
Headers nicht mehr konsistent zu den Beschreibungen <strong>in</strong> den .cpp Files s<strong>in</strong>d.<br />
B.1 Implementationen der e<strong>in</strong>zelnen Klassen<br />
Zu den Implementationen der Klassen, die <strong>in</strong> der Folge abgedruckt s<strong>in</strong>d,<br />
möchte ich ke<strong>in</strong>e weiteren Erklärungen abgeben. Der Code sollte verständlich<br />
genug geschrieben se<strong>in</strong>, dass er als selbstdokumentierend anzusehen ist.<br />
B.1.1 Das Hauptprogramm<br />
1 //memory ma<strong>in</strong> . cpp − ma<strong>in</strong> program that s t a r t s memory<br />
2<br />
3 #<strong>in</strong>clude <br />
4<br />
5 #<strong>in</strong>clude ” simple commandl<strong>in</strong>e handl<strong>in</strong>g . h”<br />
6 #<strong>in</strong>clude ”memory commandl<strong>in</strong>e arg handler . h”<br />
7 #<strong>in</strong>clude ”memory gameboard . h”<br />
8 #<strong>in</strong>clude ” simple output handl<strong>in</strong>g . h”<br />
9 #<strong>in</strong>clude ” s i m p l e d i s p l a y a b l e . h”<br />
10 #<strong>in</strong>clude ”memory game control . h”<br />
11 #<strong>in</strong>clude ” simple <strong>in</strong>put handl<strong>in</strong>g . h”<br />
12<br />
13 us<strong>in</strong>g std : : c e r r ;<br />
14 us<strong>in</strong>g std : : endl ;<br />
15<br />
16 <strong>in</strong>t ma<strong>in</strong> ( <strong>in</strong>t argc , char ∗ argv [ ] )<br />
17 {<br />
18 // i n s t a n t i a t e the commandl<strong>in</strong>e handler , d e c l a r e the
544 B. Vollständige Implementation des Memory Spiels<br />
19 // required arguments ( row / column ) and l e t i t parse<br />
20 // the commandl<strong>in</strong>e .<br />
21 Commandl<strong>in</strong>eHandl<strong>in</strong>g commandl<strong>in</strong>e handler ( 3 ) ;<br />
22 MemoryCommandl<strong>in</strong>eArgumentHandler arg handler ;<br />
23 commandl<strong>in</strong>e handler . declareArgument (<br />
24 1 , Commandl<strong>in</strong>eHandl<strong>in</strong>g : : UINT32 ARG,& arg handler ) ;<br />
25 commandl<strong>in</strong>e handler . declareArgument (<br />
26 2 , Commandl<strong>in</strong>eHandl<strong>in</strong>g : : UINT32 ARG,& arg handler ) ;<br />
27 commandl<strong>in</strong>e handler . handleCommandl<strong>in</strong>e ( argc , argv ) ;<br />
28<br />
29 // analyse the number of rows and columns<br />
30 u<strong>in</strong>t32 rows = arg handler . getRows ( ) ;<br />
31 u<strong>in</strong>t32 c o l s = arg handler . getCols ( ) ;<br />
32 i f ( ( rows < = 0 ) | | ( c o l s 9 ) | | ( c o l s > 9))<br />
34 {<br />
35 c e r r
B.1.2 Implementation von Vector<br />
B.1 Implementationen der e<strong>in</strong>zelnen Klassen 545<br />
1 // s i m p l e v e c t o r . cpp − implementation o f the vector<br />
2<br />
3 #<strong>in</strong>clude ” s i m p l e v e c t o r . h”<br />
4 #<strong>in</strong>clude ” c o n c r e t e o b j e c t d e l e t o r s . h”<br />
5<br />
6 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
7 Vector : : Vector ( u<strong>in</strong>t32 num elements ,<br />
8 ObjectDeletor & d e l e t o r ) :<br />
9 elements ( 0 ) ,<br />
10 num elements ( num elements ) ,<br />
11 d e l e t o r ( d e l e t o r )<br />
12 {<br />
13 i f ( ! num elements )<br />
14 return ;<br />
15 elements = new void ∗ [ num elements ] ;<br />
16 // the f o l l o w i n g i n i t i a l i z a t i o n i s slow , but f o r demo<br />
17 // purposes i t ’ s ok and easy to read .<br />
18 while ( num elements−−)<br />
19 elements [ num elements ] = 0 ;<br />
20 }<br />
21<br />
22 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
23 Vector : : Vector ( const Vector & s r c ) :<br />
24 d e l e t o r ( DontDelete : : getInstance ( ) )<br />
25 {<br />
26 }<br />
27<br />
28 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
29 Vector : : ˜ Vector ( )<br />
30 {<br />
31 i f ( ! elements )<br />
32 return ;<br />
33<br />
34 // i t ’ s no problem here to decrement t h i s v a r i a b l e<br />
35 // because the o bject i s deleted anyway .<br />
36 while ( num elements −−)<br />
37 d e l e t o r . deleteObject ( elements [ num elements ] ) ;<br />
38 delete [ ] elements ;<br />
39 }<br />
40<br />
41 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
42 void Vector : : setElementAt ( u<strong>in</strong>t32 <strong>in</strong>dex , void ∗ element )<br />
43 {<br />
44 i f ( <strong>in</strong>dex >= num elements )<br />
45 return ;<br />
46 i f ( elements [ <strong>in</strong>dex ] )<br />
47 d e l e t o r . deleteObject ( elements [ <strong>in</strong>dex ] ) ;<br />
48 elements [ <strong>in</strong>dex ] = element ;<br />
49 }<br />
50<br />
51 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
52 void ∗ Vector : : getElementAt ( u<strong>in</strong>t32 <strong>in</strong>dex ) const<br />
53 {<br />
54 i f ( <strong>in</strong>dex >= num elements )<br />
55 return ( 0 ) ;<br />
56 return ( elements [ <strong>in</strong>dex ] ) ;<br />
57 }<br />
58<br />
59 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
60 void ∗ Vector : : getAndRemoveElementAt ( u<strong>in</strong>t32 <strong>in</strong>dex ) const<br />
61 {<br />
62 i f ( <strong>in</strong>dex >= num elements )<br />
63 return ( 0 ) ;
546 B. Vollständige Implementation des Memory Spiels<br />
64 void ∗ r e t = elements [ <strong>in</strong>dex ] ;<br />
65 elements [ <strong>in</strong>dex ] = 0 ;<br />
66 return ( r e t ) ;<br />
67 }<br />
B.1.3 Implementation von Commandl<strong>in</strong>eHandl<strong>in</strong>g<br />
1 // simple commandl<strong>in</strong>e handl<strong>in</strong>g . cpp − implementation o f the<br />
2 // commandl<strong>in</strong>e handl<strong>in</strong>g c l a s s<br />
3<br />
4 #<strong>in</strong>clude < c s t d l i b><br />
5 #<strong>in</strong>clude ” simple commandl<strong>in</strong>e handl<strong>in</strong>g . h”<br />
6<br />
7 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
8 void Commandl<strong>in</strong>eHandl<strong>in</strong>g : : declareArgument (<br />
9 u<strong>in</strong>t32 <strong>in</strong>dex , u<strong>in</strong>t32 type , ArgumentHandler ∗ handler )<br />
10 {<br />
11 i f ( ( type > HIGHEST ARG TYPE ) | |<br />
12 ( <strong>in</strong>dex >= static cast(max num args ) ) | |<br />
13 ( ! handler ) )<br />
14 return ;<br />
15 switch ( type )<br />
16 {<br />
17 case UINT32 ARG:<br />
18 {<br />
19 u<strong>in</strong>t32 ∗ type element = new u<strong>in</strong>t32 ( type ) ;<br />
20 types . setElementAt ( <strong>in</strong>dex , type element ) ;<br />
21 handlers . setElementAt ( <strong>in</strong>dex , handler ) ;<br />
22 }<br />
23 break ;<br />
24 default :<br />
25 // noth<strong>in</strong>g to be done , but d e l e t e the handler because<br />
26 // the c a l l e r r e l i e s on t h i s behaviour !<br />
27 delete handler ;<br />
28 }<br />
29 }<br />
30<br />
31 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
32 void Commandl<strong>in</strong>eHandl<strong>in</strong>g : : handleCommandl<strong>in</strong>e ( <strong>in</strong>t32 num args ,<br />
33 char ∗ args [ ] )<br />
34 {<br />
35 i f ( num args > max num args ) // don ’ t handle more than what<br />
36 num args = max num args ; // has been declared<br />
37 for ( <strong>in</strong>t32 current = 0 ; current < num args ; current++)<br />
38 {<br />
39 u<strong>in</strong>t32 ∗ element type ;<br />
40 i f ( ! ( element type = static cast(<br />
41 types . getElementAt ( current ) ) ) )<br />
42 cont<strong>in</strong>ue ;<br />
43 switch (∗ element type )<br />
44 {<br />
45 case UINT32 ARG:<br />
46 {<br />
47 u<strong>in</strong>t32 converted arg = s t r t o l ( args [ current ] ,NULL, 1 0 ) ;<br />
48 re<strong>in</strong>terpret cast(<br />
49 handlers . getElementAt ( current))−><br />
50 argumentNotification ( current ,& converted arg ) ;<br />
51 break ;<br />
52 }<br />
53 default :<br />
54 break ;<br />
55 }<br />
56 }
57 }<br />
B.1 Implementationen der e<strong>in</strong>zelnen Klassen 547<br />
B.1.4 Implementation von SimpleOutputHandl<strong>in</strong>g<br />
1 // simple output handl<strong>in</strong>g . cpp − implementation o f the<br />
2 // simple output handl<strong>in</strong>g c l a s s<br />
3<br />
4 #<strong>in</strong>clude <br />
5<br />
6 #<strong>in</strong>clude ” simple output handl<strong>in</strong>g . h”<br />
7<br />
8 us<strong>in</strong>g std : : cout ;<br />
9<br />
10 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
11 void SimpleOutputHandl<strong>in</strong>g : : addDisplayable (<br />
12 Displayable & d i s p l a y a b l e )<br />
13 {<br />
14 i f ( ( num displayables >= d i s p l a y a b l e s . getMaxNumElements ( ) ) )<br />
15 return ;<br />
16 d i s p l a y a b l e s . setElementAt ( num displayables ++,&d i s p l a y a b l e ) ;<br />
17 d i s p l a y a b l e . d i s p l a y a b l e R e g i s t e r e d (∗ this ) ;<br />
18 }<br />
19<br />
20 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
21 void SimpleOutputHandl<strong>in</strong>g : : writeOutput ( )<br />
22 {<br />
23 for ( u<strong>in</strong>t32 current = 0 ; current < num displayables ;<br />
24 current++)<br />
25 {<br />
26 static cast(<br />
27 d i s p l a y a b l e s . getElementAt ( current))−><br />
28 writeDisplayRep ( output context ) ;<br />
29 }<br />
30 }<br />
B.1.5 Implementation von GameCard<br />
1 // game card v3 . cpp − implementation o f GameCard<br />
2<br />
3 #<strong>in</strong>clude ”game card v3 . h”<br />
4<br />
5 const u<strong>in</strong>t8 GameCard : : FRONT SIDE;<br />
6 const u<strong>in</strong>t8 GameCard : : BACK SIDE;<br />
B.1.6 Implementation von MemoryGameCard<br />
1 // memory game card v5 . cpp − implementation o f MemoryGameCard<br />
2<br />
3 #<strong>in</strong>clude < c s t r i n g><br />
4 #<strong>in</strong>clude <br />
5<br />
6 #<strong>in</strong>clude ”memory game card v5 . h”<br />
7<br />
8 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
9 /∗
548 B. Vollständige Implementation des Memory Spiels<br />
10 ∗ @param front symbol the f r o n t symbol o f the card<br />
11 ∗ @param back symbol the symbol f o r the back o f the card<br />
12 ∗/<br />
13<br />
14 MemoryGameCard : : MemoryGameCard( const char ∗ front symbol ,<br />
15 const char ∗ back symbol ) :<br />
16 GameCard(BACK SIDE ) , Displayable ( )<br />
17 {<br />
18 i f ( front symbol )<br />
19 {<br />
20 front symbol = new char [ s t r l e n ( front symbol ) + 1 ] ;<br />
21 strcpy ( front symbol , front symbol ) ;<br />
22 }<br />
23 else<br />
24 {<br />
25 front symbol = new char [ 1 ] ;<br />
26 front symbol [ 0 ] = ’ \0 ’ ;<br />
27 }<br />
28<br />
29 i f ( back symbol )<br />
30 {<br />
31 back symbol = new char [ s t r l e n ( back symbol ) + 1 ] ;<br />
32 strcpy ( back symbol , back symbol ) ;<br />
33 }<br />
34 else<br />
35 {<br />
36 back symbol = new char [ 1 ] ;<br />
37 back symbol [ 0 ] = ’ \0 ’ ;<br />
38 }<br />
39 }<br />
40<br />
41 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
42 /∗<br />
43 ∗/<br />
44<br />
45 MemoryGameCard : : ˜ MemoryGameCard( )<br />
46 {<br />
47 delete [ ] front symbol ;<br />
48 delete [ ] back symbol ;<br />
49 }<br />
B.1.7 Implementation von MemoryGameboard<br />
1 //memory gameboard . cpp − implementation o f the Memory Gameboard<br />
2<br />
3 #<strong>in</strong>clude ”memory gameboard . h”<br />
4 #<strong>in</strong>clude ” simple output handl<strong>in</strong>g . h”<br />
5 #<strong>in</strong>clude ” s i m p l e t e x t d i s p l a y a b l e . h”<br />
6 #<strong>in</strong>clude ” s i m p l e i n t d i s p l a y a b l e . h”<br />
7 #<strong>in</strong>clude ” c o n c r e t e o b j e c t d e l e t o r s . h”<br />
8<br />
9 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
10 MemoryGameboard : : MemoryGameboard( u<strong>in</strong>t32 num rows ,<br />
11 u<strong>in</strong>t32 num cols ) :<br />
12 num rows ( num rows ) ,<br />
13 num cols ( num cols ) ,<br />
14 num cards front side up ( 0 ) ,<br />
15 row vector ( 0 ) ,<br />
16 r o w h e a d i n g d i s p l a y a b l e s ( 0 ) ,<br />
17 c o l h e a d i n g d i s p l a y a b l e s ( 0 ) ,<br />
18 e o l d i s p l a y a b l e s ( 0 ) ,<br />
19 conta<strong>in</strong>er output handl<strong>in</strong>g ( 0 ) ,<br />
20 c o n t a i n e r o u t p u t c o n t e x t (0)
B.1 Implementationen der e<strong>in</strong>zelnen Klassen 549<br />
21 {<br />
22 i f ( ( num rows ∗ num cols ) % 2) // not an even number<br />
23 num rows ++;<br />
24<br />
25 row vector = new Vector ( num rows ,<br />
26 VectorDeletor : : getInstance ( ) ) ;<br />
27 for ( u<strong>in</strong>t32 current row = 0 ; current row < num rows ;<br />
28 current row ++)<br />
29 row vector −>setElementAt (<br />
30 current row ,new Vector ( num cols ,<br />
31 DisplayableDeletor : : getInstance ( ) ) ) ;<br />
32 }<br />
33<br />
34 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
35 MemoryGameboard : : ˜ MemoryGameboard ( )<br />
36 {<br />
37 // no need to d e l e t e any d i s p l a y a b l e s r e g i s t e r e d with<br />
38 // i t , because they are a l l stored <strong>in</strong> our v e c t o r s<br />
39 // and deleted from there<br />
40 delete conta<strong>in</strong>er output handl<strong>in</strong>g ;<br />
41<br />
42 // t h i s a l s o d e l e t e s a l l the column v e c t o r s which <strong>in</strong><br />
43 // turn d e l e t e a l l the cards .<br />
44 delete row vector ;<br />
45<br />
46 // t h i s a l s o d e l e t e s a l l the head<strong>in</strong>g d i s p l a y a b l e s<br />
47 delete r o w h e a d i n g d i s p l a y a b l e s ;<br />
48<br />
49 // t h i s a l s o d e l e t e s a l l the head<strong>in</strong>g d i s p l a y a b l e s<br />
50 delete c o l h e a d i n g d i s p l a y a b l e s ;<br />
51<br />
52 // t h i s a l s o d e l e t e s a l l the e o l d i s p l a y a b l e s<br />
53 delete e o l d i s p l a y a b l e s ;<br />
54<br />
55 delete c o n t a i n e r o u t p u t c o n t e x t ;<br />
56 }<br />
57<br />
58 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
59 void MemoryGameboard : : d i s p l a y a b l e R e g i s t e r e d (<br />
60 SimpleOutputHandl<strong>in</strong>g &handler )<br />
61 {<br />
62 // already r e g i s t e r e d with some handler , no f u r t h e r<br />
63 // setup necessary<br />
64 i f ( r o w h e a d i n g d i s p l a y a b l e s )<br />
65 return ;<br />
66<br />
67 u<strong>in</strong>t32 num displayables = ( num rows ∗ num cols ) + // cards<br />
68 num rows + 1 + // EOL elements<br />
69 num rows + 1 + // c o l head<strong>in</strong>gs<br />
70 num cols ; // row head<strong>in</strong>gs<br />
71 r o w h e a d i n g d i s p l a y a b l e s = new Vector (<br />
72 num cols , DisplayableDeletor : : getInstance ( ) ) ;<br />
73 c o l h e a d i n g d i s p l a y a b l e s = new Vector (<br />
74 num rows + 1, DisplayableDeletor : : getInstance ( ) ) ;<br />
75 e o l d i s p l a y a b l e s = new Vector (<br />
76 num rows + 1, DisplayableDeletor : : getInstance ( ) ) ;<br />
77 c o n t a i n e r o u t p u t c o n t e x t = handler . getOutputContextClone ( ) ;<br />
78 conta<strong>in</strong>er output handl<strong>in</strong>g = new SimpleOutputHandl<strong>in</strong>g (<br />
79 num displayables , ∗ c o n t a i n e r o u t p u t c o n t e x t ) ;<br />
80<br />
81 // j u s t because some compilers have a wrong treatment f o r<br />
82 // v a r i a b l e s <strong>in</strong> c o n t r o l statements . . .<br />
83 u<strong>in</strong>t32 row count = 0;<br />
84 u<strong>in</strong>t32 col count = 0;<br />
85<br />
86 // generate the top l i n e with the column head<strong>in</strong>gs and
550 B. Vollständige Implementation des Memory Spiels<br />
87 // r e g i s t e r i t<br />
88 IntDisplayable ∗ head<strong>in</strong>g = 0;<br />
89 TextDisplayable ∗ text = 0;<br />
90 // a space needs to be the f i r s t element o f the head<strong>in</strong>gs<br />
91 text = new TextDisplayable ( ” ” ) ;<br />
92 c o l h e a d i n g d i s p l a y a b l e s −>setElementAt ( 0 , text ) ;<br />
93 conta<strong>in</strong>er output handl<strong>in</strong>g −>addDisplayable (∗ text ) ;<br />
94 // the r e s t of the head<strong>in</strong>g l i n e conta<strong>in</strong>s the consecutive<br />
95 // numbers of the columns<br />
96 for ( col count = 1 ; col count setElementAt ( c o l count , head<strong>in</strong>g ) ;<br />
100 conta<strong>in</strong>er output handl<strong>in</strong>g −>addDisplayable (∗ head<strong>in</strong>g ) ;<br />
101 }<br />
102 // an e o l i s placed at the end o f the l i n e<br />
103 text = new TextDisplayable ( ”\n” ) ;<br />
104 e o l d i s p l a y a b l e s −>setElementAt ( 0 , text ) ;<br />
105 conta<strong>in</strong>er output handl<strong>in</strong>g −>addDisplayable (∗ text ) ;<br />
106 // now f o r each row :<br />
107 // ( 1 ) generate the row head<strong>in</strong>g and r e g i s t e r i t<br />
108 // ( 2 ) r e g i s t e r a l l cards <strong>in</strong> the row .<br />
109 // ( 3 ) generate an EOL d i s p l a y a b l e and r e g i s t e r i t<br />
110 for ( row count = 0 ; row count < num rows ; row count++)<br />
111 {<br />
112 head<strong>in</strong>g = new IntDisplayable ( row count + 1 ) ;<br />
113 r o w h e a d i n g d i s p l a y a b l e s −>setElementAt ( row count , head<strong>in</strong>g ) ;<br />
114 conta<strong>in</strong>er output handl<strong>in</strong>g −>addDisplayable (∗ head<strong>in</strong>g ) ;<br />
115 for ( col count = 0 ; col count < num cols ; c o l c o unt++)<br />
116 {<br />
117 // not very e f f i c i e n t but e a s i e r to read<br />
118 conta<strong>in</strong>er output handl<strong>in</strong>g −>addDisplayable (<br />
119 ∗dynamic cast(<br />
120 <strong>in</strong>ternalGetCard ( row count , c o l count ) ) ) ;<br />
121 }<br />
122 text = new TextDisplayable ( ”\n” ) ;<br />
123 e o l d i s p l a y a b l e s −>setElementAt ( row count + 1, text ) ;<br />
124 conta<strong>in</strong>er output handl<strong>in</strong>g −>addDisplayable (∗ text ) ;<br />
125 }<br />
126 }<br />
127<br />
128 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
129 void MemoryGameboard : : putAllCardsFrontSideUp ( )<br />
130 {<br />
131 for ( u<strong>in</strong>t32 current row = 0; current row < num rows ;<br />
132 current row++)<br />
133 {<br />
134 for ( u<strong>in</strong>t32 c u r r e n t c o l = 0; c u r r e n t c o l < num cols ;<br />
135 c u r r e n t c o l++)<br />
136 {<br />
137 // not very e f f i c i e n t but e a s i e r to read . . .<br />
138 MemoryGameCard ∗ current card = <strong>in</strong>ternalGetCard (<br />
139 current row , c u r r e n t c o l ) ;<br />
140 i f ( current card )<br />
141 current card−>putFrontSideUp ( ) ;<br />
142 }<br />
143 }<br />
144 num cards front side up = num rows ∗ num cols ;<br />
145 }<br />
146<br />
147 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
148 void MemoryGameboard : : putAllCardsBackSideUp ( )<br />
149 {<br />
150 for ( u<strong>in</strong>t32 current row = 0; current row < num rows ;<br />
151 current row++)<br />
152 {
B.1 Implementationen der e<strong>in</strong>zelnen Klassen 551<br />
153 for ( u<strong>in</strong>t32 c u r r e n t c o l = 0; c u r r e n t c o l < num cols ;<br />
154 c u r r e n t c o l++)<br />
155 {<br />
156 MemoryGameCard ∗ current card = <strong>in</strong>ternalGetCard (<br />
157 current row , c u r r e n t c o l ) ;<br />
158 i f ( current card )<br />
159 current card−>putBackSideUp ( ) ;<br />
160 }<br />
161 }<br />
162 num cards front side up = 0;<br />
163 }<br />
164<br />
165 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
166 void MemoryGameboard : : putCardFrontSideUp ( u<strong>in</strong>t32 row , u<strong>in</strong>t32 c o l )<br />
167 {<br />
168 i f ( ( row >= num rows ) | |<br />
169 ( c o l >= num cols ) )<br />
170 return ;<br />
171 MemoryGameCard ∗ card = <strong>in</strong>ternalGetCard ( row , c o l ) ;<br />
172 i f ( card &&<br />
173 ( card−>g e t V i s i b l e S i d e ( ) ! = MemoryGameCard : : FRONT SIDE) )<br />
174 {<br />
175 card−>putFrontSideUp ( ) ;<br />
176 num cards front side up ++;<br />
177 }<br />
178 }<br />
179<br />
180 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
181 void MemoryGameboard : : putCardBackSideUp ( u<strong>in</strong>t32 row , u<strong>in</strong>t32 c o l )<br />
182 {<br />
183 i f ( ( row >= num rows ) | |<br />
184 ( c o l >= num cols ) )<br />
185 return ;<br />
186 MemoryGameCard ∗ card = <strong>in</strong>ternalGetCard ( row , c o l ) ;<br />
187 i f ( card &&<br />
188 ( card−>g e t V i s i b l e S i d e ( ) ! = MemoryGameCard : : BACK SIDE) )<br />
189 {<br />
190 card−>putBackSideUp ( ) ;<br />
191 num cards front side up −−;<br />
192 }<br />
193 }<br />
194<br />
195 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
196 MemoryGameCard ∗MemoryGameboard : : getCard (<br />
197 u<strong>in</strong>t32 row , u<strong>in</strong>t32 c o l ) const<br />
198 {<br />
199 i f ( ( row >= num rows ) | |<br />
200 ( c o l >= num cols ) )<br />
201 return ( 0 ) ;<br />
202 return ( <strong>in</strong>ternalGetCard ( row , c o l ) ) ;<br />
203 }<br />
204<br />
205 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
206 void MemoryGameboard : : putCardOnBoard (MemoryGameCard ∗ card ,<br />
207 u<strong>in</strong>t32 row , u<strong>in</strong>t32 c o l )<br />
208 {<br />
209 i f ( ( row > num rows ) | |<br />
210 ( c o l > num cols ) | |<br />
211 ( ! card ) | |<br />
212 <strong>in</strong>ternalGetCard ( row , c o l ) ) // already occupied<br />
213 return ;<br />
214 re<strong>in</strong>terpret cast(<br />
215 row vector −>getElementAt ( row))−>setElementAt ( c o l , card ) ;<br />
216 i f ( card−>g e t V i s i b l e S i d e () == MemoryGameCard : : FRONT SIDE)<br />
217 num cards front side up ++;<br />
218 }
552 B. Vollständige Implementation des Memory Spiels<br />
B.1.8 Implementation von SimpleInputHandl<strong>in</strong>g<br />
1 // simple <strong>in</strong>put handl<strong>in</strong>g . cpp − implementation o f SimpleInputHandl<strong>in</strong>g<br />
2<br />
3 #<strong>in</strong>clude <br />
4<br />
5 #<strong>in</strong>clude ” simple <strong>in</strong>put handl<strong>in</strong>g . h”<br />
6 #<strong>in</strong>clude ” simple word event . h”<br />
7<br />
8 us<strong>in</strong>g std : : c<strong>in</strong> ;<br />
9<br />
10 bool SimpleInputHandl<strong>in</strong>g : : s t o p d i s p a t c h e r l o o p = f a l s e ;<br />
11 EventHandler ∗ SimpleInputHandl<strong>in</strong>g : : event handler = 0;<br />
12<br />
13 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
14 void SimpleInputHandl<strong>in</strong>g : : runDispatcher ( )<br />
15 {<br />
16 i f ( ! event handler )<br />
17 return ;<br />
18 s t o p d i s p a t c h e r l o o p = false ;<br />
19 while ( ! s t o p d i s p a t c h e r l o o p )<br />
20 {<br />
21 char word read [MAXWORDLENGTH] ;<br />
22<br />
23 c<strong>in</strong> >> word read ;<br />
24 WordEvent event ( word read ) ;<br />
25 event handler −>handleEvent ( event ) ;<br />
26 }<br />
27 }<br />
B.1.9 Implementation von MemoryGameControl<br />
1 //memory game control . cpp − implementation o f the game c o n t r o l<br />
2<br />
3 #<strong>in</strong>clude < c s t d l i b><br />
4 #<strong>in</strong>clude <br />
5<br />
6 #<strong>in</strong>clude ”memory game control . h”<br />
7 #<strong>in</strong>clude ” memory card symbol generator . h”<br />
8 #<strong>in</strong>clude ” simple <strong>in</strong>put handl<strong>in</strong>g . h”<br />
9 #<strong>in</strong>clude ” simple word event . h”<br />
10<br />
11 us<strong>in</strong>g std : : cout ;<br />
12 us<strong>in</strong>g std : : endl ;<br />
13<br />
14 const char MemoryGameControl : :CARD BACK SYMBOL [ ] ;<br />
15<br />
16 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
17 void MemoryGameControl : : putCardsOnGameboard ( )<br />
18 {<br />
19 u<strong>in</strong>t32 num rows = gameboard . getNumRows ( ) ;<br />
20 u<strong>in</strong>t32 num cols = gameboard . getNumCols ( ) ;<br />
21 u<strong>in</strong>t32 num pairs = ( num rows ∗ num cols ) / 2 ;<br />
22 MemoryCardSymbolGenerator symbol generator ( num pairs ) ;<br />
23<br />
24 // random d i s t r i b u t i o n here i s numerically very weak ,<br />
25 // but f o r demonstration purposes . . .<br />
26 srand ( num pairs ) ;<br />
27
B.1 Implementationen der e<strong>in</strong>zelnen Klassen 553<br />
28 MemoryGameCard ∗ card = 0;<br />
29 char symbol [ ] = ”x” ; // symbols here are only one char long !<br />
30 while ( num pairs−−)<br />
31 {<br />
32 symbol [ 0 ] = symbol generator . getNextSymbol ( ) ;<br />
33 card = new MemoryGameCard( symbol ,CARD BACK SYMBOL) ;<br />
34 putCardOnBoardRandomly( card , num rows , num cols ) ;<br />
35 card = new MemoryGameCard( symbol ,CARD BACK SYMBOL) ;<br />
36 putCardOnBoardRandomly( card , num rows , num cols ) ;<br />
37 }<br />
38 }<br />
39<br />
40 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
41 void MemoryGameControl : : putCardOnBoardRandomly(<br />
42 MemoryGameCard ∗ card , u<strong>in</strong>t32 num rows , u<strong>in</strong>t32 num cols )<br />
43 {<br />
44 for ( ; ; )<br />
45 {<br />
46 u<strong>in</strong>t32 row = rand () % num rows ;<br />
47 u<strong>in</strong>t32 c o l = rand () % num cols ;<br />
48 i f ( gameboard . getCard ( row , c o l ) )<br />
49 cont<strong>in</strong>ue ;<br />
50 gameboard . putCardOnBoard ( card , row , c o l ) ;<br />
51 break ;<br />
52 }<br />
53 }<br />
54<br />
55 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
56 void MemoryGameControl : : handleEvent ( const Event &event )<br />
57 {<br />
58 switch ( event . getType ( ) )<br />
59 {<br />
60 case Event : :WORDEVENT:<br />
61 {<br />
62 const char ∗ word = dynamic cast(<br />
63 event ) . getWord ( ) ;<br />
64 i f ( ! word )<br />
65 break ;<br />
66 i f ( c o o r d w a i t s t a t u s == WAITING FOR ROW AND COL)<br />
67 {<br />
68 switch ( word [ 0 ] )<br />
69 {<br />
70 case ’ q ’ :<br />
71 SimpleInputHandl<strong>in</strong>g : : stopDispatcher ( ) ;<br />
72 return ;<br />
73 case ’ s ’ :<br />
74 gameboard . putAllCardsFrontSideUp ( ) ;<br />
75 output handler . writeOutput ( ) ;<br />
76 return ;<br />
77 case ’ h ’ :<br />
78 gameboard . putAllCardsBackSideUp ( ) ;<br />
79 output handler . writeOutput ( ) ;<br />
80 return ;<br />
81 }<br />
82 }<br />
83 coordWasPassed ( word ) ;<br />
84 break ;<br />
85 }<br />
86 default :<br />
87 break ;<br />
88 }<br />
89 }<br />
90<br />
91 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
92 void MemoryGameControl : : coordWasPassed ( const char ∗ word )<br />
93 {
554 B. Vollständige Implementation des Memory Spiels<br />
94 u<strong>in</strong>t32 converted arg = s t r t o l ( word ,NULL, 1 0 ) ;<br />
95 i f ( converted arg == 0)<br />
96 return ;<br />
97 switch ( c o o r d w a i t s t a t u s )<br />
98 {<br />
99 case WAITING FOR ROW AND COL:<br />
100 stored row num = converted arg − 1;<br />
101 c o o r d w a i t s t a t u s = WAITING FOR COL;<br />
102 break ;<br />
103 case WAITING FOR COL:<br />
104 actOnCard ( stored row num , converted arg − 1 ) ;<br />
105 c o o r d w a i t s t a t u s = WAITING FOR ROW AND COL;<br />
106 break ;<br />
107 default :<br />
108 break ;<br />
109 }<br />
110 }<br />
111<br />
112 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
113 void MemoryGameControl : : actOnCard ( u<strong>in</strong>t32 row , u<strong>in</strong>t32 c o l )<br />
114 {<br />
115 i f ( c u r r e n t c a r d p a i r . whichCardsAreSet () == 0) // card one<br />
116 {<br />
117 c u r r e n t c a r d p a i r . turnCardOneFrontSideUp ( row , c o l ,<br />
118 gameboard ) ;<br />
119 output handler . writeOutput ( ) ;<br />
120 return ;<br />
121 }<br />
122 c u r r e n t c a r d p a i r . turnCardTwoFrontSideUp ( row , c o l ,<br />
123 gameboard ) ;<br />
124 output handler . writeOutput ( ) ;<br />
125 i f ( c u r r e n t c a r d p a i r . cardSymbolsMatch<strong>in</strong>g ( gameboard ) )<br />
126 {<br />
127 i f ( gameboard . getNumCardsBackSideUp()
B.1 Implementationen der e<strong>in</strong>zelnen Klassen 555<br />
13 symbols ( num symbols rema<strong>in</strong><strong>in</strong>g , CharDeletor : : getInstance ( ) )<br />
14 {<br />
15 fillVectorWithSymbols ( ) ;<br />
16 distributeSymbolsRandomlyInVector ( ) ;<br />
17 }<br />
18<br />
19 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
20 void MemoryCardSymbolGenerator : : fillVectorWithSymbols ( )<br />
21 {<br />
22 u<strong>in</strong>t32 count = num symbols rema<strong>in</strong><strong>in</strong>g ;<br />
23 char l e t t e r = ’A’ ;<br />
24 while ( count−−)<br />
25 {<br />
26 symbols . setElementAt ( count ,new char ( l e t t e r ) ) ;<br />
27 i f ( ( ( l e t t e r >= ’A’ ) && ( l e t t e r < ’ Z ’ ) ) | |<br />
28 ( ( l e t t e r >= ’ a ’ ) && ( l e t t e r < ’ z ’ ) ) | |<br />
29 ( ( l e t t e r >= ’ 0 ’ ) && ( l e t t e r < ’ 9 ’ ) ) )<br />
30 l e t t e r ++;<br />
31 else<br />
32 {<br />
33 switch ( l e t t e r )<br />
34 {<br />
35 case ’ Z ’ :<br />
36 l e t t e r = ’ a ’ ;<br />
37 break ;<br />
38 case ’ z ’ :<br />
39 l e t t e r = ’ 0 ’ ;<br />
40 break ;<br />
41 }<br />
42 }<br />
43 }<br />
44 }<br />
45<br />
46 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
47 void MemoryCardSymbolGenerator : : distributeSymbolsRandomlyInVector ( )<br />
48 {<br />
49 // d i s t r i b u t e the symbols randomly by swapp<strong>in</strong>g them<br />
50 // <strong>in</strong> the vector . Please note that the f o l l o w i n g<br />
51 // implementation i s numerically weak <strong>in</strong> r e s p e c t to<br />
52 // the use of the random number generator .<br />
53 u<strong>in</strong>t32 count = num symbols rema<strong>in</strong><strong>in</strong>g ∗ SYMBOL SWAP FACTOR;<br />
54 srand ( count ) ; // s e t the seed ( should be done b e t t e r . . . )<br />
55 while ( count−−)<br />
56 {<br />
57 <strong>in</strong>t swap pos1 = rand () % num symbols rema<strong>in</strong><strong>in</strong>g ;<br />
58 <strong>in</strong>t swap pos2 = rand () % num symbols rema<strong>in</strong><strong>in</strong>g ;<br />
59 char ∗ swap help = static cast(<br />
60 symbols . getAndRemoveElementAt ( swap pos1 ) ) ;<br />
61 symbols . setElementAt ( swap pos1 , static cast(<br />
62 symbols . getAndRemoveElementAt (<br />
63 swap pos2 ) ) ) ;<br />
64 symbols . setElementAt ( swap pos2 , swap help ) ;<br />
65 }<br />
66 }<br />
67<br />
68 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
69 char MemoryCardSymbolGenerator : : getNextSymbol ( )<br />
70 {<br />
71 i f ( num symbols rema<strong>in</strong><strong>in</strong>g )<br />
72 return (∗ static cast(<br />
73 symbols . getElementAt(−−num symbols rema<strong>in</strong><strong>in</strong>g ) ) ) ;<br />
74 return ( ’ \0 ’ ) ;<br />
75 }
556 B. Vollständige Implementation des Memory Spiels<br />
B.1.11 Implementation von MemoryCardpair<br />
1 // memory cardpair . cpp − implementation o f MemoryCardpair<br />
2<br />
3 #<strong>in</strong>clude < c s t r i n g><br />
4<br />
5 #<strong>in</strong>clude ”memory cardpair . h”<br />
6<br />
7 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
8 void MemoryCardpair : : turnCardOneFrontSideUp (<br />
9 u<strong>in</strong>t32 row , u<strong>in</strong>t32 c o l , MemoryGameboard &gameboard )<br />
10 {<br />
11 card1 row = row ;<br />
12 c a r d 1 c o l = c o l ;<br />
13 c a r d s e t s t a t u s |= CARD ONE SET;<br />
14 MemoryGameCard ∗ card = gameboard . getCard ( row , c o l ) ;<br />
15 i f ( ! card )<br />
16 return ;<br />
17 i f ( card−>g e t V i s i b l e S i d e () == MemoryGameCard : : FRONT SIDE)<br />
18 return ;<br />
19 gameboard . putCardFrontSideUp ( row , c o l ) ;<br />
20 c a r d s e t s t a t u s |= CARD ONE TURNED;<br />
21 }<br />
22<br />
23 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
24 void MemoryCardpair : : turnCardTwoFrontSideUp (<br />
25 u<strong>in</strong>t32 row , u<strong>in</strong>t32 c o l , MemoryGameboard &gameboard )<br />
26 {<br />
27 card2 row = row ;<br />
28 c a r d 2 c o l = c o l ;<br />
29 c a r d s e t s t a t u s |= CARD TWO SET;<br />
30 MemoryGameCard ∗ card = gameboard . getCard ( row , c o l ) ;<br />
31 i f ( ! card )<br />
32 return ;<br />
33 i f ( card−>g e t V i s i b l e S i d e () == MemoryGameCard : : FRONT SIDE)<br />
34 return ;<br />
35 gameboard . putCardFrontSideUp ( row , c o l ) ;<br />
36 c a r d s e t s t a t u s |= CARD TWO TURNED;<br />
37 }<br />
38<br />
39 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
40 bool MemoryCardpair : : cardSymbolsMatch<strong>in</strong>g (<br />
41 MemoryGameboard &gameboard )<br />
42 {<br />
43 i f ( ( c a r d s e t s t a t u s & CARD SET BITS) ! = CARD SET BITS)<br />
44 return ( false ) ; // not both cards were s e l e c t e d<br />
45 MemoryGameCard ∗ card1 = ( c a r d s e t s t a t u s & CARD ONE SET) ?<br />
46 gameboard . getCard ( card1 row ,<br />
47 c a r d 1 c o l ) : 0 ;<br />
48 MemoryGameCard ∗ card2 = ( c a r d s e t s t a t u s & CARD TWO SET) ?<br />
49 gameboard . getCard ( card2 row ,<br />
50 c a r d 2 c o l ) : 0 ;<br />
51 i f ( ( ! card1 ) | | ( ! card2 ) )<br />
52 return ( false ) ; // at l e a s t one o f the cards does not e x i s t<br />
53 return ( ! strcmp ( card1−>getFrontSymbol ( ) , card2−>getFrontSymbol ( ) ) ) ;<br />
54 }<br />
55<br />
56 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
57 void MemoryCardpair : : turnCardsBackToTheirOrig<strong>in</strong>alState (<br />
58 MemoryGameboard &gameboard )<br />
59 {<br />
60 i f ( c a r d s e t s t a t u s & CARD ONE TURNED)<br />
61 gameboard . putCardBackSideUp ( card1 row , c a r d 1 c o l ) ;<br />
62 i f ( c a r d s e t s t a t u s & CARD TWO TURNED)<br />
63 gameboard . putCardBackSideUp ( card2 row , c a r d 2 c o l ) ;
64 }<br />
65<br />
B.1 Implementationen der e<strong>in</strong>zelnen Klassen 557<br />
66 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
67 void MemoryCardpair : : c l e a r ( )<br />
68 {<br />
69 card1 row = 0;<br />
70 c a r d 1 c o l = 0;<br />
71 card2 row = 0;<br />
72 c a r d 2 c o l = 0;<br />
73 c a r d s e t s t a t u s = 0;<br />
74 }<br />
B.1.12 Variablen für die konkreten Deletors<br />
1 // c o n c r e t e o b j e c t d e l e t o r s . cpp − implementations o f the<br />
2 // concrete d e l e t o r s<br />
3<br />
4 #<strong>in</strong>clude ” c o n c r e t e o b j e c t d e l e t o r s . h”<br />
5<br />
6 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
7 // a l l s t a t i c <strong>in</strong>stance member v a r i a b l e s<br />
8 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−<br />
9 DontDelete DontDelete : : d e l e t o r ;<br />
10 CharDeletor CharDeletor : : d e l e t o r ;<br />
11 U<strong>in</strong>t32Deletor U<strong>in</strong>t32Deletor : : d e l e t o r ;<br />
12 VectorDeletor VectorDeletor : : d e l e t o r ;<br />
13 DisplayableDeletor DisplayableDeletor : : d e l e t o r ;<br />
B.1.13 Das MemoryMakefile<br />
1 OBJS = memory game control . o \<br />
2 memory ma<strong>in</strong> . o \<br />
3 memory card symbol generator . o \<br />
4 memory cardpair . o \<br />
5 memory gameboard . o \<br />
6 game card v3 . o \<br />
7 memory game card v5 . o \<br />
8 simple <strong>in</strong>put handl<strong>in</strong>g . o \<br />
9 simple output handl<strong>in</strong>g . o \<br />
10 s i m p l e v e c t o r . o \<br />
11 simple commandl<strong>in</strong>e handl<strong>in</strong>g . o \<br />
12 c o n c r e t e o b j e c t d e l e t o r s . o<br />
13<br />
14 CC = g++<br />
15 LD = g++<br />
16 EXTRA CCINCLUDES =<br />
17 CC FLAGS = −c $ (EXTRA CCINCLUDES) −Wall<br />
18 EXTRA LIBDIRS =<br />
19 EXTRA LIBS =<br />
20 EXECUTABLE = memory<br />
21 LD FLAGS = −o $ (EXECUTABLE) $ (EXTRA LIBDIRS) $ (EXTRA LIBS)<br />
22<br />
23 memory : $ (OBJS)<br />
24 $ (LD) $ (LD FLAGS) $ (OBJS)<br />
25<br />
26 memory game control . o : memory game control . cpp \<br />
27 memory game control . h \<br />
28 simple event handler . h \<br />
29 simple event . h \<br />
30 u s e r t y p e s . h \
558 B. Vollständige Implementation des Memory Spiels<br />
31 memory gameboard . h \<br />
32 s i m p l e d i s p l a y a b l e . h \<br />
33 simple output context . h \<br />
34 simple output handl<strong>in</strong>g . h \<br />
35 s i m p l e v e c t o r . h \<br />
36 o b j e c t d e l e t o r . h \<br />
37 memory game card v5 . h \<br />
38 game card v3 . h \<br />
39 s i m p l e t e x t o u t p u t c o n t e x t . h \<br />
40 memory cardpair . h \<br />
41 memory card symbol generator . h \<br />
42 simple <strong>in</strong>put handl<strong>in</strong>g . h \<br />
43 c o n c r e t e o b j e c t d e l e t o r s . h \<br />
44 simple word event . h<br />
45 $ (CC) $ (CC FLAGS) memory game control . cpp<br />
46<br />
47 memory ma<strong>in</strong> . o : memory ma<strong>in</strong> . cpp \<br />
48 simple commandl<strong>in</strong>e handl<strong>in</strong>g . h \<br />
49 u s e r t y p e s . h \<br />
50 s i m p l e v e c t o r . h \<br />
51 o b j e c t d e l e t o r . h \<br />
52 simple argument handler . h \<br />
53 c o n c r e t e o b j e c t d e l e t o r s . h \<br />
54 s i m p l e d i s p l a y a b l e . h \<br />
55 simple output context . h \<br />
56 memory commandl<strong>in</strong>e arg handler . h \<br />
57 memory gameboard . h \<br />
58 simple output handl<strong>in</strong>g . h \<br />
59 memory game card v5 . h \<br />
60 game card v3 . h \<br />
61 s i m p l e t e x t o u t p u t c o n t e x t . h \<br />
62 memory game control . h \<br />
63 simple event handler . h \<br />
64 simple event . h \<br />
65 memory cardpair . h \<br />
66 simple <strong>in</strong>put handl<strong>in</strong>g . h<br />
67 $ (CC) $ (CC FLAGS) memory ma<strong>in</strong> . cpp<br />
68<br />
69 memory card symbol generator . o : memory card symbol generator . cpp \<br />
70 memory card symbol generator . h \<br />
71 s i m p l e v e c t o r . h \<br />
72 u s e r t y p e s . h \<br />
73 o b j e c t d e l e t o r . h \<br />
74 c o n c r e t e o b j e c t d e l e t o r s . h \<br />
75 s i m p l e d i s p l a y a b l e . h \<br />
76 simple output context . h \<br />
77 simple argument handler . h<br />
78 $ (CC) $ (CC FLAGS) memory card symbol generator . cpp<br />
79<br />
80 memory cardpair . o : memory cardpair . cpp \<br />
81 memory cardpair . h \<br />
82 u s e r t y p e s . h \<br />
83 memory gameboard . h \<br />
84 s i m p l e d i s p l a y a b l e . h \<br />
85 simple output context . h \<br />
86 simple output handl<strong>in</strong>g . h \<br />
87 s i m p l e v e c t o r . h \<br />
88 o b j e c t d e l e t o r . h \<br />
89 c o n c r e t e o b j e c t d e l e t o r s . h \<br />
90 memory game card v5 . h \<br />
91 game card v3 . h \<br />
92 s i m p l e t e x t o u t p u t c o n t e x t . h<br />
93 $ (CC) $ (CC FLAGS) memory cardpair . cpp<br />
94<br />
95 memory gameboard . o : memory gameboard . cpp \<br />
96 memory gameboard . h \
B.1 Implementationen der e<strong>in</strong>zelnen Klassen 559<br />
97 s i m p l e d i s p l a y a b l e . h \<br />
98 simple output context . h \<br />
99 simple output handl<strong>in</strong>g . h \<br />
100 s i m p l e v e c t o r . h \<br />
101 u s e r t y p e s . h \<br />
102 o b j e c t d e l e t o r . h \<br />
103 memory game card v5 . h \<br />
104 game card v3 . h \<br />
105 s i m p l e t e x t o u t p u t c o n t e x t . h \<br />
106 s i m p l e i n t d i s p l a y a b l e . h \<br />
107 s i m p l e t e x t d i s p l a y a b l e . h \<br />
108 c o n c r e t e o b j e c t d e l e t o r s . h \<br />
109 simple argument handler . h<br />
110 $ (CC) $ (CC FLAGS) memory gameboard . cpp<br />
111<br />
112 game card v3 . o : game card v3 . cpp \<br />
113 game card v3 . h \<br />
114 u s e r t y p e s . h<br />
115 $ (CC) $ (CC FLAGS) game card v3 . cpp<br />
116<br />
117 memory game card v5 . o : memory game card v5 . cpp \<br />
118 memory game card v5 . h \<br />
119 game card v3 . h \<br />
120 u s e r t y p e s . h \<br />
121 s i m p l e d i s p l a y a b l e . h \<br />
122 simple output context . h \<br />
123 s i m p l e t e x t o u t p u t c o n t e x t . h<br />
124 $ (CC) $ (CC FLAGS) memory game card v5 . cpp<br />
125<br />
126 simple <strong>in</strong>put handl<strong>in</strong>g . o : simple <strong>in</strong>put handl<strong>in</strong>g . cpp \<br />
127 simple <strong>in</strong>put handl<strong>in</strong>g . h \<br />
128 simple event handler . h \<br />
129 simple event . h \<br />
130 u s e r t y p e s . h \<br />
131 simple word event . h<br />
132 $ (CC) $ (CC FLAGS) simple <strong>in</strong>put handl<strong>in</strong>g . cpp<br />
133<br />
134 simple output handl<strong>in</strong>g . o : simple output handl<strong>in</strong>g . cpp \<br />
135 simple output handl<strong>in</strong>g . h \<br />
136 s i m p l e d i s p l a y a b l e . h \<br />
137 simple output context . h \<br />
138 s i m p l e v e c t o r . h \<br />
139 u s e r t y p e s . h \<br />
140 o b j e c t d e l e t o r . h \<br />
141 c o n c r e t e o b j e c t d e l e t o r s . h<br />
142 $ (CC) $ (CC FLAGS) simple output handl<strong>in</strong>g . cpp<br />
143<br />
144 s i m p l e v e c t o r . o : s i m p l e v e c t o r . cpp \<br />
145 s i m p l e v e c t o r . h \<br />
146 u s e r t y p e s . h \<br />
147 o b j e c t d e l e t o r . h \<br />
148 c o n c r e t e o b j e c t d e l e t o r s . h<br />
149 $ (CC) $ (CC FLAGS) s i m p l e v e c t o r . cpp<br />
150<br />
151 simple commandl<strong>in</strong>e handl<strong>in</strong>g . o : simple commandl<strong>in</strong>e handl<strong>in</strong>g . cpp \<br />
152 simple commandl<strong>in</strong>e handl<strong>in</strong>g . h \<br />
153 u s e r t y p e s . h \<br />
154 s i m p l e v e c t o r . h \<br />
155 o b j e c t d e l e t o r . h \<br />
156 simple argument handler . h \<br />
157 c o n c r e t e o b j e c t d e l e t o r s . h \<br />
158 s i m p l e d i s p l a y a b l e . h \<br />
159 simple output context . h<br />
160 $ (CC) $ (CC FLAGS) simple commandl<strong>in</strong>e handl<strong>in</strong>g . cpp<br />
161<br />
162 c o n c r e t e o b j e c t d e l e t o r s . o : c o n c r e t e o b j e c t d e l e t o r s . cpp \
560 B. Vollständige Implementation des Memory Spiels<br />
163 c o n c r e t e o b j e c t d e l e t o r s . h \<br />
164 o b j e c t d e l e t o r . h \<br />
165 u s e r t y p e s . h \<br />
166 s i m p l e v e c t o r . h \<br />
167 s i m p l e d i s p l a y a b l e . h \<br />
168 simple output context . h<br />
169 $ (CC) $ (CC FLAGS) c o n c r e t e o b j e c t d e l e t o r s . cpp<br />
170<br />
171 a l l : memory<br />
172<br />
173 clean :<br />
174 rm $ (OBJS) $ (EXECUTABLE)
Literaturverzeichnis<br />
[Coplien 1991] Coplien J. O.: Advanced C ++ Programm<strong>in</strong>g Styles and Idioms, Addison<br />
Wesley (1991).<br />
[Gamma et al. 1998] Gamma E., Helm R., Jonson R., Vlissides J.: Design Patterns<br />
- Elements of Reusable Object-Oriented Software, Addison Wesley (1998).<br />
[Meyer 1997] Meyer B.: Object-Oriented Software Construction, Second Edition,<br />
Prentice Hall (1997).<br />
[Musser et al. 2001] Musser D. R., Derge G. J., Sa<strong>in</strong>i A.: STL Tutorial and Reference<br />
Guide: C ++ Programm<strong>in</strong>g with the Standard Template Library (2nd<br />
Edition), Addison Wesley (2001).<br />
[Myers 1999] Myers G.J.: Methodisches Testen von Programmen, Oldenbourg<br />
(1999).<br />
[Ottmann, Widmayer 1996] Ottmann, T., Widmayer P.: Algorithmen und Datenstrukturen,<br />
Spektrum Akad. Vlg., Hdg. (1996).<br />
[Schmaranz 2001] Schmaranz K.: <strong>Softwareentwicklung</strong> <strong>in</strong> C, Spr<strong>in</strong>ger Heidelberg<br />
(2001).<br />
[Sedgewick 1992] Sedgewick R.: Algorithmen, Addison Wesley (1992).<br />
[Stroustrup 1997] Stroustrup B.: The C ++ Programm<strong>in</strong>g Language, Third Edition<br />
- Special Edition, AT&T (1997).
Index<br />
? :, 75<br />
#def<strong>in</strong>e, 138<br />
#else, 137<br />
#endif, 137<br />
#if, 137<br />
#ifdef, 137<br />
#ifndef, 137<br />
#<strong>in</strong>clude, 27, 136<br />
– Project Header, 136<br />
– System Header, 136<br />
1-er Komplement, 60<br />
Ableitung, 160, 164<br />
– Class Template, 428<br />
– virtual, 236<br />
Access Specifiers, 173, 201<br />
– private, 174<br />
– protected, 174<br />
– public, 174<br />
ADD, 257<br />
Ambiguität, 92, 209, 213, 359, 416<br />
and, 475<br />
and eq, 475<br />
Architectural Design Document, 257<br />
Array<br />
– dynamisch, 31, 122<br />
– Indizierung, 31<br />
– Initialisierung, 32<br />
– – explizit, 32<br />
– – implizit, 33<br />
– mehrdimensional, 34<br />
– statisch, 31<br />
associative Array, 507<br />
Ausnahmezustand, 311<br />
auto, 370<br />
automatic Memory, 370<br />
bad alloc, 333<br />
bad cast, 245, 333<br />
bad exception, 485<br />
bad typeid, 333, 481<br />
Base-Po<strong>in</strong>ter, 115, 120<br />
basic ios, 531<br />
basic istream, 523<br />
basic ostream, 523<br />
basic str<strong>in</strong>g, 519<br />
big Endian, 133<br />
bitand, 475<br />
bitor, 475<br />
bitset, 509<br />
Block, 73<br />
bool, 13, 15<br />
break, 76, 81<br />
C-Style Cast, 70<br />
call-by-reference, 103, 118<br />
– auf Po<strong>in</strong>ter, 126<br />
call-by-value, 85<br />
calloc, 365<br />
case, 76<br />
Cast, 61<br />
– C-Style, 70<br />
– Compiletime-checked, 67<br />
– const cast, 70, 112, 463<br />
– Downcast, 241<br />
– dynamic cast, 245, 246, 478, 481<br />
– re<strong>in</strong>terpret cast, 67, 69, 133, 247<br />
– remove-const, 70<br />
– Runtime-checked, 69<br />
– static cast, 67, 247<br />
– unchecked, 67<br />
– Upcast, 241<br />
catch, 312, 317<br />
catch(...), 484<br />
cerr, 522<br />
char, 13, 14<br />
char traits, 520<br />
Character-Code, 14<br />
c<strong>in</strong>, 522<br />
Class<br />
– friend, 247<br />
class, 171<br />
– Ableitung, 200<br />
– abstrakt, 232
564 Index<br />
– Access Specifiers, 173<br />
– – private, 174<br />
– – protected, 174<br />
– – public, 174<br />
– and struct, 171<br />
– Class Member, 192<br />
– – Deklaration, 193<br />
– Class Method, 193<br />
– copy Konstruktor, 179, 184<br />
– copy Konstruktor implizit, 186<br />
– default Konstruktor, 179<br />
– – implizit, 183<br />
– Destruktor, 175, 215<br />
– Konstruktor, 175, 215<br />
– – Basisklasse, 204<br />
– public Interface, 174<br />
Class Method, 163<br />
Class Member, 192<br />
– Deklaration, 193<br />
Class Method, 193<br />
Class Template, 419<br />
– Ableitung, 428<br />
– Deklaration, 419<br />
Compiletime-checked Cast, 67<br />
compl, 475<br />
conditional Expression, 75<br />
const, 47, 109<br />
– B<strong>in</strong>dung an, 131<br />
– Methode, 222<br />
const cast, 70, 112, 463<br />
cont<strong>in</strong>ue, 81<br />
copy Konstruktor, 179, 184<br />
– implizit, 186<br />
cout, 27, 29, 522<br />
Datentyp, 13<br />
– Aggregat, 37<br />
– bool, 13, 15, 30<br />
– boolscher, 13<br />
– char, 13, 14<br />
– Character, 13<br />
– double, 13, 18<br />
– float, 13, 18<br />
– float<strong>in</strong>g-po<strong>in</strong>t, 13<br />
– Ganzzahlen, 13<br />
– – Vorzeichenbit, 30<br />
– generisch, 405<br />
– Gleitkommazahlen, 13<br />
– <strong>in</strong>t, 13, 16<br />
– <strong>in</strong>tegraler, 13<br />
– long, 17<br />
– long double, 18<br />
– long <strong>in</strong>t, 17<br />
– long long, 17<br />
– long long <strong>in</strong>t, 17<br />
– mischen von, 19<br />
– natürliche Größe, 14<br />
– primitiver, 13<br />
– Qualifier, 14<br />
– – long, 14<br />
– – short, 14<br />
– – signed, 14<br />
– – unsigned, 14<br />
– short, 17<br />
– short <strong>in</strong>t, 17<br />
– signed char, 15<br />
– struct, 37<br />
– – Deklaration, 38<br />
– union, 41<br />
– unsigned, 17<br />
– unsigned char, 15<br />
– unsigned <strong>in</strong>t, 17<br />
– unsigned long, 17<br />
– unsigned long <strong>in</strong>t, 17<br />
– unsigned long long, 18<br />
– unsigned long long <strong>in</strong>t, 18<br />
– unsigned short, 17<br />
– unsigned short <strong>in</strong>t, 17<br />
– wchar t, 13, 15<br />
– Zeichen, 13<br />
DDD, 260<br />
default, 77<br />
default Konstruktor, 179<br />
– implizit, 183<br />
default Parameter, 89<br />
– Regeln, 90<br />
Def<strong>in</strong>ition, 20<br />
– Funktion, 28<br />
– Reference, 105<br />
Deklaration, 20<br />
– Class Template, 419<br />
– const, 47<br />
– extern, 21, 22, 46<br />
– Function Template, 407<br />
– Funktion, 28, 88<br />
– static, 46<br />
delete, 119<br />
– 0-Po<strong>in</strong>ter, 122<br />
– void *, 265<br />
delete[], 124<br />
deque, 501<br />
Destruktor, 175, 215<br />
Detailed Design Document, 260<br />
Diagraph, 476<br />
do ... while, 80<br />
doppelte Genauigkeit, 18
double, 13, 18<br />
double Precision, 18<br />
Downcast, 241<br />
dynamic B<strong>in</strong>d<strong>in</strong>g, 223<br />
dynamic cast, 245, 246, 478, 481<br />
– bad cast, 333<br />
dynamische Speicherverwaltung, 370<br />
dynamische Memory Verwaltung, 119<br />
dynamisches Array, 122<br />
e<strong>in</strong>fache Genauigkeit, 18<br />
End of L<strong>in</strong>e, 29<br />
endl, 29<br />
erweiterte Genauigkeit, 18<br />
Exception, 311, 482<br />
– bad alloc, 333<br />
– bad cast, 245, 333<br />
– bad exception, 485<br />
– bad typeid, 333, 481<br />
– catch, 312, 317<br />
– catch(...), 484<br />
– Deklaration, 316<br />
– fangen, 312<br />
– <strong>in</strong> Destruktor, 328, 488<br />
– <strong>in</strong> Konstruktor, 325<br />
– Kopie, 318<br />
– re-throw, 324<br />
– silent throw, 317<br />
– silent Catch, 329<br />
– Stack Unw<strong>in</strong>d<strong>in</strong>g, 312<br />
– temporäres Objekt, 318<br />
– throw, 312, 316, 318<br />
– try, 317<br />
– uncaught, 323<br />
– weiterwerfen, 324<br />
– werfen, 312<br />
explicit, 189, 191<br />
export, 448<br />
Expression, 73<br />
extended Precision, 18<br />
extern, 21, 22, 46<br />
false, 16<br />
Fehlerbehandlung, 311<br />
Fehlersignalisierung, 311<br />
float, 13, 18<br />
for, 80<br />
free, 119, 365<br />
free Memory, 370<br />
friend, 247<br />
Function Template, 407<br />
– Deklaration, 407<br />
– Overload<strong>in</strong>g, 414<br />
Funktion, 28, 85<br />
– Ambiguität, 92<br />
– default Parameter, 89<br />
– Def<strong>in</strong>ition, 28<br />
– Deklaration, 28, 88<br />
– <strong>in</strong>l<strong>in</strong>e, 96<br />
– Kopf, 85<br />
– ma<strong>in</strong>, 29<br />
– Overload<strong>in</strong>g, 86<br />
– Parameter, 85<br />
– return-Value, 28, 85<br />
– Rumpf, 85<br />
Funktionskopf, 85<br />
Funktionspo<strong>in</strong>ter, 125, 470<br />
Funktionsrumpf, 85<br />
generische Funktion, 407<br />
generischer Datentyp, 405<br />
goto, 82<br />
HAS-A Relation, 164<br />
Heap, 370<br />
Identifier, 13<br />
if ... else, 74<br />
ifstream, 528<br />
impliziter Po<strong>in</strong>ter, 104<br />
Inheritance, 160<br />
– e<strong>in</strong>fach, 161<br />
– mehrfach, 161, 205<br />
Initialisierung, 20, 188, 189<br />
– dynamic Memory, 121<br />
– explizit, 22<br />
– – Array, 32<br />
– implizit, 22<br />
– – Array, 33<br />
– – struct, 39<br />
– Konstruktoraufruf<br />
– – explizit, 189, 191<br />
– – implizit, 188, 192<br />
– Reference, 105<br />
– vs. Zuweisung, 350<br />
<strong>in</strong>l<strong>in</strong>e, 96, 233<br />
– Def<strong>in</strong>ition, 97<br />
– Deklaration, 97<br />
<strong>in</strong>t, 13, 16<br />
ios base, 531<br />
iostream, 29<br />
IS-A Relation, 164<br />
istream, 523<br />
istr<strong>in</strong>gstream, 530<br />
Iterator, 513<br />
Klasse, 156, 162<br />
Index 565
566 Index<br />
– Ableitung, 160, 164<br />
– Class Method, 163<br />
– Class-Member Variable, 162<br />
– Inheritance, 160<br />
– Member Variable, 162, 164<br />
– Methoden, 162<br />
– multiple Inheritance, 161, 205<br />
– s<strong>in</strong>gle Inheritance, 161<br />
– Vererbung, 160<br />
– – e<strong>in</strong>fach, 161<br />
– – mehrfach, 161, 205<br />
Kommentar, 27<br />
Komplement, 60<br />
Konstanten, 47<br />
Konstruktor, 175, 215<br />
Lifetime, 46<br />
– temporäres Objekt, 254<br />
list, 499<br />
Literals, 23<br />
– bool, 24<br />
– char, 24<br />
– – Escape Sequenz, 24<br />
– Ganzzahlen, 23<br />
– – dezimal, 23<br />
– – hexadezimal, 23<br />
– – long, 24<br />
– – oktal, 23<br />
– – unsigned, 23<br />
– Gleitkomma, 24<br />
– – float, 25<br />
– Str<strong>in</strong>g, 24<br />
– – Unicode, 24<br />
– Unicode, 24<br />
– wchar t, 24<br />
little Endian, 133<br />
logical Constness, 70, 114, 463<br />
logische Konstantheit, 70, 114<br />
logische Konstanz, 463<br />
long, 14, 17<br />
long double, 18<br />
long <strong>in</strong>t, 17<br />
long long, 17<br />
long long <strong>in</strong>t, 17<br />
lvalue, 57<br />
ma<strong>in</strong>, 29<br />
malloc, 119, 365<br />
map, 507<br />
Matrix, 35<br />
Member, 38<br />
memchr, 366<br />
memcmp, 366<br />
memcpy, 366<br />
memmove, 366<br />
memset, 366<br />
Methode<br />
– abstrakt, 232<br />
– Overload<strong>in</strong>g, 179<br />
– pure virtual, 234<br />
– struct, 169<br />
Methoden, 162<br />
Modul, 162<br />
Modularität, 3<br />
multimap, 507<br />
multiple Inheritance, 161, 205<br />
multiset, 509<br />
mutable, 114, 463<br />
Name-Mangl<strong>in</strong>g, 88<br />
Namespace, 453<br />
– Alias, 460<br />
– unnamed, 460<br />
– us<strong>in</strong>g, 458, 459<br />
namespace, 453<br />
new, 119<br />
– bad alloc, 333<br />
– Initialisierung, 121<br />
new[], 122<br />
– Base-Po<strong>in</strong>ter, 122<br />
new handler, 397<br />
not, 475<br />
not eq, 475<br />
NULL, 118<br />
numeric limits, 18, 531<br />
Objekt, 156<br />
ofstream, 528<br />
Operator<br />
– b<strong>in</strong>är arithmetisch, 57<br />
– Operatorentabelle, 55<br />
– Overload<strong>in</strong>g, 53<br />
– Precedence, 54<br />
– Rang, 54<br />
– unär arithmetisch, 58<br />
Operator Overload<strong>in</strong>g, 335<br />
– b<strong>in</strong>äre Operatoren, 336<br />
– delete, 363<br />
– delete mit zwei Parametern, 381<br />
– delete[], 371<br />
– globales delete, 385<br />
– globales delete[], 385<br />
– globales new, 385<br />
– globales new[], 385<br />
– new, 363<br />
– – bad alloc, 366<br />
– new(), 374
– new[], 371<br />
– Typumwandlung, 354<br />
– unäre Operatoren, 336<br />
Operatorentabelle, 55<br />
Optimierung, 99<br />
or, 475<br />
or eq, 475<br />
ostream, 523<br />
ostr<strong>in</strong>gstream, 530<br />
Overload<strong>in</strong>g, 86, 88<br />
– const / non-const, 250<br />
– Function Template, 414<br />
– Methode, 179<br />
– Operator, 335<br />
Overrid<strong>in</strong>g, 202<br />
Performance, 99<br />
– objektiv, 99<br />
– subjektiv, 99<br />
physical Constness, 463<br />
physikalische Konstanz, 463<br />
Placement Operator, 374<br />
Po<strong>in</strong>ter, 103, 114<br />
– , 118<br />
– Arithmetik, 123<br />
– Base-Po<strong>in</strong>ter, 120<br />
– Def<strong>in</strong>ition, 117<br />
– dereference, 117<br />
– dynamic Memory, 119<br />
– Funktionspo<strong>in</strong>ter, 125, 470<br />
– Interpretation, 115, 117<br />
– mehrfach, 128<br />
– NULL, 118<br />
– untypisiert, 133<br />
– void *, 133<br />
Po<strong>in</strong>terarithmetik, 104, 123<br />
Polymorphismus, 223<br />
Preprocessor, 135<br />
– #def<strong>in</strong>e, 138<br />
– #else, 137<br />
– #endif, 137<br />
– #if, 137<br />
– #ifdef, 137<br />
– #ifndef, 137<br />
– #<strong>in</strong>clude, 27, 136<br />
– Anweisung, 135<br />
priority queue, 503<br />
private, 174<br />
Programmzeile, 73<br />
protected, 174<br />
public, 174<br />
public Interface, 174<br />
pure virtual Method, 234<br />
Qualifier, 14<br />
– long, 14<br />
– short, 14<br />
– signed, 14<br />
– unsigned, 14<br />
queue, 502<br />
Index 567<br />
realloc, 119, 365<br />
Reference, 103<br />
– als return-Value, 107<br />
– const, 109<br />
– Def<strong>in</strong>ition, 105<br />
– impliziter Po<strong>in</strong>ter, 104<br />
– Initialisierung, 105<br />
re<strong>in</strong>terpret cast, 67, 69, 133, 247<br />
remove-const Cast, 70<br />
return, 85<br />
RTTI, 62, 477<br />
Run-Time-Type-Information, 62, 477<br />
Runtime-checked Cast, 69<br />
Scope, 46<br />
– Member, 170<br />
– static, 461<br />
Scope Operator, 170, 195, 458<br />
Selection Statements, 74<br />
self Po<strong>in</strong>ter, 346<br />
set, 508<br />
set new handler, 397<br />
set term<strong>in</strong>ate, 488<br />
set unexpected handler, 485<br />
short, 14, 17<br />
short <strong>in</strong>t, 17<br />
signed, 14<br />
signed char, 15<br />
silent Catch, 329<br />
silent throw, 317<br />
s<strong>in</strong>gle Inheritance, 161<br />
s<strong>in</strong>gle Precision, 18<br />
sizeof, 19<br />
Stack, 370<br />
stack, 506<br />
Stack Unw<strong>in</strong>d<strong>in</strong>g, 312<br />
Standard Output, 27<br />
Standard Template Library, 493<br />
Startadresse, 115, 120<br />
Statement, 73<br />
– Block, 73<br />
– conditional Expression, 75<br />
– Selection, 74<br />
– zusammengesetzt, 73<br />
static, 30, 46, 192, 193, 461<br />
– const, 198
568 Index<br />
static B<strong>in</strong>d<strong>in</strong>g, 223<br />
static Memory, 370<br />
static cast, 67, 247<br />
STL, 493<br />
– Algorithmus, 496, 534<br />
– Allocator, 495, 517<br />
– basic str<strong>in</strong>g, 519<br />
– bitset, 509<br />
– char traits, 520<br />
– Conta<strong>in</strong>er, 495<br />
– – assoziativ, 497<br />
– – e<strong>in</strong>fach, 497<br />
– Conta<strong>in</strong>er Operationen, 510<br />
– deque, 501<br />
– Funktionsobjekt, 496<br />
– Iterator, 495, 513<br />
– list, 499<br />
– map, 507<br />
– multimap, 507<br />
– multiset, 509<br />
– Numerik, 496<br />
– priority queue, 503<br />
– queue, 502<br />
– set, 508<br />
– stack, 506<br />
– Str<strong>in</strong>g, 495<br />
– str<strong>in</strong>g, 520<br />
– vector, 497<br />
– wstr<strong>in</strong>g, 520<br />
Stream, 496, 521<br />
– basic ios, 531<br />
– basic istream, 523<br />
– basic ostream, 523<br />
– cerr, 522<br />
– c<strong>in</strong>, 522<br />
– cout, 522<br />
– ifstream, 528<br />
– ios base, 531<br />
– istream, 523<br />
– istr<strong>in</strong>gstream, 530<br />
– ofstream, 528<br />
– ostream, 523<br />
– ostr<strong>in</strong>gstream, 530<br />
– wistream, 523<br />
– wostream, 523<br />
Streams<br />
– cout, 27<br />
str<strong>in</strong>g, 520<br />
struct, 37, 168<br />
– Base-Po<strong>in</strong>ter, 40<br />
– Deklaration, 38<br />
– Member, 38<br />
– – Anordnung, 40<br />
– Methode, 169<br />
– – Def<strong>in</strong>ition, 170<br />
– – Deklaration, 169<br />
switch, 75<br />
Template, 405<br />
– Class, 419<br />
– – Ableitung, 428<br />
– – Deklaration, 419<br />
– Codemenge, 431<br />
– export, 448<br />
– Function, 407<br />
– – Deklaration, 407<br />
– – Overload<strong>in</strong>g, 414<br />
– Methoden, 445<br />
– Spezialisierung, 432<br />
– – Def<strong>in</strong>ition, 434<br />
– – Deklaration, 434<br />
temporäre Objekte, 253<br />
temporäre Variable, 112<br />
this, 346<br />
throw, 312, 316, 318<br />
Trigraph, 477<br />
true, 16<br />
try, 317<br />
Typdef<strong>in</strong>ition, 49<br />
Type-Identification, 62<br />
type <strong>in</strong>fo, 65, 480<br />
Typecast, 61<br />
typedef, 49<br />
typeid, 63, 478<br />
– bad typeid, 333, 481<br />
Typumwandlung<br />
– implizit, 58<br />
uncaught exception, 490<br />
unchecked Cast, 67<br />
unexpected handler, 485<br />
Unicode, 15<br />
union, 41, 465<br />
– anonymous, 44, 468<br />
– Destruktor, 468<br />
– Konstruktor, 468<br />
– Member, 41<br />
– Methode, 468<br />
– Speicherbedarf, 41<br />
unnamed Namespace, 460<br />
unsigned, 14, 17<br />
unsigned char, 15<br />
unsigned <strong>in</strong>t, 17<br />
unsigned long, 17<br />
unsigned long <strong>in</strong>t, 17<br />
unsigned long long, 18<br />
unsigned long long <strong>in</strong>t, 18
unsigned short, 17<br />
unsigned short <strong>in</strong>t, 17<br />
Upcast, 241<br />
us<strong>in</strong>g, 29, 458, 459<br />
Variable, 13<br />
– auto-Variable, 370<br />
– Def<strong>in</strong>ition, 77<br />
– global, 29, 370<br />
– Life Cycle, 46<br />
– Life-Cycle, 178<br />
– Lifetime, 46, 77<br />
– Scope, 46, 77<br />
– temporär, 112<br />
Varianten, 41<br />
vector, 497<br />
Vererbung, 160<br />
– e<strong>in</strong>fach, 161<br />
– mehrfach, 161, 205<br />
virtual, 223<br />
– Destruktor, 230, 231<br />
virtual Table, 223<br />
void, 86<br />
volatile, 477<br />
wchar t, 13, 15<br />
while, 79<br />
Wiederverwendbarkeit, 3<br />
wistream, 523<br />
wostream, 523<br />
wstr<strong>in</strong>g, 520<br />
xor, 475<br />
xor eq, 475<br />
Index 569<br />
Zeichensatztabelle, 15<br />
– Annahmen über, 20<br />
zusammengesetztes Statement, 73
Über den Autor<br />
Klaus Schmaranz ist Mitglied des Instituts<br />
für Informationsverarbeitung<br />
und computergestützte neue Medien<br />
(IICM) der Technischen Universität<br />
Graz. Im Rahmen se<strong>in</strong>er Forschungsund<br />
Projekttätigkeit ist er technischer<br />
Leiter mehrerer sehr großer Softwareprojekte<br />
im Bereich massiv verteilter<br />
Komponenten- und Objektsysteme.<br />
Der E<strong>in</strong>satz dieser Systeme reicht<br />
vom allgeme<strong>in</strong>en Informations- und<br />
Knowledge-Management Bereich über<br />
den mediz<strong>in</strong>ischen Bereich bis h<strong>in</strong> zur<br />
Der Autor auf dem Weg zu e<strong>in</strong>er<br />
Projektbesprechung<br />
Luft- und Raumfahrtunterstützung. Im Rahmen dieser Projekttätigkeiten<br />
gibt es auch e<strong>in</strong>e sehr enge Kooperation mit dem Deutschen Zentrum für<br />
Luft- und Raumfahrt (DLR).<br />
Neben dieser Forschungs- und Projekttätigkeit hält der Autor an der TU-<br />
Graz e<strong>in</strong>e Reihe von Vorlesungen zu verschiedenen Themen im Bereich der<br />
<strong>Softwareentwicklung</strong>. Dies umfasst sowohl e<strong>in</strong>führende Vorlesungen <strong>in</strong> die<br />
strukturierte Entwicklung mit C, C ++ und Java als auch sehr spezielle Vorlesungen<br />
zum Thema <strong>Softwareentwicklung</strong> für große Programmbibliotheken,<br />
verteilte Objektsysteme, verteilte Komponentensysteme und Übertragungsprotokolle.