Datencontainer programmieren
-
Für ein Uni-Projekt darf ich Framework in C++ programmieren. Hauptbestandteil ist ein Datencontainer der Daten aufnimmt und auf Anfrage wieder ausgibt. Konkret sieht es so aus, dass es Module geben wird (die ich noch nicht kenne), welche sich Daten abonnieren und selbst wieder Daten einliefert. Die verschiedenen Daten sind Objekte die von einem Modul eingeliefert werden. Ein Modul liefert immer den gleichen Typ an Daten ein, sobald der Datencontainer diese erhalten hat, informiert er die jeweiligen Abonnenten der Daten darüber dass es ein neues Datum vom jeweiligen Typ Daten gibt. Diese holen sich das Datum ab und geben Bescheid sobald sie es nicht mehr brauchen. Wenn es niemand mehr braucht, löscht es der Datencontainer.
Soweit so gut. Ich habe alle Daten nun in 5 Typen eingeteilt (ergibt sich aus der Anwendung). Das heißt die Modulbauer können sich einen der 5 Typen aussuchen, davon ableiten und ihren eigenen Typ erstellen, welchen sie später beim Datencontainer einliefern werden (und welchen andere wiederum abonnieren können). Worüber ich mir nicht ganz im klaren bin ist, ob ich nun einen gemeinsamen Vater der 5 Typen machen soll, oder diese 5 einfach als Väter belassen. So könnte ein gemeinsamer Vater aussehen:#ifndef Daten_hpp #define Daten_hpp #include <string> class Daten { public: Daten(); Daten(int, std::string); virtual ~Daten(); int leseErstellungszeit() const; std::string leseErsteller() const; private: int Erstellungszeit; std::string Ersteller; }; #endifEs wird am Ende schätzungsweise 20-30 verschiedene Datentypen geben die von den 5 Vätern abgeleitet sind. Und es werden wohl sehr viele pro Minute erstellt und nachdem sie unbrauchbar wurden wieder vernichtet werden. Das ganze ist wie ein Kreislauf um Sensorwerte auszuwerten und daraus Informationen zu gewinnen, die wiederum anderen Modulen als Grundlage dienen.
Ich muss also Datenobjekte aufnehmen und den Abonnenten Bescheid geben dass diese existieren, sowie eine Speicherverwaltung implementieren. Nun suche ich erstmal einen konkreten Container der STL den ich verwenden soll. Wie man sieht hat jedes Datum einen integer Erstellungszeit, soll ich diesen als Index in einer map verwenden? Und wie erkenne ich nun was für einen Typ von Daten eingeliefert wurde? Ich dachte ich schreibe eine Funktion einliefern(Daten
die allgemein Datenobjekte annimmt, welche dann gecasted werden. Ich könnte auch für jeden einzelnen Typ einen Container aus der STL nehmen ohne cast, oder ist ein gemeinsamer besser?
-
Ich würde vermutlich eine gemeinsame Basisklasse verwenden, und nur einen grossen Container der alles verwaltet. Wozu Code verfünffachen?
-
hustbaer schrieb:
Ich würde vermutlich eine gemeinsame Basisklasse verwenden, und nur einen grossen Container der alles verwaltet. Wozu Code verfünffachen?
Ok, und wie erkenne ich um was für einen konkreten Typ es sich handelt? Ich bekomme einach ein Daten* und füge es z.B. in meine map ein. Nun muss ich dieses konkrete Objekt irgendwie identifizieren, z.B. mit der Erstellungszeit als Index in der map. Ich muss nun wissen was für ein Typ von den fünfen (SensorDaten, LogDaten, SteuerungsDaten, ...) es ist. Anschließend allen die diesen Typ abonniert haben auch als solchen ausliefern, also einen cast machen? Soll ich einfach in jedem Typ einen String anlegen wo drin steht um welchen Typ es sich handelt? Oder gibt es eine Objektfunktion wie bei Java wo man das erkennen kann?
-
fabske schrieb:
Ich muss nun wissen was für ein Typ von den fünfen (SensorDaten, LogDaten, SteuerungsDaten, ...) es ist.
Bist du sicher, dass du das musst? In den meisten Fällen kannst du die Schnittstelle in der Basisklasse einheitlich halten und über Polymorphie (virtuelle Funktionen) einen Auswahlmechanismus einbauen, sodass du keine explizite Fallunterscheidung mehr hast. Somit weiss die Basisklasse nichts mehr über die einzelnen Typen (was sie designtechnisch eigentlich auch nicht muss). Und wenn diese Typen mit irgendwelchen Algorithmen interagieren, werden je nach Typ spezifische Aktionen durchgeführt.
-
fabske schrieb:
Ok, und wie erkenne ich um was für einen konkreten Typ es sich handelt? Ich bekomme einach ein Daten* und füge es z.B. in meine map ein. Nun muss ich dieses konkrete Objekt irgendwie identifizieren...
Hiermit: http://en.wikipedia.org/wiki/Run-time_type_information

-
general bacardi schrieb:
Hiermit: http://en.wikipedia.org/wiki/Run-time_type_information

Wie gesagt: In vielen Fällen ist Laufzeitpolymorphie durch virtuelle Funktionen die bessere Wahl. Von daher würde ich es zuerst damit versuchen.
-
Soweit ich das sehe muss der Datencontainer wissen welcher Typ eingeliefert wird. Z.B. abonniert sich Modul4 DatenTypA. Das heißt wann immer ein DatenTypA eingeliefert wird muss ich Modul4 benachrichtigen. Also muss der DatenContainer wissen was eingeliefert wird, oder?
Nebenbei: Kann ich als DatenContainer auch sehen WER ein DatenObjekt einliefert? Also wer die Methode einliefern(Daten
aufruft?
-
fabske schrieb:
Soweit ich das sehe muss der Datencontainer wissen welcher Typ eingeliefert wird. ...
Ich sehe das anders. Ich würde die Datenhaltung (den Container) von den abgelegten Objekten trennen. Du hast doch schon "Daten" als eigenen Typen (Wrapper) ... mehr braucht der Container nicht zu kennen, um seine Arbeit zu erledigen.
Die Konvertierung zwischen den konkreten Typen und "Daten" (der serialisierten Präsentation der Objekte) würde ich den Typen selbst und einer "factory" überlassen.Gruß,
Simon2.
-
Hört sich interessant an, glaub aber nicht dass das geht. Was ist denn eine Fabrik genau, hast du ein passendes Beispiel. Das auf Wikipedia ist sehr mager. Die http://de.wikipedia.org/wiki/Fabrikmethode hab ich hingegen verstanden, aber die passt nicht, oder meinst du das so, dass ich eine Basisklasse Daten habe und alles weitere einfach ebenfalls ein Objekt ist, welches aus Daten verlinkt ist? Alle Daten sehen also gleich aus, nur ein Zeiger auf die konkreten unterschiedlichen Objekte ändert sich?
Wie soll denn der Datencontainer einem konkreten Modul sagen, ein neues Datum von TypA ist für dich da, wenn er gar nicht weiß dass es sich um TypA handelt?
-
fabske schrieb:
hustbaer schrieb:
Ich würde vermutlich eine gemeinsame Basisklasse verwenden, und nur einen grossen Container der alles verwaltet. Wozu Code verfünffachen?
Ok, und wie erkenne ich um was für einen konkreten Typ es sich handelt?
Viele Wege führen nach Rom.
Du könntest jeder Klasse eine eigene ID verpassen, und diese mit einer virtuellen Funktion in der Basisklasse abfragbar machen ("int id = obj->GetClassID()").
Du könntest alle Möglichkeiten mit dynamic_cast durchprobieren.
Du könntest auch nur die "Anlieferungs-Funktion" in der Container-Klasse fünffach machen, und dafür KEINE "Anlieferungs-Funktion" haben, die die Basisklasse annimmt. Damit wüsstest du den Typ dann implizit.
Du könntest auch das Visitor-Pattern einsetzen, und die Aufgabe zu entscheiden wer benachrichtigt wird der Daten-Klasse übertragen. Dadurch könnte die Daten-Klasse dann - wenn sie möchte - auch zwei (oder mehr) Daten-Typen gleichzeitig "sein".
Ich würde vermutlich einen der beiden letzten Wege wählen.
Nun muss ich dieses konkrete Objekt irgendwie identifizieren, z.B. mit der Erstellungszeit als Index in der map.
Was du als Key verwenden solltest/könntest, hängt wohl davon ab, nach was du "suchen" willst. Also was du als "ID" für so ein Daten-Dings hast, wenn du es anfinden willst.
Wenn du dir die ID aussuchen kannst, dann würde ich *nicht* die Erstellungszeit verwenden. Entweder eine eigene ID, die du einfach mit jedem Daten-Stück hochzählst (sollte dann idealerweise 64 Bit sein). Oder irgendwas anderes mit dem du ein Daten-Stück wirklich *eindeutig* identifizieren kannst. z.B. einfach die Adresse des Objekts (**).*: dummerweise erlaubt C++ streng genommen nicht, dass man Adressen von beliebigen Objekten auf grösser/kleiner vergleicht. Man darf auf gleich/unglich testen, aber eben nicht grösser/kleiner. Real funktioniert es zwar, aber ich will dir nichts empfehlen, was laut Standard "nicht OK" ist.
Es sollte aber gehen, wenn du eine Hash-Map Klasse zur Hand hast, die eine Hash-Funktion für Zeiger (void) bereitstellt. Dann "garantiert" dir die Klasse dass die Implementierung der Hash-Funktion OK ist, und du musst dir darüber keinen Kopf machen.
-
Vielen Dank für die ausführliche Antwort hustbaer! Da sehr viele solcher Daten pro Minute anfallen werden halte ich persönlich den dynamic_cast für zu unperformant. Die vielen casts und if Abfragen sehen mir so aus, als ob das Resourcen frisst!
Die "Anlieferungs-Funktion" für jeden der 5 "Basistypen" zu überladen kam mir noch gar nicht in den Sinn! Aber vielleicht hab ich mich schlecht ausgedrückt. Die 5 Typen sind Basistypen von denen jeder Modulersteller neue ableiten darf. Es gibt also in Wirklichkeit unbestimmt viele Typen, die zu einem der 5 Typen gehört, die aber alle wieder einen Vater haben (können, wäre ja auch ohne möglich).
Neben dem Fabrikpattern, das ich immer noch nicht verstanden hab, wirst du nun das Visitorpattern in den Raum. Ich hab es mir angeschaut und folgende Idee verstanden: Jedes einzelne DatenObjekt, egal welchen Typs, soll seine Abonnenten selber verwalten? Damit spare ich mir eine große Tabelle in der alle Abonnenten aufgelistet sind. Ja?
-
fabske schrieb:
...
Die "Anlieferungs-Funktion" für jeden der 5 "Basistypen" zu überladen kam mir noch gar nicht in den Sinn! Aber vielleicht hab ich mich schlecht ausgedrückt. Die 5 Typen sind Basistypen von denen jeder Modulersteller neue ableiten darf. Es gibt also in Wirklichkeit unbestimmt viele Typen, die zu einem der 5 Typen gehört, die aber alle wieder einen Vater haben (können, wäre ja auch ohne möglich)....Das war's, was ich meinte (war gestern nur zu müde für eine Ideenskizze).
Wenn Du auf templates gehst, brauchst Du für die Erzeugung auch gar keine "5 Basistypen" - sie stören aber auch nicht: Du kannst halt alles erzeugen, für das entsprechende (template-)Spezialisierungen bestehen.
Ich schau mal, ob ich eine lauffähige Ideenskizze hinbekomme...
Gruß,
Simon2.
-
Such vielleicht mal im Netz nach Publish/Subscribe. Da gibt es 'ne Menge Infos zu, uns so wie ich das sehe, könnte es genau das sein, was Du benötigst.
-
Damit spare ich mir eine große Tabelle in der alle Abonnenten aufgelistet sind. Ja?
Nö, das Datenobjekt verwaltet die Abonnenten nicht selbst. Es wählt nur aus, wer benachrichtigt werden soll.
class Data; class DataTypeA; class DataTypeB; class DataTypeC; class DataTypeD; class DataTypeE; class SubscriberList { public: void NotifyTypeASubscribers(DataTypeA* data) const; void NotifyTypeBSubscribers(DataTypeB* data) const; void NotifyTypeCSubscribers(DataTypeC* data) const; void NotifyTypeDSubscribers(DataTypeD* data) const; void NotifyTypeESubscribers(DataTypeE* data) const; private: // ... }; class Data { public: virtual void NotifySubscribers(SubscriberList const& slist) = 0; }; class DataTypeA : public Data { public: virtual void NotifySubscribers(SubscriberList const& slist) { slist.NotifyTypeASubscribers(this); } // ... }; class DataTypeB : public Data { public: virtual void NotifySubscribers(SubscriberList const& slist) { slist.NotifyTypeBSubscribers(this); } // ... }; // ... // nur als Beispiel was damit auch ginge: class DataTypeAB : public DataTypeA, public DataTypeB { public: virtual void NotifySubscribers(SubscriberList const& slist) { slist.NotifyTypeASubscribers(static_cast<DataTypeA*>(this)); slist.NotifyTypeBSubscribers(static_cast<DataTypeB*>(this)); } // ... }; class Container { public: void FeedData(Data* data) { data->NotifySubscribers(m_slist); } // ... private: SubscriberList m_slist; // ... };
-
hustbaer schrieb:
Ich würde vermutlich eine gemeinsame Basisklasse verwenden, und nur einen grossen Container der alles verwaltet. Wozu Code verfünffachen?
Zustimmung.
Ich hab zufällig gerade letzte Woche ein sehr ähnliches Modul mit Abonnementfunktion entwickelt.
Eine Basisklasse -> mehrere abgeleitete Klassen -> ein Container
Dazu kommen dann jeweils noch Informationen zu Menge und Art der Abonnenten, die auch mit in der Basisklasse enthalten sind. D.h. sobald die Menge der Abonnenten bei 0 ankommt, wird der entsprechende Datenstream abgemeldet und die Datenmenge wird gelöscht. Eigentlich genau so wie du das brauchst.
Lässt sich mit der Basisklasse und ihren Ableitungen übrigens hervorragend und angenehm programmieren. Ist unter Ausnutzung der STL auch relativ wenig Code.
-
Hallo Leute! Ich melde mich erst jetzt wieder weil sich die Dimensionen stark erweitert haben. Vielleicht erkläre ich kurz was ich damit eigentlich vor hab:
Die einzelnen DatenTypen sind Sensorwerte der Sensoren unseres autonom fahrenden Autos[/url]. Das bedeutet es werden pro Sekunden viele solcher Sensorwerte erstellt und nur für wenige Millisekunden überhaupt benötigt, danach wieder vernichtet. Das ganze soll ein Framework werden, wo sich jetzt noch nicht bekannte Module anmelden können und Sensorwerte abonnieren. Die Module können daraus selbst virtuelle Sensorwerte berechnen (neue DatenTypen) und wiederum dem Datenmodul übergeben um wiederum Abonnenten auszuliefern. Ein riesiger Kreislauf eben.Ich habe mich die letzten Tage in die Sache eingearbeitet und mit zum Beispiel das [url="http://de.wikipedia.org/wiki/Observer_(Entwurfsmuster)"Observer Paddern"[/url] klar gemacht (danke Tachyon). Vielen Dank für deinen Kode hustbaer, das ist eine sehr gute Idee. Und wenn mit It0101 seinen Kode mal zeigen könnte wäre das sicher sehr nützlich.
Nur wurde ich darauf aufmerksam gemacht, dass ich ein solches Framework ohne Threads vergessen kann. Ich hatte bisher gar nichts mit Threads zu tun und mich auch darin mal eingelesen.
So wie ich das identifizieren konnte, brauche ich ein Modell um die einzelnen Module als Threads zu realisieren, und dabei ein Event-Management zur Synchronisation. Außerdem muss ich wohl die einzelnen DatenTypen-Objekte recyceln, denn die Speicherverwaltung würde zu viel Resourcen fressen wenn ich ständig so viele Objekte anlege und wieder vernichte.
Das ganz bereitet mir grad Kopfzerbrechen. In der Wikipedia steht zum Observer bei Nachteile: "Bei der gerade durchgeführten Observierung eines Objektzustands kann es notwendig sein, einen konsistenten Subjektzustand zu garantieren. Dies kann durch synchrone Aufrufe der Notifizierungsmethode des Observers sichergestellt werden. In einem Multithreading System sind evtl. Lockingmechanismen oder Threads mit queuing zur Observer-Notifizierung erforderlich."