Aggregation und Komposition realisieren



  • Hallo,

    ich versuche mir gerade die OOP unter C++ verständlich zu machen und bin dabei über die Realisierung von Aggregationen und Kompositionen gestolpert.

    Ich weiß, daß ich dieses mit dynamischen Arrays realisieren könnte, suche aber einen leichteren, übersichtlicheren Weg.

    In Delphi würde ich einfach ObjectLists verwenden, aber in C++ fehlt mir einfach noch das Wissen über die Feinheiten.

    Vielleicht hat ja der eine oder andere einen Tip.

    Vielen Dank im voraus,

    Fingolfin



  • zB std::list oder std::vector,...

    schau dir mal die standard library an: http://www.sgi.com/tech/stl/



  • Danke schonmal, ich werfe da jetzt mal 'nen Blick drauf. Vielleicht könnte ja jemand ein kleines Beispiel-Programm hier abbilden, so in der Art Fisch hat beliebige Anzahl von Flossen (nicht ganz korrekt, aber mir gehts es besonders um Kompositionen mit dynamischer Anzahl).

    Bitte Bitte 🙂

    Fingolfin



  • Was mich ein bißchen stört ist, daß Du Komposition und Aggregation gleich mit mehr als einem Element verbindest... z.B. hat ein Auto ein Lenkrad (1-Komposition), einen Koffer im Kofferraum (0..1-Aggregation, kann ja kein Koffer da sein und der Koffer lebt auch ohne Auto weiter) und 4 Räder (4-Komposition), aber n-Insassen (n <= 5, n-Aggregation), was sich so abbildet:

    class Auto
    {
    private:
       Lenkrad m_lenkrad;
       Koffer* m_pKoffer; // NULL bei 0, Zeiger bei einem Koffer
       Rad     m_Rad[4];  // Anzahl fest, Komposition => normales Array
       std::vector<Person*> m_Insassen; // Anzahl dynamisch, Objekte leben weiter (Aggregation)
    };
    

    Man müßte eher eine Liste machen:

    Komposition, 1 Element      Objektinstanz
    Komposition, 0..1 Elemente  Zeiger auf Objektinstanz, 0 wird durch NULL angezeigt
    Komposition, n Elemente     Array aus Objektinstanzen
    Komposition, 0..* Elemente  dyn. Array (vector<T>, list<T>) aus Objektinstanzen
    Aggregation, 1 Element      Referenz auf Objektinstanz
    Aggregation, 0..1 Elemente  Zeiger auf Objektinstanz, NULL zeigt 0 an
    Aggregation, n Elemente     Array aus Zeigern auf Objektinstanzen
    Aggregation, 0..* Elemente  dyn. Array (vector<T*>, list<T*>) aus Zeigern auf Objektinstanzen
    

    Das wären typische Realisierungen für Deine Fälle.



  • Ich weiß, meine Frage war noch 'n bissel schwammig, aber ich wollte lieber zuviele Antworten als zuwenige.

    Vielen Dank, deine Übersicht ist hammergut, ich werde damit jetzt mal 'ne Weile rumspielen.

    Nur noch eine Kleinigkeit, wo liegt der Unterschied zwischen vector und list?

    Fingolfin



  • Fingolfin schrieb:

    Nur noch eine Kleinigkeit, wo liegt der Unterschied zwischen vector und list?

    In der unterschiedlichen Implementation, was zu unterschiedlicher Performance und verschiedenen Schnittstellen führt. Listen sind stark darin, an beliebigen Stellen Elemente einzufügen und zu entfernen, dafür kannst du sie nur sequentiell durchlaufen (also nicht bspw. direkt auf das 10. Element zugreifen). Vektoren dagegen haben diesen Random-Access, dafür ist das Einfügen und Entfernen in der Mitte oder am Anfang extrem ineffizient (am Ende Einfügen (push_back) ist OK)



  • vector bietes schnellen Zugriff auf die Elemente, dafür ist das einfügen neuer Elemente Langsam. bei list ist es umgekehrt. Kommt eben drauf an, was wichtiger ist.



  • Helium schrieb:

    vector bietes schnellen Zugriff auf die Elemente, dafür ist das einfügen neuer Elemente Langsam. bei list ist es umgekehrt. Kommt eben drauf an, was wichtiger ist.

    Vielen Dank.

    Mein Gott, das ist ein klasse Forum, die Antworten kommen ja reingeprasselt wie nix 🙂

    Fingolfin



  • Und weil mir noch niemand einen Grund genannt hat, das nicht ständig zu predigen: Wenn du Zeiger verwenden *musst*, sind die smarten aus Boost (http://www.boost.com) meistens besser als die eingebauten. Für ein "gibt es vielleicht"-Feld bietet sich z.B. boost::scoped_ptr an; um Objekte in einen Container zu verfrachten boost::shared_ptr. Das ist zwar nochmal eine anfängliche Hürde (vor allem shared_ptr), aber spart später Arbeit.



  • Ich habe ein bissel rumgefummelt und bin auf ein Problem gestoßen:

    Klasse Konto:

    class Konto
    {
      private:
        int m_Betrag;
      protected:
      public:
        void m_SetBetrag(const int& value);
        int m_GetBetrag(void);
    };
    

    Klasse Person:

    class Person 
    { 
    private: 
       std::vector<Konto> m_Konto; //Ich gehe davon aus, daß bei Tod der Person
                                   //die Konten aufgelöst werden, deswegen diese
                                   // Komposition
    public:
       void AddKonto();
    };
    

    Implementierung vom Hinzufügen eines Kontos:

    void Person::m_AddKonto()
    {
       Konto value;
       m_Konto.push_back(value);	
    }
    

    Das Hinzufügen scheint geklappt zu haben. Aber da m_Konto private ist konnte ich Folgendes nicht machen:

    int main(void)
    {
       Konto meinKonto;
       meinKonto.m_AddKonto;
       //soweit wunderbar
       meinKonto[0].m_SetBetrag(200);
    }
    

    Bei m_SetBeitrag(200) hat der Debugger dann gemeckert, da m_Konto private ist und er da anscheinend nicht rankommt. Jetzt nochmal für jede Zugriffsmethode von m_Konto in Person eine neue anzulegen erschien mir unübersichtlich und komisch.
    Hier liegt ja wahrscheinlich schon ein Fehler meinerseits, aber dann hab ich mir gedacht, da ich ja in Konto schon Zugriffsmethoden benutze, kann ich m_Konto in Person eigentlich auch Public machen.

    Dann habe ich Folgendes mal gemacht:

    int main(void)
    {
       Konto meinKonto;
       meinKonto.m_AddKonto;
       meinKonto[0].m_SetBetrag(200);
       cout<<meinKonto[0].m_GetBetrag;
    }
    

    m_SetName hat er compiliert und nicht gemeckert. Aber sobald m_GetName benutzt wurde, bekam ich die Fehlermeldung "statement cannot resolve address of overloaded function".

    😕

    Wahrscheinlich tun sich hier Abgründe für Euch auf, Ich wäre über Hilfe echt dankbar.

    Vielen Dank im voraus,

    Fingolfin



  • operator void schrieb:

    Wenn du Zeiger verwenden *musst*, sind die smarten aus Boost (http://www.boost.com) meistens besser als die eingebauten.

    nein.
    nur wenn du zeiger im sinne von ressourcen verwendest - jeder zeiger zeigt auf seinen (mit new/new[] angelegten) speicher.

    die meisten zeiger verwendet man aber nicht in diesem zusammenhang (zumindest ich nicht)



  • Oh mein Gott,

    anstatt

    cout<<meinKonto[0].m_GetBetrag;
    

    muß es natürlich

    cout<<meinKonto[0].m_GetBetrag();
    

    heißen.

    Neue Sprache, schwere Sprache. 😃 Aber jetzt scheint alles zu funzen. Nochmal ein Danke an alle für ihre Hilfe.

    Fingolfin



  • Ich habe dann doch noch eine weitere Frage. Wenn ich per vector.erase() Elemente des Vectors löschen möchte, löscht man ja immer bis zum Ende. Gibt es einen einfacheren Weg so vector.erase(3), daß halt nur das dritte Element gelöscht wird und ich mir danach keine Sorgen um Probleme durch fehlerhafte Sortierung usw. machen muß?

    Ich habe im Netz noch etwas über valarray gelesen, was so eine Art erweiterter Vektor sein soll. Wäre das eher für meine Zwecke zu gebrauchen?

    Vielen Dank schonmal,

    Fingolfin



  • Fingolfin schrieb:

    Ich habe dann doch noch eine weitere Frage. Wenn ich per vector.erase() Elemente des Vectors löschen möchte, löscht man ja immer bis zum Ende. Gibt es einen einfacheren Weg so vector.erase(3), daß halt nur das dritte Element gelöscht wird und ich mir danach keine Sorgen um Probleme durch fehlerhafte Sortierung usw. machen muß?

    vec.erase(vec.begin()+2);

    Ich habe im Netz noch etwas über valarray gelesen, was so eine Art erweiterter Vektor sein soll. Wäre das eher für meine Zwecke zu gebrauchen?

    valarray ist IMHO ein witz.
    habe noch nie sinnvollen einsatz davon gesehen.

    der grundgedanke war gut, aber die umsetzungen mies. vergiss valarray am besten.



  • Shade Of Mine schrieb:

    Fingolfin schrieb:

    Ich habe dann doch noch eine weitere Frage. Wenn ich per vector.erase() Elemente des Vectors löschen möchte, löscht man ja immer bis zum Ende. Gibt es einen einfacheren Weg so vector.erase(3), daß halt nur das dritte Element gelöscht wird und ich mir danach keine Sorgen um Probleme durch fehlerhafte Sortierung usw. machen muß?

    vec.erase(vec.begin()+2);

    Vielen Dank!

    Shade Of Mine schrieb:

    Ich habe im Netz noch etwas über valarray gelesen, was so eine Art erweiterter Vektor sein soll. Wäre das eher für meine Zwecke zu gebrauchen?

    valarray ist IMHO ein witz.
    habe noch nie sinnvollen einsatz davon gesehen.

    der grundgedanke war gut, aber die umsetzungen mies. vergiss valarray am besten.

    Danke für die Info, aber könntest du das vielleicht etwas genauer erklären? Mich würde schon interessieren, was daran so schlecht ist. *wißbegierig ist

    Fingolfin



  • Fingolfin schrieb:

    void m_SetBetrag(const int& value);
    int m_GetBetrag(void);
    

    Du tippst wohl gerne? 🙂 Üblicher ist es jedenfalls, nur private Datenelemente mit m_ zu prefixen, ints direkt zu übergeben ("int value") und das zweite "void" ist auch nicht nötig.

    Shade of Mine schrieb:

    nein.
    nur wenn du zeiger im sinne von ressourcen verwendest - jeder zeiger zeigt auf seinen (mit new/new[] angelegten) speicher.

    die meisten zeiger verwendet man aber nicht in diesem zusammenhang (zumindest ich nicht)

    Hm, ich schon, zumindest scoped_ptr. Nur zum Beobachten habe ich eher Referenzen. Aber hier im Thread ging es imho auch eher um besitzende Zeiger, und die sind auch ohne Container-Verwendung schon problematisch genug:

    Marc++us schrieb:

    Aggregation, 0..* Elemente dyn. Array (vector<T*>, list<T*>) aus Zeigern auf Objektinstanzen



  • Fingolfin schrieb:

    Danke für die Info, aber könntest du das vielleicht etwas genauer erklären? Mich würde schon interessieren, was daran so schlecht ist. *wißbegierig ist

    C++ Templates - The Complete Guide schrieb:

    A precursor of valarray had been designed with the intention that compilers aiming at amrket for scientific computation would recognize the array type and use highly optimized internal code for their operations. Such compilers would have "understood" the types in some sesnse. However, this never happend.
    [..]Hier gehts jetzt darum, dass Vandervoorde vorgeschlagen hat, valarray mit expression templates zu implementieren[..]The valarray porposal came late in the C++ standardization process and practically rewrote all the text regarding valarray in the standard. It was rejected as a result, and instead a few tweaks were added to the existing text to allow implementations based on expression templates.[..]At time of this writing, no such implementation is known, and standard valarrays are, generally speaking, quite inefficient at performing the operations for which they where designed.

    das sagt wohl alles 😉
    [/quote]



  • operator void schrieb:

    Aber hier im Thread ging es imho auch eher um besitzende Zeiger, und die sind auch ohne Container-Verwendung schon problematisch genug:

    Marc++us schrieb:

    Aggregation, 0..* Elemente dyn. Array (vector<T*>, list<T*>) aus Zeigern auf Objektinstanzen

    Nope, bei einer Aggregation sind die Zeiger verweisend und nicht besitzend, da die Lebensdauer des Objekts nichts mit der Lebensdauer des Objekts zu tun hat, das die Aggregation besitzt. Die Speicherverwaltung des Objekts "macht jemand anderes".



  • @ShadeofMine

    Danke für die Info 🙂

    operator void:
    Du tippst wohl gerne? Üblicher ist es jedenfalls, nur private Datenelemente mit m_ zu prefixen, ints direkt zu übergeben ("int value") und das zweite "void" ist auch nicht nötig.

    Oh, da ich gerade erst mit C++ anfange, wollte ich gleich auf Nummer sicher gehen, um später nicht umlernen zu müssen. Naja, manchmal schießt man halt über's Ziel hinaus. 😉

    Ich hätte da noch eine Frage:

    Ich habe jetzt folgende Klasse;

    class Konto 
    { 
      private: 
        int m_Betrag; 
      protected: 
      public: 
        void m_SetBetrag(const int& value); 
        int m_GetBetrag(void); 
    };
    

    und:

    class Person  
    {  
    private:  
       std::vector<Konto> m_Konto; //Ich gehe davon aus, daß bei Tod der Person 
                                   //die Konten aufgelöst werden, deswegen diese 
                                   // Komposition 
    public: 
       void AddKonto(); 
    };
    

    Das ist jetzt also 'ne Komposition mit beliebig vielen Elementen. Klappt auch alles wunderbar.
    Jetzt habe ich noch eine Frage.
    Wie kann ich jetzt realisieren, daß ich per Konto an die Person rankomme, also quasi eine Komposition ohne Abhängigkeit in eine definierte Richtung.
    Also z.B. so: konto.person. Ist das irgendwie möglich?

    Fingolfin

    Edit: Frage hinzugefügt



  • Du kannst jedem Objekt von Konto einen Verweis auf das besitzende Objekt mitgeben. Das kann ein Zeiger oder eine Referenz (hier besser, da Kardinalität 1 vorliegt) auf ein Person-Objekt sein, das in der Klasse Konto vorhanden ist. Du mußt ja bedenken, daß eine Assoziation immer zwei Seiten hat - mit der Komposition deckst Du nur die Besitzer-Seite ab, aber Du kannst den Pfad auch von der anderen Seite her gehen (falls notwendig).

    Dies wäre die Analyse-Sicht!

    Jeder Programmierer wird wahrscheinlich aufheulen, da man hier eine unschöne "Überkreuz-Verbindung" bekommt, wahrscheinlich würde man sich dann während des Entwurfs dazu entschliessen, eine zusätzliche Assoziationsklasse einzuführen, die Konto mit Person verbindet (also eine dritte Klasse). Ich denke das führt hier aber zunächst zu weit.

    ---
    Führst Du das Beispiel weiter aus wirst Du übrigens bemerken, daß die Komposition hier eine zu starke Kopplung ist - ein Konto wird ja nicht wirklich entfernt, nur weil der Besitzer stirbt. Es wird dann als geschlossen weitergeführt und noch einige Jahre in der Datenbank stehen. Ist in den kleinen Modellbeispielen ein oft gemachter Fehler, daß z.B. Person-Objekte zerstört werden, weil die Person stirbt. Das entspricht oft nicht der Realität, da die Objekte dann mit dem Attribut "verstorben" weiterleben (schöner Satz), damit man weiterhin einen korrekten Datenbestand hat. Denn schließlich gab es ja Zahlungen und Verweise auf den lebenden Besitzer, die werden ja nicht alle ungültig nur weil die Person heute gestorben ist. Aber damit macht auch wieder die 3. Klasse Sinn, die die Assoziationsklasse spielt (z.B. "AktivesKonto").



  • Marc++us schrieb:

    Du kannst jedem Objekt von Konto einen Verweis auf das besitzende Objekt mitgeben. Das kann ein Zeiger oder eine Referenz (hier besser, da Kardinalität 1 vorliegt) auf ein Person-Objekt sein, das in der Klasse Konto vorhanden ist. Du mußt ja bedenken, daß eine Assoziation immer zwei Seiten hat - mit der Komposition deckst Du nur die Besitzer-Seite ab, aber Du kannst den Pfad auch von der anderen Seite her gehen (falls notwendig).

    Dies wäre die Analyse-Sicht!

    Jeder Programmierer wird wahrscheinlich aufheulen, da man hier eine unschöne "Überkreuz-Verbindung" bekommt, wahrscheinlich würde man sich dann während des Entwurfs dazu entschliessen, eine zusätzliche Assoziationsklasse einzuführen, die Konto mit Person verbindet (also eine dritte Klasse). Ich denke das führt hier aber zunächst zu weit.

    Mir kann gar nix zu weit führen 🙂 Könntest du diese "Konnektor-Klasse" vielleicht an meinem kleinen Beispiel darstellen? Ich bin kein Programmier-Anfänger, bloß einer in C++.

    Marc++us schrieb:

    Führst Du das Beispiel weiter aus wirst Du übrigens bemerken, daß die Komposition hier eine zu starke Kopplung ist - ein Konto wird ja nicht wirklich entfernt, nur weil der Besitzer stirbt. Es wird dann als geschlossen weitergeführt und noch einige Jahre in der Datenbank stehen. Ist in den kleinen Modellbeispielen ein oft gemachter Fehler, daß z.B. Person-Objekte zerstört werden, weil die Person stirbt. Das entspricht oft nicht der Realität, da die Objekte dann mit dem Attribut "verstorben" weiterleben (schöner Satz), damit man weiterhin einen korrekten Datenbestand hat. Denn schließlich gab es ja Zahlungen und Verweise auf den lebenden Besitzer, die werden ja nicht alle ungültig nur weil die Person heute gestorben ist. Aber damit macht auch wieder die 3. Klasse Sinn, die die Assoziationsklasse spielt (z.B. "AktivesKonto").

    Das war mir schon klar, mir ist auf Anhieb nur kein besseres Beispiel eingefallen. 😉

    Fingolfin


Anmelden zum Antworten