 |
C++Talk.NET C++ language newsgroups
|
| View previous topic :: View next topic |
| Author |
Message |
Martin Henkel Guest
|
Posted: Sat Sep 25, 2004 12:35 pm Post subject: Preconditions - assert auch in überschriebener Memberfunktio |
|
|
Hallo,
ich habe eine Basisklasse. Diese hat eine Memberfunktion mit zum
Beispiel 2 Preconditions.
Die Preconditions checke ich mit assert.
class Base
{
public:
virtual foo(int param1, int param2)
{
assert(param1 > 100);
assert(param2 < 1000);
// weiterer Code
}
};
Jetzt leite ich eine Klasse davon ab und überschreibe die foo
Memberfunktion.
Dort rufe ich aber zuerst nochmal die Implementation der Basisklasse auf.
class Derived : public Base
{
public:
virtual foo(int param1, int param2)
{
// Hier nochmal die asserts?
Base::foo(param1, param2);
// weiterer Code
}
};
Jetzt hat ja Derived::foo die selben Preconditions wie Base::foo.
Soll ich also die asserts nochmal dahin schreiben?
Ich würde eigentlich sagen ja, aber wenn sich dann mal eine Precondition
ändert oder hinzukommt müsste ich sie überall ändern. :-(
Was meint ihr dazu?
--
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 |
|
 |
Christoph Rabel Guest
|
Posted: Sun Sep 26, 2004 4:48 pm Post subject: Re: Preconditions - assert auch in überschriebener Memberfun |
|
|
Martin Henkel wrote:
| Quote: |
ich habe eine Basisklasse. Diese hat eine Memberfunktion mit zum
Beispiel 2 Preconditions.
Die Preconditions checke ich mit assert.
class Base
{
public:
virtual foo(int param1, int param2)
{
assert(param1 > 100);
assert(param2 < 1000);
// weiterer Code
}
};
|
Ein besseres Desing wäre für dich vermutlich folgendes:
class Base
{
public:
foo(int param1, int param2)
{
assert(param1 > 100);
assert(param2 < 1000);
foo_internal();
}
private:
virtual foo_internal(int param1, int param2)
{
//weiterer Code
}
};
class Derived : public Base
{
virtual internal(int param1, int param2)
{
Base::foo(param1, param2);
// weiterer Code
}
};
Dadurch, das du die virtuell Funktion private machst, kann
sie nicht von aussen aufgerufen werden. Möglich ist nur der
Aufruf der nichtvirtuellen "Wrapperfunktion" die auch
gleichzeitig die Preconditions prüft.
hth
Christoph
--
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 |
|
 |
Rolf Magnus Guest
|
Posted: Sun Sep 26, 2004 5:49 pm Post subject: Re: Preconditions - assert auch in überschriebener Memberfun |
|
|
Martin Henkel wrote:
| Quote: | Hallo,
ich habe eine Basisklasse. Diese hat eine Memberfunktion mit zum
Beispiel 2 Preconditions.
Die Preconditions checke ich mit assert.
class Base
{
public:
virtual foo(int param1, int param2)
{
assert(param1 > 100);
assert(param2 < 1000);
// weiterer Code
}
};
Jetzt leite ich eine Klasse davon ab und überschreibe die foo
Memberfunktion.
Dort rufe ich aber zuerst nochmal die Implementation der Basisklasse auf.
class Derived : public Base
{
public:
virtual foo(int param1, int param2)
{
// Hier nochmal die asserts?
Base::foo(param1, param2);
// weiterer Code
}
};
Jetzt hat ja Derived::foo die selben Preconditions wie Base::foo.
Soll ich also die asserts nochmal dahin schreiben?
Ich würde eigentlich sagen ja, aber wenn sich dann mal eine Precondition
ändert oder hinzukommt müsste ich sie überall ändern. :-(
Was meint ihr dazu?
|
Eine andere relativ gängige Möglichkeit wäre, die von außen aufzurufende
Funktion nichtvirtuell zu machen und die virtuelle nur von dort aufzurufen
und dann private zu machen, etwa so:
class Base
{
public:
void foo(int param1, int param2)
{
assert(param1 > 100);
assert(param2 < 1000);
do_foo(param1, param2);
}
private:
virtual void do_foo(int param1, int param2);
};
class Derived : public Base
{
private:
virtual void do_foo(int param1, int param2)
{
//Prüfung wurde schon von Base::foo() gemacht
}
};
Dadurch sind alle Prüfungen an einer Stelle, es funktioniert unabhängig
davon, ob Derived::do_foo nochmal Base::do_foo aufruft oder nicht, und man
das auch gleich dazu benutzen, um auch Postconditions zu testen.
--
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 |
|
 |
Rolf Magnus Guest
|
Posted: Sun Sep 26, 2004 5:52 pm Post subject: Re: Preconditions - assert auch in überschriebener Memberfun |
|
|
Rolf Magnus wrote:
| Quote: | Eine andere relativ gängige Möglichkeit wäre, die von außen aufzurufende
Funktion nichtvirtuell zu machen und die virtuelle nur von dort aufzurufen
und dann private zu machen, etwa so:
|
Hmpf, warum fallen mir solche Fehler immer zwei Sekunden _nach_ dem Absenden
auf? Wenn Base::do_foo evtl. von abgeleiteten Implementationen aus
aufgerufen werden können soll, muß es natürlich protected sein und nicht
private.
--
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 |
|
 |
kanze@gabi-soft.fr Guest
|
Posted: Tue Sep 28, 2004 7:07 am Post subject: Re: Preconditions - assert auch in überschriebener Memberfu |
|
|
Martin Henkel <martinhenkel1 (AT) gmx (DOT) de> wrote
| Quote: | ich habe eine Basisklasse. Diese hat eine Memberfunktion mit zum
Beispiel 2 Preconditions.
Die Preconditions checke ich mit assert.
class Base
{
public:
virtual foo(int param1, int param2)
{
assert(param1 > 100);
assert(param2 < 1000);
// weiterer Code
}
};
|
Wenn man Programmierung durch Vertrag implementiert, ist es gewöhnlich,
die virtuellen Funktionen private zu vereinbaren. Die nicht virtuellen
public Funktionnen definieren die Schnittstelle und den Vertrag, und
rufen die private Functionen an:
class Base
{
public:
void foo( int param1, int param2 )
{
assert( param1 > 100 ) ;
assert( param2 < 1000 ) ;
doFoo( param1, param2 ) ;
}
private:
virtual void doFoo( int param1, int param2 ) ;
} ;
| Quote: | Jetzt leite ich eine Klasse davon ab und überschreibe die foo
Memberfunktion. Dort rufe ich aber zuerst nochmal die Implementation
der Basisklasse auf.
|
Interessanter Vorgang. Bei mir sind die virtuellen Funktionnen der
Basisklasse fast immer abstrakt; sie dürfen also nicht angerufen werden.
Ich bin etwas skeptisch über diesem Vorgang. Wenn die Basisklasse
irgendwas zu tun hat, das getan werden muss, um an den Vertrag zu
halten, dann soll es schon in der nicht virtuellan Funktion der
Basisklasse aufgerufen werden, damit es nicht von der abgeleiteten
Klasse vergessen werden kann. Und wenn es um eine Art "default"
Verhaltung ist, die die abgeleitete Klasse zur Bequemheit angeboten
wird, und die sie benutzen kann oder nicht, genau wie es ihr gefällt,
dann gehört es lieber in einer ganz anderen (protected) Funktion, mit
einem anderen Namen.
| Quote: | class Derived : public Base
{
public:
virtual foo(int param1, int param2)
{
// Hier nochmal die asserts?
Base::foo(param1, param2);
// weiterer Code
}
};
Jetzt hat ja Derived::foo die selben Preconditions wie Base::foo.
|
Das Problem liegt halt darein, dass die public Funktion direkt der
virtuellen Funktion ist, und damit mehrere Rolen ausübt. Public
Funktionen sollen ausschließlich eine Schnittstelle darstellen. Wenn
aber eine public Funktion virtuell ist, und in der abgeleiteten Klasse
implementiert wird, besteht die Frage, gehört sie zur Schnittstelle der
abgeleiteten Klasse, oder geht es nur um die Implementierung der
Funktion der Basisklasse.
Derived ist hier eine Klasse für sich. Durch das public Erben verspricht
sie wohl, die verträgliche Schnittstelle von Base zu implementieren.
Nichts aber verbietet ihr, ihre eigene Schnittstelle auch anzubieten,
insofern diese nicht den Vertrag der Basisklasse wiederspricht. Und in
ihrer eigenen Schnittstelle darf sie freilich die Präconditionnen
stimmen, wie sie sie will.
Wenn Du die polymorphische Implementierung in eine andere Funktion
auslagerst, als die Schnittstelle, und die gemeinsame Funktionnalität
auch in seine eigenen Funktionnen auslagerst, dann denke ich, dass das
conceptuelle Problem verschwindet. Man erreicht etwas wie:
class Base
{
public:
void foo( int param1, int param2 )
{
assert( param1 > 100 ) ;
assert( param2 < 1000 ) ;
initFoo( param1, param2 ) ;
doFoo( param1, param2 ) ;
}
protected:
void helpWithFoo( int param1, int param2 ) ;
private:
// Zwangsläufige Initialisierung...
void initFoo( int param1, int param2 ) ;
virtual void doFoo( int param1, int param2 ) = 0 ;
private:
} ;
class Derived : public Base
{
public:
void foo( int param1, int param2 )
{
// Ich kann mehr...
assert( param1 > 0 ) ;
assert( param2 < 10000 ) ;
// Aber vielleicht brauche ich die zwangsläufige
// Initialisierung nicht. Es geht endlich um einen
// anderen Vertrag.
initFoo( param1, param2 ) ;
doFoo( param1, param2 ) ;
}
private:
virtual void doFoo( int param1, int param2 )
{
// Wenn es mir hilft...
helpWithFoo( param1, param2 ) ;
// Aber auch mein eigenes Verhaltens
// ...
}
} ;
--
James Kanze GABI Software http://www.gabi-soft.fr
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34
--
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 |
|
 |
kanze@gabi-soft.fr Guest
|
Posted: Tue Sep 28, 2004 9:43 am Post subject: Re: Preconditions - assert auch in überschriebener Memberfun |
|
|
Rolf Magnus <ramagnus (AT) t-online (DOT) de> wrote
| Quote: | Rolf Magnus wrote:
Eine andere relativ gängige Möglichkeit wäre, die von außen
aufzurufende Funktion nichtvirtuell zu machen und die virtuelle nur
von dort aufzurufen und dann private zu machen, etwa so:
Hmpf, warum fallen mir solche Fehler immer zwei Sekunden _nach_ dem
Absenden auf? Wenn Base::do_foo evtl. von abgeleiteten
Implementationen aus aufgerufen werden können soll, muß es natürlich
protected sein und nicht private.
|
Vielleicht deshalb, weil es normalerweise keine gute Idee ist, dass sie
aufgerufen sein soll?
--
James Kanze GABI Software http://www.gabi-soft.fr
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34
--
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 |
|
 |
Tibor Pausz Guest
|
Posted: Tue Sep 28, 2004 11:16 am Post subject: Re: Preconditions - assert auch in überschriebener Memberfu |
|
|
<kanze (AT) gabi-soft (DOT) fr> wrote:
| Quote: | Wenn man Programmierung durch Vertrag implementiert, ist es gewöhnlich,
die virtuellen Funktionen private zu vereinbaren. Die nicht virtuellen
public Funktionnen definieren die Schnittstelle und den Vertrag, und
rufen die private Functionen an:
class Base
{
public:
void foo( int param1, int param2 )
{
assert( param1 > 100 ) ;
assert( param2 < 1000 ) ;
doFoo( param1, param2 ) ;
}
private:
virtual void doFoo( int param1, int param2 ) ;
} ;
|
Ich halte das für schlechtes Design.
Offensichtlich soll der Parametersatz für die member Funktion doFoo für
alle abgeleiteten Klassen derselbe sein mit denselben Vorbedingung, er
erfordert daher auch kein polymorphes Verhalten - ganz im Gegenteil. Es
wäre sogar schädlich, wenn dies zutreffen würde. In so einem Fall halte
ich es für besser eine eigene Parameterklasse zu vereinbaren. Dann kann
ich das ganze so umschreiben:
class Parameter {
int const p1_, p2_;
public:
explicit Paramter (int const p1, int const p2) :
p1_ (p1), p2_ (p2)
{
// nur eine Beispiel für die Bedigungen
if ((p1_ < 1 && p1_ > 100) ||
(p2_ < 1000 && p2_ > 20000))
{
throw std::logic_error ("ein text");
}
};
int const p1 () const {
return p1_;
};
int const p2 () const {
return p2_;
};
};
Es bliebe jetzt noch das Problem, daß C++ kein "final" kennt und man
somit nicht so ohne weiteres verhindern kann, daß jemand von Parameter
ableitet, aber dafür gibt es bekannte Lösungsansätze.
class Base {
public:
virtual ~Base () = 0;
virtual void Foo (Parameter const& p) = 0;
};
Jetzt habe ich ein polymorphes Verhalten, egal ob ich Foo via
Basiszeiger oder direkt aufrufe. Es ist auch weiterhin gewährleistet,
daß egal wie eine Überladung von Foo aussieht immer ein korrekter
Parametersatz übergeben wird. Die Validierung des Endzustandes ist meist
abhängig von jeweiligen Typ und kann daher gar oftmals nicht in einer
nicht virtuellen Member Funktionen überprüft werden, die man direkt
aufruft.
Daher müßte
class Derived : virtual public Base {
void checkDerived () throw std::logic_error () const;
public:
virtual void Foo (Parameter const& p) {
// Implementation
checkDerived ();
}
}
eine eigene Invariante definieren. Sinnigerweise darf man diese nicht
direkt aufrufen.
| Quote: | Wenn Du die polymorphische Implementierung in eine andere Funktion
auslagerst, als die Schnittstelle, und die gemeinsame Funktionnalität
auch in seine eigenen Funktionnen auslagerst, dann denke ich, dass das
conceptuelle Problem verschwindet. Man erreicht etwas wie:
class Base
{
public:
void foo( int param1, int param2 )
{
assert( param1 > 100 ) ;
assert( param2 < 1000 ) ;
initFoo( param1, param2 ) ;
doFoo( param1, param2 ) ;
}
protected:
void helpWithFoo( int param1, int param2 ) ;
private:
// Zwangsläufige Initialisierung...
void initFoo( int param1, int param2 ) ;
virtual void doFoo( int param1, int param2 ) = 0 ;
private:
} ;
class Derived : public Base
{
public:
void foo( int param1, int param2 )
{
// Ich kann mehr...
assert( param1 > 0 ) ;
assert( param2 < 10000 ) ;
// Aber vielleicht brauche ich die zwangsläufige
// Initialisierung nicht. Es geht endlich um einen
// anderen Vertrag.
initFoo( param1, param2 ) ;
doFoo( param1, param2 ) ;
}
private:
virtual void doFoo( int param1, int param2 )
{
// Wenn es mir hilft...
helpWithFoo( param1, param2 ) ;
// Aber auch mein eigenes Verhaltens
// ...
}
} ;
|
Das ist ja richtig übler Code. Ich erlaube ihn mal so zu benutzen.
Derived derived;
Base* b = &derived;
Derived* d = &derived;
d->foo (1,5000); // korrekt im Sinne von Derived
b->foo (1,5000); // hier wird Base::foo aufgerufen
// und das führt zu einer assertion
Rufen jetzt unterschiedliche Funktionen auf! Das kann es also nicht
sein. b->foo (1,5000) ist für eine Derived Objekt ein korrekter Aufruf
und es gibt keinen guten Grund warum eine Klasse mit polymorphen
Verhalten eben nicht über einen Basiszeiger benutzt werden darf. Gerade
weil man dieses Verhalten will benutzt man ja Polymorphie.
Irgend wie scheint es mir, daß Du das Bridge Pattern vermeiden willst,
obwohl Du nur so das nicht virtuelle Interface bekommst und gleichzeitig
das polymorphe Verhalten der Implementation.
--
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 |
|
 |
Tobias Güntner Guest
|
Posted: Tue Sep 28, 2004 12:24 pm Post subject: Re: Preconditions - assert auch in überschriebener Memberfu |
|
|
Tibor Pausz wrote:
| Quote: | Das ist ja richtig übler Code. Ich erlaube ihn mal so zu benutzen.
Derived derived;
Base* b = &derived;
Derived* d = &derived;
d->foo (1,5000); // korrekt im Sinne von Derived
b->foo (1,5000); // hier wird Base::foo aufgerufen
// und das führt zu einer assertion
Rufen jetzt unterschiedliche Funktionen auf! Das kann es also nicht
sein. b->foo (1,5000) ist für eine Derived Objekt ein korrekter Aufruf
und es gibt keinen guten Grund warum eine Klasse mit polymorphen
Verhalten eben nicht über einen Basiszeiger benutzt werden darf. Gerade
weil man dieses Verhalten will benutzt man ja Polymorphie.
|
Doch, mindestens einen Grund gibt es. Die "Preconditions" (ich zähle
dazu auch Aufrufparameter) der abgeleiteten Klasse müssen gleich oder
gelockert sein, die "Postconditions" müssen gleich oder noch strenger
sein, sonst verstößt das Design gegen das LSP und Base wäre nicht mehr
reibungslos durch Derived ersetzbar (nicht umgekehrt!).
Der Fehler ist, dass b->foo(1, 5000) aufgerufen wird, weil genau das im
"Vertrag" von Base verboten wird - man kann von keiner abgeleiteten
Klasse verlangen, dass sie Parameter in dieser Größenordnung akzeptiert.
Sollte jemand wissen, dass es sich tatsächlich um Derived handelt, hat
er eine völlig andere Schnittstelle mit anderen Bedingungen und kann
sich d->foo(1, 5000) erlauben.
--
Regards,
Tobias
--
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 |
|
 |
Torsten Robitzki Guest
|
Posted: Tue Sep 28, 2004 6:23 pm Post subject: Re: Preconditions - assert auch in überschriebener Memberfu |
|
|
Tibor Pausz wrote:
| Quote: | kanze (AT) gabi-soft (DOT) fr> wrote:
Wenn man Programmierung durch Vertrag implementiert, ist es gewöhnlich,
die virtuellen Funktionen private zu vereinbaren. Die nicht virtuellen
public Funktionnen definieren die Schnittstelle und den Vertrag, und
rufen die private Functionen an:
class Base
{
public:
void foo( int param1, int param2 )
{
assert( param1 > 100 ) ;
assert( param2 < 1000 ) ;
doFoo( param1, param2 ) ;
}
private:
virtual void doFoo( int param1, int param2 ) ;
} ;
Ich halte das für schlechtes Design.
|
Ich halte das für guten Design :-)
| Quote: | Offensichtlich soll der Parametersatz für die member Funktion doFoo für
alle abgeleiteten Klassen derselbe sein mit denselben Vorbedingung, er
erfordert daher auch kein polymorphes Verhalten - ganz im Gegenteil. Es
wäre sogar schädlich, wenn dies zutreffen würde.
|
Richtig, und der Teil, der prüft ist doch auch garnicht virtuell im
Beispiel von James.
| Quote: | In so einem Fall halte
ich es für besser eine eigene Parameterklasse zu vereinbaren. Dann kann
ich das ganze so umschreiben:
|
Das würde nahe legen, das die beiden Parameter in irgend einem
Verhältnis zu einander stehen würden. Dann wäre es sicher ein
Designfehler, daraus nicht eine eigene Klasse zu machen. Ist dem aber
nicht so und eine ableitende Klasse möchte die Vorbedingungen
aufweichen, bräuchtest Du plötzlich eine neue Klasse, wo eigentlich nur
ein Satz von Werten gebraucht wird.
| Quote: | class Parameter {
snip> };
Es bliebe jetzt noch das Problem, daß C++ kein "final" kennt und man
somit nicht so ohne weiteres verhindern kann, daß jemand von Parameter
ableitet, aber dafür gibt es bekannte Lösungsansätze.
|
Bleibt aber immer noch das Problem, das C++ keine Java ist ;-)
| Quote: | class Base {
public:
virtual ~Base () = 0;
virtual void Foo (Parameter const& p) = 0;
};
Jetzt habe ich ein polymorphes Verhalten, egal ob ich Foo via
Basiszeiger oder direkt aufrufe.
|
Wieso ist das erstrebenswert? Wann immer möglich möchte ich eigentlich
auf eine zusätzliche Indirektion verzichten.
mfg Torsten
--
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 |
|
 |
