Headerdateien "inlude" immer wieder neu?



  • Ich meine mal gelesen zu haben dass die Vorwärtsdeklaration von String nicht funktioniert, weil string keine Klasse sondern ein typedef auf std::basic_string<char> ist.



  • pumuckl schrieb:

    Ich meine mal gelesen zu haben dass die Vorwärtsdeklaration von String nicht funktioniert, weil string keine Klasse sondern ein typedef auf std::basic_string<char> ist.

    Möchte ich nicht ausschließen, dann wäre es ein zusätzliches Include im Header. Arbeite leider nur privat mit der STL und hatte bisher eigentlich immer den Fall das wenn ich einen String im Header verwendet habe, auch eine Methode mit einer Stringrückgabe (Kopie) existierte - und ich somit eh das Include brauchte.

    Aber was mir wichtiger war zu zeigen das man viele Includes (wenn auch nicht alle) im Header umgehen kann.

    cu André



  • @asc
    Warum soll ich meine ganzen Elementvariablen in einem "struct" zusammenfassen?
    Und was genau ist das hier:

    namespace std
    {
      class string;
    }
    

    ? Und das hier:

    boost::scoped_ptr<cryptImpl> Impl;
    

    ?

    MfG
    Stromberg

    PS: Wenn mein Code fertig ist, dann mach ich noch einen Thread auf in dem ihr mir sagen könnt was ich alle falsch gemacht habe....weil hier ging es ja jetzt eigentlich um die Header...



  • Stromberg schrieb:

    @asc
    Und was genau ist das hier:

    namespace std
    {
      class string;
    }
    

    Eine Vorwärtsdeklaration, die wie wir jetzt geklärt haben aber bei std::string leider nicht funktioniert. Aber eine weitere Vorwärtsdeklaration ist aber im private-Teil der Klasse zu sehen (die berüchtigte Struktur).

    Stromberg schrieb:

    Warum soll ich meine ganzen Elementvariablen in einem "struct" zusammenfassen?

    Such mal im Netz nach dem Begriff "Handle-Body" "Body-Handle" oder "pImpl" 😉

    Kurz zusammengefasst: Ich habe hier zwar absichtlich die Extremumsetzung zeigen wollen, aber grundsätzlich sehe ich 2 Vorteile (Wenn man den Overhead/Indirektion als Nachteil akzeptiert):
    1. Reduzierung der Linkzeiten
    2. Erhöhung der Lesbarkeit des Headers (Implementierungsdetails außen vor lassen; Ich lese grundsätzlich den Header wenn ich Code überschauen muss... wie die Klasse implementiert ist interessiert mich nur wenn Fehler zu korrigieren oder Anpassungen vorzunehmen sind)

    Stromberg schrieb:

    ? Und das hier:

    boost::scoped_ptr<cryptImpl> Impl;
    

    Dies ist ein Smartpointer auf die Implementierungsstruktur, wobei ich hier beispielsweise mich auf die Smartpointer der (empfehlenswerten) Boost-Bibliothek beziehe. Gibt aber auch vergleichbares im TR1.

    Alternativ hätte es auch ein Zeiger getan den man im Destruktor löscht, nur sind Smartpointer einfach sicherer.

    cu André


  • Mod

    asc schrieb:

    Alternativ hätte es auch ein Zeiger getan den man im Destruktor löscht, nur sind Smartpointer einfach sicherer.

    Allerdings brauchst du dann trotzdem einen eigenen Destruktor für crypt:
    crypt::~crypt wird in allern ÜEs definiert, die dies benötigen; in allen ÜEs außer der, in der sich die Implementation befindet, ist cryptImpl aber unvollständig, die Instantiierung des Destruktors des scoped_ptr dort daher ein Fehler (die boost-Dokumentation ist sehr deutlich - denn der Destruktor von crptyImpl würde nicht aufgerufen werden, nur ein shared_ptr wäre möglich), auch mit aut_ptr sieht es nicht anders aus: dort ist die bloße Definition in der Klasse bereits undefiniert.
    Aus diesen Gründen sollte dieses Idiom nicht (oder nur mit Vorbedacht) mit Smartpointern umgesetzt werden - diese haben in diesem speziellen Fall ohnehin keinen Vorteil.



  • camper schrieb:

    asc schrieb:

    Alternativ hätte es auch ein Zeiger getan den man im Destruktor löscht, nur sind Smartpointer einfach sicherer.

    Allerdings brauchst du dann trotzdem einen eigenen Destruktor für crypt:
    crypt::~crypt wird in allern ÜEs definiert, die dies benötigen; in allen ÜEs außer der, in der sich die Implementation befindet, ist cryptImpl aber unvollständig...

    Sprich: Die automatisch generierten Destruktoren können unter Umständen etwas anderes machen wenn man sie nicht selbst (und sei es leer) definiert?

    Okay, das war mir nicht bewusst. Aber man lernt niemals aus (Wobei ich dann zumindest privat nicht darüber stoßen würde, da ich Destruktoren privat aus reiner Gewohnheit immer definiere).

    Das hier die Smartpointer an sich nicht unbedingt nötig sind ist mir auch bewusst, ich wollte nur das Extrembeispiel nennen.

    cu André



  • Äh was genau ist den so ein "smartpointer"? Was bringt mir des auf das struct einen Zeiger zu legen....?

    MfG
    Stromberg



  • Stromberg schrieb:

    Äh was genau ist den so ein "smartpointer"? Was bringt mir des auf das struct einen Zeiger zu legen....?

    Beginnen wir bei Smartpointern:

    Ein Smartpointer ist ein Objekt das sie fast wie ein Zeiger verhält, aber um die Speicherfreigabe kümmtert. Es gibt verschiedene Formen hiervon. Die einfachen (wie scoped_ptr) übernehmen im wesentlichen nur den Aufruf von delete am Ende ihrer Lebenszeit.

    Minibeispiel:

    #include <boost/smart_ptr.hpp>
    
    void foo1()
    {
      scoped_ptr<int> value(new int(4));
    } // <-- hier wird der Wert automatisch gelöscht
    
    void foo2()
    {
      int* value = new int(4);
      delete value; // hier muss es manuell erfolgen
    }
    

    Was ist an sich der Vorteil? Nehmen wir mal an das die Funktion länger ist und mehrere Austritspunkte (return/exception...) hat. Bei ein Zeiger müsstest du in jeden Fall dann extra ein delete schreiben, beim Smartpointer wird dies (unabhängig wo die Methode beendet wird) automatisch durch seinen Destruktor gemacht.

    Dann gibt es aber noch die komplizierteren wie shared_ptr die Referenzzählung betreiben und sich so direkt nicht durch Zeiger simulieren lassen. Der Sinn hiervon ist, das man diese Kopieren kann, und erst mit dem löschen der letzten Smartpointerinstanz der Zeiger gelöscht wird. Was man vermeiden muss ist aber das Objekte sich dabei gegenseitig am Leben halten.

    Warum nun einen Zeiger im Header auf die Struktur?

    Man kann Zeiger und Referenzen im Gegensatz zu Werten ohne Kenntnis ihres genauen Typs definieren (Stichwort: Vorwärtsdeklaration). Erst mit dem Zugriff auf den Wert muss der Typ bekannt sein.

    Minibeispiel:

    // foo.h (Includeguards etc. als Beispiel weggelassen)
    class A;                  // <-- nur Vorwärtsdeklaration
    void foo(const A& value); // <-- Hier kein genauer Typ nötig
    
    // cpp
    #include "foo.h
    #include "A.h" // s.u.
    
    void foo(const A& value)
    {
      A->foo(); // Hier wird zugegriffen, daher Typ nötig (und daher include)
    }
    

    Man sollte an sich möglichst wenig includes im Header machen (wegen Compilezeiten), daher Zeiger/Referenzen und Vorwärtsdeklarationen.

    cu André



  • Na, wir haben doch im Magazin einen ausführlichen Smart-Pointer-Artikel. ⚠ Lest ihr das Magazin nicht 😞 ?



  • Artchi schrieb:

    Na, wir haben doch im Magazin einen ausführlichen Smart-Pointer-Artikel. ⚠ Lest ihr das Magazin nicht 😞 ?

    Lesen oder Überfliegen schon, aber immer daran denken: nein ;p



  • Ik mach die ganzen Scherze mit dem Zeiger nur das meine Compilierzeit kürzer wird, wa? Mehr bringt mir das nischt?
    Ich mein, wenn da eine "include" Datei mehr oder weniger im Header drinsteht, dann is es halt eine hunfertstel sekunde langsamer...aber des kann einem doch egal sein oder? Auf was es doch zum Schluss ankommt, dass ist doch die ".exe" oder ".o" Datei, und die sind dadurch ja nicht langsamer oder?
    Aber mir is es glaub eigentlich egal, ob ich 1sek oder 6sek compiliere. Oder gibts da noch größere Zeitunterschied?
    Wenns mal n Unterschied zwischen 10sek und 10std is, okay, dann überleg ichs mir nochmal 😃

    MfG
    Stromberg



  • Stromberg schrieb:

    Wenns mal n Unterschied zwischen 10sek und 10std is, okay, dann überleg ichs mir nochmal 😃

    Nur als grobe Angabe mal in den Raum geschmissen: An der Arbeit habe ich bei Teilprojekten Linkzeiten von bis zu 8 Minuten. Das Gesamtprojekt linkt in nicht weniger als 2-3 Stunden.

    Okay, die Entwicklungsumgebung ist eh nicht wirklich gut, aber es gibt irgendwann dennoch Größenordnungen wo man darüber nachdenkt.

    cu Andé



  • Okay, da hab ihr dann aber doch auch so 100000 Zeilen Code oder? Was für eine IDE benutzt ihr den da? Was für IDE's benutzt man für so große Projekte, und was für n Compiler? würd mich ma interessieren.

    MfG
    Stromberg



  • Stromberg schrieb:

    Okay, da hab ihr dann aber doch auch so 100000 Zeilen Code oder? Was für eine IDE benutzt ihr den da? Was für IDE's benutzt man für so große Projekte, und was für n Compiler? würd mich ma interessieren.

    Es sind zwei IDE's/Compiler (was besonders die Kommunikation erschwert) und beides sind Dinosaurier... Ansi C++, STL? Wäre schön. Der eine ist Visual Studio 6.0 Professionell, der andere eine schon mehr als 8 Jahre lang eingestellte Rad-Umgebung von Sybase, daher keine Erwähnung wert.

    Wenn ich es neu aufsetzen würde, wäre die Umgebung wohl (wegen den Anforderungen, und Rahmenbedingungen) Visual Studion 2008 Prof (oder höher) mit C# und C++/CLI oder rein auf C++ Basis (wohl dann mit der MFC auch wenn ich persönlich die MFC eher verabscheue).

    Das Projekt dürfte so in der Größenordung um die 10 Millionen Zeilen Code liegen, und je nach dem ob man externe Komponenten und Reports zurechnet ist das fertige Programm zwischen 90 und 390 MB groß...

    cu André



  • Naja, man kompiliert ja nicht jedesmal das komplette Projekt, wenn man eine Änderung macht. Meistens macht man Änderungen in der Implementierung (.cpp) und dann wird nur diese eine Datei kompiliert. Passiert eine Änderung im Header, kompiliert natürlich mehr. Kommt also darauf an, ob man viel in Headern ändert.

    Achja, man kann auch die Zeit verkürzen, in dem man PCHs benutzt.

    Das Linken kann man verkürzen, in dem man viel in dynamisch gelinkte Libs auslagert. Usw. usf. Also 2 bis 3 Stunden für eine EXE halte ich irgendwie für komisch. Für ein gesamtes Projekt (mehrere Libs usw.) ist es aber verständlich. Aber ändert man immer in allen Projekten gleichzeitig was? Habe die Erfahrung gemacht, das man immer nur in ganz bestimmten Teilprojekten aktiv ist.



  • Was hier ordentlich Zeit spart ist ein Shared Repository mit Nightly Builds dessen Objektdateien immer dann verwendet werden, wenn lokal keine Änderungen vorliegen.



  • Artchi schrieb:

    Das Linken kann man verkürzen, in dem man viel in dynamisch gelinkte Libs auslagert. Usw. usf. Also 2 bis 3 Stunden für eine EXE halte ich irgendwie für komisch. Für ein gesamtes Projekt (mehrere Libs usw.) ist es aber verständlich.

    Ich habe auch nie was anderes gesagt (Gesamtprojekt nicht unter 2-3 Stunden). Die Einzelprojekte (überwiegend dll's linken in maximal 8 Minuten, wobei die IDE manchmal meint erstmal wegen einer winzigen Änderung an der UI 5+ Minuten in 100% Auslastung verfallen zu müssen...

    cu André



  • asc schrieb:

    ...wobei die IDE manchmal meint erstmal wegen einer winzigen Änderung an der UI 5+ Minuten in 100% Auslastung verfallen zu müssen...

    Meine nicht, dass das mit Visual Studio 2005+ besser würde 😃

    -scnr-



  • LordJaxom schrieb:

    asc schrieb:

    ...wobei die IDE manchmal meint erstmal wegen einer winzigen Änderung an der UI 5+ Minuten in 100% Auslastung verfallen zu müssen...

    Meine nicht, dass das mit Visual Studio 2005+ besser würde 😃

    In VC2005+ bin ich aber je nach Sprache aber auch etwas flexibler mit den Designern (z.B. gebe ich persönlich XAML bei WPF unter C# eh händisch ein xD). Zu dem MFC und WinForms Designer kann ich aber nichts sagen. Das letzte mal das ich mit dem MFC Designer etwas gemacht habe war zu VC 1.5 Zeiten...

    cu André



  • Irgendwie hab ich das immer noch nicht genau kapiert, wie man ein "struct" mittels einens Zeigers Vorwärtsdeklarieren soll, wenn dieses Typen enthält die erst in den include Dateien von der .cpp vorkommen, und man wegen der Compilierzeit diese nicht im Header laden möchte.
    Schätzungsweise ich hab folgendes struct im Header, mit den Deklarationen:
    crypt.hpp

    struct test
    {
        fstream file;
        vector<int> a;
        string b;
    };
    

    Was genau mach ich jetzt in "crypt.hpp" noch dass das läuft, und was genau mach ich in crypt.cpp? Also in .hpp muss jetzt erst mal irgednwie ein Zeiger auf ein struct Objekt gelegt werden ( machen wir das bitte mit Zeigern, später schau ich mir mal smarpointer an).
    Kann mir jemand n Beispiel geben?

    MfG
    Stromberg


Anmelden zum Antworten