Klassenteile in versch. Dateien aufspalten



  • Dravere schrieb:

    Wo ist hier der Unterschied zu dienem System? Was für einen Müll gibt es da speziell zu beachten, was es sonst nicht gäbe?

    Subklassen eines Klassentemplates sind implizit wieder Klassentemplates. Lagert man sie aus muss man sie zu expliziten Klassentemplates machen. Subklassen haben außerdem Zugriff auf die Interna der umgebenden Klasse, das kann nötig sein. Lagert man die Subklasse aus kann bei entsprechend starker Abhängigkeit ein friend vonnöten sein. IMO ist genau das das Entscheidungskriterium Subklasse vs. eigenständige Klasse: Ist es Kontextfrei, dann machs eigenständig. Ists eine starke Abhängigkeit von der umgebenden Klasse, lass es da.

    unskilled schrieb:

    PS: @pumuckl: auch studium oder wieso vector implementieren? ^^

    Nein, mein Studium hab ich gottseidank letztes Jahr abgeschlossen (leider wars nichtmal Informatik). Ich hatte angefangen für meine Iteratorklasse (kommt im besagten dritten Artikel) eine Grundlage zu schaffen worauf sie iterieren kann - und erweitere es just for fun zu einem "echten" std::vector, zumindest vom Interface her.

    Meine Anordnung der Klassen bisher:

    //array_iterator.hpp /////////////////////////
    namespace pumu {
      namespace util {
    
    template <class T>
    class array_iterator //...
    }} //end namespace pumu::util
    
    //memchunk.hpp /////////////////////////
    namespace pumu {
      namespace memory {
    
    template <class T, class Allocator>
    struct MemChunk //...
    }} //end namespace pumu::memory
    
    //vector.hpp /////////////////////////
    #include "pumu/memory/memchunk.hpp"
    #include "pumu/util/array_iterator.hpp"
    
    namespace pumu {
      namespace container {
    
    template <class T, class Allocator>
    class vector
    {
      /*...*/
    public:
      typedef typename pumu::util::array_iterator<T> iterator;
      /*...*/
    private:
      MemChunk<T, Allocator> mem;
    
      struct ConstructGuard; //private Klasse, braucht Zugriff auf private Methoden.
    };
    

    Die Iteratorklasse ist von vornherein ls eigenständige Klasse konzipiert gewesen. Die MemChunk-Klasse war zuerst überhaupt nicht vorhanden, dann habe ich bei einem ersten Refactoring die Speicherverwaltung in eine private Subklasse ausgelagert, die aber eigenständig genug war, so dass sie als eigene Klasse ausgelagert werden konnte. Was ich hier schon skizziert habe ist der nächste Schritt: das Delegieren der Speicherbeschaffung an eine Allokatorklasse statt an operator new direkt - die Änderung erfordert keinerlei Änderungen an der vector-Klasse sondern nur an memchunk selber, ein weiteres Indiz dafür dass Memchunk als eingenständige Klasse bestehen sollte.

    Nein, er implementiert einen Vektor für seinen dritten Artikel, wo er WIEDER AUF BOOST::OPERATORS EINGEHT, STATT SEIN VERSPRECHEN AUS DEM ERSTEN ARTIKEL ZU HALTEN ... dort hat er nämlich gesagt, er würde im zweiten Artikel etwas über die Operatoren new und new[] sagen.
    ->

    pumuckl schrieb:

    Eine umfassende Besprechung der Speicherverwaltungsoperatoren und ihrer Anwendung ist für den zweiten Teil des Artikels geplant, ...

    Das hätte mich sehr interessiert 🙂

    Naja, vielleicht holt er es ja mit der Speicherverwaltung des Vektors nach. Wenn nicht gibt es haue 😉

    Grüssli

    "Ist geplant" heißt nicht versprochen 😛
    die Planung hat sich seit Januar etwas verschoben, das stimmt. Und ja, Teil 3 geht wieder auf boost::operators ein, das war nciht geplant, aber ich musste den Artikel splitten weil er sonst zu lang geworden wäre. Operator new/delete sind auf jeden Fall noch geplant, in welchem Umfang und was alles behandelt wird weiß ich noch nicht. Die Speicherverwaltung des vector gibt da nicht viel her. Der vector selber macht nur placement-new und explizite Dtor-Aufrufe, die Speicherbeschaffung wird über den Memchunk an den Allocator delegiert.



  • @pumu':
    Auf deine ursprüngliche Frage:
    Ich würde *niemals* ein Header-File machen, welches *in* einer Klasse inkludiert werden muss.

    #include hat gefälligst immer nur auf "File-Scope" zu stehen, also ausserhalb jeder Namespaces, Klassen und Funktionen.

    Die einzige Ausnahme hier wären Headers die für Preprozessor-Magick verwendet werden. Wobei ich auch kein besonderer Freund von Preprozessor-Magick bin.

    Wenn du deine Klasse auf mehrere Files aufteilen willst, dann ...
    a) verwende Klassen in Detail-Namespaces anstelle von nested Classes
    b) splitte Deklaration und Definition auf (das geht ja schliesslich auch bei Templates) - dann kannst du ganz leicht mehrere Files für die Definition verwenden wenn du unbedingt willst. Am ende des ".hpp" Files inkludierst du dann einfach alle ".ipp"/".imp" Files (=die Files mit den Definitionen drinnen).

    Oft ist IMO (a) die bessere Möglichkeit, da man dadurch die IMO hässliche Syntax für von der Deklaration getrennte Definitionen umgeht. Und oft kann man einige dieser Klassen dann für mehr als nur eine "äussere" Klasse einsetzen.

    Oft verbietet man den Zugriff auf diese Detail Klassen dann auch nicht (wäre meist ziemlich viel "friend" Tippaufwand). Ist aber IMO kein Problem. Wer in seinem Code Fremde Detail-Klassen verwendet, ist des selberen schuld wenns mit der nächsten Version des Fremd-Codes nichtmehr geht.



  • War meine Frage, aber danke ^^

    und ich werds trennen (in nen detail namespace)

    ob ichs noch in deklaration und *.inl / .impl (.ipp und nur *.imp hab ich noch nie gesehen - aber das hat ja nix zu sagen ^^) trennen, weiß ich noch nicht genau - also selbst verständlich nur den code, der jz im "main"-file stehen geblieben ist - also der zu list... eigtl bin ich da bei templates nich so der fan von - bsp.:

    void _erase(node *to_del, node_base *before, node_base *after)
    {
    	assert(to_del && before && after);
    	assert( (to_del != &m.anchor_begin) && (to_del != &m.anchor_end) );
    
    	before->next = after;
    	after->prev = before;
    	delete to_del;
    }
    

    vs

    template <typename T>
    void list<T>::_erase(typename list<T>::node *to_del, typename list<T>::node_base *before, typename list<T>::node_base *after)
    {
    	assert(to_del && before && after);
    	assert( (to_del != &m.anchor_begin) && (to_del != &m.anchor_end) );
    
    	before->next = after;
    	after->prev = before;
    	delete to_del;
    }
    

    bb



  • War meine Frage, aber danke ^^

    lol
    Hast du auch wieder Recht... 😃

    Und genau das meinte ich mit "hässlicher Syntax" 🙂

    Deswegen bin ich in letzter Zeit auch dazu übergegangen, Klassen-Templates fast ausschliesslich implizit inline zu schreiben.



  • Da bin ich mir beim vector noch unschlüssig. Das Ding hat ja eine ganze Menge Methoden, die Datei kommt am Ende sicherlich auf 700-1000 Zeilen. Bei so einer Menge finde ich eine möglichst kurze Klassendefinition (allein mit Methodendeklarationen und 2 Membervariablen schon 87 Zeilen) schon lang genug.

    Ich bin am überlegen ob man die hässliche Syntax durch ein oder zwei Makros etwas aufpolieren könnte:

    //alte Version:
    template <class T>
    typename vector<T>::iterator vector<T>::begin()
    {
      return iterator(ptrbegin());
    }
    
    template <class T>
    void vector<T>::resize(typename vector<T>::size_type sz, T c = T())
    {
      if (sz <= size()) return;
      reserve(sz);
      constructBackN(sz-size(), c);
    }
    
    //neue Version:
    #define VEC_METHOD(type) \
    template <class T> \
    type vector<T>::
    
    #define INNER_TYPE_METHOD(type) \
    template <class T> \
    typename vector<T>::##type vector<T>::
    
    #define DEP_T(type) \
    typename vector<T>::#type
    
    INNER_TYPE_METHOD(iterator) begin()
    {
      return iterator(ptrbegin());
    }
    
    VEC_METHOD(void) resize(DEP_T(size_type) sz, T c = T())
    {
      if (sz <= size()) return;
      reserve(sz);
      constructBackN(sz-size(), c);
    }
    

    so ganz glücklich bin ihc damit aber nicht...



  • @pumuckl:
    Also ich hasse solche Makros. Inbrünstig.
    Weil sie die Zeit die man braucht um den Code zu lesen (wenn man ihn noch nicht kennt) vervielfachen.



  • hustbaer schrieb:

    @pumuckl:
    Also ich hasse solche Makros. Inbrünstig.
    Weil sie die Zeit die man braucht um den Code zu lesen (wenn man ihn noch nicht kennt) vervielfachen.

    Wenns zu viele sind durchaus. Wie schon gesagt, glücklich war ich damit nicht.
    Mit einer 800-Zeilen Klasse wäre ich aber noch weniger glücklich, und die Syntax der externen definition ist halt hässlich....



  • hustbaer schrieb:

    Deswegen bin ich in letzter Zeit auch dazu übergegangen, Klassen-Templates fast ausschliesslich implizit inline zu schreiben.

    Ich auch - aber wie pumuckl schon sagt... es ist nicht gerade übersichtlicher dadurch x)
    das makro find ich aber auch hässlich... es bringt dir eine zeile pro funktion - also vll 40 zeichen pro funktion - ich würd sie lieber mitschreiben, als mich dann durch makros wühlen zu müssen....

    bb


  • Administrator

    Darf ich da kurz nochmals nachfragen? Ihr findet sowas hässlich?

    //////////////////////////////////////////////////////////////////////
    // NamedObject
    
    template<typename T>
    class NamedObject
    {
      // Typedefs //
    public:
      typedef T Type;
    
      // Attributes //
    public:
      Type m_object;
      std::string m_name;
    
      // Constructors //
    public:
      NamedObject(std::string const& name, Type const& object);
    
      // Methods //
    public:
      Type const& get_object() const;
      std::string const& get_name() const;
    };
    
    //////////////////////////////////////////////////////////////////////
    // Templates implementation
    
    //////////////////////////////////////////////////////////////////////
    // NamedObject
    
    /********************************************************************/
    /* Constructors                                                     */
    /********************************************************************/
    
    template<typename T>
    NamedObject<T>::NamedObject(std::string const& name, Type const& object)
      : m_name(name)
      , m_object(object)
    {
    }
    
    /********************************************************************/
    /* Methods                                                          */
    /********************************************************************/
    
    template<typename T>
    T const& NamedObject<T>::get_object() const
    {
      return m_object;
    }
    
    /********************************************************************/
    
    template<typename T>
    std::string const& NamedObject<T>::get_name() const
    {
      return m_name;
    }
    

    Was genau mögt ihr daran nicht? Ich mag nämlich so eine Aufteilung. Gut wir reden hier jetzt über Geschmack, aber es würde mich interessieren, was genau ihr daran nicht mögt 😉

    Grüssli



  • Zwei Möglichkeiten: entweder die Klasse ist tatsächlich so kurz, dann sind die diversen Kommentare überflüssig - die Klasse wäre dann übersichtlich genug. 😛

    Oder die Klasse ist sehr viel länger, dann hätte man nicht vor drei sondern vor zig Methoden ein template<typename T> NamedObject<T>:: usw. stehen - und die ständige Wiederholung ist halt auch nicht sehr schön.
    Vector hat mit Überladungen mal schlappe 40 Methoden, viele haben davon interne typedefs als Rückgabetyp und/oder Parameter, so dass da jedesmal noch ein typename vector<T, Allocator>::iterator oder ähnliches dazu muss - dann kommen noch die freien Vergleichsfunktionen dazu. Überschlagen sind das 60-80 mal vector<T,Allocator>:: , 40x template<class T, class Allocator> , 20-40x typename - nervig!



  • ich hatte doch oben scho nen Beispiel gemacht...

    template <typename T> 
    void list<T>::_erase(typename list<T>::node *to_del, typename list<T>::node_base *before, typename list<T>::node_base *after) 
    { 
        assert(to_del && before && after); 
        assert( (to_del != &m.anchor_begin) && (to_del != &m.anchor_end) ); 
    
        before->next = after; 
        after->prev = before; 
        delete to_del; 
    }
    

    ist einfach hässlich -.-

    vor allem, wenn ichs auch so machen könnte:

    void _erase(node *to_del, node_base *before, node_base *after)
    {
        assert(to_del && before && after);
        assert( (to_del != &m.anchor_begin) && (to_del != &m.anchor_end) );
    
        before->next = after;
        after->prev = before;
        delete to_del;
    }
    

    meinste nicht auch? der nachteil liegt natürlich auch auf der hand:
    die klasse ist voll mit definitionen und so sind die deklarationen schwerer zu finden...
    naja - so, wie es jz aussieht, werd ich es zwar trennen, aber so ganz glücklich damit bin ich eben auch noch nicht... -.-

    Man sieht ja schon hier:
    3x typename
    4x list<T>::
    1x template <typename T>

    und das ist nur eine einzige Funktion... -.-

    bb

    PS: Hab gerad pumuckls Post gesehen(Vorschau) - genau das ist es eben - nervig und unschön - bei mir wäre es eben auch so in etwa...


  • Administrator

    pumuckl schrieb:

    Zwei Möglichkeiten: entweder die Klasse ist tatsächlich so kurz, dann sind die diversen Kommentare überflüssig - die Klasse wäre dann übersichtlich genug. 😛

    Ich bleibe bei der Darstellung der Klassen einheitlich, deshalb sind die Kommentare bei mir immer vorhanden, egal ob es eine grosse oder kleine Klasse ist 😉

    pumuckl schrieb:

    Überschlagen sind das 60-80 mal vector<T,Allocator>:: , 40x template<class T, class Allocator> , 20-40x typename - nervig!

    Ach, es geht dir hier nur ums schreiben? Da sehe ich keine Probleme, da man sich hier ganz einfache Abhilfe über Copy&Paste schaffen kann, weil es eben immer das gleiche ist. Sowas hat man in ca. einer Minute erledigt, auch bei 60-80 Methoden 😉
    1. Man kann die Deklaration der Funktionen kopieren und für die Definition verwenden.
    2. Vor die Funktionen muss jeweils zuerst ein template<...> und in einem zweiten Durchlauf ein Class<...>:: . Maus & <Ctrl> + <V> -> zack, zack, zack 😉
    3. Zum Teil muss noch ein typename Class<...>:: vor den Rückgabewert. Wieder Maus & <Ctrl> + <V> -> zack, zack, zack 😉
    4. Ich muss noch meine Kommentare einfügen. Die grobe Unterteilung der Bereiche ist einfach, da habe ich zusätzlich sogar noch Snippets.
    Für die Trennung der Methoden, ist es auch immer die gleiche Linie und noch eine neue Linie. Falls ein zusätzliche Abstand noch nicht vorhanden ist, füge ich per <Enter> noch eine ein. Wieder Copy&Paste, dass geht wieder zack, zack, zack 😉
    5. Alle Semikolons durch eine neue Zeile und darunter '{' neue Zeile '}' ersetzen lassen. Das ist auch schnell erledigt.
    (Edit: 4. und 5. mache ich oft sogar zusammen und füge dann nur noch fehlende neue Zeilen per <Enter> ein ;))

    @unskilled,
    Die Funktionssignatur kannst du kürzen:

    template<typename T> 
    void list<T>::_erase(node *to_del, node_base *before, node_base *after)
    

    Grüssli



  • Oo

    Wusst ich noch gar nicht - auf die Idee wär ich auch nich gekommen ^^

    Danke : >


  • Administrator

    unskilled schrieb:

    Wusst ich noch gar nicht - auf die Idee wär ich auch nich gekommen ^^

    Hmmm, vielleicht noch zur Erklärung, wieso das hier geht:
    Sobald du mit list<T>:: gesagt hast, wo die Funktion liegt, ist der entsprechende Scope bekannt. Somit wird für die Suche nach den Typen auch der Scope der Klasse list<T> herangezogen.
    Das ist auch der Grund, wieso es nicht für den Rückgabetypen geht. Dort ist der Scope der Funktion noch nicht bekannt.
    Und das ist einer der Vorteile von C++0x, wo man den Rückgabetypen hinter die Funktionssignatur schreiben können wird, da dort wieder der Scope bekannt ist. In C++0x wird man somit nicht mal mehr beim Rückgabetypen typename list<T>:: hinschreiben müssen 😉

    Grüssli



  • Danke - fie Erklärung an sich hatte ich jz auch scho ergoogelt ^^

    Das Feature des neuen Standards kannte ich noch gar nich - aber hab mich allgemein noch nich so sehr damit beschäfitgt - gibt auch so noch genug, was ich nich weiß 😉
    Aber danke noch mal : >

    bb



  • template <class GreenType, class ShadyBlueType>
    class FooBarBazQux
    {
    public:
    	class State {};
    	class LaliDo {};
    
    	// lieber so ...
    	static boost::shared_ptr<State> FiFaFunction(GreenType green, LaliDo lali);
    };
    
    // ... oder so?
    template <class GreenType, class ShadyBlueType>
    boost::shared_ptr<typename FooBarBazQux<GreenType, ShadyBlueType>::State> FooBarBazQux<GreenType, ShadyBlueType>::FiFaFunction(
            GreenType green,
            typename FooBarBazQux<GreenType, ShadyBlueType>::LaliDo lali)
    {
    }
    

    Ich finde da doch eher die erste Variante "besser" 😉



  • Dravere schrieb:

    Ach, es geht dir hier nur ums schreiben?

    Nein, es geht NIE ums schreiben, sondern immer ums lesen. Und wenn der Code vollgemüllt ist mit template<...> , Class<...> und typename Class<...>:: dann machts das Lesen deutlich schwerer.
    Ich denke dass die implizit-inline Version dann doch besser lesbar ist - und da man in den meisten IDEs inzwischen soetwas wie ein "collapse all" hat, ists auch nicht schwer, die ganzen Definitionen auf die Funktionssignaturen zu reduzieren.


  • Administrator

    @pumuckl,
    Dann könntest du aber das gleiche über die Trennung sagen, wenn es sich nicht um Templates handelt. Man hat auch überall ein Class:: davor auch vor Rückgabetypen, welche aus der Klasse stammen. Das ganze wiederholt sich auch die gane Zeit und wenn man alles inline machen würde, könnte man mit der IDE durch "collapse all" auch ohne Probleme die reine Deklaration sehen. 😉

    Wenn du allgemein der Meinung bist, dass die Trennung unübersichtlich ist, dann versteh ich es zwar nicht, bzw. bin anderer Meinung, kann es aber unter Geschmacksache versorgen.
    Wenn du nur Trennung bei Templates als Problem siehst, dann wird in meinem Gehirn eine std::logic_error Exception geworfen 🙂

    @hustbaer,
    Du hast unteranderem den gleichen Fehler gemacht, wie unskilled ihn bereits getan hat 😉
    Zudem kann man das durchaus auch noch ein wenig besser strukturieren.

    template <class GreenType, class ShadyBlueType>
    boost::shared_ptr
    <
      typename FooBarBazQux
      <
        GreenType,
        ShadyBlueType
      >::State
    >
    FooBarBazQux<GreenType, ShadyBlueType>::FiFaFunction(GreenType green, LaliDo lali)
    {
    }
    

    Und mit dem neuen Standard wäre es wohl so, oder? (kenne mich mit der Syntax noch nicht so genau aus:

    template <class GreenType, class ShadyBlueType>
    auto FooBarBazQux<GreenType, ShadyBlueType>::FiFaFunction(GreenType green, LaliDo lali)
      -> boost::shared_ptr<State>
    {
    }
    

    Und ja, ich ziehe sowas vor. Vor allem sind solche komplexe Ausdrücke, wie du einen hier präsentierst, eher selten anzutreffen. Und wenn sie vermehrt anzutreffen sind, dann kann man sie meistens durch ein simples typedef irgendwo oder einer kleinen Hilfstruktur wesentlich vereinfachen.

    Grüssli


Anmelden zum Antworten