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 

Defekt in der Sprache? || Serializable-Interface realisieren

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





PostPosted: Tue Dec 09, 2008 9:50 am    Post subject: Defekt in der Sprache? || Serializable-Interface realisieren Reply with quote



Hallo LiNG

Vor langer, langer Zeit ist mir folgendes Problem beim Ableiten von
Klassen unter gekommen:

struct Inaccessible { int i; };
struct Accomplice : Inaccessible { int a; };
struct Culprit : Accomplice, Inaccessible { int c; };

int main(int argc, char * argv[]) {
Inaccessible i;
Accomplice a;
Culprit c;

i.i; // ok
a.a; // ok
a.i; // ok
c.c; // ok
c.a; // ok
c.i; // ambiguous <--- ???
}
/*
test.cpp:3: warning: direct base 'Inaccessible' inaccessible in 'Culprit' due to ambiguity
test.cpp: In function 'int main(int, char**)':
test.cpp:15: error: request for member 'i' is ambiguous
test.cpp:1: error: candidates are: int Inaccessible::i
test.cpp:1: error: int Inaccessible::i
*/

Das Problem hierbei ist einfach, daß der Kompiler sich einbildet, nicht zu
wissen, ob C::I::i oder C::A::I::i gemeint ist. Das hatte ich damals, als
ich Nachforschungen angestellt hatte, notgedrungenerweise geschluckt.

Interessanterweise kann man das ganze auch nicht umgehen indem man das c
nach i castet, kurz, an die direkte i-Basis kommt man GARNICHT mehr dran.

Umgehen kann man das ganze Problem natürlich, indem man eine Hilfsklasse
um I packt und dann c von h statt von i direkt ableitet. Dann kann man c
nach h casten und von dort aus auf das i zugreifen - das ganze nennt man
dann Workaround.

***

Jetzt bin ich aber auf ein anderes Problem gestoßen, daß mich an der
Intelligenz der Macher des C++-Standards zweifeln lassen:

struct Base {
int foo(int a);
};

struct Derived {
int foo(char * a);
};

int main(int argc,char * argv[]) {
Base b;
Derived d;
int i;
char * cp;

b.foo(cp); // fehler -> zurecht
b.foo(i); // ok
d.foo(cp); // pl
d.foo(i); // fehler <--- ???
}

Dieses Verhalten wurde damit begründet, daß der Compiler im aktuellen
Scope von d eine Funktion foo hat und daher die Suche in diesem Scope
aufhört[1]. Nun gibt es dort aber nur die char * - Version, daher kann
foo(int) nicht aufgelöst werden. Jetzt kann man sich natürlich streiten,
ob die Parameterliste zum Funktionsnamen dazugehören oder nicht, nervig
ist das ganze aber auf jeden Fall.

[1] ganz dumm ist diese Regel auch nicht, nimmt man dieses Beispiel an:
int foo(int a);
class C { int foo(long); int bar() { return foo(1); } };
Soll nun ::foo(int) oder C::foo(long) aufgerufen werden? Allerdings
involviert dieses Beispiel unterschiedliche Scope-Klassen (wenn man
es so bezeichnen will), die Elemente der Basisklassen sollten sich
nach meinem Verständnis so verhalten, als wären sie in der aktuellen
Klasse selbst enthalten.

***

So, nun zum Vorwurf des Defekts: Wenn der aktuelle Skope (Culprit aus dem
ersten Beispiel) die direkte Basisklasse Inaccessible kennt, warum hört
dann dort die Suche nicht auf, wenn ich aber auf Elemente zugreifen will
schon? Denn die Tatsache, daß Inaccessible zweimal als Basis vorkommt,
kann der Compiler ja erst erkennen, wenn er eben NICHT nur die
direkten Basisklassen anschaut, sondern weiter runter wandert.

***

Gut, warum kommt das ganze jetzt wieder hoch?

Ich bin gerade dabei, ein Serialize-Interface für einige Klassen
vorzuschreiben (kurz: es geht um ein Echtzeitstrategiespiel, die Klassen,
die Serialize implementieren müssen sind die, die in den Spielstand
gedumpt werden müssen bzw. die Order-Klassen, die über's Netzwerk an die
anderen Spieler transportiert werden müssen - aber das nur, um die
Motivation klarzustellen).

