Kontakt  Anfahrtplan   Datenschutzerklärung  Impressum Englisch 

Service: C/C++ Tipps: Konstanten, Enums und Inlines statt Makros benutzen


Man könnte diesen Artikel auch anders benennen: Benutzen Sie den Compiler statt des Präprozessors. Jede Präprozessor-Anweisung wird potentiell nicht als Teil der Programmiersprache an sich betrachtet. Genau das ist das Problem. Wenn Sie zum Beispiel folgende Zeile schreiben:

    #define PI 3.1416

wird der symbolische Name PI höchstwahrscheinlich niemals vom Compiler gesehen. Stattdessen wird PI vom Präprozessor aus dem Code entfernt bevor der Compiler in Aktion tritt.

Dies kann zum Beispiel während der Kompilierung sehr irritierend sein, weil der Compiler bei einem Fehler die Zahl, nicht den symbolischen Namen anzeigt. So verbringt man möglicherweise eine Menge Zeit mit der Suche nach der entsprechenden Deklaration. Dies umso mehr, wenn die betreffende Deklaration in einer Header Datei steht, die man nicht selbst geschrieben hat.

Die Lösung besteht darin, den Makro durch eine Konstante zu ersetzen:

    const double Pi = 3.1416; // Großschreibung geändert,
                              // denn dies ist eine Konstante, kein Makro.

Falls Sie, wie oben beschrieben, auf eine Makrodeklaration aus einer 'fremden' Header-Datei angewiesen sind, müssen Sie trotzdem nicht unbedingt mit dem Makro arbeiten:

    const double Pi = PI; // Konstante definieren und dabei #define zuweisen

Diese Konstante wird nun definitiv vom Compiler erkannt. Außerdem wird bei Fließkommazahlen - wie in diesem Beispiel - unter Umständen der Code kleiner. Das liegt daran, dass der Präprozessor möglicherweise eine Vielzahl von Kopien des Wertes von PI in den Quellcode einfügt. Von 'Pi' auf der anderen Seite gibt es immer nur eine einzige Kopie.

Was man berücksichtigen sollte, wenn man Makros durch Konstanten ersetzt.

Bei Zeigern, die ja typischerweise in Header Dateien stehen, ist es wichtig, den Zeiger als const zu deklarieren (zusätzlich zu dem worauf der Zeiger zeigt). Also zum Beispiel so:

    const char * const None = "None";

Um die Sichtbarkeit von Konstanten in Klassen zu begrenzen, müssen diese Elemente der Klasse sein. Um sicherzustellen, dass nur eine Kopie der Konstanten existiert, muss sie ein statisches Element sein.

    class Spass
    {
    private:
        static const long Zaehler = 5;
        int SpassMacher[Zaehler];
        // ...
    }:

Hier steht eine Deklaration für 'Zaehler', keine Definition. Normalerweise verlangt C++ Definition für alles und jedes, das benutzt werden soll. Für statische Konstanten innerhalb von Klassen mit ganzzahligen Typen gilt jedoch eine Ausnahme.

Hiervon gibt es wiederum folgende Ausnahme: Wenn Sie die Adresse einer Klassenkonstante verwenden möchten oder wenn ihr Compiler fälschlicherweise die Definition beanstandet (obwohl sie die Adresse nicht verwenden), müssen sie eine gesonderte Definition schreiben.

    const long Spass::Zaehler; // In der Implementierungsdatei.
                               // Keinen Wert zuweisen. Warum: siehe unten.

Da der Wert der Konstanten bei der Deklaration angegeben wurde, ist bei der Definition keine Wertangabe mehr erforderlich.

Einige Compiler (insbesondere ältere) akzeptieren diese Schreibweise nicht. Außerdem funktioniert sie nur für ganzzahlige Typen. In diesen Fällen kann die Wertzuweisung an die Stelle der Definition verschoben werden.

    class Foo
    {
    private:
        static const double Baa;
        // ...
    };
    const double Foo::Baa = 2.25; // In der Implementierungsdatei.

