30.06.2013 Aufrufe

Softwareentwicklung in C++ - ASC

Softwareentwicklung in C++ - ASC

Softwareentwicklung in C++ - ASC

MEHR ANZEIGEN
WENIGER ANZEIGEN

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.

Hurra! Ihre Datei wurde hochgeladen und ist bereit für die Veröffentlichung.

Erfolgreich gespeichert!

Leider ist etwas schief gelaufen!