C++Talk.NET Forum Index C++Talk.NET
C++ language newsgroups
 
Archives   FAQFAQ   SearchSearch   MemberlistMemberlist   UsergroupsUsergroups   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

Serialisierung in Allgemein / nicht-konstante Referenz auf t

 
Post new topic   Reply to topic    C++Talk.NET Forum Index -> C++ (German)
View previous topic :: View next topic  
Author Message
Peter Wortmann
Guest





PostPosted: Sun Jun 12, 2005 5:36 pm    Post subject: Serialisierung in Allgemein / nicht-konstante Referenz auf t Reply with quote




Um klarzumachen, woher das Problem kommt (Kommentare hierzu sind
allerdings ebenfalls willkommen): Ich versuche gerade, eine Art
Serialisierungs/Deserialisierungs-Framework zusammenzubauen, ähnlich der
Serialization-Sache aus Boost.

Dabei soll später, sofern möglich, sowohl abstrahiert sein, ob es sich
gerade um eine Serialisierung oder eine Deserialisierung handelt, als
auch die Art, wie serialisiert wird. Speziell soll es möglich sein, mit
demselben Code sowohl binäre Daten als auch textuelle Formate (wo z.B.
Werten Namen zugeordnet sind) auszugeben.

Dazu existiert ein Objekt, es soll mal analog zu Boost "Archiv" genannt
sein, das Werte durch eine Funktion "Value" serialisiert bzw.
deserialisiert. Dabei sind entsprechende Funktionen für Grundtypen
("int" etc.) vordefiniert. "Kennt" das Archiv keine Möglichkeit, einen
Typ zu bearbeiten, nimmt es an, dass es sich um ein Objekt handelt und
versucht, eine archive()-Funktion der Klasse aufzurufen. Diese
schlüsselt sich dann gegenüber dem Archiv in dessen Einzelkomponenten
auf, indem es selbst weitere Aufrufe an Value tätigt. Benennungen werden
durch entsprechende Funktionen im Interface wie Name() und NameEnd()
erreicht.

Das könnte dann in der Anwendung grob folgendermaßen aussehen:

class A {
int a, b;
public:
A() : a(0), b(1) { }
void archive(Archive *a) {
ar->Name("a"); ar->Value(a); ar->NameEnd();
ar->Name("b"); ar->Value(b); ar->NameEnd();
}
};

int main() {
std::cout << Archive::serialize return 0;
}

Dabei erzeugt Archive::serialize die entsprechende Archivierungsklasse
nach angegebenem Template-Parameter und führt die Serialisierung durch.
Dafür wird intern bereits ein const_cast benötigt, der ist aber wohl
systembedingt nicht wegzubekommen. Letztlich wird intern
"ar->Value(const_cast<A &>(a))" ausgeführt, welches wegen unbekanntem
Typ "a.archive(ar)" entspricht.

Zurückgegeben wird dann ein von der Archivierungsklasse abhängiger
Puffer. Hier z.B. ein String wie "<a>0</a>1".


Jetzt ist die Name()/NameEnd()-Regelung nicht sehr befriedigend, da
leicht bei komplizierteren Konstrukten entsprechende Paarungen verloren
gehen können. Da bereits per Standard eine archive-Funktion aufgerufen
wird, liegt es nahe, sich einen "Adapter" zu schreiben, der die
entsprechenden Aufrufe ergänzt:

template <class T>
class NameAdapt {
T &rVal; std::string name;
public:
NameAdapt(T &rVal, const std::string &name) : rVal(rVal), name(name)
{ }
void archive(Archive *ar) {
ar->Name(name); ar->Value(rVal); ar->NameEnd();
}
};
template <class T>
NameAdapt<T> mkNameAdapt(T &rVal, const std::string &name)
{ return NameAdapt<T>(rVal, name); }

Damit kann die archive-Funktion (theoretisch) geschrieben werden als:

[...]
ar->Value(mkNameAdapt(a, "a"));
ar->Value(mkNameAdapt(b, "b"));
[...]

.... wenn da nicht die Beschränkung von C++ wäre, dass temporäre Werte
(wie NameAdapt einer ist), nicht in eine nicht-konstante Referenz
konvertiert werden können.

Dies ist an dieser Stelle allerdings erforderlich, da der Adapter ja
wirklich ein Adapter sein und damit dieselben Eigenschaften wie das
"adaptierte" Objekt haben soll (mit dem Unterschied, dass der
Value-Aufruf in eine Name-"Umgebung" eingebettet ist).

Gleichzeitig macht diese Beschränkung meiner Meinung nach wenig Sinn. Denn:
* Die Lebenszeit ist entsprechend lang - das Problem hätte man bei
konstanten Referenzen ja auch. Seltsamerweise ist auch das Erzeugen von
nicht-konstanten _Pointern_ erlaubt (wenn auch mit Warnung bei GCC),
* Das temporäre Objekt ist nicht wirklich konstant, es können ja auch
nicht-konstante Member-Funktionen aufgerufen werden. Dies nutzt bereits
meine hiesige STL-Implementierung von auto_ptr aus, die ja ganz ähnliche
Probleme hat. Entsprechend kann man das Verhalten auch "korrigieren",
indem man die etwas seltsame Konvertierung definiert:

[...]
operator NameAdapt & () { return *this; }
[...]

.... wonach auch alles kompiliert und anstandslos funktioniert.