Compiler, die eine Wertzuweisung im Rahmen der Deklaration nicht erlauben, verursachen ein Problem, wenn die betreffende Konstante bereits zur Compilierzeit initialisiert sein muss (z.B. bei Größenangaben für Arrays). In diesem Fall kann man sich mit einer enum-Deklaration aushelfen.

    class Spass
    {
    private:
        enum { Zaehler = 5 };
        int SpassMacher[Zaehler];
        // ...
    }:

Darüberhinaus hat diese Vorgehensweise den Vorteil, dass enum-Elemente nicht als Zeiger verwendet werden können und somit die versehentliche Verwendung als Zeiger definitiv unterbunden wird.

Ein weiterer wichtiger Grund, diesen Mechanismus zu kennen und zu verstehen, besteht schlicht darin, dass er sehr gerne verwendet wird. Das heißt: wenn Sie in Zukunft solche Konstruktionen in fremdem Quellcode sehen, verstehen Sie was es damit auf sich hat.

Schließlich: Inline-Funktionen statt Makros

Betrachten Sie den folgenden Makro:

    #define CALL_BY_MAX(a, b) f((a) > (b) ? (a) : (b))

Solche Art von Makros haben eine Vielzahl von Nachteilen. Z.B.: Alleine schon darüber nachzudenken verursacht Bauchschmerzen.

Zuerst ist es sehr wichtig, bei solchen Makros immer daran zu denken, alle Argumente in Klammern zu fassen. Wird dies vergessen, treten massive Schwierigkeiten auf, wenn der Makro mit Ausdrücken aufgerufen wird. Doch selbst wenn dies berücksichtigt wird, können völlig unerwartete Effekte auftreten:

    long a = 6, b = 1;
    CALL_BY_MAX(++a, b);       // incrementiert zweimal
    CALL_BY_MAX(++a; b + 10);  // incrementiert einmal

In diesen Fällen hängt die Anzahl der Inkrementierungen, vom Ergebnis des Vergleichs innerhalb des Makros ab.

Glücklicherweise besteht keine Notwendigkeit, sich mit solchem Unsinn herumzuschlagen. Mit Hilfe von Vorlagenfunktionen erreicht man sowohl die Effektivität eines Makros als auch das eindeutige Verhalten und die Typsicherheit einer normalen Funktion.

    template
    inline void CallByMax(const T& a, const T& b)
    {
        f(a > b ? a : b);
    }

Diese Funktion hat sämtliche Vorteile des entsprechenden Makros ohne die oben beschriebenen Nachteile. Darüberhinaus unterstützt dieser Mechanismus Kontext und Zugriffsrechte, da es sich um eine echte Funktion handelt. So ist es möglich, eine solche Funktion im Rahmen einer Klasse 'private' zu deklarieren - etwas, das mit einem Makro unmöglich ist.

Wenn Sie mit Template-Funktionen noch keine Erfahrung haben: Template-Funktionen werden genau wie Makros in Header-Dateien geschrieben. Beachten Sie dazu bitte auch den Artikel Grundlegendes zur vorlagenbasiserter Programmierung.

Hinweis:

Die Idee zu diesem Artikel basiert auf einem Abschnitt aus dem Buch 'Effective C++ - 55 Specific Ways to Improve Your Programs and Designs' von Scott Meyers aus dem Addison-Wesley Verlag (ISBN: 0-31-33487-6).

Seitenanfang


Wider dem Blindflug!

Wenn Sie wissen wollen, was so alles auf Ihrem PC passiert!

Lesen Sie dies ...


Einfach - Schnell - Günstig!

Tischrechner als Software.

Jetzt herunterladen und kostenlos testen!

Mehr lesen ...


Effektiver Arbeiten!

Tastenkombinationen können PC-Arbeit erheblich beschleunigen.

Mehr lesen ...


Kleine Helfer für Sie:

Eine Reihe von kostenlosen Online-Berechnungen zur Erleichterung der täglichen Arbeit.

Ausprobieren ...


Wissenswertes!

Sicherheit im PC-Bereich

Es existiert eine kostenlose, einfache und äußert effektive Methode, fast alle Viren, Trojaner, Würmer ...

Mehr lesen ...