kanze@gabi-soft.fr Guest
|
Posted: Wed Sep 29, 2004 1:01 pm Post subject: Re: Preconditions - assert auch in überschriebe ner Memberf |
|
|
[email]pausz (AT) stud (DOT) uni-frankfurt.de[/email] (Tibor Pausz) wrote in message
news:<1gktgyu.15b604x1emjnrnN%pausz (AT) stud (DOT) uni-frankfurt.de>...
| Quote: | kanze (AT) gabi-soft (DOT) fr> wrote:
Wenn man Programmierung durch Vertrag implementiert, ist es
gewöhnlich, die virtuellen Funktionen private zu vereinbaren. Die
nicht virtuellen public Funktionnen definieren die Schnittstelle und
den Vertrag, und rufen die private Functionen an:
class Base
{
public:
void foo( int param1, int param2 )
{
assert( param1 > 100 ) ;
assert( param2 < 1000 ) ;
doFoo( param1, param2 ) ;
}
private:
virtual void doFoo( int param1, int param2 ) ;
} ;
Ich halte das für schlechtes Design.
Offensichtlich soll der Parametersatz für die member Funktion doFoo
für alle abgeleiteten Klassen derselbe sein mit denselben
Vorbedingung,
|
Gar nicht. Base definiert einen Vertrag. Oder lieber, zwei Verträge,
einen mit den Benutzern, und einen mit den abgeleiteten Klassen. Der
Vertrag mit den Benutzern enthält einen Klausus, dass foo nie mit einem
ersten Parameter kleiner als 100 oder einem zweiten größer als 1000
aufgerufen soll. Das ist der Vertrag von Base -- abgeleitete Klassen
dürfen andere Verträge auch anbieten.
Der Vertrag mit den abgeleiteten Klassen enthält einen Kausus, dass die
abgeleitete Klasse doFoo implementieren muss, und zwar, dass sie es so
implementieren, damit es auch foo implementiert. Wenn die
Implementierung locherer Präconditionnen erlaubt, darf die abgeleitete
Klasse diese auch über seiner Schnittstelle zur Verfügung stellen.
| Quote: | er erfordert daher auch kein polymorphes Verhalten - ganz im
Gegenteil.
|
Was meinst Du dann da? Der Vertrag bestimmt das Was, nicht das Wie. Die
Implementierung von doFoo in der abgeleiteten Klassen bestimmt das Wie.
| Quote: | Es wäre sogar schädlich, wenn dies zutreffen würde.
|
Wenn was zutreffen würde? Es wäre schädlich, wenn eine Klasse *nicht*
den Vertrag für seine Schnittstelle bestimmt.
| Quote: | In so einem Fall halte ich es für besser eine eigene Parameterklasse
zu vereinbaren. Dann kann ich das ganze so umschreiben:
class Parameter {
int const p1_, p2_;
public:
explicit Paramter (int const p1, int const p2) :
p1_ (p1), p2_ (p2)
{
// nur eine Beispiel für die Bedigungen
if ((p1_ < 1 && p1_ > 100) ||
(p2_ < 1000 && p2_ > 20000))
{
throw std::logic_error ("ein text");
}
};
int const p1 () const {
return p1_;
};
int const p2 () const {
return p2_;
};
};
|
Das scheint mir viele Komplikationen für nichts. Wo siehst Du den
Vorteil eines solchen Vorgangs.
| Quote: | Es bliebe jetzt noch das Problem, daß C++ kein "final" kennt und man
somit nicht so ohne weiteres verhindern kann, daß jemand von Parameter
ableitet,
|
Insofern, dass keine der Funktionen virtual ist, sehe ich keine
Problem. Obwohl ich auch "final" als eine sinnvolle Ergänzung C++ siehe.
| Quote: | aber dafür gibt es bekannte Lösungsansätze.
class Base {
public:
virtual ~Base () = 0;
virtual void Foo (Parameter const& p) = 0;
};
Jetzt habe ich ein polymorphes Verhalten, egal ob ich Foo via
Basiszeiger oder direkt aufrufe.
|
So kommst du auf genau dasselbe als ich, nur mit viel mehr Code. Dazu
sind die Vertragsbedingungen foo() in einer ganz anderen Klasse
versteckt, und damit viel schwieriger zu finden.
Dazu wird wahrscheinlich dann die Definition von foo() in der
abgeleiteten Klasse auch public. Was m.E. zu Verwirrung führt: ist die
Funktion Bestandteil der Schnittstelle von Derived (und also mit einem
Vertrag, der durch Derived bestimmt wird), oder geht es nur um die
Implementierung der Funktion in Base. In Allgemein finde ich es besser,
die Implementierungen in private zu halten, auch dann, wenn die in einer
anderen Klasse stehen.
| Quote: | Es ist auch weiterhin gewährleistet, daß egal wie eine Überladung von
Foo aussieht immer ein korrekter Parametersatz übergeben wird. Die
Validierung des Endzustandes ist meist abhängig von jeweiligen Typ und
kann daher gar oftmals nicht in einer nicht virtuellen Member
Funktionen überprüft werden, die man direkt aufruft.
Daher müßte
class Derived : virtual public Base {
void checkDerived () throw std::logic_error () const;
public:
virtual void Foo (Parameter const& p) {
// Implementation
checkDerived ();
}
}
eine eigene Invariante definieren. Sinnigerweise darf man diese nicht
direkt aufrufen.
Wenn Du die polymorphische Implementierung in eine andere Funktion
auslagerst, als die Schnittstelle, und die gemeinsame
Funktionnalität auch in seine eigenen Funktionnen auslagerst, dann
denke ich, dass das conceptuelle Problem verschwindet. Man erreicht
etwas wie:
class Base
{
public:
void foo( int param1, int param2 )
{
assert( param1 > 100 ) ;
assert( param2 < 1000 ) ;
initFoo( param1, param2 ) ;
doFoo( param1, param2 ) ;
}
protected:
void helpWithFoo( int param1, int param2 ) ;
private:
// Zwangsläufige Initialisierung...
void initFoo( int param1, int param2 ) ;
virtual void doFoo( int param1, int param2 ) = 0 ;
private:
} ;
class Derived : public Base
{
public:
void foo( int param1, int param2 )
{
// Ich kann mehr...
assert( param1 > 0 ) ;
assert( param2 < 10000 ) ;
// Aber vielleicht brauche ich die zwangsläufige
// Initialisierung nicht. Es geht endlich um einen
// anderen Vertrag.
initFoo( param1, param2 ) ;
doFoo( param1, param2 ) ;
}
private:
virtual void doFoo( int param1, int param2 )
{
// Wenn es mir hilft...
helpWithFoo( param1, param2 ) ;
// Aber auch mein eigenes Verhaltens
// ...
}
} ;
Das ist ja richtig übler Code. Ich erlaube ihn mal so zu benutzen.
|
Ich wollt halt alle Möglichkeiten in einem Zeigen. Typischerweise
benutzt man nicht alle Möglichkeiten gleichzeitig. (In der Tat benuzte
ich die meistens selten.)
| Quote: | Derived derived;
Base* b = &derived;
Derived* d = &derived;
d->foo (1,5000); // korrekt im Sinne von Derived
b->foo (1,5000); // hier wird Base::foo aufgerufen
// und das führt zu einer assertion
|
Das ist genau, was ich erreichen wollte. Wenn ich ein Base* habe, und
weiss nicht woher es kommt, will ich auf dem Vertrag von B
anhalten. Wenn ich weiß, dass das Objekt in der Tat ein Derived ist,
dann kann ich das mit dynamic_cast explizit sagen, und auf der lockeren
Schnittstelle von Derived ankommen.
| Quote: | Rufen jetzt unterschiedliche Funktionen auf! Das kann es also nicht
sein. b->foo (1,5000) ist für eine Derived Objekt ein korrekter Aufruf
und es gibt keinen guten Grund warum eine Klasse mit polymorphen
Verhalten eben nicht über einen Basiszeiger benutzt werden
darf.
|
Meiner Meinung nach ist gerade b->foo(1,5000) nie ein korrekter Aufruf.
Wenn ich der Schnittstelle Base adressiere, heißt es, dass ich am
Vertrag der Schnittstelle Base anhalten soll, egal was der dynamische
Typ ist. Wenn ich kenne den dynamischen Typ, und davon Benutzung machen
will, dann sage ich es explizit:
dynamic_cast< Derived* >( b )->foo( 1, 5000 ) ;
| Quote: | Gerade Weil man dieses Verhalten will benutzt man ja Polymorphie.
|
Nein. Man benutzt Polymorphism, weil man mehrere Verhalten haben will,
die denselben Vertrag implementieren. Wenn ich weiß, dass ich einen
Derived haben, dann sage ich es laut und klar im Programm, mittels
dynamic_cast. Und wenn ich es nicht weiß, dann jeder Versuch, das Objekt
als Derived zu verwenden, ist ein Programmierfehler.
Entweder ich weiß es, oder ich weiß es nicht. Und wenn ich es weiß, dann
soll ich es sagen, und zwar wenn es so geht in einer Art sagen, dass die
Wahrheit meines Wissens zur Laufzeit überprüft werden kann.
| Quote: | Irgend wie scheint es mir, daß Du das Bridge Pattern vermeiden willst,
obwohl Du nur so das nicht virtuelle Interface bekommst und
gleichzeitig das polymorphe Verhalten der Implementation.
|
Was ich mache liegt m.E. auf einer Ebene tiefer, als die Pattern. Es
geht um Programmierung durch Vertrag, und ich benutze es mit den meisten
Patterns. (Es gibt Ausnahme, wie zum Beispiel Visitor, wo die bedeutende
Eigenschaft des Patterns ist, den Benutzer vom Vertrag zu befreien. In
solchen Fällen ist diese Model freilich nicht angebracht.)
--
James Kanze GABI Software http://www.gabi-soft.fr
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34
--
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 |
|
 |