Also abschließend die Fragen:

Warum ist es nicht möglich, nicht-konstante Referenzen auf temporäre
Werte zu erzeugen?
Ist es möglich, diese Beschränkung irgendwie anders zu umgehen, ohne
zwischen Adaptern und Werten unterscheiden zu müssen?
Was ist von dieser Lösung zu halten? Gibt das in irgendeinem Kontext
Probleme?

(Btw: Ja, es ist mir klar, dass man einfach den Adapter als Variable
einführen könnte und dann keine Probleme mehr hätte - das ist allerdings
nicht Ziel der ganzen Sache, weil es spätestens wenn man größere Listen
von Werten und/oder Verschachtelungen von Adaptern hat nicht mehr
praktikabel ist)

Gruß
Peter Wortmann

--
de.comp.lang.iso-c++ - Moderation: mailto:voyager+mod (AT) bud (DOT) prima.de
FAQ: http://www.voyager.prima.de/cpp/ mailto:voyager+send-faq (AT) bud (DOT) prima.de
Back to top
Stefan Reuther
Guest





PostPosted: Mon Jun 13, 2005 6:20 pm    Post subject: Re: Serialisierung in Allgemein / nicht-konstante Referenz a Reply with quote



Hallo,

Peter Wortmann wrote:
[mit der gleichen Methode serialisieren und deserialisieren]
Quote:
Dabei erzeugt Archive::serialize die entsprechende Archivierungsklasse
nach angegebenem Template-Parameter und führt die Serialisierung durch.
Dafür wird intern bereits ein const_cast benötigt, der ist aber wohl
systembedingt nicht wegzubekommen. Letztlich wird intern
"ar->Value(const_cast<A &>(a))" ausgeführt, welches wegen unbekanntem
Typ "a.archive(ar)" entspricht.

Wozu du hier den const_cast brauchst, ist mir nicht klar. Solange du nur
non-const-Objekte durch die Gegend reichst (was bei der Anforderung, mit
einer Methode serialisieren und deserialisieren zu können nötig ist),
brauchst du das m.M.n. nicht.

Quote:
Jetzt ist die Name()/NameEnd()-Regelung nicht sehr befriedigend, da
leicht bei komplizierteren Konstrukten entsprechende Paarungen verloren
gehen können.

Ich würde ja ganz spontan der 'Value'-Funktion diesen Namen mit übergeben.

Quote:
[...]
ar->Value(mkNameAdapt(a, "a"));
ar->Value(mkNameAdapt(b, "b"));
[...]

Gegenvorschlag, mit Beibehaltung der Name/NameEnd:
class WithName {
Archive* a;
public:
WithName(string name, Archive* a)
: a(a)
{ a->Name(name); }
~WithName()
{ a->NameEnd(); }

template<typename T>
void Value(const T& t)
{ a->Value(t); }

// alternativ:
Archive* operator->()
{ return a; }
};

WithName("a",ar).Value(a);
Ist auch noch Exception-sicher, falls bei der Übergabe des Parameters an
'ar->Value' was schief geht.

Quote:
Also abschließend die Fragen:

Warum ist es nicht möglich, nicht-konstante Referenzen auf temporäre
Werte zu erzeugen?

Im Normalfall ist es einfach nicht sinnvoll. Ich nutze Referenzparameter
immer als "in/out" bzw. "out"-Parameter. Die aufgerufene Funktion wird
den übergebenen Wert potenziell verändern; bei einem Temporary wäre die
Arbeit umsonst.

IMHO hast du ein Designproblem oder mindestens eine Inkonsistenz:

- Die ursprüngliche 'Value'-Funktion hat die Aufgabe, den übergebenen
Wert anhand der bisherigen Einstellungen ('Name()') zu de-/
serialisieren. Dazu ist der Parameter ein "in/out"-Parameter.

- Die 'Value'-Funktion mit deinem Adapter hat die Aufgabe, anhand
einer übergebenen Beschreibung einen Wert zu serialisieren. Die
übergebene Beschreibung (der Adapter) wird dazu jedoch nicht
verändert, ist also ein reiner "in"-Parameter.

Auch ist die Aufrufsequenz eine andere: der ursprünglichen 'Value' geht
immer ein 'Name' voraus. Der zweiten nicht. Was, wenn 'Value' mit einem
'assert(vorigerBefehl()=="Name")' beginnt?

Meiner Meinung nach wäre die sauberste Lösung, den Namen als Parameter
in Value() zu geben. Alternativ, grundsätzlich Proxy-Objekte wie den
Adapter zu verwenden; dann kann 'Value' auch einen 'const Adaptar&' als
Parameter bekommen.


Stefan

--
de.comp.lang.iso-c++ - Moderation: mailto:voyager+mod (AT) bud (DOT) prima.de
FAQ: http://www.voyager.prima.de/cpp/ mailto:voyager+send-faq (AT) bud (DOT) prima.de

Back to top
PeterWortmann
Guest





PostPosted: Tue Jun 14, 2005 11:54 pm    Post subject: Re: Serialisierung in Allgemein / nicht-konstante Referenz a Reply with quote




Stefan Reuther schrieb:
Quote:
Wozu du hier den const_cast brauchst, ist mir nicht klar. Solange du nur
non-const-Objekte durch die Gegend reichst (was bei der Anforderung, mit
einer Methode serialisieren und deserialisieren zu können nötig ist),
brauchst du das m.M.n. nicht.
Erstmal danke für die Antwort. Ja, der const_cast würde nicht benötigt,

