danlei schriebAussehen sollte er so:
with_open_file(i, "dateiname") {
   while (getline(i, line)) {
      cout << line << endl; 
   }
}
Nein, sollte er nicht. Es ist schlicht Auffassungssache.
danlei schriebEin kleines Verständnisproblem habe ich: Woher weiß der Stream, wann er geschossen werden soll? (Keine rhetorische Frage, sondern eine ernst gemeinte.)
Jedes automatisch erzeugte Objekt (d.h. eins, das nicht mit new auf dem heap erzeugt wurde, sondern lokal auf dem stack) lebt bis zum Ende des Blockes, in dem es erzeugt wurde. So ist die Regel. Bereits am Blockanfang wird Platz auf dem Stack reserviert, und zwar für alle Variablen des Blockes in Summe. Irgendwann später (nämlich wenn die Zeile kommt, vor der noch etliches andere stehen kann) wird der Konstruktor aufgerufen, der das Objekt auf dem stack initialisiert. Ab dann lebt das Objekt (der Compiler verhindert vorherige Zugriffe, indem er den Namen des Objekts für unbekannt hält), bis irgendwann der Block zu Ende ist. Dann werden alle Destruktoren aufgerufen, die zu stack-Objekten dieses Blocks gehören, und zwar in umgekehrter Reihenfolge. Nach deren Ende wird der Platz auf dem Stack freigegeben.

Dieses wichtige und ebenso einfache wie wirkungsvolle Prinzip ist anfänglich selbst vielen C++-Programmierern nicht klar. Statt dessen findet man alberne lokale Konstruktionen mit smart pointers und dynamisch erzeugten Objekten, weil in der Vorlesung gesagt wurde, daß das gut sei, die dann noch falsch angewendet werden, um am Ende über die schwierige und ohnehin pöse Speicherverwaltung herzuziehen.

Dabei kann man mit diesem Prinzip nicht nur superschnell Objekte erzeugen, nämlich ohne Speicher allokieren zu müssen, sondern auch ihre Lebensdauer explizit bestimmen, d.h. steuern, indem man einfach den Block genau so groß macht, wie er sein soll.

Man kann übrigens das Schließen eines streams auch explizit veranlassen. Die stream-Klassen haben eine close-Methode. Dies ist nützlich, wenn man den stream später wieder öffnen will. Meist überläßt man das Schließen allerdings dem Destruktor.
T.M. schriebNein, sollte er nicht. Es ist schlicht Auffassungssache.
Naja, dagegen kann ich nicht viel vorbringen. Außer vielleicht: Wenn man wollte, dass er so aussieht, sollte es einem ermöglicht werden. (Ich kann mir aussuchen, ob ich etwas objektorientiert, sprachorientiert (als DSL, die wirklich Teil meiner Sprache wird, nichts davon getrenntes), kontextorientiert (siehe die Papers von P. Costanza), oder wie auch immer löse.) Allerdings kannst Du auch hier wieder mit "Auffassungssache" kontern, wie eigentlich bei fast allem. Fakt ist allerdings auch, dass es Teil der Aufgabe war.

