Projektstruktur: Header Dateien im seperaten include folder?



  • @Mechanics Ich wollte es zuerst nicht erwähnen weil @Leon0402 IDE-Unterstützung explizit ausgeschlossen hat.

    Hat man ein Projekt in dem man auch incude-files für externe Verwendung hat ist ein separates Verzeichnis dafür sicher nicht kerfehrt.



  • Ich hatte seinen Satz über die IDE etwas anders interpretiert. Mit Visual Studio (zumindest mit Visual Assist) kann ich zwischen Header und Cpp springen, auch wenn die in unterschiedlichen Verzeichnissen liegen. So habe ich das interpretiert.
    Ist aber nochmal ein etwas anderer Punkt, wenn man sich die Dateien tatsächlich mal im Explorer anschauen will, und z.B. löschen, zur Versionsverwaltung hinzufügen (mach ich nicht in VS) oder ähnliches.

    Es gibt übrigens Projekte, die haben einen install step oder ähnliches, der dann includes in ein separates Verzeichnis rauskopiert. Glaube ich zumindest, würde jetzt aber nicht ausschließen, dass ich nicht genau genug hingeschaut habe.



  • @Mechanics sagte in Projektstruktur: Header Dateien im seperaten include folder?:

    Ist aber nochmal ein etwas anderer Punkt, wenn man sich die Dateien tatsächlich mal im Explorer anschauen will, und z.B. löschen, zur Versionsverwaltung hinzufügen (mach ich nicht in VS) oder ähnliches.

    Dann klicke ich auf "Open Folder in File Explorer", doppelklicke auf "include" und lösche die Datei.



  • Du meinst, wenn die includes in einem Unterverzeichnis der cpp´s liegen?



  • @Mechanics Ich? ja.



  • Ja, das würde noch gehen. Wir haben bei uns teilweise Verzeichnisstrukturen, die in etwa so aufgebaut sind:

    root

    • includes
      • a
        • b
    • source
      • a
        • b

    D.h., wenn man den Ordner der cpp Datei öffnet, befindet man sich in der Verzeichnisstruktur ganz woanders... Und wie das genau aufgebaut ist, ist auch nicht ganz einheitlich, d.h. einfach im Pfad noch includes eintippen klappt auch nicht immer.



  • Generell wollte ich hier mal IDE Unterstützung ausschließen, da jeder einen anderen Editor verwendet und andere plugins etc. ... sowas für ein größeres Projekt zu erwarten, dass jeder diese tools hat (und auch gerne benutzt) würde ich denke ich nicht so gut finden.

    Hat man ein Projekt in dem man auch incude-files für externe Verwendung hat ist ein separates Verzeichnis dafür sicher nicht kerfehrt.

    Also uneinheitlich? Nur das public interface in das include verzeichnis stecken und private header etc. im src folder lassen?
    Diese Variante, so logisch ich sie irgendwo auch finde, würde mich vermutlich am meisten verwirren.



  • Bei einer Library macht dies aber Sinn, da man so das gesamte (public interface) "include"-Verzeichnis mitausliefern kann (ohne explizit die einzelnen benötigten Headerdateien zusammenkopieren zu müssen).

    Und wenn man bei einem eigenständigen Projekt dann evtl. einen Teil davon als Library auslagern möchte, müßte man (höchstwahrscheinlich) die Struktur ersteinmal umbauen (z.B. relative Pfade anpassen).


  • Mod

    Der Knackpunkt ist aber trotzdem noch, was du mit den Templates machst, wenn diese Teil des öffentlichen Interfaces sind. Packst du sie mit in einen separaten Ordner für das öffentliche Interface, ist das hinderlich bei der Entwicklung, weil dir dann die Trennung nichts mehr bringt außer einer selbstgemachten Verkomplizierung. Packst du sie mit in einen Sourcefolder macht das auch wieder den Sinn eines separaten Interfaceordners zunichte, weil du dann am Ende doch wieder aufpassen musst, welche Teile wirklich zur Funktion des öffentlichen Interfaces notwendig sind.

    Das Grundproblem ist, dass man in C++ sprachbedingt Teile hat, die gleichzeitig sowohl Interface als auch Wirkcode sind. Das Problem, alles in diese zwei Kategorien aufzuteilen kann man daher nicht zufriedenstellend lösen, außer durch komplizierte Build-Magic, die das Problem versteckt (Oder ich kenne die geniale Lösung nicht. Ich wäre für Lösungen dankbar!)



  • Da die Templates Teil des öffentlichen Interfaces sind, gehören sie selbstverständlich auch in diesen Include-Ordner (entweder direkt in den Headerdateien oder aber eben ausgelagert, z.B. in einem Unterordner davon und per #includeeingebunden).

    Ich verstehe aber anscheinend das Problem nicht. Jede vernünftige IDE bietet doch heute entsprechende Funktionalität zum Wechseln zwischen Source und Header (bei gleichem Namen bzw. explizit über Öffnen der Headerdatei beim #include).
    Und wenn ich an einer Klasse arbeite, dann öffne ich einmalig die beiden Dateien in 2 Tabs nebeneinander (und diese bleiben dann auch beim nächsten Projektöffnen erhalten).



  • @Th69 sagte in Projektstruktur: Header Dateien im seperaten include folder?:

    Bei einer Library macht dies aber Sinn, da man so das gesamte (public interface) "include"-Verzeichnis mitausliefern kann (ohne explizit die einzelnen benötigten Headerdateien zusammenkopieren zu müssen).

    Hierzu noch eine Empfehlung: Es macht Sinn, dass das include-Verzeichnis des Projekts genau so strukturiert ist, wie es im globalen include-Verzeichnis nach der Installation aussehen soll.

    Also statt include/mylib.hpp lieber include/myproject/mylib.hpp.

    So kann ich innerhalb des Projekts einfach include als Include-Verzeichnis angeben und die Header dann genau so verwenden, wie es letztendlich auch der Endanwender tut, also z.B. mit #include <myproject/mylib.hpp>.

    Die Trennung von Header und Source in unterschiedliche Verzeichnisse empfinde ich in den IDEs, die ich verwende als unproblematsich, selbst ohne die Funktion zu nutzen, mit der ich zwischen Deklaration und Definition wechseln kann. Ich verwende in meinen Projekten oft viele weitere Unterverzeichnise unterhalb von include und source, so dass ich auch bei großen Projekten meist nur eine Handvoll Dateien pro Verzeichnis habe. In der Projektübersicht habe ich dann meist nur die relavanten Unterverzeichnisse aufgeklappt, so dass ich die zugehörigen Dateien sehr schnell finde. Ich denke auch mit "Lowlevel-IDEs" wie vim sollte so etwas möglich sein (?).

    @SeppJ Diese Verquickung von "Interface" und "Wirkcode" ist aber m.E. keine Notwendigkeit, sondern lediglich eine Art syntaktischer Zucker, der einem Tipparbeit erspart. Die Aufteilung von Interface und Implementierung in separate Dateien ist immer möglich.

    Nicht vermeidbar hingegen ist, dass es "Wirkcode" gibt, der einer Übersetzungseinheit vollständig bekannt sein muss, wenn sie diesen verwenden will (Template- oder inline-Funktionen). Diese Sichtweise führt aber leider auch nicht zu einer guten Lösung, sondern eher zu sowas:

    include_headers
    include_source
    source
    

    Wurgs! Das mag zwar die tatsächlichen Zusammenhänge besser auf die Verzeichnisstruktur abbilden, ist aber nicht wirklich ein Mehrwert oder irgendwie gut handhabbar.

    Ich hoffe ja, das Modules das irgendwann alles weitgehend überflüssig machen werden 😉



  • @SeppJ sagte in Projektstruktur: Header Dateien im seperaten include folder?:

    Das Problem, alles in diese zwei Kategorien aufzuteilen kann man daher nicht zufriedenstellend lösen, außer durch komplizierte Build-Magic, die das Problem versteckt (Oder ich kenne die geniale Lösung nicht. Ich wäre für Lösungen dankbar!)

    Weiss nicht was daran speziell kompliziert ist. Alles was extern benötigt wird kommt ins "include" Verzeichnis und alles was nicht extern benötigt wird ins "src" Verzeichnis. Man darf die auch gerne anders nennen, z.B. eins "public" und eins "impl", "private", "internal" oder was auch immer.

    Wie sinnvoll das ganze ist kommt dann natürlich immer drauf an. Also wenn fast alles in Templates ist oder inline sein muss wegen constexpr, dann macht es klar weniger Sinn. Gibt aber Projekte wo überraschend wenig übrig bleibt wenn man konsequent alles wo es (bewiesenermassen, Profiler) wörscht ist aus den .h Files raus in die .cpp-s verschiebt.
    (Bzw. oft hat das Verschieben ins .cpp sogar Vorteile. Compiler sind oft viel zu aggressiv beim Inlinen und inlinen dummen "kalten" Fehlerbehandlungscode 10 Ebenen tief und müllen damit den ICache zu.)



  • @hustbaer sagte in Projektstruktur: Header Dateien im seperaten include folder?:

    Gibt aber Projekte wo überraschend wenig übrig bleibt wenn man konsequent alles wo es (bewiesenermassen, Profiler) wörscht ist aus den .h Files raus in die .cpp-s verschiebt.

    Ich würde auch sagen, dass sowas haupstächlich in Bibliotheken Anwendung finden sollte. Vor allem, weil diese nicht wissen können, in welchem Kontext sie letztendlich verwendet werden und es eben nicht "wörscht" sein könnte. In Anwendungscode habe ich selbst auch nur selten Bedarf für inline oder exzessive Nutzung von Templates.

    (Bzw. oft hat das Verschieben ins .cpp sogar Vorteile. Compiler sind oft viel zu aggressiv beim Inlinen und inlinen dummen "kalten" Fehlerbehandlungscode 10 Ebenen tief und müllen damit den ICache zu.)

    Ich würde nicht versuchen, dieses Verhalten über das inline-Schlüsselwort zu steuern. Wenn sich hier tatsächlich ein Problem zeigt, erachte ich es als sinnvoller, das mit compilerspezifischen noinline-Mechanismen zu erschlagen:

    Erstens, weil sich die Bedeutung von inline mittlerweile von "bitte inlinen" zu "mehrfache Definitionen sind erlaubt" gewandelt hat (siehe auch z.B. inline-Variablen, wo die ursprüngliche Bedeutung von inline nicht wirklich Sinn macht).

    Zweitens, weil Compiler auch nicht mit inline markierte Funktionen und - sofern man z.B. LTO verwendet - auch Funktionen aus verschiedenen .cpp-Übersetzungseinheiten programmübergreifend inlinen.



  • @Finnegan sagte in Projektstruktur: Header Dateien im seperaten include folder?:

    @hustbaer sagte in Projektstruktur: Header Dateien im seperaten include folder?:

    Gibt aber Projekte wo überraschend wenig übrig bleibt wenn man konsequent alles wo es (bewiesenermassen, Profiler) wörscht ist aus den .h Files raus in die .cpp-s verschiebt.

    Ich würde auch sagen, dass sowas haupstächlich in Bibliotheken Anwendung finden sollte. Vor allem, weil diese nicht wissen können, in welchem Kontext sie letztendlich verwendet werden und es eben nicht "wörscht" sein könnte. In Anwendungscode habe ich selbst auch nur selten Bedarf für inline oder exzessive Nutzung von Templates.

    Nene, das Verschieben raus aus den Headers rein ins cpp macht auch bei Libraries extrem viel Sinn. Ist ja nicht jeder Algorithmus ein dürres Gerüst um irgendwelche Iteratoren/Funktoren/Traits die nach Inlining u.U. komplett verdampfen. Sowas wie nen Regex Matcher generisch auf Ranges/Iteratoren zu implementieren macht IMO exakt keinen Sinn. Das macht man 1x für ein Zeigerpaar, im .cpp File, und dann stoppelt man inline Wrapper für contiguous sequences drann.

    Ich würde nicht versuchen, dieses Verhalten über das inline-Schlüsselwort zu steuern. Wenn sich hier tatsächlich ein Problem zeigt, erachte ich es als sinnvoller, das mit compilerspezifischen noinline-Mechanismen zu erschlagen:
    ...

    Richtig, steuern sollte man es nicht darüber. Ich wollte lediglich darauf hinaus dass weniger Inlining sogar oft bessere Performance bedeutet.



  • Dieser Beitrag wurde gelöscht!


  • @hustbaer sagte in [Projektstruktur: Header Dateien im

    Ich würde auch sagen, dass sowas haupstächlich in Bibliotheken Anwendung finden sollte.

    Nene, das Verschieben raus aus den Headers rein ins cpp macht auch bei Libraries extrem viel Sinn. Ist ja nicht jeder Algorithmus ein dürres Gerüst um irgendwelche Iteratoren/Funktoren/Traits die nach Inlining u.U. komplett verdampfen.

    Ich habe nicht gesagt, dass es in die .cpp zu packen bei Bibliotheken keinen Sinn macht, sondern dass Templates und inline-Funktionen eher ein, Library-Ding sind.

    Sowas wie nen Regex Matcher generisch auf Ranges/Iteratoren zu implementieren macht IMO exakt keinen Sinn. Das macht man 1x für ein Zeigerpaar, im .cpp File, und dann stoppelt man inline Wrapper für contiguous sequences drann.

    Das ist ein schönes Beispiel. Es macht schon manchmal Sinn für generischen Code "Terminatoren" zu haben, die das "Generische" an dem Punkt unterbrechen, wo man das Problem auf ein spezielles heruntergebrochen hat - z.B. auf zusammenhängende chars im Speicher. Generisch muss der Code hier nur sein um diese chars aus beliebigen Datenstrukturen "auspacken" zu können.

    Ich mache das auch schonmal gerne so, dass ich einen lowlevel-Algorithmus schreibe, der beinahe C sein könnte und dann ein schönes generisches C++-Interface oben drauf setze. Für manche Probleme ist das eine gute Lösung.

    Richtig, steuern sollte man es nicht darüber. Ich wollte lediglich darauf hinaus dass weniger Inlining sogar oft bessere Performance bedeutet.

    Ja, ich verstehe, worauf du hinaus wolltest. Ich habe den Thread zu den Exceptions auf ARM im Nachbarforum gelsesen.

    Das ist schon etwas erschreckend, dass die Compiler sowas scheinbar nicht immer gut hinbekommen. Vielleicht liegt es aber auch daran, dass der Compiler nur die eine Übersetzungseinheit sieht und in dieser ist das dann isoliert betrachtet vertretbar (?).

    Schlimm wäre es, wenn das mit LTO oder PGO+LTO in grossen Programmen immer noch so aussähe. Werde das mal im Hinterkopf behalten und in Zukunft genauer beobachten. Bisher hatte ich das noch nicht so auf dem Schirm.



  • @Finnegan Dort ging's ja eher um das Einziehen von Hilfsfunktionen als darum ob man die manuell als "noinline" markieren muss. Und da finde ich nicht erschreckend dass Compiler das nicht können. Also das Erkennen von identischen Teilabschnitten von Funktionen + Deduplication dieser Teilabschnitte. Es wäre cool wenn sie es könnten, aber... is halt nich.

    Explizit als "noinline" markieren muss man nämlich anscheinend oft gar nicht. Zumindest GCC und Clang sind schlau genug z.B. "noreturn" Funktionen nicht zu inlinen . Also sowohl welche die explizit "noreturn" gemacht wurden als auch welche wo der Compiler sieht dass es einfach keinen normalen Exit-Pfad gibt.
    D.h. wenn man wo

    inline void throwBlahError(...) {
       ...
       throw BlahError(...);
    }
    

    stehen hat, dann reicht das. Explizites "noreturn" oder gar "noinline" bei der Definition von throwBlahError ist nicht nötig.

    Obwohl ich es (noinline) dann trotzdem auch dazuschreibe. Weil wenn ich mir schon die Arbeit mache throwBlahError als Funktion rauszuziehen, dann is das auch schon wurscht, bzw. sogar gut weil es dokumentiert wozu throwBlahError überhaupt in eine Funktion rausgezogen wurde.



  • @hustbaer sagte in Projektstruktur: Header Dateien im seperaten include folder?:

    Weil wenn ich mir schon die Arbeit mache throwBlahError als Funktion rauszuziehen, dann is das auch schon wurscht, bzw. sogar gut weil es dokumentiert wozu throwBlahError überhaupt in eine Funktion rausgezogen wurde.

    Mich wundert schon etwas, dass der Compiler das throw nicht erstmal intern wie eine Funktion behandelt - wie z.B. auch das new mit einer ähnlichen Syntax auch erstmal eine (Operator-)Funktion ist - und dann seine übliche Entscheidung trifft, ob er es inlined oder nicht. Das würde es zumindest nicht mehr nötig machen, dass du dir darüber den Kopf zerbrechen musst (gut, throw kann man nicht überladen, aber es sollte nicht zu kompliziert sein, da implizit eine Funktion draus zu machen).

    Bevor ich den Thread gelesen habe, hätte ich eigentlich gedacht, dass throw dass es so wäre - vielleicht mit ein paar Sonderbehandlungen, dass z.B. __internal_throw den Stack Frame wieder zurücksetzt, so dass es von aussen (z.B. für den Debugger) so aussieht, als sei dort geworfen worden wo auch das throwsteht, und nicht in __internal_throw.

    Naja, kann alles nur besser werden. Die Compiler sind dennoch schon beeindruckend gut geworden 😉



  • @Finnegan sagte in Projektstruktur: Header Dateien im seperaten include folder?:

    Mich wundert schon etwas, dass der Compiler das throw nicht erstmal intern wie eine Funktion behandelt - wie z.B. auch das new mit einer ähnlichen Syntax auch erstmal eine (Operator-)Funktion ist - und dann seine übliche Entscheidung trifft, ob er es inlined oder nicht.

    Macht er ja auch mehr oder weniger. Nur muss trotzdem noch das Objekt konstruiert werden das dann geworfen wird. Eine Funktion aufzurufen die eine Exception als Parameter nimmt ist auch nicht viel teurer als die selbe Exception zu werfen. Der Trick ist dass man der throwBlah Funktion möglichst billig zu konstruierende Parameter gibt. Also char const*, int etc.



  • @hustbaer sagte in Projektstruktur: Header Dateien im seperaten include folder?:

    @Finnegan sagte in Projektstruktur: Header Dateien im seperaten include folder?:

    Mich wundert schon etwas, dass der Compiler das throw nicht erstmal intern wie eine Funktion behandelt

    Macht er ja auch mehr oder weniger.

    Oh, dann habe ich wohl beim (nur) Überfliegen des Threads etwas falsch verstanden. Danke nochmal für die Aufklärung.

    Das mit den billig zu konstruierenden Objekten ist mir die Tage allerdings auch wieder aufgefallen, als ein T = std::string völlig verdeckt hat, wie wunderbar eine umgebende Template -Abstraktion tatsächlich wegoptimiert wurde 😉


Anmelden zum Antworten