Optimierung beim Arbeiten mit Objekten


Bei der Diskussion um den Kopierkonstruktor und die Übergabe von Parametern haben wir gesehen, daß das Arbeiten mit Objekten u. U. zu einer ganzen Reihe von Methodenaufrufen führt. Jeder Aufruf kostet Laufzeit. Die Frage ist daher berechtigt, ob nicht die vielen schönen Möglichkeiten von C++ letztlich zu langsamen Programmen führen. Um es vorweg zu nehmen: die Laufzeit von C++ Programmen entspricht in etwa den C Programmen. Diese Aussage ist nicht einfach nachzuprüfen, da sich ganz gleiche Programme nicht schreiben lassen. Außerdem hängt die Effizienz auch von der Größe ab. Ein kleines C++ Programm zur Demonstration eines bestimmten Sachverhaltes wird vielleicht langsamer sein als ein entsprechendes C Programm. Ein großes Programm, das die Vorteile, insbesondere die bessere Strukturierung, von C++ nutzen kann, wird etwas schneller ablaufen.


Wenden wir uns zwei Möglichkeiten zu, die der Programmierer zur Optimierung verwenden kann. Beim Aufruf von Funktionen haben wir gesehen, daß die Übergabe von großen Objekten aufwendig ist. Der Kopierkonstruktor muß den formalen Parameter anlegen und initialisieren. Insbesondere bei Objekten mit dynamischem Speicherbedarf läuft dann innerhalb des Kopierkonstruktors auch noch die Verwaltung des Freispeichers bei “new”.

Optimierung durch Referenzübergaben


Übergeben Sie an Methoden und Funktionen komplexe Objekte nach Möglichkeit mit Hilfe der Referenz. Aber: keine Rückgabe von lokalen Variablen und Parametern mit Referenzen!



Schauen wir uns die Auswirkung an einem bekannten Beispiel an. Die Klasse “Zeile” wollen wir einmal mit Wertübergabe von Objekten und einmal mit Referenzübergabe von Objekten definieren. Um die Arbeitsweise zu sehen, bekommt jede Methode eine Ausgabeanweisung. An Hand der Bildschirmausgabe können wir dann den Unterschied erkennen. Rahmen1In der Klassendefinition wurden alle Methoden, soweit zulässig, mit formalen Parameter deklariert, die die Wertübergabe benutzen. Der Kopierkonstruktor muß seinen Parameter mit Referenzübergabe erhalten. Mit jeder Übergabe oder Rückgabe eines Objektes per Wertübergabe muß ein Kopierkonstruktor laufen. In unserer Klasse “Zeile” gibt es noch den Destruktor. Für jedes angelegte Objekt, also auch für formale Parameter und anonyme Rückgabeobjekte, wird der Destruktor gerufen.


Optimierung beim Arbeiten mit Objekten    Seite 133


Eine Chance zur Verbesserung des Laufzeitverhaltens bietet die Übergabe per Referenz. Da bei einer Referenz technisch nur eine Adresse und kein Objekt übergeben werden muß, entfällt auch die sonst notwendige Initialisierung mit einem Konstruktor. In unserer Klasse “Zeile” legt der Konstruktor dynamisch Speicherplatz an. Deshalb benötigen wir außerdem noch einen Destruktor, der die Aufräumarbeiten erledigt. Doch nun zuerst zur Implementierung mit Wertübergabe (Bild 10-3).


Seite 134    Einsteigerseminar C++

Rahmen4


Optimierung beim Arbeiten mit Objekten    Seite 135

Rahmen4 (2)Und das Hauptprogramm.


Rahmen6


zmainopcpp1


Seite 136    Einsteigerseminar C++

Im Hauptprogramm werden Zeilen-Objekte angelegt. Der “+”-Operator konkateniert Zeilen (hängt sie zusammen). Das Ergebnis wird mit der Zuweisung in dem Objekt “z3" abgelegt. Die Ausgabe zeigt schließlich das Ergebnis. Alle Methoden wurden mit Wertübergabe definiert. Eine Ausgabe in der Methode zeigt die Bearbeitung an. Mit diesem Hauptprogramm und der Implementierung erhalten wir die folgende Bildschirmausgabe (evtl. Compiler abhängig!).


Rahmen8

