Gegenseitiges Include - wie am besten zu lösen?



  • Ja schön ist sowas nicht aber irgendwann brauch man doch ne Krücke.

    1.Methode
    Im Headerfile per Macro ausschließen das ein File nicht zweimal eingebunden wird.
    Das kann dann so aussehen.

    #ifndef  ___Headerfile_No1__h__
    #define ___Headerfile_No1__h__
    ..  
    ..
    ..
    #endif
    

    Dann einfach im Headerfile das andere File einbinden.

    2.Methode
    Wird nur der Pointer und nicht die Klasse als Objekt im Headerfile verwendet, dann kann man vor der Verwendung des Pointers diese Klasse quasi anmelden (Weiß nicht mehr wie das in dem Zusammenhang heißt)

    Headerfile 1

    class CKlasseAusHeader1
    {
       ...
    };
    

    Headerfile 2

    class CKlasseAusHeader1;  // geht in diesem Fall anstatt #include <Headerfile 1>
    class CMachwas
    {
        void MachWas(CKlasseAusHeader1* );
    };
    

    Der include von Headerfile 1 muß dann in allem .cpp-Files seperat geschehen welche Headerfile 2 einbinden und zwar nach dem einbinden von Headerfile 2.

    Ich weiß nicht welche Compiler Methode 2 unterstützen ich kann mich auch nicht erinnern woher ich das weiß.

    3.Methode
    Das Object als void pointer übergeben und dann später casten. (evtl noch ne Typenüberprüfung)

    Alle Methoden sind nicht optimal, aber für den Hausgebrauch gehts.

    Auf jeden Fall sollte man das nach Möglichkeit vermeiden da schließe ich mich meinem Vorredner an.

    Babbage



  • Methode 1 benutze ich sowieso standardmäßig in jedem Header, ansonsten würde ich ja mit "Redefinition"-Fehlermeldungen überschwemmt.

    Ich habe jetzt mein Programm so optimiert, dass Methode 2 ganz bequem passt. Ich hatte komplett die Option vergessen, aus der Source-Datei auf den anderen Header zu verweisen. Das löst das Problem natürlich recht schnell.



  • Dazu brauchst du eigentlich nur eine forward-deklaration. Du schreibst zB sowas:

    // header 1.h
    class A;
    
    class B // benutzt A
    {
    B(A* kA);
    };
    
    // datei 2.h
    class A
    {
    // ...
    };
    
    //datei 1.cpp
    #include "1.h"
    #include "2.h"
    
    B::B(A* a) { a->machwas(); }
    
    // datei 2.cpp
    #include "1.h"
    #include "2.h"
    
    A::A() { irgendwas; }
    

    Das selbe geht auch mit Referenzen, da der compiler dann weiß zeiger und refernezen sind nur 4 byte groß. Wenn du allerdings kompette Objekte der einen Klasse einbetten willst und umgekehrt auch, dann kann das ganze eh nicht funktionieren, weil dann der ein konstruktor den anderen aufrufen würde und wieder zurück und so weiter.



  • Babbage schrieb:

    2.Methode
    Wird nur der Pointer und nicht die Klasse als Objekt im Headerfile verwendet, dann kann man vor der Verwendung des Pointers diese Klasse quasi anmelden (Weiß nicht mehr wie das in dem Zusammenhang heißt)

    Das ist btw die Standardlösung - nennt sich "Forward Deklaration". Und wenn du tatsächlich irgendwo damit nicht weiterkommst, hast du ein schwerwiegendes Problem in deinem Klassendesign.



  • Ja thx , Forward Deklaration, kam nicht mehr auf den Namem weil ich's schon ne Weile nicht mehr eingesetzt habe.



  • Und was setzt du sonst ein - doch nicht etwa void-Zeiger?



  • Also wenn ich das mit den void-pointern lese, läuft es mir kalt den Rücken runter.
    Damit machst du dir nur Probleme.

    Klar, wenn du selber die Methoden immer richtig aufrufst, klappt das. Wenn aber mal wer anders an deinem Programm weiterarbeiten muss, fliegt dem das eventuell um die Ohren. Außerdem ist Quellcode mit vielen void pointer 100 mal schwerer zu durchschauen wie mit festen Typen.



  • Es wurde hier nicht nach "schönen" Möglichkeiten gefragt. Mit so'ner Einschränkung hätte ich ganz klar gesagt: REDESIGN

    Aber eingesetzt hab ich gecastete void Zeiger auch schon 🤡



  • Babbage schrieb:

    Es wurde hier nicht nach "schönen" Möglichkeiten gefragt. Mit so'ner Einschränkung hätte ich ganz klar gesagt: REDESIGN

    Wieso? Forward Deklarationen sind eine saubere - und schöne Lösung zur Beseitigung gegenseitiger Abhängigkeiten.

    Aber eingesetzt hab ich gecastete void Zeiger auch schon 🤡

    Ich auch - aber erst, wenn gar nichts anderes mehr ging.
    (die einzige Situation, wo ich void-Zeiger einsetzen würde, ist - eine externe Bibliothek, die ihre Parameter als void-Zeiger fordert)



  • Das Problem mit den Ring-Includes ist leider/gottseidank ein Produkt der "ich-include-einfach-mal-alles-was-mir-in-den-sinn-kommt"-Seuche und damit hausgemacht und relativ leicht vermeidbar. Es gibt eine Handvoll Moeglichkeiten, diese Seuche und damit das Include-Problem einzugrenzen:

    - includes nur dann nutzen, wenn man das Interface der eingebundenen Klassendefinitionen auch wirklich braucht (sonst forward-deklaration wie oben).
    - Klassenimplementation von Klassendefinition trennen. Dadurch koennen die meisten, wenn nicht sogar alle Includes aus dem header in die source datei verlagert werden.
    - sog. compilation firewalls einbauen ("pimpl-idiom"), das reduziert die Abhaengigkeiten weiter.

    Ein gutes Beispiel wie man das erreichen kann, gibt einer der GoTW-Artikel von Herb Sutter.



  • pumuckl schrieb:

    - Klassenimplementation von Klassendefinition trennen. Dadurch koennen die meisten, wenn nicht sogar alle Includes aus dem header in die source datei verlagert werden.

    Das wird bei Templates aber sehr schwierig, da diese im Header stehen. Genau da bekomme ich hin und wieder solche Probleme...



  • Auch bei Templates kann man Vorwärtsreferenzen einsetzen (siehe z.B. <iosfwd>), auch wenn die Syntax etwas kryptisch ist.



  • Es klingt mir hier fast schobn so, als wären includes von der cpp-Datei aus eine häufigere Sache. Ich habe aber gelernt, von der cpp-Datei nur den entsprechenden Header zu inkludieren und von da alles weitere zu machen. Wie ist denn das jetzt?



  • Wie schon gesagt wurde, sollte man in den Header-Dateien so wenig wie möglich inkludieren (da diese ja von anderen Header-Dateien und/oder Source-Dateien wiederum inkludiert werden können - und das bedeutet größere Abhängigkeiten und längere Compilezeiten).

    Und in die Source-Datei sollten dann eben jene Header-Dateien eingebunden werden, welche dann zwingend benötigt werden (also auch nicht einfach überall alle Standard-Header einbinden).

    Etwas anderes ist es, wenn man mit vorcompilierten Header-Dateien arbeitet (aber dies ist dann häufig compiler- und projektspezifisch).



  • David Schneider schrieb:

    Es klingt mir hier fast schobn so, als wären includes von der cpp-Datei aus eine häufigere Sache. Ich habe aber gelernt, von der cpp-Datei nur den entsprechenden Header zu inkludieren und von da alles weitere zu machen. Wie ist denn das jetzt?

    Wie auch Th sagt:
    In den Header sollten so wenige Includes stehen wie irgendwie möglich.

    Wenn man sich daran hält, so wird der Linkaufwand teilweise stark reduziert (auch ohne Precompiled Header, die mir persönlich alleine schon wegen dem Einsatz unterschiedlicher Compilern ein Dorn im Auge sind) sofern man nicht die Schnittstelle in den Headerdateien ändert, sondern nur die Implementierung (cpp). Zudem reduziert man die hier erwähnte Gefahr zyklischer Abhängigkeiten (Was muss zuerst eingelinkt werden: Das Huhn oder das Ei? ;p).

    Zu dem Thema gab es erst vor Kurzen eine ausgiebige Diskussion in einen anderen Thread... ("Basisklasse undefiniert" glaube ich)

    cu André



  • asc schrieb:

    Wenn man sich daran hält, so wird der Linkaufwand teilweise stark reduziert (auch ohne Precompiled Header, die mir persönlich alleine schon wegen dem Einsatz unterschiedlicher Compilern ein Dorn im Auge sind) sofern man nicht die Schnittstelle in den Headerdateien ändert, sondern nur die Implementierung (cpp).

    das ist ein bißchen durcheinander, finde ich. Wieso ändert sich der Link-Aufwand? Wenn ich eine Implementierungs-datei ändere, wird diese neu übersetzt und dann wird frisch gelinkt... das kostet immer gleich. Damit haben auch precompiled header nichts zu tun.

    Wo also spart man wirklich? -- Genau, bei der Compilezeit. Zum einen muß der Compiler weniger lesen, wenn weniger includes drin sind, also wird's schneller, zum anderen werden, selbst wenn man doch mal nen header ändert nur die Implementierungsdateien neu übersetzt, die auch wirklich von dessen Funktionalität abhängen.

    Precompiled headers wirken eigentlich nur dem entgegen, dass der Compiler so viel lesen muß, dass Sachen, die eigentlich garnicht betroffen wären neu übersetzt werden verhindert afaik auch der nicht.


Anmelden zum Antworten