kanze@gabi-soft.fr Guest
|
Posted: Wed Sep 29, 2004 1:15 pm Post subject: Re: Preconditions - assert auch in überschriebe ner Memberf |
|
|
Tobias Güntner <fatbull (AT) users (DOT) sourceforge.net> wrote
| Quote: | Tibor Pausz wrote:
Das ist ja richtig übler Code. Ich erlaube ihn mal so zu benutzen.
Derived derived;
Base* b = &derived;
Derived* d = &derived;
d->foo (1,5000); // korrekt im Sinne von Derived
b->foo (1,5000); // hier wird Base::foo aufgerufen
// und das führt zu einer assertion
Rufen jetzt unterschiedliche Funktionen auf! Das kann es also nicht
sein. b->foo (1,5000) ist für eine Derived Objekt ein korrekter
Aufruf und es gibt keinen guten Grund warum eine Klasse mit
polymorphen Verhalten eben nicht über einen Basiszeiger benutzt
werden darf. Gerade weil man dieses Verhalten will benutzt man ja
Polymorphie.
Doch, mindestens einen Grund gibt es. Die "Preconditions" (ich zähle
dazu auch Aufrufparameter) der abgeleiteten Klasse müssen gleich oder
gelockert sein, die "Postconditions" müssen gleich oder noch strenger
sein, sonst verstößt das Design gegen das LSP und Base wäre nicht mehr
reibungslos durch Derived ersetzbar (nicht umgekehrt!). Der Fehler
ist, dass b->foo(1, 5000) aufgerufen wird, weil genau das im "Vertrag"
von Base verboten wird - man kann von keiner abgeleiteten Klasse
verlangen, dass sie Parameter in dieser Größenordnung akzeptiert.
Sollte jemand wissen, dass es sich tatsächlich um Derived handelt, hat
er eine völlig andere Schnittstelle mit anderen Bedingungen und kann
sich d->foo(1, 5000) erlauben.
|
Ich bin wohl deiner Meinung. Aber historisch betrachtet ist es gängig,
mindestens in Sprachen wie Eiffel, die Freiheiten der abgeleiteten
Klasse auch über der Schnittstelle zu benutzen, wenn man weiß, dass das
Objekt tatsächlich von abgeleitetem Typ ist. Ich denke sogar, dass ich
unter den ersten war, der es als Fehler erklärt habe und gemeint habe,
dass es lieber wäre, wenn man ausdrucklich sagt, dass er die abgeleitete
Schnittstelle benutzen will. Durch einen dynamic_cast in C++, zum
Beispiel.
--
James Kanze GABI Software http://www.gabi-soft.fr
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34
--
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 |
|
 |