Die Idee war es jetzt, einfach alle Klassen von der abstrakten Klasse
Serialize (besser wäre wohl der Name Serializable, den man ja schon von
Kaffee kennt) abzuleiten. Leider holt mich da das erste in diesem
Post genannte Problem wieder ein: Sobald eine Klasse Serialize
implementiert, können die davon abgeleiteten Klassen nicht mehr direkt von
dieser Klasse abgeleitet werden. Das hat vor allem aber den Nachteil, daß
die serialize-Funktionen mitvererbt werden.

Die Frage, die sich mir jetzt also stellt ist: Wie macht man das am
besten? Oder ist der einzige Weg, das in ISO-C++ zu lösen, indem man
schlicht aufpasst, daß man die virtuellen Funktionen der Serialize-Klasse
konsequent überall reimplementiert und dann nur EINMAL von Serialize
abzuleiten?

Gruß, Bodo
Back to top
Michael Karcher
Guest





PostPosted: Tue Dec 09, 2008 11:35 pm    Post subject: Re: Defekt in der Sprache? || Serializable-Interface realisi Reply with quote



Bodo Thiesen <bothie (AT) gmx (DOT) de> wrote:
Quote:
Jetzt bin ich aber auf ein anderes Problem gestoßen, daß mich an der
Intelligenz der Macher des C++-Standards zweifeln lassen:

struct Base {
int foo(int a);
};

struct Derived {
int foo(char * a);
};

int main(int argc,char * argv[]) {
Base b;
Derived d;
int i;
char * cp;

b.foo(cp); // fehler -> zurecht
d.foo(i); // fehler <--- ???
}

Dieses Verhalten wurde damit begründet, daß der Compiler im aktuellen
Scope von d eine Funktion foo hat und daher die Suche in diesem Scope
aufhört[1]. Nun gibt es dort aber nur die char * - Version, daher kann
foo(int) nicht aufgelöst werden.
Du kannst aber in Derived auch "using Base::foo" schreiben, dann gelangt

auch Base::foo in den Scope von Derived.

Quote:
So, nun zum Vorwurf des Defekts: Wenn der aktuelle Skope (Culprit aus dem
ersten Beispiel) die direkte Basisklasse Inaccessible kennt, warum hört
dann dort die Suche nicht auf, wenn ich aber auf Elemente zugreifen will
schon? Denn die Tatsache, daß Inaccessible zweimal als Basis vorkommt,
kann der Compiler ja erst erkennen, wenn er eben NICHT nur die
direkten Basisklassen anschaut, sondern weiter runter wandert.
Das Problem ist, dass Du Äpfel mit Birnen vergleichst. Das Beispiel Culprit

aus dem ersten Beispiel holt zwei Symbole "i" nebeneinander (Ableitung von
Inaccessible, Ableitung von Accomplice) in den Scope. Das ist ambigous,
da es für das Lookup keine Bevorzugung von früher genannten Basisklassen
gibt. Im Beispiel Base/Derived kommen die Symbole nicht nebeneinander
sondern untereinander ins Spiel. Das gilt als Verdecken.

Quote:
Die Idee war es jetzt, einfach alle Klassen von der abstrakten Klasse
Serialize (besser wäre wohl der Name Serializable, den man ja schon von
Kaffee kennt) abzuleiten.
Die Frage, die sich mir jetzt also stellt ist: Wie macht man das am
besten? Oder ist der einzige Weg, das in ISO-C++ zu lösen, indem man
schlicht aufpasst, daß man die virtuellen Funktionen der Serialize-Klasse
konsequent überall reimplementiert und dann nur EINMAL von Serialize
abzuleiten?
Die Lösung ist, Serializable zu einer *virtuellen* Basisklasse zu machen.

Generell in allen Deinen Objekten. Dann sorgt der Compiler dafür, dass sie
immer nur einmal vorhanden ist.

Gruß,
Michael Karcher
Back to top
Stefan Reuther
Guest





PostPosted: Tue Dec 09, 2008 11:39 pm    Post subject: Re: Defekt in der Sprache? || Serializable-Interface realisi Reply with quote



TaX,

