Hallo zusammen,
ich schreibe grade an einem Programm, und da ich langsam aber sicher von
C weg will übe ich mich dabei in C++.
Die Syntax und das Objektorientierte Paradigma sind mir schon klar.
Was mir eher fehlt sind Konzepte, wie man bestimmte Problemstellungen
löst, die ich in "plain-C" wohl ganz anders lösen würde als es ein
erfahrener C++ Programmierer macht.
Konkret geht es darum:
Ein "Unterprogramm" soll eine Textdatei parsen, dabei geht es um
Datensätze (> 500 Stück, aber unbekannte Anzahl), diese bestehen aus
einem symbolischen Name (= String) und ein paar Zahlen.
Mir ist nicht ganz klar, wie ich das am geschicktesten anstelle.
Wahrscheinlich eine Klasse anlegen, die einen "Parser" representiert.
Dem Konstruktor würd ich den Dateinamen der Textdatei mitgeben.
Ich denke, ich lege mir für einen Datensatz eine Struktur (oder interne
Klasse) an, un sammle diese in einem std::vektor.
Dann wirds aber schwammig. Soll der Konstruktor dann das ganze Parsen
gleich in einem Rutsch erledigen? Wie komm ich an die Datein ran?
Grundsätzlich wohl über einen Getter, aber dazu muss der Benutzer der
Klasse erstmal wissen, wieviele Datensätze eingelesen wurden.
Denkbar wäre auch, dem Getter einen String (also den Bezeichner des
Datensatzes) zu übergeben, aber dann müsste ich im Getter jeweils alle
Datensätze auf diesen String vergleichen. Erscheint mir recht aufwendig.
Joa, also die technische Umsetzung würd ich schon hinkriegen, allerdings
solls schon irgendwie C++ Style sein, sonst könnt ich auch gleich bei C
bleiben...
Wie würdet ihr das lösen?
Ein Parser ist wohl denkbar ungünstig als Einstiegsprojekt mit
objektorientiertem Ansatz, den wie du schon merkst würgst du Dir
irgendwas zu recht was dann deine verschiedenen Objekte rechtfertigt.
Mach das mit c und such warte auf ein besseres Projekt für deine
Metamorphose...
gruß jibi
Was ich bei deinen Ausführungen vermisse, ist eine Klasse, um deine
eigentlichen Daten zu repräsentieren. Dann weißt du auch, wie du an die
Daten kommst. Die Klasse kapselt die Daten und bietet nach außen eine
Schnittstelle zum Zugriff. Nach dem Parsen mußt du mit den Daten ja auch
irgendwas machen.
Der Parser könnte nun eine eigene Klasse sein. Dem übergibst du ein
Datenobjekt, das dann vom Parser befüllt wird. So kannst du auch sehr
leicht mehrere verschiedene Parser schreiben, von denen du dann per
Polymorphie zur Laufzeit einen auswählst, z.B. um mehrere verschiedene
Dateiformate zu unterstüzen.
Hans schrieb:> Wie komm ich an die Datein ran?> Grundsätzlich wohl über einen Getter, aber dazu muss der Benutzer der> Klasse erstmal wissen, wieviele Datensätze eingelesen wurden.
Warum?
> Denkbar wäre auch, dem Getter einen String (also den Bezeichner des> Datensatzes) zu übergeben, aber dann müsste ich im Getter jeweils alle> Datensätze auf diesen String vergleichen. Erscheint mir recht aufwendig.
Dafür gibt's std::map. Das scheint mir für deinen Anwendungsfall eh
besser geeignet als ein std::vector.
Hans schrieb:
Also erst mal stimme ich jibi zu: Ein Parser ist kein so gut geeignetes
Beispiel. Denn die eigentlichen Parsing-Techniken sind da wie dort
dieselben.
> Ein "Unterprogramm" soll eine Textdatei parsen, dabei geht es um> Datensätze (> 500 Stück, aber unbekannte Anzahl), diese bestehen aus> einem symbolischen Name (= String) und ein paar Zahlen.> Mir ist nicht ganz klar, wie ich das am geschicktesten anstelle.
Du hast prinzipiell erst mal eine Entscheidung zu treffen:
willst du die Datei in einem Rutsch parsen und in späterer Folge dir das
erkannte Stück für Stück abholen, oder willst du sukzessive Parsen,
während du ausserhalb das Gelesene vom Parser abholst.
Der Unterschied ist wie bei den XML-Parsern. Da gibt es 2 grundsätzlich
verschiedene Ansätze.
Das eine sind die DOM Parser, die die XML Datei in einem Rutsch in eine
interne Baumrepräsentierung bringen, die dann ein weiterer Folge mit
Operationen auf dem Baum abgefragt, behandelt, verändert und auch von
dort wieder geschrieben werden können.
Oder willst du XML mit SAX parsen, bei dem du den Parser auf die Datei
löslässt und jedesmal, wenn der wieder einen Teil erkannt hat, macht der
Parser Callbacks in deinen Code um dir mitzuteilen, dass (und was) er
etwas erkannt hat.
Das sind erst mal die beiden grundsätzlichen Möglichkeiten, die du hast.
Beide haben ihre Vor- und Nachteile, die sich hauptsächlich (aus meiner
Sicht) um den Speicherverbrauch drehen. Bei einem DOM-Parser baut nun
mal der Parser selbst eine interne Datenstruktur auf, aus der du dir
dann möglicherweise Ausschnitte holst, um sie dann in deiner eigenen
Datenstruktur zu duplizieren. Ein SAX-Parser hingegen speichert selbst
keine bzw. nur kaum Daten zwischen, sondern es obliegt dem Aufrufer
dafür zu sorgen, dass er die Teile speichert, die er haben will. Das
kann durchaus in der Form sein, dass du eine Klasse (bzw. ein paar
Klassen) hast, die gemeinsam die Datenstruktur repräsentieren, die es zu
füllen gilt.
Das wäre deine erste Entscheidung, die du treffen musst.
> Wahrscheinlich eine Klasse anlegen, die einen "Parser" representiert.> Dem Konstruktor würd ich den Dateinamen der Textdatei mitgeben.
Kann man machen. Wird sogar sinnvoll sein.
> Ich denke, ich lege mir für einen Datensatz eine Struktur (oder interne> Klasse) an, un sammle diese in einem std::vektor.
Das kommt drauf an, was deine Daten Repräsentieren. Sind die alle vom
gleichen Typ?
> Dann wirds aber schwammig. Soll der Konstruktor dann das ganze Parsen> gleich in einem Rutsch erledigen?
Finde ich keine so gute Idee.
Wenn du dem Parser einen Filenamen mitgibst, dann ok - kann man so
machen. Aber ich würde auf jeden Fall eine Funktion vorsehen, mit der
ich explizit dem Parser auffordern kann: Jetzt File parsen.
> Wie komm ich an die Datein ran?
Das hängt jetzt davon ab, für welches Modell du dich entschieden hast.
Baut der Parser selbst eine Datenstruktur auf oder nicht?
Im ersten Fall:
Wie ist deine Datenhierarchie? Ist das eine lineare Hierarchie oder ist
das eine Baumstruktur? Welche Zugriffe benötigst du? Wie identifizierst
du einen Datensatz? Hast du etwas, das als Schlüssel fungieren kann?
> Grundsätzlich wohl über einen Getter
Na ja. Das ist jetzt aber sehr plakativ vereinfacht. Ich will ein Auto
bauen - was brauch ich? 4 Räder. No, na.
PS: du solltest dir angewöhnen, in Klassen nicht in Form von Gettern und
Settern zu denken. Die Gefahr ist groß, dass du dann nicht
objektorientiert arbeitest sondern "C mit Klassen" erzeugst. Letzteres
hat mit Objektorientierheit wenig zu tun.
> aber dazu muss der Benutzer der> Klasse erstmal wissen, wieviele Datensätze eingelesen wurden.
Muss er das?
Warum kann er nicht einen 'Iterator' haben, der beim ersten Datensatz
beginnt und den er laufend 'erhöht' um sich einen Datensatz nach dem
anderen zu holen. Wenn er überhaupt sequentiell auf die Daten zugreifen
können muss - was ja noch lange nicht gesagt ist. Denn: wer sagt dass
der Parser sich darum kümmern muss? In erster Linie ist es ja eine
Datenstruktur, die du um Werte befragst, wo ich immer diese
Datenstruktur dann auch ihre Ergüsse her hat. Selbstverständlich kann
bei einem DOM Ansatz der Parser selber so eine Datenstruktur beinhalten
- muss aber nicht. Die kann genausogut auch vom Verwender kommen.
> Denkbar wäre auch, dem Getter einen String (also den Bezeichner des> Datensatzes) zu übergeben, aber dann müsste ich im Getter jeweils alle> Datensätze auf diesen String vergleichen.
Wieso musst du das?
Das ist ja wohl ein Implementierungsdetail in der Datenhaltung des
Parsers, wie der seine Daten speichert. Von aussen interessiert dich das
herzlich wenig. Im Parser spricht auch nichts dagegen (wenn deine
Strings eindeutig sind), dass die Datensätze als std::map gespeichert
sind.
> Wie würdet ihr das lösen?
Erst mal, indem ich mich frage:
Komme ich damit durch, dass der Parser die Daten hält? Kann ich den
Parser an sich, gleich als meine 'Datenhaltung' verwenden? Ist DOM für
mich das richtige, oder ist der SAX Ansatz das richtige, weil ich den
Parser zb von einer Klasse 'ConfigData' benutze und es da völlig
ausreicht, wenn der Parser dieser Klasse bescheid gibt, dass er einen
Satz von der Datei gelesen hat und ConfigData sich dann die Werte dieses
einen Satzes holt und in seiner eigenen Datenstruktur aufhebt.
Rolf Magnus schrieb:> Was ich bei deinen Ausführungen vermisse, ist eine Klasse, um deine> eigentlichen Daten zu repräsentieren. Dann weißt du auch, wie du an die> Daten kommst. Die Klasse kapselt die Daten und bietet nach außen eine> Schnittstelle zum Zugriff. Nach dem Parsen mußt du mit den Daten ja auch> irgendwas machen.
Stimmt, da hast du recht.
Ich hätte bisher schon auch eine Klasse zur Datenrepresentation
angelegt. Allerdings hätt ich diese intern im Parser verwendet, und dort
auch einen Vektor (oder eben Map) dieser Klasse angelegt.
Aber stimmt, macht natürlich mehr Sinn die geparsten Datensätze nach
außen hin verfügbar zu machen. Nur stellt sich dann die frage, wem
"gehören" die Datensätze dann? Der main()?
Wenn Parser und Datensatz zwei getrennte Klassen sind, was liefert dann
der Parser zurück? Einen Vektor?
>Finde ich keine so gute Idee.>Wenn du dem Parser einen Filenamen mitgibst, dann ok - kann man so>machen. Aber ich würde auf jeden Fall eine Funktion vorsehen, mit der>ich explizit dem Parser auffordern kann: Jetzt File parsen.
Sogar sehr schlechte Idee, den du musst zum Parsen dann jedesmal ein
Objekt erzeugen, was (mal ein bisschen großräumiger gedacht) den
Speicher voll müllt. Rufst du das Ganze dann in einer Schleife auf, kann
es dir sogar den Speicher dicht machen.
Geb zu vielleicht ein bisschen viel Schwarzmalerei, aber wenn man es
gleich richtig lernen will...
Gruß Jonas
Hans schrieb:> Stimmt, da hast du recht.> Ich hätte bisher schon auch eine Klasse zur Datenrepresentation> angelegt. Allerdings hätt ich diese intern im Parser verwendet, und dort> auch einen Vektor (oder eben Map) dieser Klasse angelegt.> Aber stimmt, macht natürlich mehr Sinn die geparsten Datensätze nach> außen hin verfügbar zu machen. Nur stellt sich dann die frage, wem> "gehören" die Datensätze dann? Der main()?
Demjenigen, der sie anlegt :-)
> Wenn Parser und Datensatz zwei getrennte Klassen sind, was liefert dann> der Parser zurück? Einen Vektor?
Ich würde in dem Fall sogar soweit gehen, dass der Parser da überhaupt
nichts 'liefert'. Der Parser kriegt einen Filenamen und eine
Datenstruktur und sein Job ist es diese Datenstruktur zu befüllen.
Punkt. Aus.
Egal wer dann auch immer den Parser benutzt, egal wie oft der Parser in
einem Programm benutzt wird, derjenige der den Parser benutzen will, hat
ein Datenstruktur-Objekt zur Verfügung zu stellen. Der Parser schreibt
da hinein, so wie ein Promi auf das Papier schreibt, welches ihm der Fan
unter die Nase hält. Der Fan ist für Papier und Bleistift zuständig, der
Promi benutzt das nur.
jibi schrieb:>>Finde ich keine so gute Idee.>>Wenn du dem Parser einen Filenamen mitgibst, dann ok - kann man so>>machen. Aber ich würde auf jeden Fall eine Funktion vorsehen, mit der>>ich explizit dem Parser auffordern kann: Jetzt File parsen.>> Sogar sehr schlechte Idee, den du musst zum Parsen dann jedesmal ein> Objekt erzeugen, was (mal ein bisschen großräumiger gedacht) den> Speicher voll müllt. Rufst du das Ganze dann in einer Schleife auf, kann> es dir sogar den Speicher dicht machen.>> Geb zu vielleicht ein bisschen viel Schwarzmalerei, aber wenn man es> gleich richtig lernen will...
Im Prinzip ja. Wie schlimm das wirklich ist, hängt von den Details des
Einsatzes ab. Ein Objekt in einer Schleife immer wieder zu erzeugen hat
auch den Vorteil, dass du garantiert immer mit einem frischen Objekt
anfängst. Bei Wiederverwendung von Objekten ist das immer so eine Sache.
Ich würde mich da an den fstream Klassen orientieren. Die können auch
beides: Sowohl Dateiname im Konstruktor als auch explizite open calls.
Hans schrieb:> Ich hätte bisher schon auch eine Klasse zur Datenrepresentation> angelegt. Allerdings hätt ich diese intern im Parser verwendet, und dort> auch einen Vektor (oder eben Map) dieser Klasse angelegt.
Ich meinte nicht für einen einzelnen Datensatz, sondern für die gesamten
Daten. Dieses Objekt hält dann intern eine Map aus Datensätzen. Ob ein
Datensatz dann eine einfache Struktur oder wieder eine eigene Klasse
sein soll, hängt von dessen Komplexität ab und davon, ob es typische
Aktionen gibt, die man mit einem Datensatz immer wieder erledigt, und
die man enstprechend als Memberfunktionen einbauen kann.
> Aber stimmt, macht natürlich mehr Sinn die geparsten Datensätze nach> außen hin verfügbar zu machen. Nur stellt sich dann die frage, wem> "gehören" die Datensätze dann? Der main()?
Wem hätten sie in deinem C-Programm gehört?
> Wenn Parser und Datensatz zwei getrennte Klassen sind, was liefert dann> der Parser zurück? Einen Vektor?
Er bekommt ein Datenobjekt, das er befüllt. Zurückliefern muß er dann
gar nichts. Das Datenobjekt legt dann intern die Datensätze auf
Anweisung des Parsers an. Da gibt's dann irgendein addEntry(...) in der
Datenklasse, die vom Parser benutzt wird. Der greift dann also nie
direkt auf die Daten zu.
Aber ich glaube viel wichtiger wäre ein theoretischer Ansatz wie man an
ein Objektmodel kommt. Ich mach das meistens so:
1. Anwendungsfälle
Ich sammle Anwendungsfälle die im Zusammenhang mit dem Programm stehen.
In deinem Fall:
Ich will eine Datei parsen.
2. Objektdiagram
Ich überlege welche Objekte sich aus den Anwendungsfällen ergeben und
überlege welche "Aufgaben" sie haben. Stift und Zettel sind gefragt.
Nicht jede Überlegung ist gleich sinnvoll, da is Übung gefragt.
In deinem Fall:
Es muss eine Datei geparst werden, das macht ein Objekt, nennen wir es
"Parser". Die Aufgabe des parsens übernimmt die Methode "parsen".
Das wärs dann laut deinen Anforderungen soweit auch schon, was genau für
Daten zu parsen sind (und da vielleicht eine Klassifizierung sinnvoll)
hast du ja noch nicht verraten.
Aber mit diesem, auch erstmal sehr einfach beschriebenen Ansatz kommt
man an gute Ansätze, weil die Objektorientierung ein natürlicher Ansatz
ist Probleme zu beschreiben und eigentlich eher für Menschen zu
verstehen und zu modellieren als ein funktionaller. Man muss halt die
Syntax verstehen, ist aber mit C# (nur als Beispiel!!!) einfacher als
mit good old c++.
Wünsch dir viel Erfolg!
Gruß Jibi
So, danke euch erstmal für eure Beiträge. Vor allem natürlich
Karl-Heinz, wie immer sehr informativ :-)
Das mit dem Promi-Autogramm find ich grad gut. Denke so werd ichs
erstmal probieren. Das hat grad noch einen Vorteil: hab ich zwei Dateien
geb ich den beiden Parsern nacheinander dieselbe Datenstruktur (einen
Vektor) und habe danach die Datensätze aus zwei Dateien in einer
einzigen Datenbasis zur Verfügung. Der Anwender muss sich nicht mehr
darum kümmern, woher denn die Daten nun stammen.
Rolf Magnus schrieb:> Hans schrieb:>> Ich hätte bisher schon auch eine Klasse zur Datenrepresentation>> angelegt. Allerdings hätt ich diese intern im Parser verwendet, und dort>> auch einen Vektor (oder eben Map) dieser Klasse angelegt.>> Ich meinte nicht für einen einzelnen Datensatz, sondern für die gesamten> Daten. Dieses Objekt hält dann intern eine Map aus Datensätzen. Ob ein> Datensatz dann eine einfache Struktur oder wieder eine eigene Klasse> sein soll, hängt von dessen Komplexität ab und davon, ob es typische> Aktionen gibt, die man mit einem Datensatz immer wieder erledigt, und> die man enstprechend als Memberfunktionen einbauen kann.>>> Aber stimmt, macht natürlich mehr Sinn die geparsten Datensätze nach>> außen hin verfügbar zu machen. Nur stellt sich dann die frage, wem>> "gehören" die Datensätze dann? Der main()?>> Wem hätten sie in deinem C-Programm gehört?>>> Wenn Parser und Datensatz zwei getrennte Klassen sind, was liefert dann>> der Parser zurück? Einen Vektor?>> Er bekommt ein Datenobjekt, das er befüllt. Zurückliefern muß er dann> gar nichts. Das Datenobjekt legt dann intern die Datensätze auf> Anweisung des Parsers an. Da gibt's dann irgendein addEntry(...) in der> Datenklasse, die vom Parser benutzt wird. Der greift dann also nie> direkt auf die Daten zu.>den wie du schon merkst würgst du Dir>irgendwas zu recht was dann deine verschiedenen Objekte rechtfertigt.
Macht doch kein Sinn.
>Mach das mit c und such warte auf ein besseres Projekt für deine>Metamorphose...>Bei Wiederverwendung von Objekten ist das immer so eine Sache.
Klingt so nach Resten im Objekt nach Funktionsaufrufen und unsaubere
Initialisierung vor dem Aufruf...das ist aber immer Schlamperei und ganz
ehrlich, wer erwartet bei einem Parser, das ich ihn jedesmal erzeugen
muss zum Parsen, mal abgesehen von dem Overhead der massiv ist wenn es
um Performance geht...da muss ja Hauptspeicher angefordert werden, das
kostet doch massiv cpu-Zeit... Ich kann nicht aufhören, muss mal andere
Farben suchen.
Gruß Jibi
jibi schrieb:> Aber ich glaube viel wichtiger wäre ein theoretischer Ansatz wie man an> ein Objektmodel kommt. Ich mach das meistens so:>> 1. Anwendungsfälle>> Ich sammle Anwendungsfälle die im Zusammenhang mit dem Programm stehen.> In deinem Fall:>> Ich will eine Datei parsen.>> 2. Objektdiagram>> Ich überlege welche Objekte sich aus den Anwendungsfällen ergeben und> überlege welche "Aufgaben" sie haben.
Tip von mir:
Die Aufgabe ohne Ansehen von Implementierungsdetails durchdenken.
Alles was dabei an Hauptwörtern auftaucht, ist sehr oft schon mal ein
heißer Kandidat für eine Klasse.
Welche Hauptwörter kommen in der Aufgabenstellung vor:
Parser
Daten
Datensatz
...
Das sind alles schon mal heiße Kandidaten für eigene Klassen.
Der nächste Schritt ist, Zuständigkeiten zu definieren. Welche Klasse
ist wofür zuständig, was ist ihre Aufgabe?
Dann überlegt man sich, wie die einzelnen Klassen zusammenspielen
können, wenn sie 'Beamte' wären (keine Beleidigung beabsichtigt): Jede
Klasse macht das und nur das, wofür sie zuständig ist. Fällt ein
Teilbereich nicht in ihr definiertes Aufgabengebiet, dann gibt sie die
Zuständigkeit an geeigneter Stelle ab. Ein Parser kann und will sich
nicht um die Details kümmern, WIE die Daten gespeichert werden. Das ist
Aufgabe der Datenklasse.
Aufgrund dieser Überlegungen kristallisiert sich dann meist recht
schnell ein erstes Klassenschema heraus, samt zugehörigen notwendigen
Member-Funktionen. Betrachte die Member-Funktionen als 'Befehle' an eine
Klasse etwas zu tun. Genau da liegt der Unterschied zur traditionellen
Methode: die Einheit von Daten und Funktionen ist viel stärker betont.
Wenn du nur in 'Gettern' und 'Settern' denkst, dann bist du noch nicht
in der OOP Welt angekommen.
jibi schrieb:> Klingt so nach Resten im Objekt nach Funktionsaufrufen und unsaubere> Initialisierung vor dem Aufruf...das ist aber immer Schlamperei und ganz> ehrlich, wer erwartet bei einem Parser, das ich ihn jedesmal erzeugen> muss zum Parsen, mal abgesehen von dem Overhead der massiv ist wenn es> um Performance geht...da muss ja Hauptspeicher angefordert werden, das> kostet doch massiv cpu-Zeit...
So schlimm ist das auch wieder nicht.
Die Compiler sind so dämlich dann auch wieder nicht.
Ob du jetzt auf einem Speicher deine eigene Init-Funktion aufrufst, oder
ob der Compiler in einer Schleife auf immer demselben Speicherbereich
einen Konstruktor Aufruf legt, schenkt sich nicht viel. Nur mit dem
Unterschied, dass du einen Konstruktor/Destruktor-Aufruf nicht vergessen
kannst.
Hans schrieb:> Das mit dem Promi-Autogramm find ich grad gut.
Anderes Beispiel.
Aus der MFC
Dort gibt es eine View Klasse, deren Job es ist, die Darstellung 'seiner
Daten' durchzuführen. Dazu wird vom Framework an geeigneter Stelle eine
Paint Methode aufgerufen.
Die Paint Methode kriegt einen Device Context mit. Dieser Device Context
steht für die Ausgabefläche. Die Paint Methode setzt also nicht selbst
Pixel im Video-Speicher, sondern sie beauftragt den Device Context eine
Linie zu zeichnen. Ob dieser Device Context in ein Fenster führt, ob das
vielleicht am Drucker landet, am Plotter oder gar über Netzwerk auf
einen ganz anderen Rechner führt, das braucht die Paint Methode nicht zu
kümmern. Sie kann den Device Context nach seinen Eigenschaften befragen
(wie groß er ist) und kann ganz abstrakt dem DC den Auftrag erteilen:
zeichne Linie von-bis. Das eigentliche (physikalische) Zeichnen wird vom
DC erledigt.
Die Paint-Methode vom View wird auch vielleicht nicht alles selbst
zeichnen. Vielleicht malt sie selber nur Dinge wie ein Koordinatenkreuz
hin und eine Beschriftung und beauftragt dann die Datenstruktur (wieder
unter Angabe des DC) sich dort auszugeben. Vielleicht gibt es aber auch
ein eigenes Darstellungsobjekt, welches dieselben Daten einmal als
Liniengrafik und malen kann, während ein anderes Methodenobjekt
dieselben Daten als Tortengrafik malen kann. Vielleicht ist aber auch
Koordinatenkreus und Beschriftung selbst wieder in einer Klasse
gekapselt, so dass der View im Grunde nur die 'Arbeitsaufträge' an
weitere Objekte weiterreicht, sich zu zeichnen. All das interessiert
aber zb das Framework wieder nicht. Wenn es gilt etwas zu malen, dann
wendet sie sich mit einem DC an den View mit dem Auftrag "da rein
malen".
Jede Klasse hat ihre Zuständigkeit. Was kann sie, welche Aufgabe hat
sie? Und ja, das kann durchaus sein, dass es dann auch Klassen gibt,
deren Aufgabe darin besteht, nach 'aussen hin' als Ansprechpartner zu
fungieren und dann die Arbeitsaufträge intern zu verteilen.
So wie in einer Firma: Als Mechaniker interessiert dich nicht, wie die
Werkzeugausgabe intern funktioniert. Der Mann an der Ausgabe ist dein
Ansprechpartner. Brauchst du etwas, wendest du dich an ihn. Ob der dann
den Azubi ins Lager schickt oder selber geht; nach welchem System er das
gesuchte Werkzeug findet, ist nicht dein Bier. Dafür bist du nicht
zuständig. Genauso wie es den Mann am Schalter nicht interessiert, wie
du das Werkzeug dann einsetzt. Natürlich hat man sich beim 'Design' der
Werkzeugausgabe daran orientiert, wie die Werkzeugausgabe wohl meistens
benutzt werden wird - man will ja schliesslich auch niemandem Prügel
zwischen die Beine werfen. Aber im Grunde hast du mit der Theke in der
Ausgabe eine ganz klare Trennlinie zwischen den Zuständigkeiten. Und das
ist gut so, denn es erlaubt dir jeden der beiden Bereiche zu verändern,
ohne das dieses den anderen Bereich großartig verändert.
jibi schrieb:> Klingt so nach Resten im Objekt nach Funktionsaufrufen und unsaubere> Initialisierung vor dem Aufruf...das ist aber immer Schlamperei und ganz> ehrlich, wer erwartet bei einem Parser, das ich ihn jedesmal erzeugen> muss zum Parsen, mal abgesehen von dem Overhead der massiv ist wenn es> um Performance geht...
Was für ein "massiver" Overhead soll denn da anfallen?
> da muss ja Hauptspeicher angefordert werden, das kostet doch massiv cpu-> Zeit...
So ein Unsinn. Selbst wenn man es dynamisch allokikert, kostet das kaum
signifkiant Zeit. Abgesehen davon ist nicht damit zu rechnen, daß
mehrere Millionen Dateien pro Sekunde geparst werden müssen.
Hallo zusammen, erstmal Danke für die vielen Vorschläge.
Momentan arbeite ich an einer Lösung wie von KHB vorgeschlagen (Parser,
Datensatz, Datenbasis).
Klappt ganz gut.
Nur Hab ich jetzt noch eine Schwierigkeit: ein Datensatz kann (aber muss
nicht) einen untergeordneten Datensatz beinhalten, unter Umständen sogar
mehrere. Wenn ich so drüber nachdenke, kann man sich das resultierende
Gebilde fast wie eine Struktur in C vorstellen:
1
classDatensatz{
2
private:
3
inta;
4
intb;
5
std::vector<UnterDatensatz>inner;
6
7
public:
8
voidfoo();
9
}
10
11
classUnterDatensatz{
12
public:
13
voidbar();
14
}
15
16
voidUnterDatensatz::bar(){
17
Datensatz::foo();
18
}
Mir ist klar, dass diese Lösung nicht klappt. Das Beispiel soll eher
verdeutlichen, was ich vorhabe. Ein UnterDatensatz muss in der Lage
sein, z.B. auf "a" oder "b" des Datensatzes zuzugreifen, der ihn
beinhaltet.
Ich denke, dass ist nur möglich wenn ich einem UnterDatensatz beim
Erstellen (im Konstruktor?) eine Referenz auf den "Besitzer" übergebe.
Oder gibts da eine andere Möglichkeit?
Viele Grüße
Hans schrieb:> Mir ist klar, dass diese Lösung nicht klappt. Das Beispiel soll eher> verdeutlichen, was ich vorhabe. Ein UnterDatensatz muss in der Lage> sein, z.B. auf "a" oder "b" des Datensatzes zuzugreifen, der ihn> beinhaltet.> Ich denke, dass ist nur möglich wenn ich einem UnterDatensatz beim> Erstellen (im Konstruktor?) eine Referenz auf den "Besitzer" übergebe.
Das wäre der normale Weg. Du baust damit eine Art Objekthierarchie auf.
Jeder UnterDatensatz bekommt einen Zeiger auf seinen "parent", also den
Datensatz, dem er gehört. Eine Referenz wird nicht gehen, da std::vector
Zuweisbarkeit vorraussetzt, und das funktioniert (ohne schmutzige
Tricks) nicht für Objekte, die Referenzen beinhalten.
Aufpassen mußt du aber, wenn du den Datensatz irgendwo kopierst. Denn
wenn du keine entsprechenden Vorkehrungen triffst, zeigen sonst die
Parent-Zeiger der UnterDatensätze in der Kopie nicht auf diese, sondern
aufs Original, also nicht mehr auf das Objekt, zu dem sie gehören.
Also entweder nie einen Datensatz kopieren (und sicherstellen, daß du es
auch nicht versehentlich tust) oder einen Kopierkonstruktor und einen
Zuweisungsoperator implementieren, der die Zeiger aller UnterDatensätze
anpasst.