wenn ich die für die Serialisierung einfach voraussetzuen würde, dass
das Objekt nicht-konstant zugreifbar ist. Das ist allerdings nicht immer
der Fall - und eine entsprechende Änderung des Programms ist auch keine
schöne Alternative. Da die Archivtypen entsprechend Flags besitzen, ob
sie das Ziel ändern, ist es an dieser Stelle sicher.

Quote:
Ich würde ja ganz spontan der 'Value'-Funktion diesen Namen mit übergeben.
So ungefähr sah auch die erste Idee aus, aus der die ganze Sache

ursprünglich entstand.

Das Problem dabei ist, dass es z.B. auch möglich sein soll, im
Textformat "unbenannte" Abschnitte zu erzeugen - sozusagen
selbstdefinierte Textformate, die man sich z.B. mittels Zahlen und
Seperatoren zusammenbaut (Beispiel: Komplexe Zahlen würde man ja nicht
unbedingt als "<real>0</real><img>1</img>" speichern wollen, sondern
direkt als "0+1i").

Wollte man jetzt soetwas wie "<cpx>0+1i</cpx>" erzeugen, hätte man
sofort das Problem, welcher der Werte genau den Namen tragen soll - das
Archiv wäre nicht flexibel genug, das darzustellen. Daher definiert das
Interface nur Einzel"operationen", die sozusagen den kleinsten
gemeinsamen Nenner darstellen. Alle "high-level"-Konzepte werden dann in
Adaptern gekapselt.

Quote:
WithName("a",ar).Value(a);
Wäre auch eine Möglichkeit, wobei hier natürlich nicht mehr der Wert

adaptiert wird, sondern das Archiv - das wäre ein ganz andere
Herangehensweise. Es wird auch spätenstens bei mehreren verschachtelten
Adaptern schwerer zu lesen.

Quote:
Ist auch noch Exception-sicher, falls bei der Übergabe des Parameters an
'ar->Value' was schief geht.
Der real verwendete Adapter verwendet entsprechend finally-ähnliche

Konstrukte, um das zu erreichen.

Quote:
Im Normalfall ist es einfach nicht sinnvoll. Ich nutze Referenzparameter
immer als "in/out" bzw. "out"-Parameter. Die aufgerufene Funktion wird
den übergebenen Wert potenziell verändern; bei einem Temporary wäre die
Arbeit umsonst.
Nun, in diesem Fall ist es sinnvoll, weil ich einen (veränderbaren) Wert

adaptieren möchte (also ein Objekt erzeugen, dass sich in einem gewissen
Punkt genauso verhält wie das adaptierte Objekt). Dazu gehört auch die
vom Typsystem statisch übertragene Information, dass das Objekt
veränderbar ist. (Ich implementiere in Adaptern btw für Standardwert-
Behandlungen auch "operator =" und Konsorten - die werden dann von einem
äußeren "Standardwert"-Adapter benutzt)

Dass irgendetwas grundsätzlich "umsonst" ist, ist vor dem Hintergrund,
dass C++ sehr seiteneffektreich sein kann, sowieso eine gewagte Annahme.
Der Standard verbietet ja z.B. auch nicht Aufruf von
nicht-const-Methoden auf solche temporären Objekte, weil Methodenaufrufe
durchaus den Inhalt des Objektes verändern könnten, ohne dass es
"umsonst" wäre.

Auf jeden Fall erscheint es mir im Moment fast wie eine künstliche
Beschränkung, die keinen richtigen Grund außer "es erschien in trivialen
Fall nicht sinnvoll" hat - und damit ein paar meiner Meinung nach recht
sinnvolle Sachen wie diese Adapter oder auch den zum Standard gehörenden
auto_ptr behindert...

Quote:
Die übergebene Beschreibung (der Adapter) wird dazu jedoch nicht
verändert, ist also ein reiner "in"-Parameter.
Es fließen Informationen an dieser Stelle aus dem Archiv in den

entsprechenden Wert - mit Filterung im Adapter. Man kann also sagen,
dass es sich semantisch schon um einen gültigen in/out-Parameter handelt
- aber genau das will C++ ja scheinbar nicht anerkennen.

Quote:
Auch ist die Aufrufsequenz eine andere: der ursprünglichen 'Value' geht
immer ein 'Name' voraus. Der zweiten nicht. Was, wenn 'Value' mit einem
'assert(vorigerBefehl()=="Name")' beginnt?
Wie soll "vorigerBefehl" aussehen?

Im Ernst: der einzig sichtbare Effekt wäre ja der für das Archiv - und
der ist ja eingetreten - die Aufrufsequenz "Name", "Value", "NameEnd"
bleibt unangetastet. Ob man den Aufruf von Name() jetzt über einen
Konstruktor, einen Adapter oder manuell macht, ändert ja am Kontext, mit
dem der Wert kompiliert wird, nichts. Irgendeine Annahme darüber zu
machen, wie man aufgerufen wird, erscheint mir auch grundsätzlich sehr
gute Idee zu sein.

Quote:
Alternativ, grundsätzlich Proxy-Objekte wie den
Adapter zu verwenden; dann kann 'Value' auch einen 'const Adaptar&' als
Parameter bekommen.
Eigentlich war ja gerade die Idee bei den Adaptern, dass sie sich genau