Bodo Thiesen wrote:
[Beispiel 1:]
Quote:
struct Inaccessible { int i; };
struct Accomplice : Inaccessible { int a; };
struct Culprit : Accomplice, Inaccessible { int c; };
[Beispiel 2:]
struct Base { int foo(int a); };
struct Derived { int foo(char * a); };

Ich nehme an, das soll 'Base : Derived' heißen...

Quote:
So, nun zum Vorwurf des Defekts: Wenn der aktuelle Skope (Culprit aus dem
ersten Beispiel) die direkte Basisklasse Inaccessible kennt, warum hört
dann dort die Suche nicht auf, wenn ich aber auf Elemente zugreifen will
schon? Denn die Tatsache, daß Inaccessible zweimal als Basis vorkommt,
kann der Compiler ja erst erkennen, wenn er eben NICHT nur die
direkten Basisklassen anschaut, sondern weiter runter wandert.

Der Compiler schaut in *alle* direkten Basisklassen. Im ersten Beispiel
findet er innerhalb von 'Accomplice' ein 'i' (nämlich das von
'Accomplice's Basis) und in der zweiten Basisklasse 'Inaccessible' ein
weiteres; beide sind nicht identisch.

Quote:
Die Idee war es jetzt, einfach alle Klassen von der abstrakten Klasse
Serialize (besser wäre wohl der Name Serializable, den man ja schon von
Kaffee kennt) abzuleiten. Leider holt mich da das erste in diesem
Post genannte Problem wieder ein: Sobald eine Klasse Serialize
implementiert, können die davon abgeleiteten Klassen nicht mehr direkt von
dieser Klasse abgeleitet werden. Das hat vor allem aber den Nachteil, daß
die serialize-Funktionen mitvererbt werden.

Virtuelle Vererbung?


Stefan
Back to top
Florian Weimer
Guest





PostPosted: Wed Dec 10, 2008 1:39 am    Post subject: Re: Defekt in der Sprache? || Serializable-Interface realisi Reply with quote

* Bodo Thiesen:

Quote:
Vor langer, langer Zeit ist mir folgendes Problem beim Ableiten von
Klassen unter gekommen:

struct Inaccessible { int i; };
struct Accomplice : Inaccessible { int a; };
struct Culprit : Accomplice, Inaccessible { int c; };

Das gibt zwei unabhängige Kopien von Inaccessible, und dort:

Quote:
c.i; // ambiguous <--- ???

ist nicht klar, welche gemeint ist. Um das zu umgehen, kannst Du mit
"virtual" ableiten.

Quote:
Dieses Verhalten wurde damit begründet, daß der Compiler im aktuellen
Scope von d eine Funktion foo hat und daher die Suche in diesem Scope
aufhört[1]. Nun gibt es dort aber nur die char * - Version, daher kann
foo(int) nicht aufgelöst werden. Jetzt kann man sich natürlich streiten,
ob die Parameterliste zum Funktionsnamen dazugehören oder nicht, nervig
ist das ganze aber auf jeden Fall.

Dafür gibt es using-Deklarationen.

Quote:
Die Frage, die sich mir jetzt also stellt ist: Wie macht man das am
besten? Oder ist der einzige Weg, das in ISO-C++ zu lösen, indem man
schlicht aufpasst, daß man die virtuellen Funktionen der Serialize-Klasse
konsequent überall reimplementiert und dann nur EINMAL von Serialize
abzuleiten?

Du mußt die Methoden sowieso ganz unten überschreiben, weil sie sonst
nicht erwartungsgemäß funktionieren. Deswegen verstehe ich das Problem
nicht.
Back to top
Thomas J. Gritzan
Guest





PostPosted: Wed Dec 10, 2008 1:52 am    Post subject: Re: Defekt in der Sprache? || Serializable-Interface realisi Reply with quote

Bodo Thiesen schrieb:
Quote:
Vor langer, langer Zeit ist mir folgendes Problem beim Ableiten von
Klassen unter gekommen:

struct Inaccessible { int i; };
struct Accomplice : Inaccessible { int a; };
struct Culprit : Accomplice, Inaccessible { int c; };
[...]
Interessanterweise kann man das ganze auch nicht umgehen indem man das c
nach i castet, kurz, an die direkte i-Basis kommt man GARNICHT mehr dran.