Die Zeilennummern und die kursiven Kommentare sind wieder eingefügt. In diesem Programm werden eine ganze Anzahl Methoden aufgerufen. Wenn Sie die einzelnen Aufrufe nachvollziehen wollen, beachten Sie, daß die anonymen Zwischenobjekte erst am Schluß des Programms entfernt werden. Beachten Sie auch, daß die Ausgabe nicht bei jedem Compiler gleich sein muß. Manche Compiler legen auch Parameterobjekte im Freispeicher an und übergeben nur eine Referenz.


Optimierung beim Arbeiten mit Objekten    Seite 137

Das gerade verwendete Beispiel wollen wir nun mit Hilfe der Referenzen optimieren. Jede Referenz sollte einen Kopierkonstruktor- und einen Destruktoraufruf einsparen. Weiter benutzen wir zwei Möglichkeiten, sogenannte “inline”- Funktionen zu definieren.


Rahmen10

Optimierung mit “inline”-Funktionen

Eine “inline”-Funktion ist eigentlich keine Funktion, sondern ein Makro. Eine Funktion wird aufgerufen, ein Makro expandiert. Bei der Funktion gibt es den Code genau einmal und beliebig viele Aufrufe. Bei Makros wird der Code bei jeder Verwendung expandiert d. h. einkopiert. Trotzdem bieten “inline”-Funktionen gerade bei sehr kurzen Methoden erhebliche Laufzeitgewinne, da nichts übergeben werden muß und der gesamte Verwaltungsaufwand für Funktionsaufrufe entfällt. Beachten Sie, dass das Makro in der Informationsdatei steht, nicht in der Implementierungsdatei.


Seite 138    Einsteigerseminar C++

 

Rahmen12

Ebenso wie das Schlüsselwort “register” stellt “inline” eine Empfehlung an den Compiler dar. Er soll, aber er muß sich nicht daran halten.


Innerhalb der Klassendefinition ist “inline” vorgegeben. Ein Funktionsblock in geschweiften Klammern definiert daher automatisch ein Makro.


Das Hauptprogramm muß der geänderten Implementierung nicht angepaßt werden (mit Ausnahme des “include” mit der geänderten Informationsdatei).Die Veränderung durch die “inline”-Funktionen muß man mit geeigneten Programmen messen. Aber wenn Sie bedenken, daß bei großen und umfangreichen Klassen, wie “Artikel”, “Kunde” oder "Fenster"


Optimierung beim Arbeiten mit Objekten    Seite 139

 

Rahmen14


Seite 140    Einsteigerseminar C++

sehr viele und oft kleine Methoden benutzt werden, kann der Gewinn erheblich sein.


Diese Makros unterscheiden sich grundsätzlich von den bisher bekannten “#define”-Makros des Präprozessors. Der Präprozessor kann nur Texte ersetzen, nicht aber Typen überprüfen. Im Gegensatz dazu werden “inline”-Funktionen ihrem Namen gerecht. Der Compiler (und nicht der Präprozessor) garantiert eine völlige Gleichstellung mit normalen Methoden. Alle Überprüfungen und Schutzkonzepte bleiben in Kraft. Nur die Art der Codegenerierung ändert sich.



Kleine Methoden werden als Makros schneller.



Schauen wir uns die geänderte Ausgabe an.


Rahmen16

Gegenüber dem Beispiel, das nur Wertübergaben verwendet hat, haben wir drei Wertübergaben einsparen können. Und mit jeder Wertübergabe einen Konstruktor- und einen Destruktoraufruf. In diesem kurzen Beispiel waren das immerhin sechs Funktionsaufrufe.


Optimierung beim Arbeiten mit Objekten    Seite 141

Hinweise zur Weiterarbeit

1) Was sind die grundlegende Unterschiede der beiden Möglichkeiten, ein Makro zu definieren? (#define oder inline. Geben Sie je ein Beispiel.


2) Schreiben Sie ein Programm, das eine Methode 10000 mal aufruft. Implementieren Sie die Methode zuerst als Funktion und dann als Makro Messen Sie den Zeitunterschied beim Ablauf. Vielleicht sollten Sie hier die Zeitfunktionen von C benutzten.

Im nächsten Kapitel

Die bisher besprochenen Klassen bildeten Vorlagen, nach denen Objekte erstellt wurden. Mit jedem neuen Anlegen eines Objektes wurde gemäß des Bauplans Platz reserviert und mit einem Konstruktor vorbelegt. Eine Eigenschaft, die in der Klasse deklariert wurde, existierte nach dem Anlegen von 100 Objekten genau 100 mal. Im nächsten Kapitel werden wir nun Klassenelemente besprechen, die nur zu einer Klasse, nicht aber zu Objekten gehören.






Seite 142    Einsteigerseminar C++