wie Werte (bzw. Objekte) verhalten, die eben etwas mehr darüber wissen,
wie sie zu speichern sind. Wenn es nicht unbedingt notwendig ist möchte
ich davon auch nicht abrücken...

Gruß
Peter Wortmann

--
de.comp.lang.iso-c++ - Moderation: mailto:voyager+mod (AT) bud (DOT) prima.de
FAQ: http://www.voyager.prima.de/cpp/ mailto:voyager+send-faq (AT) bud (DOT) prima.de

Back to top
Marcel Müller
Guest





PostPosted: Wed Jun 15, 2005 9:24 pm    Post subject: Re: Serialisierung in Allgemein / nicht-konstante Referenz a Reply with quote

Peter Wortmann wrote:
Quote:
template <class T
class NameAdapt {
T &rVal; std::string name;
public:
NameAdapt(T &rVal, const std::string &name) : rVal(rVal), name(name)
{ }
void archive(Archive *ar) {
ar->Name(name); ar->Value(rVal); ar->NameEnd();
}
};

Dieser Ansatz scheint mir seltsam, denn es ist ja nicht der Adapter, der
beim deserialisieren verändert werden soll.

Kann nicht Value einfach den Namensparameter direkt akzeptieren?
Es ergibt doch vermutlich ohnehin keinen Sinn mehrere Values unter einem
Namen abzulegen, oder? Sonst wäre eine Serialisierung auf Key-Value
Datenstrukturen nicht mehr sinnvoll möglich.


Im Prinzip ist das eine Reflection - nicht gerade eine Stärke von C++.
Eine compilerinterne Implementierung würde pro Klasse eine Tabelle mit
den Spalten
Name,
relative Referenz auf das Datenobjekt und
Datentyp (ggf. mit Attributen)
erzeugen. Genau diese Information steckt letztlich in dem Code von
archive(). In C++ wird es aber spätestens bei der Spalte "Datentyp" eng.

Im Prinzip geht es aber. Die relative Referenz erfordert allerdings
bereits offsetof() und reinterpret_cast<>(), müßte aber trotzdem noch
portabel sein, da das Speicherlayout einer Klasse nicht von Instanz zu
Instanz wechselt. Schönere Lösungen würden immer irgendwie daran
kranken, daß zur Laufzeit immer so viele temporäre Objekte erzeugt
werden müssen, wie die Klasse Unterelemente hat.

Für die Datentyp-Spalte braucht man eine traits-Klasse, die von einer
abstrakten, nicht-template Basisklasse erbt. Im template-Baum steckt
dann letztlich der Spiegel der Struktur-Information.


Was mir bei alle dem gänzlich unklar ist, ist wie bei der berschribenen
Lösung bei der Deserialisierung die Fabrikmethoden zur Erzeugung der
Objekte aufgerufen werden sollen. Ohne das ist die Sache ziemlich
witzlos. Ich nehme an alle Objekte müssen einen Default-Konstruktor haben.


Marcel

--
de.comp.lang.iso-c++ - Moderation: mailto:voyager+mod (AT) bud (DOT) prima.de
FAQ: http://www.voyager.prima.de/cpp/ mailto:voyager+send-faq (AT) bud (DOT) prima.de

Back to top
Peter Wortmann
Guest





PostPosted: Fri Jun 17, 2005 7:10 pm    Post subject: Re: Serialisierung in Allgemein / nicht-konstante Referenz a Reply with quote


Marcel Müller wrote:
Quote:
Dieser Ansatz scheint mir seltsam, denn es ist ja nicht der Adapter, der
beim deserialisieren verändert werden soll.
Siehe vorige Antwort - der Adapter ist kein veränderbares Objekt, soll

sich allerdings verhalten als wäre er ein solches. Es geht letztlich
darum, dass C++ mir sozusagen verbietet, das "Interface" eines Objektes
so zu verändern, dass seine Verwendung exakt dem eines veränderbaren
Objektes entspricht.

Quote:
Es ergibt doch vermutlich ohnehin keinen Sinn mehrere Values unter einem
Namen abzulegen, oder?
Doch, siehe wiederrum vorige Antwort. Es macht Sinn, wenn man davon

ausgeht, dass Namen nicht die einzige existierende Möglichkeit zur
Datenstrukturierung sind (wie ich es der Einfachheit halber dargestellt
hatte).

Quote:
Eine compilerinterne Implementierung würde pro Klasse eine Tabelle mit
den Spalten
Name,
relative Referenz auf das Datenobjekt und
Datentyp (ggf. mit Attributen)
erzeugen. Genau diese Information steckt letztlich in dem Code von
archive(). In C++ wird es aber spätestens bei der Spalte "Datentyp" eng.
Sofern man eine vollständige Serialisierung des gesamten Klasseninhalts

will - und nicht mehrere Möglichkeiten hat, wie bestimmte Datentypen
gespeichert werden können. Ich ersetze hier ein Vorgänger-System, da
sind einige Konvertierungen notwendig (die man allerdings gut in
Adaptern kapseln kann).

Quote:
da das Speicherlayout einer Klasse nicht von Instanz zu Instanz wechselt.
Sofern es keine virtuellen Basisklassen gibt - GCC generiert deshalb

