Forwärtsdeklaration: Wo macht das Sinn?



  • Hi,
    ich habe bei KDEs "Common Programming Mistakes" Wiki gelesen, dass Vorwärtsdeklarationen wo möglich, includes vorzuziehen sei, da das schneller kompiliert.
    Meine Frage: Wo macht das Sinn? Doch nur bei privaten Klassen und structs, oder?
    Komischerweise verwendet das Beispiel aber Qt-Module:

    #include <QWidget>     // slow
    #include <QStringList> // slow
    #include <QString>     // slow
    class SomeInterface
    {
    public:
        virtual void widgetAction( QWidget *widget ) =0;
        virtual void stringAction( const QString& str ) =0;
        virtual void stringListAction( const QStringList& strList ) =0;
    };
    
    class QWidget;     // fast
    class QStringList; // fast
    class QString;     // fast
    class SomeInterface
    {
    public:
        virtual void widgetAction( QWidget *widget ) =0;
        virtual void stringAction( const QString& str ) =0;
        virtual void stringListAction( const QStringList& strList ) =0;
    };
    

    Kann es sein, dass man das anwendet, weil SomeInterface sowieso nur ein Interface ist, und die Qt-Klassen lediglich als Parameter verwendet werden? Die Situation wäre doch eine andere, wenn statt ein Interface eine Klasse davon Gebrauch macht, nicht wahr?
    Was ist der Nachteil dieser Methode?

    EDIT: https://techbase.kde.org/Development/Tutorials/Common_Programming_Mistakes

    L. G.,
    IBV



  • Wer das Zeug dann verwendet, includiert eben alles, was er braucht.

    Die SomeInterface.cpp wird zum Beispiel alles inkludieren. Jemand, der SomeInterface sinnvoll verwendet, wird wohl auch alles inkludieren. Ja, es compiliert so nicht langsamer. Aber schneller? Denke, da muss noch mehr zusammenkommen. Ne weiter Klasse, die im Header die SomeInterface forward-Deklariert und in der .cpp nur die SomeInterface.h inkludiert, wird ja nicht klappen.

    Andererseits, so ein i7… Was soll der Geiz? Hab seit laangem nicht mehr über ganz normales Sparsam-Includieren die Compilierzeit optimiert.

    Nachteil ist natürlich, daß man manchmal überraschende Compilerfehler kriegt vonwegen leeren oder nicht definierten Klassen.



  • volkard schrieb:

    Andererseits, so ein i7… Was soll der Geiz? Hab seit laangem nicht mehr über ganz normales Sparsam-Includieren die Compilierzeit optimiert.

    Naja, wir reden hier von einem Projekt wie KDE, das wirklich riesig ist!
    Ich finde diese Info jedenfalls interessant!

    L. G.,
    IBV



  • volkard schrieb:

    Andererseits, so ein i7… Was soll der Geiz? Hab seit laangem nicht mehr über ganz normales Sparsam-Includieren die Compilierzeit optimiert.

    Ach was, ich hab auch einen fetten i7 in der Arbeit und es dauert schon eine ganze Weile, unser Projekt zu kompilieren, auch nur einzelne Teile davon. Da machen Include Optimierungen auch sehr viel aus. Auch in der Hinsicht, dass man nicht plötzlich das halbe Projekt neubauen muss, nur weil man irgendwo was in einem Header geändert hat.



  • Bitte triff eine Entscheidung ob du Forward oder Voraus schreiben willst.



  • Mechanics schrieb:

    Auch in der Hinsicht, dass man nicht plötzlich das halbe Projekt neubauen muss, nur weil man irgendwo was in einem Header geändert hat.

    Genau das.

    Wenn es um Header vom eigenen Projekt geht: Vorausdeklarationen, so viel wie geht.
    Wenn es um Library-Header geht: Keine Vorausdeklaration, ausser in Ausnahmefällen, wenn es nachgewiesenermassen langsamer wird.

    Damit verschnellert man einen erneuten Erstellungsvorgang. Innerhalb der KDE Libs ist es sinnvoll QString vorauszudeklarieren, ausserhalb davon nicht.

    Zudem kann es mit zukünftigen Bibliotheksversionsn nicht mehr funktionieren. Versuche mal, std::string vorauszudeklarieren -- es geht nicht.



  • Mechanics schrieb:

    Auch in der Hinsicht, dass man nicht plötzlich das halbe Projekt neubauen muss, nur weil man irgendwo was in einem Header geändert hat.

    Aber ist es in dem obigen Beispiel nicht so, dass jemand, der SomeInterface.h includiert, nicht automatisch auch QWidget, QStringList, QString includieren muss? Wo ist da der Vorteil?

    sgtmew schrieb:

    Wenn es um Library-Header geht: Keine Vorausdeklaration, ausser in Ausnahmefällen, wenn es nachgewiesenermassen langsamer wird.

    Wieso?

    sgtmew schrieb:

    Damit verschnellert man einen erneuten Erstellungsvorgang. Innerhalb der KDE Libs ist es sinnvoll QString vorauszudeklarieren, ausserhalb davon nicht.

    QString kommt von Qt und nicht von KDE, daher verstehe ich deine Schlussfolgerung zu dem von dir Zuvorgesagten nicht.

    L. G.,
    IBV



  • IBV schrieb:

    Aber ist es in dem obigen Beispiel nicht so, dass jemand, der SomeInterface.h includiert, nicht automatisch auch QWidget, QStringList, QString includieren muss?

    Nein, warum? Vielleicht muss er nur einen Zeiger/Referenz auf SomeInterface weiterleiten und ruft da nichts auf, dann muss er auch nichts davon inkludieren. Vielleicht ruft man nur eine Funktion davon auf und muss dann z.B. QString inkludieren, aber nicht die anderen Sachen.
    Vielleicht hat man eine automatische Membervariable vom Typen SomeInterface. Die verwendet man nur intern in der cpp Datei, die ist aber halt im Header deklariert. Und jetzt gibts vielleicht tausend andere Klassen im Projekt, die diese Klasse von dir verwenden und inkludieren. Die interessiert es aber gar nicht, dass die intern SomeInterface verwendet. Deswegen ist es auch gut, wenn da keine unnötigen indirekten Includes reinkommen.



  • Hey, das ergibt absolut Sinn, was du da sagst, Mechanics! 🙂

    L. G.,
    IBV


  • Mod

    Stellt sich natürlich die Frage, wieso das Framework keine Header mit Forwarddeklarationen zur Verfügung stellt, wenn Vorwärtsdeklarationen praktisch tatsächlich so einen großen Einfluss auf den Compiliervorgang haben.



  • camper schrieb:

    Stellt sich natürlich die Frage, wieso das Framework keine Header mit Forwarddeklarationen zur Verfügung stellt, wenn Vorwärtsdeklarationen praktisch tatsächlich so einen großen Einfluss auf den Compiliervorgang haben.

    Na weil #include eine Datei von der Festplatte lesen muss, was langsam ist!!!11



  • camper schrieb:

    Stellt sich natürlich die Frage, wieso das Framework keine Header mit Forwarddeklarationen zur Verfügung stellt, wenn Vorwärtsdeklarationen praktisch tatsächlich so einen großen Einfluss auf den Compiliervorgang haben.

    Woher soll das Framework wissen, welche Vorwärtsdeklaration du brauchst? Soll es einfach alles vorwärtsdeklarieren, was es zu vorwärtsdeklarieren gibt?



  • rootofallevil schrieb:

    Na weil #include eine Datei von der Festplatte lesen muss, was langsam ist!!!11

    Jupp, historisches Ansinnen das.
    Inzwischen gibt es genug RAM. Schon überhaupt nicht mehr davon zu reden, an langsamen SSDs zu hängen. Außerdem werden Headers viel seltener geändert als Implementierungsdateien; bei großen Projekten tut man der Compilezeiten wegen gleich weniger Code in Header schreiben. Man muss sich da nicht viel quälen.



  • IBV schrieb:

    Woher soll das Framework wissen, welche Vorwärtsdeklaration du brauchst? Soll es einfach alles vorwärtsdeklarieren, was es zu vorwärtsdeklarieren gibt?

    Genau. Siehe z.B. <iosfwd>.



  • IBV schrieb:

    Woher soll das Framework wissen, welche Vorwärtsdeklaration du brauchst? Soll es einfach alles vorwärtsdeklarieren, was es zu vorwärtsdeklarieren gibt?

    Klar. Oder wenigstens sehr viel und alle "schweren" Headers. Auf jeden Fall wäre dort der viel Effekt größer, als wenn man es bloß als Benutzer macht (aber "größer" impliziert nicht "groß").

    Nicht zu vergessen Pimpl, das reduziert Header-Abhängigkeiten auch, läßt sich so gestalten, daß es im Release-Build aus ist, oder man läßt es an und macht so die Binärschnittstelle viel beständiger.



  • In grossen Projekten machen Forward Declarations schon Sinn.
    Kann die Compilezeiten schnell auf die Hälfte oder noch weniger runterbringen.
    Und wenns mit nem kompletten Rebuild mal auf ne Stunde oder gar mehrere Stunden zugeht, dann ...

    Nur dann ist's oft schon zu spät, weil es verdammt lange dauern würde überall dort wo man es ursprünglich nicht gemacht hat die Forward-Declarations nachzuziehen, bzw. die Forward Declaration Headers statt der "vollen" Headers zu inkludieren.



  • volkard schrieb:

    Andererseits, so ein i7… Was soll der Geiz? Hab seit laangem nicht mehr über ganz normales Sparsam-Includieren die Compilierzeit optimiert.

    Bei meinen Privatprojekten bin ich mittlerweile dazu übergegangen einfach alles in Header zu schreiben. Selbst wenn ich System-Header oder sowas brauche die ich verstecken will, baue ich das so auf, dass ich zwei separate .hpp Dateien habe und die zweite am Ende meiner main.cpp #include. Hab jetzt auch bei 30+ Dateien noch kein Großes Problem damit gehabt. Man macht so halt quasi immer nen full-rebuild, aber dafür hat man nur eine einzige Übersetzungseinheit, was dazu führt dass die ganzen Templates im Code (die nun mal in Headern stehen müssen) auch alle nur ein mal kompiliert werden. Bei 250+ Dateien wird es vmtl wieder langsamer, aber für weniger reichts und ist erstaunlich angenehm. 🤡



  • volkard schrieb:

    Andererseits, so ein i7… Was soll der Geiz? Hab seit laangem nicht mehr über ganz normales Sparsam-Includieren die Compilierzeit optimiert.

    War wohl mißverständlich. Gemeint war

    Andererseits, so ein i7… Was soll der Geiz? Hab seit laangem nicht mehr über ganz normales Sparsam-Includieren hinaus die Compilierzeit optimiert.

    Das ganz normale Sparsam-Includieren macht Wald-Und-Wiesen-Projekte schon mindestens dreimal so schnell compilierbar und senkt Einfluss und Notwendigkeit anderer Tricks. Die könnt Ihr natürlich gerne treiben, ich werfe halt mal in den Raum, daß sie vielleicht mit jedem weiteren Jahr weniger notwendig werden.



  • Aaaaaaa, OK 🙂
    Ja, war misverständlich.


Anmelden zum Antworten