Headerdateien "inlude" immer wieder neu?



  • Wie macht ihr das, wenn ihr z.B. 2 Header Dateien habt, eine .hpp (Klasse) und eine .cpp (Methoden, Konstruktor....).

    cllt.hpp

    #include <vector>
    #include <string>
    
    class cllt
    {
        std::string....
        std::vector....
    };
    

    wenn cllt.cpp nun auch <string> und <vector> benötigt, schreibt ihr es dann nochmal rein oder lasst ihrs bleiben?

    cllt.cpp

    #include "cllt.hpp"
    #include <vector> // <- Macht ihr das? 
    #include <string> // <-   "    "    "
    
    cllt::cllt(...)
    {
    ....
    }
    void cllt::....
    {
    .....
    }
    

    Weil eigentlich wärs ja jetzt nicht mehr nötig?!

    MfG
    Stromberg



  • also ich lass es weg 😛



  • kannst weglassen, include heisst doch eh nur, pack die datei an diese stelle, also stehts nach dem preprocessing eh da ..



  • Der springende Punkt, weshalb man Programme in Module teilt, ist wiederverwendbarkeit. Beispiel:

    foo.hpp (braucht <string>)

    #include <string>
    
    /* ... */
    

    bar.hpp (braucht <cmath>)

    #include <cmath>
    
    /* ... */
    

    main.cpp (braucht <string>

    #include "foo.hpp"
    #include "bar.hpp"
    
    int main( ) {
    
        std::string word;
    
        /* ... */
    }
    

    Hier ist es egal, dass in main.cpp <string> nicht inkludiert wird, da main.cpp wahrscheinlich nicht wiederverwendet wird / werden kann.

    Negativbeispiel:

    foo.hpp (braucht <string>)

    /* #include <string> */ // "Überflüssig, da vor der Einbindung in main.cpp <string> eingebunden wird.
    
    /* ... */
    

    bar.hpp (braucht <cmath>)

    #include <cmath>
    
    /* ... */
    

    main.cpp (braucht <string>

    #include <string>
    
    #include "foo.hpp"
    #include "bar.hpp"
    
    int main( ) {
    
        std::string word;
    
        /* ... */
    }
    

    Besteht jedoch ein Header nicht nur aus inline - und/oder template Funktionen oder Interfaces, so wird in der Regel auch eine Quellcodedatei dazugehören:

    random.hpp

    #include <cstdlib>
    #include <ctime>
    
    unsigned long faculty( unsigned long lower, unsigned long upper );
    

    random.cpp

    #include "random.hpp"
    
    unsigned long faculty( unsigned long lower, unsigned long upper ) {
    
        /* ... */
    }
    

    Spätestens hier ist es IMVHO ausgesprochen guter Stil, das, das benötigt wird, dort einzubinden, wo es benötigt wird.

    greetz, Swordfish



  • Stromberg schrieb:

    Wie macht ihr das, wenn ihr z.B. 2 Header Dateien habt, eine .hpp (Klasse) und eine .cpp (Methoden, Konstruktor....).

    Ich includiere grundsätzlich so wenige Header wie möglich in anderen Headern. Wenn eine forward-Deklaration reicht, dann bring ich die und der Header zu der Klasse/Funktion wird erst in der cpp includiert.
    Wenn ich allerdings um das include eines Headers in meinem Header nicht herumkomme, dann brauche ich ihn in der cpp nicht nochmal einzubinden. Ein Kommentar, welche Header aus dem eigenen Header mitkommen, reicht da völlig, um die Übersicht zu haben, was in der cpp alles erreichbar ist.

    Wenn ich feststelle, dass ich in einer Klassendefinition im privaten Teil vieles stehn hab, was die Einbindung eines Headers nötig macht, nutze ich eventuell das pimpl-Idiom, um die Abhängigkeiten zu verringern.



  • In der ".cpp" Datei sollte es doch reichen, wenn man einfach -> 'include ".hpp"' macht?
    Außer in der ".cpp" werden Dinge verwendet die in "
    .hpp" noch nicht includiert werden.
    Hier mal ein "echtes" Beispiel:

    crypt.hpp

    #include "random.hpp"
    #include <string>
    #include <vector>
    #include <fstream>
    
    class crypt
    {
        public:
        crypt(const std::string &file_name);
        inline bool errorless() const;
    
        private:
        unsigned short code_2byte(char character);
        unsigned int code_4byte(char character);
        void create_key();
        unsigned int transform_key();
    
        std::vector<int> m_save;
        std::string m_key;
        unsigned int m_save_key[4];
        std::fstream m_file;
        const random create_value;
    };
    

    crypt.cpp

    #include "crypt.hpp"
    using namespace std;
    
    crypt::crypt(const string &file_name)
    {
        m_file.open(file_name.c_str(), ios::out | ios::binary | ios::app);
    
    }
    
    inline bool crypt::errorless() const
    {
        return m_file;
    }
    
    unsigned short crypt::code_2byte(char character)
    {
        m_save.push_back(create_value.rnd(1,514));
        m_save.push_back(1);
        unsigned short temp=character*m_save.at(m_save.size()-1)*m_save.at(m_save.size()-2); //MAX 65278
        return temp;
    }
    
    unsigned int crypt::code_4byte(char character)
    {
        m_save.push_back(create_value.rnd(1,32767));
        m_save.push_back(create_value.rnd(1,1032));
        unsigned int temp=character*m_save.at(m_save.size()-1)*m_save.at(m_save.size()-2); //MAX 4294574088
        return temp;
    }
    
    void crypt::create_key()
    {
        for(int i=0;i<4;++i)
        {
            char character=create_value.rnd(97,122);
            m_key +=character;
        }
    }
    
    unsigned int crypt::transform_key()
    {
        for(int i=0;i<4;++i)
        {
            m_save_key[i]=m_key[i]*create_value.rnd(1,8800000);
        }
        return m_save_key[0]+m_save_key[1]+m_save_key[2]+m_save_key[3];
    }
    

    Hättet ihr genauso includiert? Einfach die "crypt.hpp"?

    MfG
    Stromberg



  • Stromberg schrieb:

    In der ".cpp" Datei sollte es doch reichen, wenn man einfach -> 'include ".hpp"' macht?
    Außer in der ".cpp" werden Dinge verwendet die in "
    .hpp" noch nicht includiert werden.

    Nein, oder zumindest unschön formuliert.

    Kurz und knapp:
    a) Includiere was für die Übersetzungseinheit (cpp+hpp) nötig ist
    b) Ziehe dabei ein Include in der Sourcedatei einem Include im Header vor
    c) Betrachte eine Übersetzungseinheit immer als eigenständig von Anderen

    Includes in einem Header lassen sich in der Regel dann umgehen wenn nur Zeiger oder Referenzen in der Schnittstelle verwendet werden (Vorwärtsdeklaration). Zudem kann unter Umständen auch das Body-Handle Idiom (pImpl-Idiom) die Abhängigkeiten im Header weiter reduzieren.

    Nehmen wir deinen Code als Beispiel, und modifizieren ihn soweit das die Abhängigkeiten so minimal wie möglich sind (stark gekürzt und ungetestet, zumal ich den Code eh etwas anders schreiben würde):

    #if !defined(CRYPT_HEADER)
    #define CRYPT_HEADER
    
    #include <boost/smart_ptr.hpp>
    
    // Vorwärtsdeklarationen
    namespace std
    {
      class string;
    }
    
    class crypt
    {
        public:
          crypt(std::string const & file_name);
          inline bool errorless() const;
    
        private:
          // Handle-Body Idiom
          struct cryptImpl;
          boost::scoped_ptr<cryptImpl> Impl;
    
          // Kopierkonstruktor und Zuweisungsoperator habe ich nur zur
          // Vereinfachung ausgeschlossen, ansonsten wären diese
          // entsprechend zu formulieren (Kopie des Inhaltes des
          // Smartpointers...)
          crypt(crypt const &);
          crypt& operator=(crypt const &);
    
          unsigned short code_2byte(char character);
          unsigned int code_4byte(char character);
          void create_key();
          unsigned int transform_key();
    };
    
    #endif
    

    Source habe ich mal gekürzt und nur Ausschnittsweise umgesetzt.

    #include "crypt.hpp"
    #include "random.hpp"
    
    #include <string>
    #include <vector>
    #include <fstream>
    
    using namespace std;
    
    struct crypt::Implementation
    {
      vector<int> Save; // Was soll m_save etc. aussagen? Sprechende Namen...
      string Key;
      unsigned int SaveKey[4];
      fstream File;
      const random create_value;
    
      Implementation(string const & file_name)
      : Save(),
        Key(""),
        File(),
        CreateValue()
      {
        File.open(file_name.c_str(), ios::out | ios::binary | ios::app);
      }
    };
    
    crypt::crypt(string const & file_name)
    : Impl(new crypt::cryptImpl(file_name))
    {
    }
    
    inline bool crypt::errorless() const
    {
      return Impl->File;
    }
    ...
    

    cu André



  • 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é


Anmelden zum Antworten