Warnungen, wenn man solche Dinge versucht (das Vorgängersystem
funktionierte ähnlich, wenn auch mit manuell erzeugten Tabellen und
festem Datentypsatz). Einer der Nebeneffekte der archive()-Lösung wäre,
dass sowas grundsätzlich kein Problem ist.

Quote:
Schönere Lösungen würden immer irgendwie daran
kranken, daß zur Laufzeit immer so viele temporäre Objekte erzeugt
werden müssen, wie die Klasse Unterelemente hat.
Ich weiß nicht genau, was diese "schönere Lösung" sein soll - aber in

den meisten Fällen sollten sich die Objekte doch inline realisieren
lassen, so dass kein echter Laufzeit-Overhead entsteht?

Quote:
Ich nehme an alle Objekte müssen einen Default-Konstruktor haben.
Stimmt. In der Art würde im Moment der Pointer-Adapter neue Objekte

deserialisieren (kann man leider scheinbar nicht als
template-Spezialisierung definieren, da macht der MSVC-Compiler
Probleme). Das ist ein Ärgernis, aber in meinem Anwendungsfall habe ich
es vor allem mit großen statisch, ineinander geschachtelten Klassen (Typ
"Konfiguration") zu tun.

Eine andere Methode wäre, allen entsprechend deserialisierbaren
Strukturen einen Konstruktor zu geben, der ein Archiv-Objekt annimmt und
ohne (bzw. nur mit nötigsten) Initialisierungen sofort die entsprechende
archive()-Funktion aufruft.

Gruß
Peter Wortmann

--
de.comp.lang.iso-c++ - Moderation: mailto:voyager+mod (AT) bud (DOT) prima.de
FAQ: http://www.voyager.prima.de/cpp/ mailto:voyager+send-faq (AT) bud (DOT) prima.de

Back to top
Stefan Reuther
Guest





PostPosted: Sat Jun 18, 2005 10:21 am    Post subject: Re: Serialisierung in Allgemein / nicht-konstante Referenz a Reply with quote

PeterWortmann wrote:
Quote:
Stefan Reuther schrieb:
Wozu du hier den const_cast brauchst, ist mir nicht klar. Solange du nur
non-const-Objekte durch die Gegend reichst (was bei der Anforderung, mit
einer Methode serialisieren und deserialisieren zu können nötig ist),
brauchst du das m.M.n. nicht.

Erstmal danke für die Antwort. Ja, der const_cast würde nicht benötigt,
wenn ich die für die Serialisierung einfach voraussetzuen würde, dass
das Objekt nicht-konstant zugreifbar ist. Das ist allerdings nicht immer
der Fall - und eine entsprechende Änderung des Programms ist auch keine
schöne Alternative.

Ich würde dann den const_cast an die Stelle setzen, wo das Objekt in die
Serialisierung gegeben wird, anstatt direkt ins Serialisierungssystem.
Aber das ist zu 99% Geschmackssache.

Quote:
Wollte man jetzt soetwas wie "<cpx>0+1i</cpx>" erzeugen, hätte man
sofort das Problem, welcher der Werte genau den Namen tragen soll - das
Archiv wäre nicht flexibel genug, das darzustellen. Daher definiert das
Interface nur Einzel"operationen", die sozusagen den kleinsten
gemeinsamen Nenner darstellen. Alle "high-level"-Konzepte werden dann in
Adaptern gekapselt.