Tibor Pausz Guest
|
Posted: Sun Oct 03, 2004 7:41 am Post subject: Re: Preconditions - assert auch in überschriebener Memberfu |
|
|
<kanze (AT) gabi-soft (DOT) fr> wrote:
| Quote: | Gar nicht. Base definiert einen Vertrag. Oder lieber, zwei Verträge,
einen mit den Benutzern, und einen mit den abgeleiteten Klassen. Der
Vertrag mit den Benutzern enthält einen Klausus, dass foo nie mit einem
ersten Parameter kleiner als 100 oder einem zweiten größer als 1000
aufgerufen soll. Das ist der Vertrag von Base -- abgeleitete Klassen
dürfen andere Verträge auch anbieten.
|
Das Problem ist aber, daß sich dieser Vertrag nicht im Interface von
Base wiederspiegelt sondern lediglich in deren Code, diesen sieht der
Nutzer der Klasse nicht, bzw. deren Dokumentation.
| Quote: | Was meinst Du dann da? Der Vertrag bestimmt das Was, nicht das Wie. Die
Implementierung von doFoo in der abgeleiteten Klassen bestimmt das Wie.
|
Der Vertrag ist nur durch Implementation definiert, nie durch das
Interface und das ist eben schlecht.
| Quote: | Das scheint mir viele Komplikationen für nichts. Wo siehst Du den
Vorteil eines solchen Vorgangs.
|
Ich führe das Gegenbeispiel wohl man komplett aus.
class Int1 {
int i_;
public:
explicit Int1 (int const i) throw (std::logic_error) : i_ (i) {
if (!(i_ > 100)) {
throw std::logic_error ("wrong argument");
}
};
int const v () const throw () {
return i_;
};
};
class Int2 {
int i_;
public:
explicit Int2 (int const i) throw (std::logic_error) : i_(i) {
if (!(i_ > 0)) {
throw std::logic_error ("wrong argument");
};
Int2 (Int1 const i) throw () : i_(i.v()) {
};
int const v () throw () {
return i_;
};
};
class Base {
public:
virtual foo (Int1 i);
};
class Derived : virtual public Base {
virtual foo (Int1 i);
virtual foo (Int2 i);
};
Derived::foo (Int1) ist lediglich die Überladung von Base::foo(Int1),
d.h. der ererbete Vertrag von Base, Derived::foo (Int2) ist die neue
Funktionalität mit einem neuem Vertrag. In Deinem Beispiel kann man das
nicht ausdrücken, weil die beiden Member Funktionen, dieselben
Parametern haben, das heißt, daß das Interface die unterschiedlichen
Verträge nicht wiederspiegelt. So kann man auch den Fehler begehen via
(base*)b->foo(int,int) zuzugreifen.
Idealerweise würde man für Int1, Int2 den ganzen Rattenschwanz an
Operatoren definieren und die Typen schon vorher benutzen und nicht erst
bei der Parameterübergabe. So bestünde die Chance Fehler früher zu
finden, an der Stelle wo sie entstehen und nicht dort wo die falschen
Werte benutzt werden sollen. Habe ich schon erwähnt, daß Subtypes
sinnvoll wären?
Wenn man Parameter übergibt, dann kann es sein, daß man sie an
verschiedene Funktionen weiterreicht ohne sie zu verändern. Bei Deinem
Beispiel müßte jedesmal der Vertrag überprüft werden.
Nennen wir den Parametersatz "Datum" und es dürfte einsichtiger sein.
Warum sollte eine "Datum" anders behandelt werden wie ein
eingeschränkter Integer Datentyp?
| Quote: | So kommst du auf genau dasselbe als ich, nur mit viel mehr Code. Dazu
sind die Vertragsbedingungen foo() in einer ganz anderen Klasse
versteckt, und damit viel schwieriger zu finden.
|
Der Vertrag spiegelt sich so im Interface der Klasse Base und Derived
wieder. Der Vertrag sagt, es dürfen nur Parameter vom Typ X übergeben
werden. Unter X kann man sich meisten mehr vorstellen als unter einem
beliebigen int (oder sonst einem POD). Nennen wir X einfach
Tag_des_Jahres und dann ist sofort einsichtig warum dieser Wert nur aus
dem Intervall [1, 366] stammen kann. Wenn ich das Dinge int nenne, muß
ich erstmal die Dokumentation beachten, die mir Einschränkungen für den
Parameter auferlegt. Ich könnte so bei flüchtiger Betrachtung vorallem
beim Hantieren mit Basiszeiger Fehler machen, wenn mich der Compiler auf
solche Fehler aufmerksam macht, ist das besser.
| Quote: | Derived derived;
Base* b = &derived;
Derived* d = &derived;
d->foo (1,5000); // korrekt im Sinne von Derived
b->foo (1,5000); // hier wird Base::foo aufgerufen
// und das führt zu einer assertion
Das ist genau, was ich erreichen wollte. Wenn ich ein Base* habe, und
weiss nicht woher es kommt, will ich auf dem Vertrag von B
anhalten. Wenn ich weiß, dass das Objekt in der Tat ein Derived ist,
dann kann ich das mit dynamic_cast explizit sagen, und auf der lockeren
Schnittstelle von Derived ankommen.
|
Wenn ich das weiß, dann kann ich einen reinterpret_cast benutzen, den
dynamic_cast brauche ich nur, wenn ich die Vermutung habe, daß dem so
sei.
| Quote: | Meiner Meinung nach ist gerade b->foo(1,5000) nie ein korrekter Aufruf.
|
Das ist eher eine Frage der Philosophie. Mal zwei Programmiersprachen
als Extreme unter Ada (da würde man eh Subtypes benutzen und da könnte
man an die Funktion diese Werte schon gar nicht übergeben) wäre es ein
Fehler und unter Smalltalk nicht.
Man vermutet, daß im obigen Fall, daß derived ein Derived* ist und kein
Base*. (Das Beispiel ist trivial in dem Fall weiß man es, daß derived
eine Derived* ist. Aber in anderen Fällen ist das nicht so trivial zu
klären.)
| Quote: | Wenn ich der Schnittstelle Base adressiere, heißt es, dass ich am
Vertrag der Schnittstelle Base anhalten soll, egal was der dynamische
Typ ist.
|
Leider kann man das ganz schnell übersehen, es gibt nichts, was einem
dazu zwingt sich entsprechend zu verhalten.
| Quote: | Entweder ich weiß es, oder ich weiß es nicht.
|
Da widerspreche ich jetzt. Es gibt drei Zustände, wahr, unwahr und
unbekannt. Im letzten Fall braucht man den dynamic_cast.
| Quote: | Und wenn ich es weiß, dann
soll ich es sagen, und zwar wenn es so geht in einer Art sagen, dass die
Wahrheit meines Wissens zur Laufzeit überprüft werden kann.
|
Wahrheit ist absolut! Da gibt es nichts mehr zu überprüfen. Die
Überprüfung braucht man nur, wenn der Zustand unbekannt ist.
Aus formalen Gründen muß man den Base-Zeiger auch zu einem
Derived-Zeiger casten, aber das kann u.a. mit einem C-Cast machen. Das
Programm würde auch mit einem C-Cast fehlerfrei funktionieren. Bei
unbekannten Zustand sollte man so etwas tunlichst unterlassen.
--
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 |
|
 |
Tibor Pausz Guest
|
Posted: Sun Oct 03, 2004 7:41 am Post subject: Re: Preconditions - assert auch in überschriebener Memberfu |
|
|
Tobias Güntner <fatbull (AT) users (DOT) sourceforge.net> wrote:
| Quote: | Der Fehler ist, dass b->foo(1, 5000) aufgerufen wird, weil genau das im
"Vertrag" von Base verboten wird - man kann von keiner abgeleiteten
Klasse verlangen, dass sie Parameter in dieser Größenordnung akzeptiert.
|
Der Fehler ist das schlechte Design. Wenn es eine Funktion gibt die
einen bestimmten Parametersatz erwartet sollte das auch durch ihr
Interface dokumentiert sein. Hier wird für Base und Derived sowohl
"void foo (int,int)" benutzt, mit unterschiedlichen gültigen
Wertebereichen für die jeweiligen ints, da liegt das eigentliche
Problem.
| Quote: | Sollte jemand wissen, dass es sich tatsächlich um Derived handelt, hat
er eine völlig andere Schnittstelle mit anderen Bedingungen und kann
sich d->foo(1, 5000) erlauben.
|
Schnickschnack das Interface ist exakt dasselbe!
Du kannst dem Interface nicht ansehen, an den non virtual Funktionen
irgend ein DbC Code dranhängt, der den Wertebereich einschränkt. Dazu
muß man die Doku lesen, und nie vergessen, daß man die Parameter
entsprechend des Zugriffs über Basiszeiger bzw. abgeleitetem Zeiger
verwenden muß. Wohl dem der dabei die Übersicht in komplexeren
Programmen nicht verliert. Der Compiler kann auf Grund des gleichen
Interface nicht helfend eingreifen.
--
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 |
|
 |
Tibor Pausz Guest
|
Posted: Sun Oct 03, 2004 7:41 am Post subject: Re: Preconditions - assert auch in überschriebener Memberfu |
|
|
Torsten Robitzki <MyFirstname (AT) Robitzki (DOT) de> wrote:
| Quote: | Richtig, und der Teil, der prüft ist doch auch garnicht virtuell im
Beispiel von James.
|
Er ist aber abhängig davon, über welchen Zeigertyp man die Funktion
aufruft und das halte ich für schlecht.
Der eigentliche Problem ist das exakt gleiche Interface von foo in Base
und Derived. Beidesmal ist es
void foo(int,int)
egal was hier geschrieben wird, der Compiler kann nur auf dieses
Interface prüfen, der DbC Code entzieht sich seiner Kontrolle. Für mich
ist das wieder nur ein Beispiel, daß man mit DbC in C++ meisten
Flickschusterei betreibt.
--
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 |
|
 |
Tobias Güntner Guest
|
Posted: Sun Oct 03, 2004 2:43 pm Post subject: Re: Preconditions - assert auch in überschriebener Memberfu |
|
|
Tibor Pausz wrote:
| Quote: |
Wenn es eine Funktion gibt die
einen bestimmten Parametersatz erwartet sollte das auch durch ihr
Interface dokumentiert sein. Hier wird für Base und Derived sowohl
"void foo (int,int)" benutzt, mit unterschiedlichen gültigen
Wertebereichen für die jeweiligen ints, da liegt das eigentliche
Problem.
Sollte jemand wissen, dass es sich tatsächlich um Derived handelt, hat
er eine völlig andere Schnittstelle mit anderen Bedingungen und kann
sich d->foo(1, 5000) erlauben.
Schnickschnack das Interface ist exakt dasselbe!
|
Das Interface, das der Compiler sieht vielleicht, aber nicht das
(konzeptionelle) Interface, das du benutzt. Base und Derived sind
unterschiedliche Schnittstellen. Da ändert sich auch nichts durch die
Tatsache, dass man die Einhaltung der "Interface-Verträge" von Hand
eingeben muss und der Compiler es nicht direkt kann (jedenfalls nicht
dass ich wüsste).
Interfacedokumentation besteht übrigens nicht nur aus dem Code, der da
steht; man darf unbesorgt auch mal die Kommentare lesen, die
(hoffentlich) darauf hinweisen.
| Quote: |
Du kannst dem Interface nicht ansehen, an den non virtual Funktionen
irgend ein DbC Code dranhängt, der den Wertebereich einschränkt. Dazu
muß man die Doku lesen,
|
Ja und? Ach so, das macht ja niemand. Selber Schuld. Spätestens mit der
Assertion wird man aber darauf hingewiesen.
| Quote: | und nie vergessen, daß man die Parameter
entsprechend des Zugriffs über Basiszeiger bzw. abgeleitetem Zeiger
verwenden muß. Wohl dem der dabei die Übersicht in komplexeren
Programmen nicht verliert. Der Compiler kann auf Grund des gleichen
Interface nicht helfend eingreifen.
|
Man muss nicht beide gleichzeitig verwenden. Man kann die ganze Zeit den
Zeiger auf die abgeleitete Klasse verwenden, da alle erlaubten
Operationen mit der Basisklasse auch in mit abgeleiteten Klasse möglich
sind. Wo geht da die Übersicht verloren?
Anderes Beispiel: Mach aus dem class Derived mal ein Derived1 und ein
Derived2. Derived1 kann mit dem erweiterten Bereich umgehen, Derived2
allerdings nicht und behält die exakten Rahmenbedingungen der
Basisklasse. Wenn Du jetzt über die Basisklasse eine Funktion aufrufen
würdest, bei der Du zwar weißt, dass Derived1 mit dem größeren
Wertebreich umgehen kann, aber Du tatsächlich ein Derived2 vor Dir hast,
was dann?
--
Regards,
Tobias
--
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 |
|
 |
|
|
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
|
|