Abhängigkeiten zwischen Dateien minimieren!



  • Hier sollen Tipps gesammelt werden, wie man sein Projekt auf mehreren Dateien verteilt, so dass bei einer kleinen Änderung möglichst wenig neu kompiliert werden muss.
    Da ich zur Zeit diesbezüglich ein paar Probleme habe, hab ich mich mal etwas schlau gemacht und hoffe, da gibt es noch was zum Ergänzen (würde mir helfen 🙄 ).

    - Wenn möglich, nur in .cpp Dateien inkludieren, nicht in Header
    - Wenn man Referenzen oder Pointer auf Objekte verwendet, anstatt die Klassen selber, reicht eine Klassen-Deklaration, solange man das Objekt nicht benutzt und der Compiler keine Informationen über die Größe des Objekts braucht. Das lässt sich in einer Headerdatei oft gut machen.
    - Methoden möglichst nicht in der .h sondern in der .cpp definieren. (inline vermeiden)
    - Klassendeklarationen kann man in einer Header sammeln und in andere Header immer wieder einbinden.

    Ich kann meistens verhindern, dass ich in einer Header mehrere andere .h Dateien inkludieren muss. Weiss jemand noch ein paar Möglichkeiten, das Inkludieren in .cpp Dateien ebenfalls zu minimieren? Mir ist natürlich schon klar, dass man bei einem Projekt streng die einzelnen Teile von einander Trennen soll und die Schnittstelle möglichst klein halten soll, aber wie geht man da in der Praxis vor?



  • wie im wirklichen leben:
    - den bösewicht finden
    - den bösewicht killen

    mal ein mittleres projekt nehmen. eins, das die konkurrent in 2,5 stunden compiliert.

    du hast natürlich schichten getrennt und nach MVC-konzept das modell (als modell.lib (nicht modell.dll!)), die ständig verwendete test.exe (konsolenprogramm) und die (fantasiename.exe) gui-programm.

    die arbeit steckt hoffentlich im modell und die gui ist trivial. modell so um 100 dateien, viele änderungen, auch in headers, änderungen in modell-headers ziehen oft komplette recompilierung nach sich. gui hat headers sehr schnell fix, änderungen fast nur in *.cpp.

    und gui ist eh unrettbar verloren, da jede einzelne gui-datei die <windows.h> inkludieren muß, die weiderum 20 andere inkludier und zusammen haste dann 38.2MB inkludiert. und das für jede einzelne deiner *.cpp-dateien der gui. hier ist ein komplettes durchcompilieren einfach teuer. ist halt so. aber ist ja nicht schlimm, kommt ja selten vor.

    die dateien des modells sollen moglichst schwach voneinander abhängig sein. also immer so wenig wie möglich inkludieren. und fein <iosfwd> statt <iostream> nehmen. inlien mußte aber oft nehmen. zahle doch keine laufzeit für ein wenig compilezeit. und bei templates mußte eh inkludieren. und die weirste hoffentlich nicht meiden. stl benutzen kostet also relativ viel compilezeit.

    aber nun zum übeltäter. die <windows.h> im modell. wie kommt man an die definition von DWORD ohne diesen brocken zu inkludieren? wenn due die <windows.h> im modell vermeiden kannst (ich kanns nur mit abartigen tricks), sparste genug, um von den 2,5h auf 15 min zu fallen. eisernes aufpassen, daß du nicht zu viel inkludierst und manchmal ein stl-verzicht senken dann auf 5 min.
    dann gilt es erstmal, insofern ein glückliches händchen zu haben, daß man so selten wie möglich einen header ändern muß. auch eigentliche inline-funktionen raustun und in die todo.txt für jede schreiben, daß sie vor auslieferung wieder reinmüssen.
    und wenn das noch nicht reicht, bei schlimmen brocken das pimpl-idiom anwenden. ist halt gefährlich, in der entwickling pimpl zu nehmen und ohne pimpl auszuliefern, aber naja, "no risc, no fun". damit kommste aber dann auf traumzeiten.



  • Beim MVC hab ich irgendwie das Gefühl, dass die Abhängigkeiten zwischen (Controller und View) und (Controller und Model) immer recht groß sind. Ist aber auch neu für mich, vielleicht mach ich es noch nicht allzu gut...

    Aber was ich nicht verstehe: Was ist am inkludieren von <windows.h> so schlimm? Die Datei wird doch eh nicht geändert, viel schlimmer ist es doch, wenn du eine Header von deinem Projekt 972643mal inkludierst und dann änderst du was an dieser Header...



  • Optimizer schrieb:

    Beim MVC hab ich irgendwie das Gefühl, dass die Abhängigkeiten zwischen (Controller und View) und (Controller und Model) immer recht groß sind. Ist aber auch neu für mich, vielleicht mach ich es noch nicht allzu gut...

    die "gui/Foo.h" tendiert dazu, von deinen headers nur die "modell/Foo.h" zu inkludieren. controller und view kannste nicht trennen in windows.

    [/quote]Aber was ich nicht verstehe: Was ist am inkludieren von <windows.h> so schlimm?[/quote]
    weil sie riesig ist und selbt mit precompiled headers fast ie ganze compilezeit frisst. weil sie ein riesiger brocken ist, den du kaum mit tricks schnellermachen kannst. weil sie einfach riesig ist.

    Die Datei wird doch eh nicht geändert, viel schlimmer ist es doch, wenn du eine Header von deinem Projekt 972643mal inkludierst und dann änderst du was an dieser Header...

    weil du bereits

    - Wenn möglich, nur in .cpp Dateien inkludieren, nicht in Header
    - Wenn man Referenzen oder Pointer auf Objekte verwendet, anstatt die Klassen selber, reicht eine Klassen-Deklaration, solange man das Objekt nicht benutzt und der Compiler keine Informationen über die Größe des Objekts braucht. Das lässt sich in einer Headerdatei oft gut machen.
    - Methoden möglichst nicht in der .h sondern in der .cpp definieren. (inline vermeiden)

    befolgst.

    was soll

    Klassendeklarationen kann man in einer Header sammeln und in andere Header immer wieder einbinden

    heißen?
    willste alle klassen in einem riesigen header sammen, damit der compiler seltener fopen/fclose machen muß? das wäre irrig. der kann licker 10000 dateien pro sekunde fopen/closen.



  • Nein, damit meine ich nur Sachen wie

    class Foo;
    class Monk;
    class Blubb;
    

    Das tut ja nicht weh zum inkludieren, oder seh ich das falsch? Spart halt Schreibarbeit.

    weil sie ein riesiger brocken ist, den du kaum mit tricks schnellermachen kannst. weil sie einfach riesig ist.

    #define WIN32_LEAN_AND_MEAN	// Exclude rarely-used stuff from Windows headers
    #include <windows.h>
    

    Ob's wirklich hilft, weiss ich nicht. :p



  • Hallo,

    • für Klassen die viel verwendet werden, für die Änderungen aber wahrscheinlich sind, sollte man "total insulation"-Techniken wie z.B. PIMPL (Letter-envelope)verwenden. Sprich:
    // Foo.h
    class Foo
    {
    public:
        // public interface
    private:
        class FooImpl;
        FooImpl* impl_;
    };
    
    // Foo.cpp
    class Foo::FooImpl
    {
        // Foo implementaion
    };
    
    Foo::Foo()
        : impl_(new FooImpl)
    {}
    
    void Foo::func()
    {
        impl_->func();
    }
    ...
    
    • Vermeide Compiler-generierte-Funktionen für unstabile Klassen. Compile-generierte-Funktionen (Destruktor, CopyCtor...) sind immer inline.
    • Direkte Abhängigkeiten zwischen konkreten Klassen können gut durch Protokoll-Klassen aufgebrochen werden. Facaden die low-level-Komponenten kapseln können auch hilfreich sein.
    • Wenn möglich: Private-Vererbung durch Containment ersetzen.
    • Private-Memberfunktionen durch file-statische Funktionen ersetzen
    • Zyklische Abhängigkeiten zwischen Komponenten vermeiden.
    • Für Templates lohnt sich häufig explizite Instanziierung.
      Man trennt eine Templateklasse in Klassendefinition und Implementation.
    // TemplateClass.h
    template <class T>
    class Foo
    {
    public:
        void func();
        void gunc();
        ...
    };
    
    // TemplateClass_Impl.h -> nicht inkludieren
    template <class T>
    void Foo<T>::func()
    {
        ...
    }
    
    template <class T>
    void Foo<T>::gunc()
    {
        ...
    }
    

    Dazu kommt dann eine cpp-Datei, die explizite Spezialisierungen enthält:

    // instantiate_foo.cpp
    template class Foo<int>;
    template class Foo<double>;
    
    • Wenn möglich: Das Hoisting-Idiom für Templates berücksichtigen.
      Hier trennt man den Typ-unabhängigen Code vom Typ-abhängigen. Der Typ-abhängige Code ist dann meist nur noch ein kleiner Wrapper.
    // ptr_list.h
    // Listenimplementation für void-Pointer
    class PtrList
    {
    public:
        void insert(void* p);
        void* first();
        ...
    };
    
    // list.h
    template <class T>
    class List
    {
        // Implementation für T
    };
    
    // partielle Spezialisierung für Pointer-Typen
    // Pointer-Typen sind kompatibel zu void*
    // -> Verwende PtrList
    template <>
    class List<T*>
    {
    public:
        void insert(T* p) {impl_.insert(p);}
        T* first() {return (T*)impl_.first();}
    private:
        PtrList impl_;
    };
    

    Eine Menge mehr zum Thema findet man z.B. in Kapitel 6 von "Large-Scale C++ Software Design".


Anmelden zum Antworten