Okay. Wie wäre es dann damit, *alles* über Adapter zu lösen? Nennen wir
die Adapter mal 'Serializer'.
class Archive {
public:
void Value(const Serializer& s)
{ s->archive(this); /* oder so */ }
};
Dann hättest du eben
template<typename T>
class NamedValue : public Serializer {
std::string name;
T& value;
public:
NamedValue(std::string s, T& value)
: name(s), value(value) { }
void archive(Archive* a)
{ a->Name(name); a->Value(value); a->NameEnd(); }
};
template<typename T>
class UnnamedValue : public Serializer {
T& value;
public:
UnnamedValue(T& value)
: value(value) { }
void archive(Archive* a)
{ a->Value(value); }
und damit z.B.
a.Value(NamedValue("foo", foo));
a.Value(UnnamedValue(bar));

Quote:
Die übergebene Beschreibung (der Adapter) wird dazu jedoch nicht
verändert, ist also ein reiner "in"-Parameter.

Es fließen Informationen an dieser Stelle aus dem Archiv in den
entsprechenden Wert - mit Filterung im Adapter. Man kann also sagen,
dass es sich semantisch schon um einen gültigen in/out-Parameter handelt
- aber genau das will C++ ja scheinbar nicht anerkennen.

Das Problem ist m.M.n. einfach, dass du mal den (veränderlichen) Wert,
und mal die (unveränderliche) Beschreibung übergeben möchtest.

Quote:
Auch ist die Aufrufsequenz eine andere: der ursprünglichen 'Value' geht
immer ein 'Name' voraus. Der zweiten nicht. Was, wenn 'Value' mit einem
'assert(vorigerBefehl()=="Name")' beginnt?

Wie soll "vorigerBefehl" aussehen?

Ich dachte an etwas wie
class Archive {
std::string name;
bool have_name;
public:
void Name(const std::string& s) { name = s; have_name = true; }
void NameEnd() { have_name = false; }

template<typename T>
void Value(T& t) {
assert(have_name);
t.archive(this);
}
};
Ich kenne deine Archiv-Klasse nicht, aber könnte mir durchaus
vorstellen, dass so eine Anforderung (vor dem Serialisieren eines Wertes
muss festgelegt werden, in welches Tag der gehört) sinnvoll sein könnte.

Quote:
Im Ernst: der einzig sichtbare Effekt wäre ja der für das Archiv - und
der ist ja eingetreten - die Aufrufsequenz "Name", "Value", "NameEnd"
bleibt unangetastet. Ob man den Aufruf von Name() jetzt über einen
Konstruktor, einen Adapter oder manuell macht, ändert ja am Kontext, mit
dem der Wert kompiliert wird, nichts. Irgendeine Annahme darüber zu
machen, wie man aufgerufen wird, erscheint mir auch grundsätzlich sehr
gute Idee zu sein.

(fehlt da ein "keine"?)

Natürlich hat eine vorgeschriebene Aufrufreihenfolge immer das Problem,
dass du statisch nicht feststellen kannst, ob sie auch eingehalten wird.

Das Problem hast du aber auch so (oder erst recht): was ist, wenn auch
der von Value() aufgerufene Adapter Name() nicht aufruft, bevor er das
erste atomare Objekt serialisiert?


Stefan

--
de.comp.lang.iso-c++ - Moderation: mailto:voyager+mod (AT) bud (DOT) prima.de
FAQ: http://www.voyager.prima.de/cpp/ mailto:voyager+send-faq (AT) bud (DOT) prima.de

Back to top
PeterWortmann
Guest





PostPosted: Sat Jun 18, 2005 1:08 pm    Post subject: Re: Serialisierung in Allgemein / nicht-konstante Referenz a Reply with quote

Stefan Reuther schrieb:
Quote:
Ich würde dann den const_cast an die Stelle setzen, wo das Objekt in die
Serialisierung gegeben wird, anstatt direkt ins Serialisierungssystem.
Aber das ist zu 99% Geschmackssache.
Für mich ist das Hauptargument, dass der const_cast an dieser Stelle

garantiert sicher ist (sofern das Archiv nicht über seine Eigenschaften
lügt). Damit umgehe ich zwar intern das Typsystem, aber wenigstens kann
man dann bei der Benutzung keine Fehler mehr machen.

Quote:
Okay. Wie wäre es dann damit, *alles* über Adapter zu lösen?

[...]
NamedValue(std::string s, T& value)
: name(s), value(value) { }
[...]

a.Value(NamedValue("foo", foo));
a.Value(UnnamedValue(bar));
In dem Fall hat man das Problem, dass man für "foo" wieder keinen

Adapter einsetzen könnte, da NamedValue wiederrum ein veränderbares
Objekt erwartet. Zur Motivation: Für bestimmte Typen wie Enumerations
oder Arrays gibt es keine gute Möglichkeit, sie "generell" zu
serialisieren - da nutze ich Adapter, um bestimmte
De/Serialisierungstaktiken "nachzurüsten".

Es funktioniert allerdings, wenn man den "UnnamedValue" als
"nicht-const-Wrapper" ansieht, z.B. in "Val" umbenennt und alle anderen
Adapter konstante Objekte annehmen lässt. Das ergäbe dann Aufrufe im
Stil von:

a.Value(Named("foo", Val(foo)));
a.Value(Val(foo)));

Das wäre auch mein "Notfall"-Plan gewesen, wenn ich den Compiler nicht
mit bewusstem Trick dazu hätte überreden können, temporäre Objekte als
veränderbar anzusehen.

Quote:
Das Problem ist m.M.n. einfach, dass du mal den (veränderlichen) Wert,
und mal die (unveränderliche) Beschreibung übergeben möchtest.
Ein Wert besteht immer aus ein paar "unveränderlichen" Eigenschaften -

denen möchte ich ein paar hinzufügen, sozusagen :)

Quote:
Ich kenne deine Archiv-Klasse nicht, aber könnte mir durchaus
vorstellen, dass so eine Anforderung (vor dem Serialisieren eines Wertes
muss festgelegt werden, in welches Tag der gehört) sinnvoll sein könnte.
Sie ist sinnvoll, wenn man einen Archivtyp schreibt, der auf bestimmte

Namensumgebungen angewiesen ist (theoretisch kann auch das Text-Archiv
nur einfach Werte hintereinanderschreiben). Dies wird von entsprechenden
Archivtypen auch geprüft, jeweils bevor ein konkreter Wert geschrieben
wird. In dem Fall wird dann ein Laufzeitfehler erzeugt.

Aber ich verstehe nicht ganz, worauf du da hinaus willst - soll diese
Eigenschaft strukturell sichergestellt werden? Man könnte z.B. die alte
Value-Methode umbenennen und privat machen, den Name-Adapter zum friend
und die Value-Methode nur noch Name-Adapter akzeptieren lassen. Dann
wäre der Aufruf offensichtlich nur noch möglich, wenn man einen
entsprechenden Namensbereich angelegt hat. Dann würde man sich
allerdings wieder die Möglichkeit verbauen, dass sich eine Klasse auch
mal nicht in einen Namensbereich serialisiert und von der umgebenden
Klasse in einen solchen eingebettet wird (wie eine solche cpx-Klasse).