Im Großen und Ganzen muss ich sagen, dass meine Meinung über C++ sich gebessert hat, zumindest für Aufgaben, die nah an der Maschine, aber trotzdem relativ hochsprachlich gelöst werden sollen. (Wobei ich, aus praktischen Gründen nach wie vor zu C tendieren würde [besseres FFI, "kleine Sprache", aber seis drum] , aber was ich nicht ganz verstehe ist, dass Du dieses Konzept nicht magst (wie gesagt gehen die Möglichkeiten weit darüber hinaus), das in Lisp(s) allgegenwärtig ist. Aber ok, man muss auch andere Meinungen akzeptieren.

De gustibus non est disputandum.
danlei schrieb[Erklärung von Objekten auf dem Stack]
Vielen Dank für die ebenso ausführliche, wie auch verständliche Erklärung!

Nur eine keine Anmerkung: Auch hier erfolgen Dinge implizit. Wenn ich Dich richtig verstanden habe, hast Du das bei anderen Beispielen für keine gute Idee gehalten. Genau deshalb ist, meiner Meinung nach, die "Kontext"-Lösung eigentlich ähnlich aufgebaut, nur dass sie auch das Anlegen des Objekts, sowie die Fehlerbereinigung (eine vernünftige) mit einschießt.

Mir erscheint die OOP-Lösung wirklich, und das ist jetzt keine Eristik, komplizierter/komplexer als die Makro-Lösung, vor allem auf Seite der Implementation, aber auch hier gestehe ich Dir zu, dass man geteiter Meinung sein kann. (Sprich: In der Makro-Lösung muss man nur schreiben, was man ansonsten von Hand schreibt, in der OOP-Lösung muss man sich mit Entwurfsmustern beschäftigen.)

Nochmals danke für die Erklärung. Da ich momentan wieder, hohen Alters, studiere und mir nicht (immer) aussuchen kann, in welcher Sprache ich arbeite, genauso wie im Berufsleben, kann ich Wissen zu jeder Sprache immer gut gebrauchen.
Sagt mal, da wir es schon hier mit Metaprogrammierung zu tun haben, hat vielleicht ein eine Idee zu folgendem Problem?

Ich schreibe zur Zeit ein kleines C++Programm, ausschließlich um die Sprache besser kennen zu lernen. Zur Datenhaltung benutze ich SQLite. Nun möchte ich Objekte aus dem relationalem Schema der Datenbank lesen und auch wieder reinschreiben. An und für sich kein Problem, nur merke ich bei der Implementierung meiner ersten Klasse eine Menge an Boilerplate. Und zwar Folgendes: Eine Klasse besteht im Grunde aus mehreren Datenelementen die so direkt in die Datenbank geschrieben werden (Integer und Strings). Für jedes Datenelement muss ich nun immer eine gewisse Menge absolut gleichen Code schreiben. Ich mach das mal exemplarisch an der "save_to_db"-Methode einer Klasse Person fest:
Person::save_to_db() {
  slq_qery << "UPDATE person SET "
           << "name='"      << this->name <<"',"
           << "firstname='" << this->firstname <<"',"
           << "age="        << this->age<<");"
}
Man sieht: Für ein Datenelement muss ich (je nach Datentyp) den Query-String praktisch immer um die gleiche Art erweitern; gleiches gilt natürlich für alle Inserts- und Select-Statements, ebenso wie für alle Setter.

Es würde mich interessieren, ob das mit C++ (mit vertrebaren Aufwand) auch generischer geht? Ich wüsste nämlich aktuell gerade nicht wie (abgesehen davon den m4 zu benutzen^^): C++ stellt keine Reflection bereit, oder? Also kann ich zur Laufzeit nicht feststellten, welche Datenelemente, mit Name und Typ ein Objekt hat um daraus ein Query-String zu basteln.

Und bei Templates, muss zur "Implementationszeit" soweit ich weiß, auch die Anzahl der Datenelemente und Funktionen bekannt sein (nur ihr Typ nicht), oder? Und wie ich mit Templates Strings basteln sollte, wüsste ich jetzt nicht.
Kinch schrieb
Person::save_to_db() {
  slq_qery << "UPDATE person SET "
           << "name='"      << this->name <<"',"
           << "firstname='" << this->firstname <<"',"
           << "age="        << this->age<<");"
}
Man sieht: Für ein Datenelement muss ich (je nach Datentyp) den Query-String praktisch immer um die gleiche Art erweitern; gleiches gilt natürlich für alle Inserts- und Select-Statements, ebenso wie für alle Setter.

Es würde mich interessieren, ob das mit C++ (mit vertrebaren Aufwand) auch generischer geht? Ich wüsste nämlich aktuell gerade nicht wie
Ich versteh nicht richtig, wo das Problem liegt. Oben das könnte ausführbarer Code sein, vor ausgesetzt Deine Datentypen für name, firstname und age haben vernünftig implementierte Schiebeoperatoren, die SQL-Syntax erzeugen. Was Du nicht machen mußt, ist eine Typüberprüfung (das ginge mit RTTI) und dann eine jeweils typabhängige verschiedene Reaktion darauf, das wäre bad style. Mal angenommen, links das sql_query ist vom Typ SQLQuery, dann brauchst Du nur Operatoren definieren:
SQLQuery& operator << (SQLQuery& q, const string& s)
{
   return (s.empty())
      ? q << "NULL"
      : q << "'" << s << "'";
}

SQLQuery& operator << (SQLQuery& q, int i)
{
   return q << i;
}

SQLQuery& operator << (SQLQuery& q, const Datum& d)
{
   return ... // hier wird's interessant! Datumswerte müssen meist
              // in einen SQL-String mit einem DB-spezifischen Format
              // konvertiert werden, z.B. '2009-10-14 10:10:13'
}
Und dann kannst Du einfach schreiben:
Person::save_to_db()
{
  slq_qery << "UPDATE person SET "
           << "name="      << name      <<","
           << "firstname=" << firstname <<","
           << "age="       << age       <<");"
}
, d.h. alle Elemente werden unabhängig von ihrem Typ einfach draufgeschoben, die jeweils notwendige Syntax regeln die Schiebeoperatoren selbst, und zwar ein für alle Mal richtig.
Also danke erstmal für die Antwort. Mit dem Shift-Operator war ich in der Tat ziemlich unzufrieden und ich werde deine Implementierung adaptieren. Vielen Dank.
Ich versteh nicht richtig, wo das Problem liegt. Oben das könnte ausführbarer Code sein, vor ausgesetzt Deine Datentypen für name, firstname und age haben vernünftig implementierte Schiebeoperatoren, die SQL-Syntax erzeugen.
Ja, ich habe vielleicht auf mein Problem nicht genau genug hingewiesen. Natürlich funktioniert die Implementierung. Und es ist auch kein Problem das so zu implementieren. Aber ich habe pro Klasse eben mehrere Stellen, an denen ich das machen muss; ändere einen Datenelement, füge ich eines hinzu oder entferne ich eines, muss ich die gleichen Änderungen an mehreren Stellen durchführen. Sowas widerstrebt mir eigentlich. Ich hatte mich deshalb gefragt, ob C++ einen Mechanismus bereitstellt, um zur Compilezeit wenn möglich, einmal die Datenelementen zu spezifieren und den restlichen Code dann automatisch generieren zu lassen.

Aber ich bestehe da auch jetzt nicht drauf.^^

Btw. danke fürs Stichwort RTTI. Den "typeid"-Operator kannte ich noch gar nicht.

Gruß
Kinch schrieb Ich schreibe zur Zeit ein kleines C++Programm, ausschließlich um die Sprache besser kennen zu lernen.
Da sind deine Lisp-Ambitionen aber schnell verflogen. 🙂
Person::save_to_db() {
  slq_qery << "UPDATE person SET "
           << "name='"      << this->name <<"',"
           << "firstname='" << this->firstname <<"',"
           << "age="        << this->age<<");"
}
Keine Antwort auf die Frage, ich weiß, aber C++ überlasse ich dann doch lieber T.M., mangels Kenntnis.
(defmethod save-to-db ((self person))
  (query (format nil "UPDATE person SET name=~a, firstname=~a, age=~a"
                 (name self)
                 (firstname self)
                 (age self))))
Also im Prinzip das Gleiche in grün. Ich wüsste auch nicht so recht, wo man das optimieren wollte, bis auf verwendung von with-slots, was dir das this ersparen würde.

Vielleicht meinst du sowas:
DHL> (defmacro sql-variable-assignment (variable)
       `(format nil "~a=~a" ,(symbol-name variable) (,variable self)))
SQL-VARIABLE-ASSIGNMENT
DHL> (macroexpand-1 '(sql-variable-assignment |foo|))
(FORMAT NIL "~a=~a" "foo" (|foo| SELF))
Das würde dann für (sql-variable-assignment |foo|) "foo=<wert von foo>" zurückgeben.

Das würde folgendes erlauben:
DHL> (with-output-to-string (query-string)
       (dolist (variable '(|foo| |bar| |baz|))
         (format query-string "~a " (sql-variable-assignment variable))))
"foo=<wert von foo> bar=<wert von bar> baz=<wert von baz> "
Das in ne Funktion sql-assignments gepackt erlaubt dann dies: (sql-assignments '(|foo| |bar| |baz|)) => "foo=<wert foo> bar=<wert bar> baz=<wert baz> ", oder eben ohne die |, wenn du den Reader case-sensitive machst. (eine zeile code)

Anm.: Man sollte noch kurz den reader umstellen vorher, damits keine Probleme mit Groß- u. Kleinschreibung gibt und man sich die "|" um die Bezeichner sparen kann, außerdem ein Readermacro, z.b. [] eibführen, aber ansonsten war das vielleicht was du wolltest. Man würde natürlich nicht da aufhören, sondern gleich eine richtige DSL schreiben.

Ansonsten, siehe:

http://www.cliki.net/SQL

Beziehungsweise als konkretes Beispiel für eine solche DSL:
http://bc.tech.coop/blog/040608.html
danlei schrieb Da sind deine Lisp-Ambitionen aber schnell verflogen. 🙂
Nene, ich hatte das C++ Projekt schon vorher angefangen und will es auch zuende bringen, zumal ich C++ vermutlich irgendwann eh können muss.

In Lisp bin ich gerade dabei ne eigene Datenbank-Engine als SQLite-Ersatz zu schreiben.^^

Außerdem macht es mir Spaß neue Programmiersprachen zu lernen.
Also im Prinzip das Gleiche in grün. Ich wüsste auch nicht so recht, wo man das optimieren wollte, bis auf verwendung von with-slots, was dir das this ersparen würde.
Na ja, ich steigere mich auch manchmal vielleicht in gewisse Lösungswege rein. Mein Gedanke war halt in Pseudocode folgendes zu schreiben:
Class Person extends SqlTable {
  int id;
  int age;
  string name;
  string firstname;
}
Und das wars dann schon; weil man aus diesen Informationen schon sämtliche Inserts, Updates und Selects konstruieren kann, ebenso wie (mit Abstrichen) das SQL-Statement um die eigentliche Tabelle anzulegen.

Das man quasi nur die Eigenschaften der Klasse festlegt und sämtliche SQL-Statements werden zur Compile-Zeit generiert.
Vielleicht meinst du sowas:
DHL> (defmacro sql-variable-assignment (variable)
       `(format nil "~a=~a" ,(symbol-name variable) (,variable self)))
SQL-VARIABLE-ASSIGNMENT
DHL> (macroexpand-1 '(sql-variable-assignment |foo|))
(FORMAT NIL "~a=~a" "foo" (|foo| SELF))
Ja so ähnlich, halt noch generischer. Was ich im Sinne hatte, war eben gar nicht mehr mit SQL-Statements in Berühung zu kommen, bei der Definition neuer Klassen.
Kinch schriebAber ich habe pro Klasse eben mehrere Stellen, an denen ich das machen muss; ändere einen Datenelement, füge ich eines hinzu oder entferne ich eines, muss ich die gleichen Änderungen an mehreren Stellen durchführen. Sowas widerstrebt mir eigentlich. Ich hatte mich deshalb gefragt, ob C++ einen Mechanismus bereitstellt, um zur Compilezeit wenn möglich, einmal die Datenelementen zu spezifieren und den restlichen Code dann automatisch generieren zu lassen.
Das ist in der Tat ein Problem. Aber wenn Du generische Lösungen suchst, wird es kompliziert.

Du könntest erstmal all Deine Werte, also die Membervariablen Deiner Objekte, in Containern halten, mit Name und einem Flag, das sagt, ob die Variable geändert wurde. Und dann könntest Du generische Algorithmen für Insert, Update und Delete machen, die alle auf diesem Container arbeiten. Alle Deine Objekte könnten dann den gleichen Container haben, nur mit anderen Feldern und Werten drin. Schwierigkeit: die Werte in dem Container müssen verschiedene, nicht unbedingt polymorphe Typen haben können, außerdem vielleicht auch SQL-NULL. (Es schreit gerade zu dem Datentyp boost::any!)

Hinzu kommt eine oft unterschätzte Dimension: das Ganze muß auch transaktionssicher sein, was als Problem über die Grenzen eines Objekts hinausreicht. Ansonsten zerschießt man sich früher oder später die DB, das ist fast sicher.

Das Speichern eines Objektmodells in einer relationalen Datenbank ist in jedem Falle durchaus eine anspruchsvolle Aufgabe. Ebenso die Rekonstruktion eines Objektmodells aus einer relationalen Datenbank.
Kinch schriebJa so ähnlich, halt noch generischer. Was ich im Sinne hatte, war eben gar nicht mehr mit SQL-Statements in Berühung zu kommen, bei der Definition neuer Klassen.
Schau nochmal den Artikel, (ich hab nachträglich sowas beispielhaft demonstriert mit sql-assignments) und die Links.

(sql-assignments '(foo bar baz)) => "foo=<wert von foo> bar=<wert von bar> ..."

Noch generischer? Das ist mir jetzt zu stressig, da gibts mMn schon genug Libs, siehe Link. Readmacros wie [] oder sowas (da ist viel moeglich) machen das ganze dann bequemer, bzw. kürzer. Ich hab nur das Konzept gezeigt. (Besser gesagt ein mögliches) Was du willst ist with-sql, oder ein ähnliches Makro, das eine volle SQL-DSL bietet, wahrscheinlich (wenn ich Dich richtig versteh, mit read-syntax) 🙂

HTH
Ok, danke. Ich werde wohl T.M.-Vorschlag mit den Container verfolgen und das zur Laufzeit basteln, wenn es nicht zu kompliziert wird. Ich denke, der Ansatz ist ganz brauchbar. Boost::Any, ist auch ein super Tipp und scheint die Probleme mit der Typisierung zu Lösen.

Habe mir die links jetzt angeschaut, danlei. Sieht auch sehr vielversprechend aus, danke dafür. Ich denke, damit könnte ich die Probleme auch lösen, wenn ich mal mein Übungsprojekt in Lisp schreibe.

So, ich wollte den Thread auch nicht weiter okkupieren.^^

Danke für die Hilfe.
Kinch schrieb So, ich wollte den Thread auch nicht weiter okkupieren.^^
Der ist, dank mir, eh nimmer zu retten. 😃

Ed: Vielleicht eher diese Links, wenns Dir vordergründig um Persistenz von Objekten geht:

http://www.cliki.net/serialization
danlei schrieb Der ist, dank mir, eh nimmer zu retten. 😃
Also ich fand den bis jetzt sehr lesenwert, ehrlich gesagt. Ich finde man kann im selten irgendwo, dass Pro und Contra verschiedener Programmiersprachen-Paradigmen nachlesen.

Dank für den Link. Muss mir erst noch genau, Lisp OO anschauen. Im Moment habe ich für die Serialisierung von Listen "with-standard-io-syntax" und "print" benutzt. Zum Einlesen "read"; das ging eigentlich erstaunlich simpel.
Kinch schrieb Dank für den Link. Muss mir erst noch genau, Lisp OO anschauen. Im Moment habe ich für die Serialisierung von Listen "with-standard-io-syntax" und "print" benutzt. Zum Einlesen "read"; das ging eigentlich erstaunlich simpel.
Ja, das ist eine einfache und gute Lösung, aber mit Objekten wirds etwas komplexer. Für später dann mal ein Beispiel:
;; klasse erstellen
(defclass person ()
  ((name :initarg name)))

;; instanz erzeugen
(defparameter *a-person* (make-instance 'person 'name "Foo Bar"))

;; print-object spezialisieren
(defmethod print-object ((person person) stream)
  (if *print-readably*
      (with-slots (name) person
        (format stream "#.(make-instance 'person 'name ~s)" name))
      (call-next-method)))

;; schreiben
(with-open-file (f ("datei ..."))
  (let ((*print-readably* t))
    (print *a-person* f)))

;; lesen
(with-open-file (f ("datei ..."))
  (defparameter *a-deserialized-person* (read f)))
Also ists, wie Du siehst, am einfachsten, wenn man eine Methode für die generische Funktion print-object angibt, die auf Deine persistente Klasse dispatcht und dann das ganze, wenn *print-readably* t ist, so schreibt, dass das Lesen automatisch wieder das Objekt erzeugt. (Da Code = Daten)

Natürlich wieder nur ein kleines Beispiel, aber für einfache Serilisation von Objekten reicht auch das schon. Was Persistenz mit realtionalen DBs angeht, so hab ich bisher nur http://common-lisp.net/project/elephant/ benutzt, und das ging locker von der Hand, fand ich.

Wenn ich das ganze "ordentlich" machen wollte, würde ich wohl eine serialisierbare Metaklasse definieren, ähnlich wie es elephant tut, aber da würd ich mir übers Design natürlich erst mal richtige Gedanken machen.

Ed: Achso, natürlich bei allem außer Beispielen, wie Dus schon erwähnt hast, in w-s-i-s wrappen.
danlei schriebIm Großen und Ganzen muss ich sagen, dass meine Meinung über C++ sich gebessert hat, zumindest für Aufgaben, die nah an der Maschine, aber trotzdem relativ hochsprachlich gelöst werden sollen.
Hab mir grad ein Haskell-Buch gekauft. Fühl mich schrecklich ....
T.M. schrieb
danlei schriebIm Großen und Ganzen muss ich sagen, dass meine Meinung über C++ sich gebessert hat, zumindest für Aufgaben, die nah an der Maschine, aber trotzdem relativ hochsprachlich gelöst werden sollen.
Hab mir grad ein Haskell-Buch gekauft. Fühl mich schrecklich ....
🙂

Das schreit geradezu nach einem neuen Thread: Die Abenteuer des T.M. im Land der Monaden ... oder so.

Ich persönlich finde Haskell ganz interessant, fühle mich aber immer etwas, sagen wir, "eingeengt".