[Design] Container in Klasse und deren Schnittstelle
-
Hallo,
Ich habe mal wieder eine Designfrage

Kurz etwas Code, der eigentlich schon das ganze Problem beschreiben sollte:class Foo { // Typedefs // public: typedef std::vector<Object> Objects; // Attributes // private: Objects m_objects; // ... andere Attribute ... // Methods // public: void add_object(Object const& object); Objects const& get_objects() const; // naja, noch ok void remove_object ... // wie? void set_object ... // wie? };Ich kann aber nicht sowas machen:
Objects& get_objects();Weil beim einfügen, löschen und verändern der Objekte zusätzliche Operationen ausgeführt werden müssen, welche die anderen Attribute in der Klasse beeinflussen. Zudem wäre so eine Funktion ja fast gleich, wie wenn man m_objects public gemacht hätte.
Wie würdet ihr sowas umsetzen? Den ganzen Container wrappen? Oder den Container rauskopieren lassen und dann eine Möglichkeit geben, ihn wieder reinkopieren zu können? Oder ist hier ein Denkfehler im Design vorhanden, wenn man soweit ist?
Grüssli
-
Was soll den die Aufgabe von Foo sein?
-
Standardbeschleunigung schrieb:
Was soll den die Aufgabe von Foo sein?
Ganz verschieden. Das kann von reiner Container von Containern bis zu Signal auslösen, wenn eine gewisse Mischung oder sonst was erreicht wurde oder einfach ein Resultat aktuell behalten usw. usf.
Es ist bei mir ein äusserst allgemeines Problem. Wie mache ich den Container in der Klasse für die Objekte ausserhalb der Klasse zugänglich, wenn man den Container verändern darf und nicht nur lesen oder hinzufügen, aber dieses verändern jeweils von der Klasse überwacht werden muss.Grüssli
-
Welchen Zugriff soll man von ausserhalb der Klasse besitzen? Soll jede Operation des Containers möglich sein? Weil es stellt sich natürlich die Frage, woher man die einzelnen Objekte kennt, wenn man diese spezifisch ändern will...
Soll der Zugriff per Index geschehen? Oder wie weiss man von aussen, welches Element des Containers verändert werden muss?
-
Dravere schrieb:
void remove_object ... // wie?
Wie ist denn das "wie?" gemeint? Hapert's an den Parametern?
-
Nexus schrieb:
Welchen Zugriff soll man von ausserhalb der Klasse besitzen? Soll jede Operation des Containers möglich sein? Weil es stellt sich natürlich die Frage, woher man die einzelnen Objekte kennt, wenn man diese spezifisch ändern will...
Die Objekte kennt man wohl im allgemeinen von:
Objects const& get_objects() const;So muss ich kein eigenes
begin,rbegin,endundrendhinschreiben. Zumindest für einen konstanten Kontainer, was ich ja auch möchte. Nur hat man dann einenconst_iterator, den man nicht einfach wieder an die Klasse zurückgeben kann, welche damit dann den Container verändern könnte, da bei erase und ähnliches Funktionen keinconst_iteratoraktzeptiert wird.Nexus schrieb:
Soll der Zugriff per Index geschehen? Oder wie weiss man von aussen, welches Element des Containers verändert werden muss?
Das ist eben die Frage. Mit einen
std::vectorkönnte ich die Sache über den Index lösen. Aber was macht man bei einerstd::list? Zudem ist ein Index dann blöd, falls man von aussen irgendwelche Suchfunktionen ausführen möchte, da man da wieder die Iteratoren bräuchte. Und der Index ist auch blöd, wenn du etwas löschen möchtest im Vector. Du musst dann wieder einen Iterator hochzählen, ok, bei einem std::vector geht das äusserst kurz, trotzdem sieht es irgendwie verkehrt aus:void Foo::remove_object(std::vector<Object>::size_type index) { m_objects.erase(m_objects.begin() + index); }Badestrand schrieb:
Dravere schrieb:
void remove_object ... // wie?
Wie ist denn das "wie?" gemeint? Hapert's an den Parametern?
Genau. Ich kann ja nicht das Objekt wieder übergeben, sondern muss irgendwie eine Möglichkeit haben auf die Stelle in der Liste zu verweisen, obwohl man grundsätzlich das Objekt hat. Irgendwie ist es zum verzweifeln.
Grüssli
-
Also so wie ich das verstanden habe, soll die Klasse nach aussen Iteratoren und Funktionen zum Löschen, Anhängen, etc. anbieten, also praktisch alles. Du willst sowohl Lese- und Schreibzugriff, der Zugriff auf den internen Container ist nicht stark eingeschränkt. Die Klasse hat nur die Aufgabe, Zugriffe zusätzlich zu überwachen und nebenbei noch etwas zu tun, und deshalb willst du auch keinen rohen Container. Sehe ich das richtig?
Falls ja, wäre es wohl das Einfachste, einen vollständigen Wrapper zu schreiben, also inklusive Iteratoren und Methoden für Container-Operationen. Oder du überlegst dir, dass nur bestimmte Operationen nach aussen angeboten werden und beispielsweise Suche oder andere Algorithmen bereits in der Klasse implementiert sind.
-
Also wenn du die ganze Funktionalität haben willst, wirst du nicht drum herum kommen alles zu wrappen..
Straight-forward wäre natürlich das ganze über Zeiger auf Object zu lösen. Und dann im Container einfach das passende Objekt zu suchen und ändern/entfernen.
(Ist schlussendlich aber in etwa dasselbe, wie mit einem Iterator.)
-
Schwierig - eine elegante, einfache Lösung sehe ich nicht.
Ohne was selbst zu basteln, bleiben dir wohl nur const-Iteratoren, die du nach außen gibst und in z.B. remove_object mit std::distance den jeweiligen non-const-Iterator zu berechnen.
Mit Selbstbasteln könntest du einen Iterator-Wrapper bauen, der nach außen hin const ist um Objekt-Änderungen zu verbieten, den du aber intern in einen non-const-Iterator umwandeln kannst1. Das ist aber insofern unschön, als dass die get_object_begin-/-end-Methoden non-const sein müssen, was wiederum die Arbeit auf einem konstanten Foo-Objekt unmöglich macht.
Eine kleine Abwandlung davon wären zwei Iterator-Wrapper, einer für intern-const und einer für inter-non-const, mit jeweiliger begin-/end-Methode (das würde ich wählen). Oder aber ein Wrapper, der intern sowohl einen const-Iterator als auch einen non-const-Iterator hat, wobei die Iterator-Aktionen über ein Flag ablaufen.
1 Iterator-Wrapper:
class Foo { ... public: typedef IterWrapper<Foo,Objects> ObjIter; ObjIter objects_begin() {return IterWrapper(m_objects.begin();} ObjIter objects_end() {return IterWrapper(m_objects.end();} void remove_object( ObjIter pos ) { // ... m_objects.erase( pos.it() ); } };Mit
template<typename Friend, typename Ctr> class IterWrapper { friend class Friend; public: IterWrapper( Ctr::iterator pos ) : i(pos) {} // Viel const-Iterator-typedef-Zeug (typedefs auf Ctr::const_iterator) // Operatoren und const-Zugriff für Iterator private: Ctr::iterator it() {return i;} Ctr::iterator i; };
-
Dravere, für welchen Ansatz hast du dich nun entschieden?

-
Dravere schrieb:
Standardbeschleunigung schrieb:
Was soll den die Aufgabe von Foo sein?
Ganz verschieden. Das kann von reiner Container von Containern bis zu Signal auslösen, wenn eine gewisse Mischung oder sonst was erreicht wurde oder einfach ein Resultat aktuell behalten usw. usf.
Es ist bei mir ein äusserst allgemeines Problem.Ich bin der Meinung, dass die Lösung sehr stark eben von der konkreten Aufgabe - bzw. Verantwortung - von Foo abhängt. Es gibt keine allgemeine Lösung.
Ist Foo mehr ein reiner Container, so sollte die Klasse Foo auch Container-Methoden besitzen.
Ist Foo etwas anderes, was auch eine gewisse Container-Funktionalität beinhaltet, so sollte man genau die Container-Methoden anbieten, die auch benötigt werden - weniger ist hier mehr.
Eine Klasse hat in der Praxis immer eine konkrete Aufgabe und eine Handvoll Benutzer - also andere Klassen, die auf sie zugreifen. Und genau für diese Benutzer sollte Foo entworfen werden.Lösungen mit Iteratoren - wie von Badestrand vorgeschlagen - finde ich i.A. besser geeignet als ein getObjects() was den gesamten Container zurückgibt.
Gruß
Werner
-
Nexus schrieb:
Dravere, für welchen Ansatz hast du dich nun entschieden?

Aktuell bleibe ich beim Indexzugriff, da ich eigentlich sowieso nur
std::vectorhabe. Es ist einfach ein wenig Schade, dassstd::vectornicht ein wenig direkter noch den Indexzugriff, bzw. z.B. das löschen per Index, unterstützt. Sieht halt nicht all zu schön aus, aber was solls.Allerdings habe ich auch an gewissen Stellen das Design leicht verändert, wodurch Speicher und Referenzen/Zeiger leicht anders behandelt werden und dadurch der Zugriff über einen Index möglich wird. Da muss ich sogar sagen, dass mir die Änderung teilweise gefällt.
Aktuell scheint alles aufzugehen und das Programm muss leider langsam vorwärts kommen, so gern ich auch immer wieder herumprobiere um was schöner zu lösen. Aber da das Programm nicht für mich ist, habe ich einen Auftraggeber, welcher auf mich Druck ausübt und endlich Ergebnisse sehen will ... Ende November soll die Betadeadline sein, also muss es halt jetzt so funktionieren

Ich habe noch ein anderes Programm, wo ich ein ähnliches Problem habe. Dieses ist dann allerdings für mich. Da werde ich mich dann nochmals mit dem Problem genauer auseinandersetzen. Grundsätzlich hat Werner Salomon schon recht, irgendwie hängt es immer etwas von der eigentlichen Aufgabe ab. Aber ich neige dazu in allen Bereichen in dieses Problem zu laufen, also wahrscheinlich mache ich irgendwo bei gewissen Entscheidungen am Anfang noch Fehler. Die gilt es irgendwann mal zu finden und natürlich zu entfernen

Danke für die Hilfe. So sehe ich mich immerhin bestätigt, dass der eigentliche Fehler wahrscheinlich woanders liegt und es hier nicht eine offensichtliche und einfache Lösung gibt.
Grüssli
-
Hallo zusammen,
sorry wenn ich mich mal einmischen darf

ich habe ein ähnliches Problem.
Bei mir besitzt ein Objekt ebenfalls einen Vector in dem wiederum Objekte enthalten sind.um auf diesen Vektor von aussen zuzugreifen, habe ich mir eine Referenz von diesem Vektor übergeben lassen. Das klappt zwar, aber spätere Änderungen an dem Vektor bekomme ich nicht mit.
Was bringt mir denn dann die Referenz?
Ich dachte dies ist ein Verweis auf die Speicherstellen auf der die Objekte liegen. Dann müsste ich doch auch die Änderungen an dem Vektor mitbekommen?
-
Du hast doch bereits einen Thread zu diesem Thema. Da deine Frage eigentlich nicht viel mit diesem Thread von Dravere zu tun hat, würde ich vorschlagen, du machst dort weiter oder eröffnest einen neuen. Ich habe mal im verlinkten Thread geantwortet.