Es könnte außerdem auch durchaus Kontext geben, wo Benennung optional
werden (Wenn man sich z.B. sicher ist, dass man nur binär serialisieren
möchte), daher halte ich es auch für sinnvoll, das vom verwendeten
Archiv abhängig und damit zum Laufzeitcheck zu machen. Ein Testdurchlauf
der Routine würde ja reichen, weil solche Strukturfehler ja unabhängig
von den Daten gefunden werden können.

Quote:
(fehlt da ein "keine"?)
Ähm, ja - war etwas spät Wink


Gruß
Peter Wortmann

--
de.comp.lang.iso-c++ - Moderation: mailto:voyager+mod (AT) bud (DOT) prima.de
FAQ: http://www.voyager.prima.de/cpp/ mailto:voyager+send-faq (AT) bud (DOT) prima.de

Back to top
Marcel Müller
Guest





PostPosted: Wed Jun 22, 2005 8:41 pm    Post subject: Re: Serialisierung in Allgemein / nicht-konstante Referenz a Reply with quote

Hallo!

Peter Wortmann wrote:
Quote:
Es geht letztlich
darum, dass C++ mir sozusagen verbietet, das "Interface" eines Objektes
so zu verändern, dass seine Verwendung exakt dem eines veränderbaren
Objektes entspricht.

Das ist korrekt. Jedenfalls dann, wenn es selber const (bzw. temporär)
ist und das Objekt hinter dem Proxy nicht.


Quote:
Es ergibt doch vermutlich ohnehin keinen Sinn mehrere Values unter einem
Namen abzulegen, oder?

Doch, siehe wiederrum vorige Antwort. Es macht Sinn, wenn man davon
ausgeht, dass Namen nicht die einzige existierende Möglichkeit zur
Datenstrukturierung sind (wie ich es der Einfachheit halber dargestellt
hatte).

Aber selbst dann sind die in diesem Kontext zu serialisierenden Objekte
oder Adapter selbst für die Generierung und Interpretation der Syntax
zuständig. Denn woher sollte die Serialisierungsroutine für double
Wissen, daß sie beim Imaginäranteil ein führendes + erzeugen muß, damit
der gesamte Datentrom interpretierbar bleibt.
Aus Sicht des Serialisierers sind die zusammengesetzten Objekte also
doch wieder atomar. Es wird eben nur ein Bytestream eingefügt, der das
komplexe Objekt beschreibt.
Letztlich erfordert eine solche semantische Kodierung im allgemeinen
immer den Kontext des Objektes /und/ der Archivierungsklasse, denn bei
einer binären Archivierung beispielsweise würde immer dasselbe
herauskommen. Wen interessiert da schon der Name, da geht es eher um die
Länge. - Und schon sind wir bei einem Multiple-Dispatch-Problem:
Datentyp + Archivierungstyp => Methodik.


Quote:
Eine compilerinterne Implementierung würde pro Klasse eine Tabelle mit
den Spalten
[...]
da das Speicherlayout einer Klasse nicht von Instanz zu Instanz wechselt.

Sofern es keine virtuellen Basisklassen gibt - GCC generiert deshalb

Solange man die Basisklassen immer schön brav isoliert serialisiert,
stellt sich die Frage so nicht.

Quote:
Warnungen, wenn man solche Dinge versucht (das Vorgängersystem
funktionierte ähnlich, wenn auch mit manuell erzeugten Tabellen und
festem Datentypsatz). Einer der Nebeneffekte der archive()-Lösung wäre,
dass sowas grundsätzlich kein Problem ist.

Das sehe ich anders, da beim Serialisieren der Basisklassen die
virtuellen Basisklassen eine Ebene darunter zweimal serialisiert würden,
denn woher soll eine Basisklasse wissen, ob noch andere Basisklassen
desselben, übergeordneten Objekts eine virtuelle Basisklasse referenzieren.
Die Anforderung, virtuelle Basisklassen zu serialisieren erfordert aus
sematischer Sicht immer das Wissen um die Redundanz. Und das ist nur auf
der obersten Objektebene vollständig. Folglich muß die erforderliche
Logik dort oder danach (Dublettenprüfung) implementiert werden. Die
Aufgabenstellung gleicht der der Serialisierung von Pointern bzw.
Referenzen.


Quote:
Schönere Lösungen würden immer irgendwie daran
kranken, daß zur Laufzeit immer so viele temporäre Objekte erzeugt
werden müssen, wie die Klasse Unterelemente hat.

Ich weiß nicht genau, was diese "schönere Lösung" sein soll - aber in
den meisten Fällen sollten sich die Objekte doch inline realisieren
lassen, so dass kein echter Laufzeit-Overhead entsteht?

Naja, ob die Adapter wirklich ohne nennenswertes Overhead auskommen,
lasse ich mal dahingestellt. So ein Objekt wird in jedem Fall
konstruiert. Inline wird die Sache nur, solange man mit partiell
spezialisierten Funktoren arbeitet. Und auch da macht nicht jeder
Compiler eine soo dolle Figur.


Wie gesagt, ich bin immer noch nicht davon überzeugt, daß es wirlich
eine Einschränkung darstellt, das Interface des Adapters, nämlich Name
und Wert, als Interface der Archivierungsklasse darzustellen. Anstelle
der Name/NameEnd Schachtellung treten halt Klammern, und die Zählt der
Compiler, was ja gerade Sinn der Sache ist.


Marcel

