STL container und abstrakte Datentypen - praktische tips



  • hehe simon! 😃
    ich bin der uebermuetige, ueberhebliche lehrling.. jaja.. hast ja recht ;-D

    Was ich aus der diskussion um map gelernt habe, ist, dass map<const IA*, const IB*> eigentlich keinen sinn gibt. weill man den gleichen effekt mit const map<IA*, IB*> bekommt, und da braucht man dann eben nicht umzukopieren. ich kann mich auch nicht erinnern, schonmal eine map mit const key oder value gesehen zu haben (aussert bei const char*). es waehre eigentlich noch sinnwoll, wenn das in tutorials fuer C++ STL containers erwaehnt wuerde, hab ich aber bis jetzt noch nie gelesen. klar, man kann es aus dem code ableiten... aber da muss man zuerst mal auf die idee kommen das es so sein koennte, und dann alle methoden durch gehen, um zu schauen ob die bei const auch wirklich keine moeglichkeit zum veraendern der keys und values erlauben.



  • hoijui schrieb:

    Was ich aus der diskussion um map gelernt habe, ist, dass map<const IA*, const IB*> eigentlich keinen sinn gibt. weill man den gleichen effekt mit const map<IA*, IB*> bekommt

    ähm nein.
    - bei einer map<const IA*, const IB*> hast du eine Map mit zeigern auf konstante Objekte. Du kannst der map sowoghl zeiger auf weitere Konstante Objkete hinzufügen und entfernen, als auch die Zeiger, die schon in der map sind, umbiegen auf andere konstante Objkete
    - const map<IA*, IB*> heißt, dass du weder neue Zeiger hinzufügen kannst noch die Zeiger umbiegen kannst (weil ein Zugriff auf eine const map nur const values, also konstante Zeiger liefert) was du sehr wohl kannst ist die Objekte zu verändern, auf die die Zeiger verweisen, denn die Objkete selbst sind nicht const. Somit sind die bieden das genaue gegenteil, const-bezüglich
    - der vollständigkeit halber: map<IA * const, IB * const> wäre eine weitere Alternative. Hier kannst du die Zeiger nicht umbiegen (sind ja const), kannst aber sehr wohl weitere konstante zeiger einfügen und entnehmen (die map selbst ist nicht const), und auch die Objkete sind veränderbar.



  • pumuckl schrieb:

    ...
    - const map<IA*, IB*> ... was du sehr wohl kannst ist die Objekte zu verändern, auf die die Zeiger verweisen, denn die Objkete selbst sind nicht const. Somit sind die bieden das genaue gegenteil, const-bezüglich
    ...

    Hmmm also ich glaube nicht.
    In der "Container-Theorie" schon (bei einem Array geht das vermutlich) ... aber Du bekommst bei einer const-map keine veränderbaren Referenzen auf die Elemente.
    Oder habe ich da was übersehen ?

    Gruß,

    Simon2.



  • ja, hab das eben auch so verstanden wie simon. von einer const map bekommt man nur const_iterators, keine iterators, und auch operator[] kann man nicht verwenden, weill der nicht const ist (hat simon schon erklaehrt). wie soll man also die elemente veraendern koennen?
    die einzige weise die ich sehe ist, wenn man pointer drinn hat, dass dann nur die pointer konstant sind, nicht aber die daten selber, auf die sie zeigen, wenn man sie ueber den const_iterator aus der map holt. ist das so?



  • Simon2 schrieb:

    In der "Container-Theorie" schon (bei einem Array geht das vermutlich) ... aber Du bekommst bei einer const-map keine veränderbaren Referenzen auf die Elemente.
    Oder habe ich da was übersehen

    Ja, hast du. Die Elemente der Map sind Pointer, nicht die Daten selbst. Die Pointer sind wie geschrieben nicht veränderbar, aber dereferenzierung ändert den Pointer ja auch nicht. Worauf diese Pointer zeigen ist aber durchaus veränderbar. Das ist der unterschied zwischen

    const T*; //pointer auf ein const objekt
    T* const; //const pointer auf ein durchaus veraenderbares Objekt
    

    nehmen wir einen konstanten Container<T> wie im Beispiel, dann ist T in dem Fall ein IA*. Ein zugriff auf den konstanten Container liefert mir ein const T&, also ein IA *const& - eine Referenz auf einen Konstanten zeiger auf ein (nicht-konstantes) IA



  • pumuckl schrieb:

    Simon2 schrieb:

    In der "Container-Theorie" schon (bei einem Array geht das vermutlich) ... aber Du bekommst bei einer const-map keine veränderbaren Referenzen auf die Elemente.
    Oder habe ich da was übersehen

    Ja, hast du. ...

    Stimmt.

    Das ist mir kurz nach meinem letzten Post aufgefallen, ich war allerdings nicht "am Platz". So gesehen ist das wirklich keine Lösung für hoijui ...

    Gruß,

    Simon2.



  • hoijui schrieb:

    ist die idee eigentlich, dass der IA copy-ctor gar nie benutzt werden muss? soll ich desshalb alles mit pointern machen?

    Das du mit Zeigern oder Smartpointern arbeiten sollst liegt nicht an den verhindern der Kopien, sondern das ein konkretes C++ Objekt auch genau zu dem Typ passen muss. Das du davon nichts in Java siehst hat mit einer meiner ersten Aussagen zu tun, das eigentlich alle Objektzugriffe in Java intern mit Zeigern realisiert werden.

    Nehmen wir mal folgendes an:

    struct A {
      int a;
    };
    
    struct B : public A {
      int b;
    }
    
    int main()
    {
      A var1;         // var1 enthält ein int (a)
      B var2;         // var2 enthält zwei int (a, b)
      A var3 = var2;  // var3 enthält ein int (a), der B-Teil wird abgeschnitten
    }
    

    Daher gehen nur Zeiger oder Referenzen, da diese nichts am Objektstatus ändern (Nur weil du über ein A-Zeiger auf B Zugreifst wird das B nicht zum A).

    cu André



  • hoijui schrieb:

    ist die idee eigentlich, dass der IA copy-ctor gar nie benutzt werden muss?

    Der IA Copy-Ctor wird immer dann benutzt, wenn ein Copy-Ctor einer abgeleiteten klasse benutzt wird. Copy-Ctoren rufen normalerweise die Copy-Ctoren ihrer Basisklassen auf, da der Basisklassen-Teil ja auch konstruiert werden muss.

    Anhand des Brötchebeispiels: Brötchen sind abstrakt (weil keiner ein trockenes brötchen ohne alles will), aber um ein Matjesbrötchen zu kopieren muss das Brötchen selbst mitkopiert werden:

    class Broetchen  //trocken und ohne alles
    {
    public:
      Broetchen(Broetchen const& rhs)
      {
        //do something
      }
    
      virtual ~Brötchen() = 0 {}
    };
    
    class Matjesbroetchen : public Broetchen
    {
      Matjes dermatjes;
    public:
      Matjesbroetchen(Matjesbroetchen const& rhs) : Broetchen(rhs), dermatjes(rhs.matjes)
      {}
    };
    


  • im moment siehts also so aus:

    class Manager {
    public:
        map<boost::smart_ptr<const IA>, boost::smart_ptr<const IB> > GetAssoc() {
    
            map<boost::smart_ptr<const IA>, boost::smart_ptr<const IB> > c_assoc;
    
            map<boost::smart_ptr<IA>, boost::smart_ptr<IB> >::const_iterator it;
            for (it=assoc.begin(); it!=assoc.end(); it++) {
                c_assoc[boost::smart_ptr<IA>(new CA(it->first))] = boost::smart_ptr<IB>(new CB(it->second));
            }
    
            return c_assoc;
        }
    
    private:
        map<boost::smart_ptr<IA>, boost::smart_ptr<IB> > assoc;
    
        void InsertAssoc(const IA& a, const IB& b) { assoc[boost::smart_ptr<IA>(new CA(a))] = boost::smart_ptr<IB>(new CB(b)); }
    }
    

    muss sagen.. das sieht schon ziemlich haesslich aus, wird wohl bald fertig sein, hm?

    sollt ich in IA und IB eine clone() methode haben, damit ich die anstatt den copy-ctor von CA und CB benutzen kann? waehre wohl schoener, auch wenn ich glaube es waere nicht notwendig fuer meine zwecke.



  • hoijui schrieb:

    im moment siehts also so aus:

    muss sagen.. das sieht schon ziemlich haesslich aus ...

    Naja, Du hast Dir ja auch maximal Mühe gegeben, den Code häßlich zu machen.
    Mit ein paar typedefs wäre das doch schon ziemlich entspannt.

    BTW: C-Style-Casts deprecated; sind die Upcasts wirklich nötig ?
    ... und Deine "Kopiersemantik" finde ich immer noch unglücklich/gefährlich. Du nutzt auch gar keinen Copy-Konstruktor (Quell- und Zieltyp identisch), sondern einen "normalen"....

    hoijui schrieb:

    sollt ich in IA und IB eine clone() methode haben, damit ich die anstatt den copy-ctor von CA und CB benutzen kann? waehre wohl schoener, ...

    Nur für "Javaisten, die an Althergebrachtem kleben". 😉 😃

    Gruß,

    Simon2.



  • Wisst ihr überhaupt was ein "abstrakter Datentyp" ist? Schlagt mal besser in einem Informatik-Handbuch/Lexikon nach.



  • in der for-schleife willst du ja smart-ptr auf const IA (bzw. const IB) in die ergebnis-map packen, solltest du also auch machen 😉

    class Manager {
    public:
        template <class T> struct sptr {
          typedef boost::smart_ptr<T> ptr;
        };
    
        template <class T, class U> struct ptrmap {
          typedef std::map<sptr<T>::ptr, sptr<U>::ptr> map;
          typedef std::map<sptr<const T>::ptr, sptr<const U>::ptr> constmap;
        };
    
        ptrmap<IA,IB>::constmap GetAssoc() {
            ptrmap<IA,IB>::constmap c_assoc(assoc.begin(), assoc.end()); // da die typen implizit konvertierbar sind, kann man auch den range-ctor nutzen :)
            return c_assoc;
        }
    
    private:
        ptrmap<IA,IB>::map assoc;
    
        void InsertAssoc(const IA& a, const IB& b) { assoc[sptr<IA>::ptr(new CA(a))] = sptr<IB>::ptr(new CB(b)); }
    }
    

    evtl müssen noch typename an der einen oder anderen Stelle eingebaut werden...



  • Hilfegebender schrieb:

    Wisst ihr überhaupt was ein "abstrakter Datentyp" ist? Schlagt mal besser in einem Informatik-Handbuch/Lexikon nach.

    in C/++ kann das vieles sein

    ein definitiv nicht java konformes beispiel 😛

    typedef <setze typ ein> AbstrakterDatentyp; //<-hier passiert die abstraktion .-P
    
    ...
    
    AbstrakterDatentyp v1, v2;
    
    v1 = 10; //setzt implizite typenkonversion voraus
    v2 = v1; //setzt kopiersemantik voraus
    
    v1 = AbstrakterDatentyp(); //setzt default konstruktor voraus
    
    ...
    

    oder noch weniger java konform
    [cpp]

    #define <insert datatype> AbstrakterTyp

    AbstrakterTyp zuAbstrakt;
    ...
    [cpp]

    scnr



  • danke pumuckl! 🙂
    hab natuerlich noch nie was von nem range-ctor gehoert 😉
    aber wenns das so gibt und das funktioniert, gut 🙂
    tja.. auch ohne den range-ctor haett ich da unnoetig objekte erzeugt.. sehr ich jetzt. danke auch da fuers aufzeigen.

    mir ist klar, dass ich bei euch C++ leutz da nicht auf zustimmung treffen werde, aber das ganze ist mit und ohne typedefs einfach sehr haesslich. obwohl es doch etwas so standardmaessiges ist, fuer OO: abstrakte datentypen in containern, mutable und unmutable. da C++ die OO variante von C ist, sollte OO damit einfach sein. dann wiederum.. dafuer gibts ja java 😃
    bei C++ kann man mer optimieren und bei Java ists dafuer einfacher.
    ich dachte immer ich werd mich mit neuen sprachen beschaeftigen, das laege mir eigentlich eher.. jetzt begeb ich ich zurueck in der zeit. 😕

    ich mach das ganze uebrigens, weill ich an einem Java interface fuer eine C/C++ schnittstelle arbeite, damit ich mich nicht mehr mit solchen sachen rumschlagen muss. 😉



  • hoijui schrieb:

    ...es doch etwas so standardmaessiges ist, fuer OO: abstrakte datentypen in containern, mutable und unmutable. da C++ die OO variante von C ist, sollte OO damit einfach sein. dann wiederum.. dafuer gibts ja java 😃
    bei C++ kann man mer optimieren und bei Java ists dafuer einfacher....

    Naja, Java hat halt deutlich geringere "const"-Möglichkeiten und eine deutlich schwächeres "Compiletime-Typsicherheit"... da werden diese Schwierigkeiten eben in die Laufzeit verlagert - wer geduldige Kunden hat, den stört das halt nicht. 😉

    Gruß,

    Simon2.



  • Also wen man deine zwei argumente zu einem einzigen zusammenfasst, versteh ich was du meinst. Bei C++ gehoert (un-/)mutable zum datentyp in der compile time. in Java normalerweise nicht; das kan vorteile haben, z.B. muss man desshalb eine map nicht umkopieren, sondern kann sie einfach mit einer anderen map wrappen, wenn dies logisch moeglich ist. diese map koennte z.B. UnmutableMap heissen und ein leeres marker interface Unmutable oder eine Annotation Unmutable enthalten, so kriegt man auch compiletime mutable checks (und der code ist immernoch viel simpler ;-)).

    Ich seh ja viele vorteile in C++, vonwegen mehr kontrolle, dass man genau sehen kann was abgeht (pointer, referenzen, values z.B.), dass man mehr fine-tuning machen kann, ...
    aber dass die Kunden laenger auf stabilen code warten muessen, das scheint mir eine ziemlich unrealistische annahme.

    uebrigens.. wie wuerde ich nun den copy konstruktor von IA implementieren? ich hab bis jetzt imer nur gehoert wies nicht geht.



  • hoijui schrieb:

    Bei C++ gehoert (un-/)mutable zum datentyp in der compile time. in Java normalerweise nicht; das kan vorteile haben, z.B. muss man desshalb eine map nicht umkopieren, sondern kann sie einfach mit einer anderen map wrappen, wenn dies logisch moeglich ist. diese map koennte z.B. UnmutableMap heissen und ein leeres marker interface Unmutable oder eine Annotation Unmutable enthalten, so kriegt man auch compiletime mutable checks (und der code ist immernoch viel simpler ;-)).

    Wobei genau siehst du den Vorteil von

    UnmutableMap constMap = new UnmutableMap(mutableMap);

    gegenüber:

    Map const& constMap = mutableMap;

    immutable ist ein konzept dass man braucht wenn man kein const in der Sprache hat. In C++ ist jeder Datentyp mutable oder immutable und du kannst dir aussuchen wann er was ist. dh auch, dass man handles auf interne Daten zurück geben kann - weil man sie immutable zurück geben kann - wo man in Java entweder eine wrapper Klasse braucht oder eine defensive kopie anlegen muss...



  • ich meinte eher den unterschied zwischen dem:

    UnmutableMap<IA, IB> um;
    

    und dem:

    template <class T> struct sptr {
          typedef boost::smart_ptr<T> ptr;
        };
    
        template <class T, class U> struct ptrmap {
          typedef std::map<sptr<T>::ptr, sptr<U>::ptr> map;
          typedef std::map<sptr<const T>::ptr, sptr<const U>::ptr> constmap;
        };
    
        ptrmap<IA,IB>::map um;
    

    Ich hab schon wieder ein problem:

    class Manager {
    public:
        template <class T> struct sptr {
          typedef boost::smart_ptr<T> ptr;
        };
    
        template <class T, class U> struct ptrmap {
          typedef std::map<sptr<T>::ptr, sptr<U>::ptr> map;
          typedef std::map<sptr<const T>::ptr, sptr<const U>::ptr> constmap;
        };
    
        ptrmap<IA,IB>::constmap GetAssoc() {
            ptrmap<IA,IB>::constmap c_assoc(assoc.begin(), assoc.end()); // da die typen implizit konvertierbar sind, kann man auch den range-ctor nutzen :)
            return c_assoc;
        }
    
        sptr<const IB>::ptr Find(const sptr<const IA>::ptr& a) const {
            return assoc.find(a); // compiler error: invalid conversion from 'const IA* const' to 'IA*'
        }
    
    private:
        ptrmap<IA,IB>::map assoc;
    
        void InsertAssoc(const IA& a, const IB& b) { assoc[sptr<IA>::ptr(new CA(a))] = sptr<IB>::ptr(new CB(b)); }
    }
    

    bei der Find() methode. Das uebergebene IA ist const, aber weill die map smart pointers auf nicht const IA als key benutzt, kann ich mit einem IA nicht in der map suchen. ich will die IAs in meiner map aber nicht const haben, und ich will in der find methode garantieren dass das uebergebene IA nicht veraendert wird. das waere ja auch nicht der fall, aber der compiler laesst mich nicht. wie soll ich das tun?



  • hoijui schrieb:

    ich meinte eher den unterschied zwischen dem:

    UnmutableMap<IA, IB> um;
    

    und dem:

    template <class T> struct sptr {
          typedef boost::smart_ptr<T> ptr;
        };
         
        template <class T, class U> struct ptrmap {
          typedef std::map<sptr<T>::ptr, sptr<U>::ptr> map;
          typedef std::map<sptr<const T>::ptr, sptr<const U>::ptr> constmap;
        };
    
        ptrmap<IA,IB>::map um;
    

    ...

    Du meinst also den Unterschied zwischen dem Anzeigen einer Implementierung und dem Weglassen ? :p

    Gruß,

    Simon2.

    P.S.: Ich glaube a nicht, dass die Implementierung von UnmutableMap so viel kürzer ist als die von ptrmap ....


Anmelden zum Antworten