Notationsfrage (Header)



  • Hallo.
    Da ich der Suchfunktion mächtig bin habe ich sie schon benutzt und eine Menge gefunden, welches mir leider nicht direkt weitergeholfen hat :), deswegen meine Frage.

    Da ich ja weiterhin dabei bin C++ zu lernen und ich nach möglichkeit recht ordentlichen, übersichtlichen und oo-Code erzeugen möchte habe ich eine Frage zu den Headerdateien.

    In meinem Buch (Jetzt lerne ich C++, von Jesse Liberty erschienen bei Markt und Technik) steht folgendes:
    [s.131]
    "...Die Deklaration einer Klasse bezeichnet man als Schnittstelle (Interface), ... Die Schnittstelle speichert man in der Regel in einer ...sogenannten Header-Datei. ...
    Die Funktionsdefinition sagt dem Compiler, wie die Funktion arbeitet. Man bezeichnet die Funktionsdefinition als Implementierung der Klassenmethode und legt sie in einer .CPP-Datei ab. ..."

    Hinzukommt, dass in dem Buch einige Funktionen inline implementiert werden (also im Interface, sprich in der Header Datei.
    Ich bin jetzt ein wenig verwirrt.

    Angenommen ich schreibe ein Projekt, WAS soll in die Headerdatei?
    NUR das Interface OHNE eine Implementierung der Funktionen?
    Und wenn dem so ist, muss ich ja die Funktionen in einer sepparaten *.cpp Datei speichern, oder?
    Und wie, bzw. wo binde ich die mit ein?
    Und ist es nicht so, dass die Funktionalität bereits in der Headerdatei mit vorhanden ist (z.B. iostream).
    Dann müsste doch folglich auch die Funktionsimplementierung enthalten sein, oder nicht?

    Ein einfaches Beispiel:
    Headerdatei:

    class pet 
    {
      public:
             int get_age() { return(age); }
    
      protected:
             int age;
    }
    

    "Die" cpp Datei

    int main() 
    {
    #include "pet"
    pet hamster;
    cout << "Hamster ist bla: " << hamster.get_age() << " alt";
    }
    

    *****************************ODER:

    class pet 
    {
      public:
             int get_age()
    
      protected:
             int age;
    }
    

    cpp Datei zur Headerdatei

    pet::get_age()
    {
    return(age);
    }
    

    "Die" cpp Datei

    int main() 
    {
    #include "pet"
    pet hamster;
    cout << "Hamster ist bla: " << hamster.get_age() << " alt";
    }
    

    Mir erscheint die erste Variante als erheblich sinnvoller, aber vielleicht fehlt mir da Wissen/Erfahrung?

    Bei der Beantwortung bitte NICHT auf den Stil der Beispiel eingehen, sondern nur auf das prinzipielle Verwenden von Headerdateien, also meine eigentliche Frage.
    Danke für euer Hilfe.



  • So -->

    Klasse.h

    #ifndef _KLASSE_
    #define _KLASSE_
    
    class Klasse
    {
    public:
        void tudies();
        void tudas();
    private:
        int geheim;
    };
    
    #endif
    

    Klasse.cpp

    void Klasse::tudies()
    {
        geheim = 12345;
    }
    void Klasse::tudas()
    {
        geheim++;
    }
    

    main

    #include "Klasse.h"
    int main()
    {
        Klasse instanz;
        /* ... */
    }
    


  • Tatsächlich so, schau an :).

    Dankeschön!



  • FireFlow hat das wichtigste schon dargestellt. Ich möchte noch etwas auf die sogenannten Inline funktionen eingehen.
    also es gibt 2 möglichkeiten eine funktion inline zu declarieren.
    1. Wie in deinen ersten Beispiel. Du schreibst die Imlementation gleich in die Declaration der Klasse.
    2. Du Verwendendest das Schlüsselwort inline im header. z.B

    class pet {
    public:
       int get_age();
      protected:
             int age;
    };
    
    inline int pet::get_age() { return age; }
    

    Der Vorteil von Inline funktionen ist der dass der Compiler keinen Funktionsaufruf generieren muss. Er kopiert den code einfach wie er ist wenn er aufgerufen wird.
    Vorteil: das ist schneller.
    Nachteil: Code wird grösser.
    Übrigens der Compiler muss inline declarierte Funktionen nicht umbedingt auch inline verwenden. Du kannst inline als Optimierungstipp an den Compiler sehen.
    Kurt



  • Ok, wenn du nochmal extra drauf eingehst habe ich doch noch die Frage.
    WARUM etwas Inline definieren?

    Ist es dann nicht doch sauberer die Funktionen in der *.cpp Datei zu implementieren (so wie FireFlow das gemacht hat)?
    Weil sonst habe ich ja DOCH wieder die Implementierung einer Funktion in der Headerdatei und das will ich doch gerade nicht, oder?
    Also wann sollte man Inlineimplementierungen vornehmen und wann nicht?

    FireFlow hat ja nun gänzlich auf inlineimplementierungen verzichtet, was ich dann ja auch als durchaus sinnvoll erachte.
    😕



  • Hatte zu früh abschicken gedrückt. Siehe 1.Edit.
    Kurt



  • Danke euch beiden für die ausführliche Erklärung! 🙂



  • Noch eine kleine Zusatzerklärung:
    Sauberes C++ ist in den meisten Fällen kein 100%ig sauberer OOCode.
    Der Grund:
    OOP hat einen grundsatz, genannt "Kapselung" bzw. "iformation hiding".
    Das beruht darauf, dass der Benutzer eines Objektes/einer Klasse zwar wissen sollte, was die Klasse alles kann, aber nicht, wie sie es macht. Die Schnittstelle der Klasse ist im streng objektorientierten Fall also nichts weiter als alles, was in C++-Headern als "public" deklariert wird. Alles weitere sind Implementationsdetails, die den Klienten nichts angehen sollten.
    Wo C++ dieses Prinzip verletzt: bei den oben erwähnten inline-methoden, die direkt im header angegeben werden, und bei allem, was private deklariert ist. Der Klient braucht schließlich nicht zu wissen, ob ich meine Werte intern in einem Struct oder einem Vector oder einem Array abspeichere, sondern nur, wie er auf die Werte zugreifen kann.
    Eine weitestmöglich objektorientierte Ausführung einer Klasse bestünde aus einem header a lá

    header:

    class DasDing
      {
      private:
      class DasDingImp; //Forward-deklaration für eine Implementationsklasse
      DasDingImp * _myImp //Zeiger auf ein Implementationsobjekt
    
      public:
      DasDing(int rhs); //oder wie auch immer die Konstruktoren aussehen sollen
      ~DasDing();
    
      zugriff1();
      zugriff2(); //usw.
      };
    

    Abgesehen davon, dass es eine Klasse gibt, die das alles implementiert, wird dem Klienten rein garnichts verraten...

    cpp-Datei:

    #include dasding.hpp
    //Definition der Implementationsklasse:
    //hier kann mit inlines usw. gearbeitet werden
    class DasDingImp
      {
      private:
      int _einWert;
      const string _noch_ein_Wert;
    
      public:
      DasDingImp(int rhs) : _einWert(rhs), _noch_ein_Wert("?") {};
      ~DasDingImp() {};
    
      zugriff1();  //alle Methoden, die in der eigentlichen Klasse
      zugriff2();  //vorkommen, haben hier ein pendant
    
      priavte:
      eine_kleine_hilfsmethode();
      }
    
    //Implementationen von DasDingImp's Methoden
    
    //...
    
    //Implementationen von DasDing-Methoden
    //Konstruktoren alloziieren Speicher für den Implementationszeiger und rufen
    //den konstruktor der Implementationsklasse auf
    //Destruktor ruft delete für den Zeiger auf
    //alle anderen Methoden werden stumpf weitergereicht
    
    inline DasDing::DasDing(int rhs)
      : _myImp(new DasDingimp(rhs)) {}
    
    inline DasDing::~DasDing() { delete _myImp; }
    
    inline zugriff1() { return _myImp->zugriff1(); }
    inline zugriff2() { return _myImp->zugriff2(); }
    //usw
    

    natürlich könnte man der Übersicht halber die cpp-Datei in 2 oder drei dateien aufsplitten, worauf es aber am Ende ankommt ist, dass der Klient nur die Schnittstelle einbindet. Dazu kommt, dass, wenn ich die interne Darstellung der Klasse ändere (z.B. statt des int plötzlich ein long verwenden will oder eine selbstgeschriebene numerische Klasse), ich nicht mein ganzes Projekt neu kompilieren muss, sondern nur dasding-cpp, weil sich die hpp ja nicht ändert.
    Wem das nach zu viel Arbeit ausschaut, dem sei gesagt, dass sich recht schnell ein perlscript (beispielsweise) schreiben lässt, dass aus der dasding.hpp ein Grundgerüst für die cpp baut mit sämtlichen öffentlichen Methodendeklarationen für die implementationsklasse und der "Implementierung" der Klasse "DasDing", die ja nur aus inline und weiterreichen besteht. So bleibt am Ende nur das übrig, was man so oder so noch tun muss: die privaten und geschützten Methoden und Datenmember deklarieren und die Methoden implementieren, nur eben nicht in der eigentlichen klasse sondern in der Implementationsklasse.

    Man kann das Ganze noch weiter treiben: man kann die Konstruktoren von "DasDing" mit einem (evtl. optionalen) zusätzlichen Parameter versehen, in dem ein Zeiger auf ein Implementationsobjekt übergeben wird oder noch besser, auf eine Fabrikmethode, die so ein implementationsobjekt erzeugt. Schließlich haben wir die Polymorphie, und DasDingImp kann eine abstrakte Basisklasse sein, die verschiedene abgeleitete Implementationsklassen besitzt. So kann man zur Laufzeit auswählen, welche Implementation man denn gerne für das Objekt hätte. Das fängt an bei Implementationen, die geschwindigkeitsoptimiert sind, im vergleich zu Implementationen, die speicherplatzoptimiert sind, bis hi zu Implementationen, die völlig verschiedene Verhalten an den Tag legen.
    (im übrigen: wenn man sich das anguckt, dann sieht man, dass DasDing und alle Implementierungen die gleiche öffentliche Schnittstelle haben, was widerum eine gemeinsame Basisklasse nahelegt...)


Anmelden zum Antworten