--
de.comp.lang.iso-c++ - Moderation: mailto:voyager+mod (AT) bud (DOT) prima.de
FAQ: http://www.voyager.prima.de/cpp/ mailto:voyager+send-faq (AT) bud (DOT) prima.de

Back to top
Stefan Reuther
Guest





PostPosted: Sat Jun 25, 2005 4:13 pm    Post subject: Re: Serialisierung in Allgemein / nicht-konstante Referenz a Reply with quote

Hallo,

PeterWortmann wrote:
Quote:
Stefan Reuther schrieb:
Ich würde dann den const_cast an die Stelle setzen, wo das Objekt in die
Serialisierung gegeben wird, anstatt direkt ins Serialisierungssystem.
Aber das ist zu 99% Geschmackssache.

Für mich ist das Hauptargument, dass der const_cast an dieser Stelle
garantiert sicher ist (sofern das Archiv nicht über seine Eigenschaften
lügt). Damit umgehe ich zwar intern das Typsystem, aber wenigstens kann
man dann bei der Benutzung keine Fehler mehr machen.

Das meine ich mit "Geschmackssache". Deine Interpretation ist typsicher,
solange das Archiv nicht lügt. Meine Interpretation würde die non-const-
ness soweit wie möglich nach außen tragen, in der Hoffnung, dass die
meisten Objekte, die ich serialisiere, sowieso non-const sein werden,
und ich würde dann an der Stelle const_casten, wo ich dummerweise nur
ein const-Objekt habe. Damit hätte ich die "Unsicherheit" nur an dieser
Stelle. Aber wie gesagt, ich glaube nicht, dass meine Interpretation
besser oder schlechter ist als deine.

Quote:
Okay. Wie wäre es dann damit, *alles* über Adapter zu lösen?
[...]
NamedValue(std::string s, T& value)
: name(s), value(value) { }
[...]
a.Value(NamedValue("foo", foo));
a.Value(UnnamedValue(bar));

In dem Fall hat man das Problem, dass man für "foo" wieder keinen
Adapter einsetzen könnte, da NamedValue wiederrum ein veränderbares
Objekt erwartet.
[...]
Es funktioniert allerdings, wenn man den "UnnamedValue" als
"nicht-const-Wrapper" ansieht, z.B. in "Val" umbenennt und alle anderen
Adapter konstante Objekte annehmen lässt. Das ergäbe dann Aufrufe im
Stil von:

a.Value(Named("foo", Val(foo)));
a.Value(Val(foo)));

Wahrscheinlich wäre das die sauberste und ineffizienteste Lösung.

Quote:
Das wäre auch mein "Notfall"-Plan gewesen, wenn ich den Compiler nicht
mit bewusstem Trick dazu hätte überreden können, temporäre Objekte als
veränderbar anzusehen.

Naja, neben dem 'operator NameAdapt&' wie in deinem OP wäre auch noch
möglich
class NameAdapt {
// ...
public:
NameAdapt& self() { return *this; }
};
was ich gelegentlich verwende.

Quote:
Ich kenne deine Archiv-Klasse nicht, aber könnte mir durchaus
vorstellen, dass so eine Anforderung (vor dem Serialisieren eines Wertes
muss festgelegt werden, in welches Tag der gehört) sinnvoll sein könnte.

Sie ist sinnvoll, wenn man einen Archivtyp schreibt, der auf bestimmte
Namensumgebungen angewiesen ist (theoretisch kann auch das Text-Archiv
nur einfach Werte hintereinanderschreiben). Dies wird von entsprechenden
Archivtypen auch geprüft, jeweils bevor ein konkreter Wert geschrieben
wird. In dem Fall wird dann ein Laufzeitfehler erzeugt.

Aber ich verstehe nicht ganz, worauf du da hinaus willst - soll diese
Eigenschaft strukturell sichergestellt werden?

Ja.

Objekte, die Status mit sich rumschleppen, sind m.M.n. meistens
problematisch. Bei dir wäre das das Problem, dass eben beispielsweise
jemand ein 'Name()' macht und das 'NameEnd()' vergisst, oder dass er
einen Wert versucht zu serialisieren, ohne vorher einen Namen anzugeben.
*Falls* meine Archiv-Klasse damit nicht umgehen könnte, würde ich
versuchen, das strukturell sicherzustellen. Wenn namenlose Objekte
unmöglich sind, würde ich also 'Value' einen Namens-Parameter übergeben.
Wenn vergessene NameEnd() problematisch sind, würde ich soetwas wie
WithName aus meinem ersten Posting im Thread verwenden.

Aber ich will mir hier nicht anmaßen, absolute Empfehlungen zu geben,
weil ich ja nicht weiß, was deine Archivklassen so können und können müssen.


Stefan

PS, ich hab neulich zum ersten Mal in meinem Leben in ein ernsthaftes
MFC-Programm reingeschaut, die scheinen da etwas ähnliches zu tun wie du
(DoDataExchange oder so).

--
de.comp.lang.iso-c++ - Moderation: mailto:voyager+mod (AT) bud (DOT) prima.de
FAQ: http://www.voyager.prima.de/cpp/ mailto:voyager+send-faq (AT) bud (DOT) prima.de

Back to top
Display posts from previous:   
Post new topic   Reply to topic    C++Talk.NET Forum Index -> C++ (German) All times are GMT
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum


Powered by phpBB © 2001, 2006 phpBB Group
SEO toolkit © 2004-2006 webmedic.