Umgehen kann man das ganze Problem natürlich, indem man eine Hilfsklasse
um I packt und dann c von h statt von i direkt ableitet. Dann kann man c
nach h casten und von dort aus auf das i zugreifen - das ganze nennt man
dann Workaround.

Brauchst du denn zwei Versionen von "Inaccessible"?
Um Java-Interfaces nachzuahmen benutzt man für gewöhnlich virtuelle
Vererbung, um dieses Problem zu umgehen.

Mehrfachvererbung ist ein schwieriges Thema, und viele andere
Programmiersprachen lösen es einfach dadurch, dass sie nur einfache
Vererbung (und Interfaces) zulassen.

Quote:
Jetzt bin ich aber auf ein anderes Problem gestoßen, daß mich an der
Intelligenz der Macher des C++-Standards zweifeln lassen:

struct Base {
int foo(int a);
};

struct Derived {

struct Derived : Base {
using Base::foo;

Damit holst du alle Base::foo in den Scope von Derived.
Ansonsten verdeckt Derived::foo alle Base::foo.

Quote:
int foo(char * a);
};

int main(int argc,char * argv[]) {
Base b;
Derived d;
int i;
char * cp;

b.foo(cp); // fehler -> zurecht
b.foo(i); // ok
d.foo(cp); // pl
d.foo(i); // fehler <--- ???
}
[...]
So, nun zum Vorwurf des Defekts: Wenn der aktuelle Skope (Culprit aus dem
ersten Beispiel) die direkte Basisklasse Inaccessible kennt, warum hört
dann dort die Suche nicht auf, wenn ich aber auf Elemente zugreifen will
schon? Denn die Tatsache, daß Inaccessible zweimal als Basis vorkommt,
kann der Compiler ja erst erkennen, wenn er eben NICHT nur die
direkten Basisklassen anschaut, sondern weiter runter wandert.

Da Accomplice kein eigenes i hat, muss der Compiler in der Basisklasse
nachgucken, da Accomplice alles von Inaccessible erbt.

Quote:
***

Gut, warum kommt das ganze jetzt wieder hoch?

Ich bin gerade dabei, ein Serialize-Interface für einige Klassen
vorzuschreiben[...]
Die Frage, die sich mir jetzt also stellt ist: Wie macht man das am
besten?

Wie schon gesagt: virtuelle Vererbung.

--
Thomas
Back to top
Thomas Richter
Guest





PostPosted: Wed Dec 10, 2008 3:24 am    Post subject: Re: Defekt in der Sprache? || Serializable-Interface realisi Reply with quote

Bodo Thiesen wrote:
Quote:
Hallo LiNG

Vor langer, langer Zeit ist mir folgendes Problem beim Ableiten von
Klassen unter gekommen:

struct Inaccessible { int i; };
struct Accomplice : Inaccessible { int a; };
struct Culprit : Accomplice, Inaccessible { int c; };

int main(int argc, char * argv[]) {
Inaccessible i;
Accomplice a;
Culprit c;

i.i; // ok
a.a; // ok
a.i; // ok
c.c; // ok
c.a; // ok
c.i; // ambiguous <--- ???
}
/*
test.cpp:3: warning: direct base 'Inaccessible' inaccessible in 'Culprit' due to ambiguity
test.cpp: In function 'int main(int, char**)':
test.cpp:15: error: request for member 'i' is ambiguous
test.cpp:1: error: candidates are: int Inaccessible::i
test.cpp:1: error: int Inaccessible::i
*/

Das Problem hierbei ist einfach, daß der Kompiler sich einbildet, nicht zu
wissen, ob C::I::i oder C::A::I::i gemeint ist.

Das sind ja auch zwei *verschiedene* i's. Falls es die gleichen sein
sollen, musst Du virtuell vererben. Woher soll der Compiler wissen,
welches Du willst?

Quote:
Das hatte ich damals, als
ich Nachforschungen angestellt hatte, notgedrungenerweise geschluckt.

Interessanterweise kann man das ganze auch nicht umgehen indem man das c
nach i castet, kurz, an die direkte i-Basis kommt man GARNICHT mehr dran.

c,i? i ist kein Typ, sondern ein Member.?? Du kommst an die direkte
Basis Culprit::Inaccessible nicht mehr heran, in der Tat.

Quote:
Jetzt bin ich aber auf ein anderes Problem gestoßen, daß mich an der
Intelligenz der Macher des C++-Standards zweifeln lassen:

struct Base {
int foo(int a);
};

struct Derived {
int foo(char * a);
};

int main(int argc,char * argv[]) {
Base b;
Derived d;
int i;
char * cp;

b.foo(cp); // fehler -> zurecht
b.foo(i); // ok
d.foo(cp); // pl
d.foo(i); // fehler <--- ???
}


struct Derived : public Base {
using Base::foo;
int foo(char * a);
};


Quote:
Dieses Verhalten wurde damit begründet, daß der Compiler im aktuellen
Scope von d eine Funktion foo hat und daher die Suche in diesem Scope
aufhört[1].

Ja. Die Definition von foo in Derived "verbirgt" die ältere Definition.
Die spezialisiertere Klasse braucht zum Arbeiten etwa zusätzliche oder
Parameter, das "foo" in der Basis kann i.Allg. nicht die Arbeit in
Derived machen. Falls das aber Deine Intention ist, also nicht foo zu
ersetzen, sondern ein weiteres hinzuzufügen, gibt es "using" mit der
Syntax wie oben.

Quote:
So, nun zum Vorwurf des Defekts: Wenn der aktuelle Skope (Culprit aus dem
ersten Beispiel) die direkte Basisklasse Inaccessible kennt, warum hört
dann dort die Suche nicht auf, wenn ich aber auf Elemente zugreifen will
schon?

Die Funktionen haben verschiedene Signaturen, sind also verschieden. Die
in der Basis wird nur verborgen. Die Member als solche sind aber
mehrdeutig, also nicht einmal durch Argumente (die es ja nicht gibt)
auseinander zu halten. Die Situation ist eine andere.

Quote:
Denn die Tatsache, daß Inaccessible zweimal als Basis vorkommt,
kann der Compiler ja erst erkennen, wenn er eben NICHT nur die
direkten Basisklassen anschaut, sondern weiter runter wandert.

Wäre Inaccessible::i eine Funktion und nicht ein int wäre die Situation
genau die gleiche: Es wäre nicht eindeutig auflösbar. Hast Du aber ein
foo hier und ein foo da, mit verschiedenen Signaturen, so sind diese
aufgrund der Argumente auflösbar. Nur verbirgt der Compiler die
Deklaration der Basisklasse. Ein dementsprechend analog konstruiertes
Beispiel für Codeschnipsel 1 wäre:

struct Culprit : Accomplice, Inaccessible { int c;
int i;};

und *natürlich* ist c.i *jetzt* eindeutig, nämlich das von der äußeren
Klasse.

Quote:
Die Idee war es jetzt, einfach alle Klassen von der abstrakten Klasse
Serialize (besser wäre wohl der Name Serializable, den man ja schon von
Kaffee kennt) abzuleiten. Leider holt mich da das erste in diesem
Post genannte Problem wieder ein: Sobald eine Klasse Serialize
implementiert, können die davon abgeleiteten Klassen nicht mehr direkt von
dieser Klasse abgeleitet werden. Das hat vor allem aber den Nachteil, daß
die serialize-Funktionen mitvererbt werden.

Zwei Lösungen: Erstens, "Serializable" virtuell vererben (was definitiv
die einfachste Variante wäre), oder "mix-in"-Klassen bauen, die
zusätzliche Funktionen bereitstellen, und die man je nach Bedarf zu
Serializable oder zu der entsprechend ablgeleiteten komplexen Klasse
hinzumischt, um die erweiterte Funktionalität zu erreichen. Zweiteres
ist meist komplizierter vom Design.

Quote:
Die Frage, die sich mir jetzt also stellt ist: Wie macht man das am
besten? Oder ist der einzige Weg, das in ISO-C++ zu lösen, indem man
schlicht aufpasst, daß man die virtuellen Funktionen der Serialize-Klasse
konsequent überall reimplementiert und dann nur EINMAL von Serialize
abzuleiten?

Das wäre Methode 2 von oben. Methode 1 ist einfach die, Serialize
virtuell zu vererben und gut ist.

Grüße,
Thomas
Back to top
Post new topic   Reply to topic    C++Talk.NET Forum Index -> C++ (German) All times are GMT
Page 1 of 1

 
 


Powered by phpBB © 2001, 2006 phpBB Group