Forum: PC-Programmierung C++ Ansatz um Datei zu parsen


von Hans (Gast)


Lesenswert?

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?

von Murkser (Gast)


Lesenswert?


von jibi (Gast)


Lesenswert?

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

von Rolf Magnus (Gast)


Lesenswert?

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.

von Karl H. (kbuchegg)


Lesenswert?

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.

: Bearbeitet durch User
von Hans (Gast)


Lesenswert?

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?

von jibi (Gast)


Lesenswert?

>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

von Karl H. (kbuchegg)


Lesenswert?

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.

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

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.

von Rolf Magnus (Gast)


Lesenswert?

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.

von jibi (Gast)


Lesenswert?

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

von Hans (Gast)


Lesenswert?

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.

von jibi (Gast)


Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

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.

von Karl H. (kbuchegg)


Lesenswert?

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.

: Bearbeitet durch User
von jibi (Gast)


Lesenswert?

>das ist aber immer Schlamperei

Nee nicht vom Compiler.

von Karl H. (kbuchegg)


Lesenswert?

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.

: Bearbeitet durch User
von Rolf Magnus (Gast)


Lesenswert?

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.

von Hans (Gast)


Lesenswert?

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
class Datensatz{
2
private:
3
  int a;
4
  int b;
5
  std::vector<UnterDatensatz> inner;
6
7
public:
8
  void foo();
9
}
10
11
class UnterDatensatz{
12
public:
13
  void bar();
14
}
15
16
void UnterDatensatz::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

von Rolf Magnus (Gast)


Lesenswert?

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.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.