🚀 go-pugleaf

RetroBBS NetNews Server

Inspired by RockSolid Light RIP Retro Guy

Thread View: de.comp.lang.c++
10 messages
10 total messages Started by Bernhard Ruch Thu, 14 Mar 1996 18:23
Probleme beim Vererben von fstream
#1830
Author: Bernhard Ruch
Date: Thu, 14 Mar 1996 18:23
62 lines
2342 bytes
Ich habe folgendes Problem:

Ich moechte eine neue Klasse ObjectStream definieren, die von der
Klasse fstream vererbt wird. Zusaetzlich zu den gewoehnlichen
fstream-Operationen soll das Lesen und Schreiben von Objekten
moeglich sein.

Ich definiere also eine Klasse Object, eine Klasse ObjectStream,
die von fstream abgeleitet ist, und einen Operator << in ObjectStream,
der als Parameter eine Referenz auf ein Object erhaelt.
Die Klasse Objekt enthaelt eine Methode SaveData, die als Parameter
einen ObjectStream erhaelt. In dieser Methode versuche ich, eine Zahl
in den ObjektStream zu schreiben. 

Dies fuehrt zum folgenden Code:

  #include <fstream.h>

  class Object;

  class ObjectStream : public fstream {

    public:
      ObjectStream &operator << (const Object &pObject);

  }; /* ObjectStream */


  class Object {

    public:
    
      void SaveData (ObjectStream &ObjStream)  {
        ObjStream << 1;
      }; /* SaveData */

  }; /* Object */


Wenn ich nun versuche, diesen Code zu compilieren, meldet der Compiler,
dass er keinen Operator << fuer (ObjectStream, int) kenne, obwohl ja
ObjectStream von fstream abgeleitet ist und diesen Operator kennen 
muesste:

  g++ -c Test.cc
  Test.cc: In method `void Object::SaveData(class ObjectStream &)':
  Test.cc:20: no match for `operator <<(class ObjectStream, int)'

Wenn ich nun hingegen aus der Klasse ObjectStream den Operator << fuer
die Klasse Object herausnehme, 

(i.e. die Linie: "ObjectStream &operator << (const Object &pObject);" )

dann compiliert das Programm ohne Fehler. 

Heisst das, dass ich, falls ich den Operator << fuer die Klasse Object
ueberladen will, ich ihn auch fuer alle anderen Typen (int, char, long,
float, etc) neu definieren muss?

Fuer Hinweise zur Loesung dieses Problems waere ich dankbar.

Bernhard
Re: Probleme beim Vererben von fstream
#1832
Author: Dietmar Kuehl
Date: Thu, 14 Mar 1996 22:16
123 lines
5949 bytes
Hi,

ich habe mich ja schonmal gehoerig auf den A**** gesetzt, als ich einen
radikal anderen Ansatz bei einem Problem mit 'ostream's vorgeschlagen
habe... Also kann ich das ruhig nochmal machen :-)

Bernhard Ruch (Bernhard.Ruch@studi.epfl.ch) wrote:
: Ich moechte eine neue Klasse ObjectStream definieren, die von der
: Klasse fstream vererbt wird. Zusaetzlich zu den gewoehnlichen
: fstream-Operationen soll das Lesen und Schreiben von Objekten
: moeglich sein.

Hm. Wenn ich ganz ehrlich bin, vermute ich eher (aufgrund des Rests
Deines Artikels), dass Du in Wirklichkeit Problem und Loesung mixt: Du
moechtest Objekte lesen und schreiben. Wie das geloest wird ist erstmal
noch offen. Allerdings ist unwahrscheinlich, dass Du es vernuenftig
loesen kannst, indem Du von irgendeinen Stream ableitest: Die
verschiedenen Klassen, die von 'ostream' bzw. 'istream' abgeleitet sind
(z.B. 'ofstream' und 'ifstream'; 'fstream' gibt es in der Standard C++
Library garnicht) sind eigentlich nur Hilfsklassen, die ein 'ios' (die
gemeinsame Basisklasse von 'ostream' und 'istream') mit einem Objekt
einer von 'streambuf' abgeleiteten Klasse initialisieren! Die
verschiedene 'streambuf's unterscheiden sich darin WIE die Sachen
geschrieben werden und nicht WAS geschrieben wird. Genauso verhaelt es
auch mit klassen wie '[io]fstream':  Es wird ein File zum
schreiben/lesen benutzt. Bei '[io]stringstream' wird ein 'string'
benutzt usw.

WIE ein Objekt von einem Stream gelesen bzw. zu einem Stream
geschrieben wird, wird durch "Extractor" und "Insertor" Funktionen
definiert, also z.B. den folgenden beiden Funktionen:

  ostream &operator<< (ostream &os, Object const &obj)
  {
    // write the 'obj' to the 'os', e.g.:
    os << obj.data;
    return os;
  }

  istram &operator>> (istream &is, Object &obj)
  {
    // read the 'obj' from the 'is', e.g.:
    is >> obj.data;
    return is;
  }

Es empfiehlt sich ggf. eine Dokumentation von Streams zu beschaffen.
Die beiden Buecher aus denen ich den Umgang mit Streams gelernt habe
sind "The C++ IOstreams Handbook", S.Teale, Addison-Wesley (ziemlich
unuebersichtlich, eigentlich nicht zu empfehlen) und "The (Draft)
Standard C++ Library", P.J.Plauger, Prentice Hall (ganz nett, setzt
aber gute C++ Kenntnis voraus und ist eher eine Definition als ein
Tutorial). Es gibt ein deutsches Buch von Bernd E... (ich kann mir den
Namen nicht merken :-(, sorry...). Zu diesem Buch kann ich nichts
sagen, da ich es [noch] nicht gelesen habe.

: Ich definiere also eine Klasse Object, eine Klasse ObjectStream,
: die von fstream abgeleitet ist, und einen Operator << in ObjectStream,
: der als Parameter eine Referenz auf ein Object erhaelt.
: Die Klasse Objekt enthaelt eine Methode SaveData, die als Parameter
: einen ObjectStream erhaelt. In dieser Methode versuche ich, eine Zahl
: in den ObjektStream zu schreiben. 

Das laesst man zu diesem Zweck besser bleiben... Wenn Du eine neue
"externe Repraesentation" baust (z.B. eine Stream der in ein Fenster
von irgendeinem GUI schreibt), dann leitest Du von 'streambuf' ab und,
damit man einen passenden Stream einfacher bauen kann (und nur dafuer),
von 'ostream' und 'istream' z.B. 'owindowstream' und 'iwindowstream',
die einen entsprechenden Stream mit dem passenden 'streambuf'
konstruiren.

: Dies fuehrt zum folgenden Code:

[Geloescht, da der Code eh nicht weiterhilft...]

: Wenn ich nun versuche, diesen Code zu compilieren, meldet der Compiler,
: dass er keinen Operator << fuer (ObjectStream, int) kenne, obwohl ja
: ObjectStream von fstream abgeleitet ist und diesen Operator kennen 
: muesste:

:   g++ -c Test.cc
:   Test.cc: In method `void Object::SaveData(class ObjectStream &)':
:   Test.cc:20: no match for `operator <<(class ObjectStream, int)'

Der Compiler hat recht! Er sieht nur den 'operator << (ObjectStream,
Object &)', alle anderen Funktionen mit dem gleichen Namen (in diesem
Fall 'operator <<') sind versteckt.  Wenn Du sie wirklich benutzen
wolltest (was Du aber in diesem Fall garnicht wirklich willst, siehe
oben), dann koenntest Du es forcieren, z.B. mit 'ostream::operator<<
(17)'.

: Wenn ich nun hingegen aus der Klasse ObjectStream den Operator << fuer
: die Klasse Object herausnehme, 

: (i.e. die Linie: "ObjectStream &operator << (const Object &pObject);" )

: dann compiliert das Programm ohne Fehler. 

Klar, die Funktionen werden ja nicht mehr versteckt!

: Heisst das, dass ich, falls ich den Operator << fuer die Klasse Object
: ueberladen will, ich ihn auch fuer alle anderen Typen (int, char, long,
: float, etc) neu definieren muss?

Wenn Du auf Deiner Loesung bestehst, dann ja. 'using' kann etwas
abhilfe schaffen, wird aber zum einen noch nicht von allen Compilern
unterstuetzt und macht es auch notwendig, dass man alle Funktionen
nennt (glaube ich). Ausserdem wuerde der folgende Code nicht
funktionieren:

  f(ObjectStream &os, Object const &o)
  {
    os << "object: " << o << "\n";
  }

weil 'ostream &ostream::operator<< (char const *)' eine 'ostream&' und
keine 'ObjectStream&' zurueckgibt. Die beste und einfachste Loesung ist
also, einfach "Extractor" und "Inserter" Funktionen zu schreiben, die
z.B. einfach eine passende virtuelle Funktion aufrufen.
--
dietmar.kuehl@uni-konstanz.de
http://www.informatik.uni-konstanz.de/~kuehl
I am a realistic optimist - that's why I appear to be slightly pessimistic
Re: Probleme beim Vererben von fstream
#1837
Author: Bernhard Ruch
Date: Fri, 15 Mar 1996 12:36
88 lines
4463 bytes
Dietmar Kuehl wrote:
> 
> Hi,
> 
> ich habe mich ja schonmal gehoerig auf den A**** gesetzt, als ich einen
> radikal anderen Ansatz bei einem Problem mit 'ostream's vorgeschlagen
> habe... Also kann ich das ruhig nochmal machen :-)
> 
> Bernhard Ruch (Bernhard.Ruch@studi.epfl.ch) wrote:
> : Ich moechte eine neue Klasse ObjectStream definieren, die von der
> : Klasse fstream vererbt wird. Zusaetzlich zu den gewoehnlichen
> : fstream-Operationen soll das Lesen und Schreiben von Objekten
> : moeglich sein.
> 
> Hm. Wenn ich ganz ehrlich bin, vermute ich eher (aufgrund des Rests
> Deines Artikels), dass Du in Wirklichkeit Problem und Loesung mixt: Du
> moechtest Objekte lesen und schreiben. Wie das geloest wird ist erstmal
> noch offen. Allerdings ist unwahrscheinlich, dass Du es vernuenftig
> loesen kannst, indem Du von irgendeinen Stream ableitest: Die
> verschiedenen Klassen, die von 'ostream' bzw. 'istream' abgeleitet sind
> (z.B. 'ofstream' und 'ifstream'; 'fstream' gibt es in der Standard C++
> Library garnicht) sind eigentlich nur Hilfsklassen, die ein 'ios' (die
> gemeinsame Basisklasse von 'ostream' und 'istream') mit einem Objekt
> einer von 'streambuf' abgeleiteten Klasse initialisieren! Die
> verschiedene 'streambuf's unterscheiden sich darin WIE die Sachen
> geschrieben werden und nicht WAS geschrieben wird. Genauso verhaelt es
> auch mit klassen wie '[io]fstream':  Es wird ein File zum
> schreiben/lesen benutzt. Bei '[io]stringstream' wird ein 'string'
> benutzt usw.

Ich muss vielleicht die Ausgangslage genauer beschreiben:

Ich benoetige eine Klasse ObjectStream, welche die folgenden Operatoren enthaelt:
(Da dies Spezifikationen meines Projektes sind, kann ich sie nicht aendern)

	ObjectStream &operator << (char);
	ObjectStream &operator << (int);
	ObjectStream &operator << (float);
	etc.

	ObjectStream &operator >> (char &);
	ObjectStream &operator >> (int &);
	ObjectStream &operator >> (float &);
	etc.

	ObjectStream &operator << (const Object &);
	ObjectStream &operator >> (Object &);
	ObjectStream &operator << (const Object *);
	ObjectStream &operator >> (Object **);
	u.a.

Dies antwortet auf die Frage, WAS geschrieben und gelesen werden soll.

Da es schon verschiedene Stream-Klassen gibt, welche die Operatoren <<
und >> fuer die vordefinierten Typen (char, int, etc) enthalten, schien
es mir das einfachste zu sein, diese Operatoren wiederzuverwenden, indem
meine Klasse ObjectStream von einer dieser Stream-Klassen vererbt wird,
und dann die neuen Operatoren hinzuzufuegen.	

Nun zur Frage, WIE geschrieben und gelesen werden soll: Da diese Operatoren
auf ein File angewendet werden sollen, draengte es sich auf, die Klasse 
ObjectStream von fstream (anstatt iostream) zu vererben, da sie somit alle
File-spezifischen Eigenschaften von fstream wiederverwenden kann.

> ...

> : Wenn ich nun versuche, diesen Code zu compilieren, meldet der Compiler,
> : dass er keinen Operator << fuer (ObjectStream, int) kenne, obwohl ja
> : ObjectStream von fstream abgeleitet ist und diesen Operator kennen
> : muesste:
> 
> :   g++ -c Test.cc
> :   Test.cc: In method `void Object::SaveData(class ObjectStream &)':
> :   Test.cc:20: no match for `operator <<(class ObjectStream, int)'
> 
> Der Compiler hat recht! Er sieht nur den 'operator << (ObjectStream,
> Object &)', alle anderen Funktionen mit dem gleichen Namen (in diesem
> Fall 'operator <<') sind versteckt.  Wenn Du sie wirklich benutzen
> wolltest (was Du aber in diesem Fall gar nicht wirklich willst, siehe
> oben), dann koenntest Du es forcieren, z.B. mit 'ostream::operator<<
> (17)'.

Gibt es eine andere Art, die vordefinierten Operatoren <<, >> und die
File-spezifischen Eigenschaften von fstream wiederzuverwenden und 
neue Stream-Operatoren hinzuzufuegen, als von einer Stream-Klasse
zu vererben?

Bernhard
Re: Probleme beim Vererben von fstream
#1839
Author: Dietmar Kuehl
Date: Fri, 15 Mar 1996 19:42
167 lines
8388 bytes
Hi,
Bernhard Ruch (Bernhard.Ruch@studi.epfl.ch) wrote:
: Dietmar Kuehl wrote:
: > Bernhard Ruch (Bernhard.Ruch@studi.epfl.ch) wrote:
: > : Ich moechte eine neue Klasse ObjectStream definieren, die von der
: > : Klasse fstream vererbt wird. Zusaetzlich zu den gewoehnlichen
: > : fstream-Operationen soll das Lesen und Schreiben von Objekten
: > : moeglich sein.

[Beschreibung von dem, was ich fuer die vorgesehene Benutzung von
'[io]stream' abgeleiteten Klasse halte, geloescht...]

: Ich muss vielleicht die Ausgangslage genauer beschreiben:

Bei dem was Du Dir da eingebrockt hast, ist das allerdings neotig...!

: Ich benoetige eine Klasse ObjectStream, welche die folgenden Operatoren enthaelt:
: (Da dies Spezifikationen meines Projektes sind, kann ich sie nicht aendern)

<<< WARNUNG: Meine persoenliche Meinung ohne genau Kenntnis des Systems: BEGIN >>>
Ich kann, ohne genaues Wissen ueber das System, was gebaut werden soll,
natuerlich keinen wirklich qualifizierten Kommentar zu dessen Design
abgeben, aber ich denke dennoch, dass an dieser Stelle das Design FUBAR
ist: Hier waere es sicher ratsam gewesen, sich VOR der Spezifikation zu
informieren, wie man IOstreams benutzt.
<<< Meinung, Flame: END >>>

: 	ObjectStream &operator << (char);
: 	ObjectStream &operator << (int);
: 	ObjectStream &operator << (float);
: 	etc.

: 	ObjectStream &operator >> (char &);
: 	ObjectStream &operator >> (int &);
: 	ObjectStream &operator >> (float &);
: 	etc.

: 	ObjectStream &operator << (const Object &);
: 	ObjectStream &operator >> (Object &);
: 	ObjectStream &operator << (const Object *);
: 	ObjectStream &operator >> (Object **);
: 	u.a.

: Dies antwortet auf die Frage, WAS geschrieben und gelesen werden soll.

OK, hier ist womoeglich ein Super-GADF (groesster anzunehmender
Design-Fehler) auszubuegeln. Als erstes ein Versuch der
Schadensbegrenzung, mit dem man das alles noch unter den Teppich kehren
kann (typisches vorgehen, etwa bei Unfaellen im Zusammenhang mit
Kernkraftwerken): Wenn die Spezifikation ist das folgendes legaler Code
sein soll:

    void f(ObjectStream &OS, Object const &o) { OS << o; }

(analog die anderen Operatoren fuer 'ObjectStream' die mit 'Object'
bzw. "u.a" arbeiten) und NICHT gefordert ist, dass es einen Member
'ObjectStream::operator<< (Object const &)' gibt (und auch keiner von
den anderen inklusive "u.a.") dann kann man noch was retten: man kommt
sogar mit einem blauen Auge (durch meine Kritik und ggf. die [kleinere]
Spezifikationsaenderung) weg. Der Ansatz ist dann einfach, dass man
virtuell je eine Klasse von 'ifstream' und 'ofstream' ableitet (etwa
'iObjstStream' und 'oObjectStream') und dann 'ObjectStream' von den
beiden anderen Klassen ableitet. Die passenden Funktionen fuer alle
vorhanden Inserters und Extractor werden geerbt und man fuegt die neuen
so zu, wie ich es in meinem vorherigen Artikel geschrieben habe.

Wann dieser Ansatz fehlschlaegt, sieht es schon duesterer aus. Jetzt
kommt zur Schadensabwendung schon richtig Arbeit auf Dich zu: Da Du die
Operatoren fuer 'Object' und Verwandte als Member von 'ObjectStream'
definieren MUSST (das sagt ja dann offenbar Deine Spezifikation, sonst
waere der Fall oben eingetreten), werden schonmal alle Operatoren fuer
vorhandene Typen versteckt. Sie muessen fuer 'ObjectStream' wieder
sichtbar gemacht werden und zwar so, dass sie einen 'ObjectStream'
zurueckliefern, also sind Funktionen der folgenden Art faellig:

  ObjectStream &operator<< (char c)
  {
    ostream::operator<< (c);
    return *this;
  }

Nicht viele, da die Anzahl von 'ostream::operator<<(T)' begrenzt ist
(zwanzig oder so).  Analog fuer 'operator>>()'. Aber das reicht
vermutlich noch nicht: Wenn alle anderen geschriebenen Inserter und
Extractor Funktionen auch noch funktionieren sollen, dann muessen die
auch einen 'ObjectStream' liefern. Die einfachste Moeglichkeit duerfte
dafuer folgendes sein:

  template <class T>
  inline ObjectStream &operator << (ObjectStream &OS, T const &t)
  {
    operator<< (static_cast<ostream&>(OS), t);
    return OS;
  }

Analog fuer 'operator>>()'. Was u.U. noch bleibt sind die verschiedenen
Manipulatoren.  Allerdings denke ich, dass die dadurch auch erschlagen
worden sind...

: Da es schon verschiedene Stream-Klassen gibt, welche die Operatoren <<
: und >> fuer die vordefinierten Typen (char, int, etc) enthalten, schien
: es mir das einfachste zu sein, diese Operatoren wiederzuverwenden, indem
: meine Klasse ObjectStream von einer dieser Stream-Klassen vererbt wird,
: und dann die neuen Operatoren hinzuzufuegen.	

Der subtile Unterschied zwischen Deiner Beschreibung und der
tatsaechlichen Implementation ist der: Es gibt jeweils genau EINE
Klasse, welche die Operatoren << bzw. >> fuer die built-in Typen
definiert: 'ostream' bzw. 'istream'. Die anderen Klassen definieren
KEINEN derartigen Operator, sondern erben ALLE! Das ist elementar
wichtig, da sonst eben gerade die passenden Operatoren versteckt sind.
Wie ich schon in meinem vorherigen Artikel geschrieben habe, sind die
von 'ostream' (bzw. 'istream') abgeleiteten Klassen nur fuer den
Komfort da: Damit man 'ofstream file("foobar")' schreiben kann und
nicht

  auto_ptr<filebuf> fb = new filebuf("foobar");
  ostream file(fb.get());

schreiben muss.

: Nun zur Frage, WIE geschrieben und gelesen werden soll: Da diese Operatoren
: auf ein File angewendet werden sollen, draengte es sich auf, die Klasse 
: ObjectStream von fstream (anstatt iostream) zu vererben, da sie somit alle
: File-spezifischen Eigenschaften von fstream wiederverwenden kann.

Da faellt mir eigentlich nur ein: RTFM. ... und zwar bevor man die
Spezifikation fuer ein Projekt akzeptiert! Wenn moeglich, empfehle ich
Dir dringend, ueber eine Aenderung der Spezifikation zu verhandeln, so
dass zumindest die erste von mir beschriebene Technik moeglich wird!
Wenn irgend moeglich, sollte auch 'ObjectStream' aus der Spezifikation
verschwinden.

: > : Wenn ich nun versuche, diesen Code zu compilieren, meldet der Compiler,
: > : dass er keinen Operator << fuer (ObjectStream, int) kenne, obwohl ja
: > : ObjectStream von fstream abgeleitet ist und diesen Operator kennen
: > : muesste:
: > 
: > :   g++ -c Test.cc
: > :   Test.cc: In method `void Object::SaveData(class ObjectStream &)':
: > :   Test.cc:20: no match for `operator <<(class ObjectStream, int)'
: > 
: > Der Compiler hat recht! Er sieht nur den 'operator << (ObjectStream,
: > Object &)', alle anderen Funktionen mit dem gleichen Namen (in diesem
: > Fall 'operator <<') sind versteckt.  Wenn Du sie wirklich benutzen
: > wolltest (was Du aber in diesem Fall gar nicht wirklich willst, siehe
: > oben), dann koenntest Du es forcieren, z.B. mit 'ostream::operator<<
: > (17)'.

: Gibt es eine andere Art, die vordefinierten Operatoren <<, >> und die
: File-spezifischen Eigenschaften von fstream wiederzuverwenden und 
: neue Stream-Operatoren hinzuzufuegen, als von einer Stream-Klasse
: zu vererben?

Ja. Wie ich schon in meinem vorherigen Artikel beschrieben habe, gibt
es eigentlich ueberhaupt keinen Grund, von 'fstream' (oder 'ofstream'
bzw. 'ifstream', da es ja 'fstream' im Standard eh nicht gibt)
abzuleiten, um weitere Operatoren << bzw. >> inzuzufuegen: Schreib
Inserter- und Extractor- Funktionen fuer 'ostream' bzw. 'istream' und
alle funktioniert, wie man es eigentlich gerne haette. ... und noch
mehr: Es funktionert naemlich fuer ALLE Streams! Das ist DIE Technik,
um sowas zu machen. So gehoert (mit allerhoechster Wahrscheinlichkeit)
die Spezifikation fuer Dein Projekt definiert!
--
dietmar.kuehl@uni-konstanz.de
http://www.informatik.uni-konstanz.de/~kuehl
I am a realistic optimist - that's why I appear to be slightly pessimistic
Re: Probleme beim Vererben von fstream
#1842
Author: Dietmar Kuehl
Date: Sat, 16 Mar 1996 09:34
347 lines
16911 bytes
Hi,

[Falls jemand meinen Artikel zu diesem Thread von Freitag abend gesehen
hat: Der war teilweise ein Flame. Daher habe ich ihn ge'cancel't: Bitte
ignoriert ihn einfach... Dieser hier wird dafuer ausfuehrlich und
technisch (allerdings nicht auf 100 Zeilen beschraenkt).]

Bernhard Ruch (Bernhard.Ruch@studi.epfl.ch) wrote:
: Dietmar Kuehl wrote:
: > 
: > Hi,

Nebenbei bemerkt: Bitte, quotet grundsaetzlich nur die releventen
Teile. Die Begruessung und die Signature gehoeren typischerweise NICHT
dazu. Neben verschwendeter Bandbreite (weniger die paar Bytes, als die
Zeit die zum Lesen der Quotes verwendet) gibt es auch ein
Copyright-Problem: Wenn der Text nicht signifikante Modifikationen
enthaelt, darf er ggf. garnicht nochmal gepostet werden! Etwa einen
fremden Artikel zu posten ist ein Verstoss gegen bestehende
Copyright-Gesetze! Zumindest in einigen Laendern und de.comp.lang.c++
wird auch ausserhalb von Deutschland gelesen...

: > Bernhard Ruch (Bernhard.Ruch@studi.epfl.ch) wrote:

[Beschreibung, wie ich denke dass '[io]stream' gedacht sind, geloescht.
Siehe ggf. vorherigen Artikel.]

: Ich muss vielleicht die Ausgangslage genauer beschreiben:

Das erscheint mir in der Tat noetig. Auch die unten beschriebene Lage
gibt noch reichlich Spielraum: Wenn ich bei allen Versuchen, eine
Interpretation davon zu geben danebenliege, dann kontaktiere mich bitte
per e-mail: Dann koennen wir das intern ausdiskutieren und ich poste
dann ggf. eine Beschreibung des Problems samt Loesung.

: Ich benoetige eine Klasse ObjectStream, welche die folgenden Operatoren enthaelt:
: (Da dies Spezifikationen meines Projektes sind, kann ich sie nicht aendern)

Schlechte Spezifikationen taugen nichts und nicht nur, dass man sie
aendern kann, man MUSS sie geradezu aenderen (selbstverstaendlich in
Absprache, da ja von den Spezifikationen vermutlich noch andere Sachen
abhaengen)! Eine Argumentation der Art "Ich muss mit einem schlechten
Design arbeiten, weil das die Spezifikationen so vorsehen" ist grob
straeflich. Es gibt nur eine Ausrede, ein [erkannt] schlechtes Design
zu verwenden: ALLE Alternativen sind noch schlechter. Schlechtes Design
ist ein beliebig grosser Kostenfaktor. Je groesser das Projekt ist,
desto groesser sind die Kosten, die man durch schlechtes Design
einfaehrt. Damit leben heisst, bewusst Kosten verursachen, anstatt zu
versuchen Kosten zu sparen. Ausserdem ist es nicht so, dass Du
demjenigen/derjenigen, der die Spezifaktion geschrieben hat schadest,
im Gegenteil: Der/die will den Programmteil von Dir ja verwenden. Wenn
der verbessert werden kann, ist allen geholfen!

: 	ObjectStream &operator << (char);
: 	ObjectStream &operator << (int);
: 	ObjectStream &operator << (float);
: 	etc.

: 	ObjectStream &operator >> (char &);
: 	ObjectStream &operator >> (int &);
: 	ObjectStream &operator >> (float &);
: 	etc.

: 	ObjectStream &operator << (const Object &);
: 	ObjectStream &operator >> (Object &);
: 	ObjectStream &operator << (const Object *);
: 	ObjectStream &operator >> (Object **);
: 	u.a.

: Dies antwortet auf die Frage, WAS geschrieben und gelesen werden soll.

Noe: Sollen die Sachen binaer oder formatiert geschrieben werden?
Sollen wirklich alle Operatoren Member sein oder ist nur die
entsprechende Aufruf-Syntax gefragt? Was ist "etc": Die built-in Typen
oder alle Typen fuer die ein Extractor/Inserter existiert? Soll
'ObjectStream' genau die genannten Sachen schreiben oder eigentlich
kompatibel zu '[io]stream' sein?

Es ist also, kurz gesagt, noch reichlich Freiraum fuer Interpretationen.

: Da es schon verschiedene Stream-Klassen gibt, welche die Operatoren <<
: und >> fuer die vordefinierten Typen (char, int, etc) enthalten, schien
: es mir das einfachste zu sein, diese Operatoren wiederzuverwenden, indem
: meine Klasse ObjectStream von einer dieser Stream-Klassen vererbt wird,
: und dann die neuen Operatoren hinzuzufuegen.	

OK, da hast Du ggf. recht: Wenn die Ein-/Ausgabe schoen formatiert sein
soll und so, dann will man die schon definierten Operatoren gerne
wiederverwenden. Allerdings kann man Objekte neuer Klassen formatiert
schreiben, ohne eine neue Klasse einzufuehren. Offenbar gibt es
allerdings irgendeinen [mir verborgenen] Grund, eine Klasse
'ObjectStream' zu definieren. Ich gehe also im folgenden erstmal davon
aus, dass 'ObjectStream' unausweichlich ist und auf jeden Fall
geschrieben werden muss. Weiter unten versuche ich dann nochmal dagegen
anzugehen, aber es scheint ja wichtige Gruende fuer 'ObjectStream' zu
geben. Eine moeglicher Grund koennte sein, dass 'ObjectStream' sowohl
Ein- als auch Ausgabe machen soll: Ein derartiger Stream ist im
Standard nicht vorgesehen.  Allerdings tut man sich bei den
Rueckgabewerten der verschiedenen Operatoren schwer, wenn man diese
wirklich von '[io]stream' erbt: Die liefern naemlich, komme was wolle,
entweder einen 'istream' oder einen 'ostream'. Wenn das nicht reicht,
dann braucht man andere Funktionen. Aber eins nach dem anderen: Erstmal
gucken, was 'ObjectStream' eigentlich sein soll.

: Nun zur Frage, WIE geschrieben und gelesen werden soll: Da diese Operatoren
: auf ein File angewendet werden sollen, draengte es sich auf, die Klasse 
: ObjectStream von fstream (anstatt iostream) zu vererben, da sie somit alle
: File-spezifischen Eigenschaften von fstream wiederverwenden kann.

Aha, 'ObjectStream' soll auf ein File schreiben bzw. von einem File
lesen. 'fstream' dafuer zu verwenden ist allerdings auf jeden Fall
gewagt (das habe ich schon in meinem vorherigen Artikel erwaehnt), da
'fstream' keine Standard-Klasse ist. Ausserdem ist 'fstream' ganz
sicher nicht dazu gedacht, dass von 'fstream' (public) abgelitten
wird.  Besser ist es direkt von 'istream' und 'ostream' abzuleiten und
einen 'filebuf' mitzugeben oder vielleicht garnicht abzuleiten. Was man
dann noch fuer Zugriffe fuer den 'filebuf' zur Vergueng stellt liegt im
Ermessen von demjenigen, der es verwenden soll: '[io]fstream' fuegen eh
nur ein paar (forwarding) Funktionen hinzu ('rdbuf()', 'isopen()',
'open()' und 'close()'), so dass es sich nicht wirklich lohnt dafuer
etwaige Problem in Kauf zu nehmen (etwa dass bei virtueller Vererbung
zwei 'filebuf's erzeugt werden oder so).

Da es es sich eh nicht lohnt, die Operatoren von 'istream' bzw.
'ostream' zu erben (die liefern wie gesagt immer den entsprechenden
Stream), versuche ich es garnicht erst! Das heisst allerdings nicht,
dass ich die entsprechenden Operatoren nicht verwenden werde. Nur mache
ich es halt anders. Also erstmal den Stream bauen (ich zeige nur die
"aeusseren" Details, schliesslich werde ich weder als Programmierer
noch als Consultant bezahlt... Naja, eigentlich schon, aber nicht von
den Leuten, denen ich hier helfe):

  // Fuer eine Implementation von 'auto_ptr' siehe etwa "More Effective
  // C++", S.Meyers, Addison-Wesley
  #include <auto_ptr>

  class ObjectStream
  {
  private:
    auto_ptr<filebuf> i_buf;  // darueber wird die IO gemacht
    istream           i_in;
    ostream           i_out;
  
  public:
    explicit ObjectStream(const char* fname, ios::openmode mode = in):
      i_buf(new filebuf()),
      i_in(i_buf.get()),
      i_out(i_buf.get())
    {
      i_buf->open(fname, mode);
    }
    // ... mal sehen, was noch so gebraucht wird.
  };

Also, was ein 'ObjectStream' macht, ist irgendwie aehnlich zu einem
'ios'-Objekt (vielleicht lohnt es sich von 'ios' abzuleiten, aber das
ist nicht klar...), nur einfacher: Es wird ein Pointer zu einem
'filebuf' verwaltet ('i_buf'). Die Verwaltung besteht insbesondere auch
darin, dass das Objekt bei allen passenden Gelegenheiten geloescht wird
(daher der 'auto_ptr'). Anstatt einem 'filebuf' kann man was beliebiges
anderes nehmen, sofern es nur eine 'concrete class' ist.

Die Member 'i_in' und 'i_out' werden benutzt, um die bereits
vorhandenen Inserter/Extractor zu verwenden. Dabei gibt es drei
moegliche Ansaetze (in der Erlaeuterung ist 'os' ein 'ObjectStream' und
't' ist ein Objekt von einem Typ, fuer den es einen Inserter und einen
Extractor gibt):

  1. Es genuegt, dass man die Expressions 'os << t' und 'os >> t'
     schreiben kann und der Typ der Expression ist jeweils
     'ObjectStream&'.
  2. Alle Operatoren muessen wirklich Memberfunktionen sein (d.h. es
     ist wirklich noetig, dass man 'os.operator<< (t)' bzw.
     'os.operator>> (t)' schreiben kann), aber es gibt fuer den
     Compiler Member-Templates (OK, ist nur ein Scherz: soweit ich
     weiss gibt es kaum/keinen Compiler, die/der bereits
     Member-Templates unterstuetzen/unterstuetzt). Insbesondere spricht
     die Existenz von 'fstream' dagegen, dass der verwendete Compiler
     in der Naehe des DWP ist...
  3. Wie 2., aber es gibt keine Member-Templates (das ist der Fall, der
     wirklich uebel ist: Da fuehrt kein Weg an einem, IMHO, schlechten
     Design dran vorbei)

Im 1. Fall geht noch alles ganz gut und es reichen zwei zusaetliche
Member-Funktionen von 'ObjectStream' und zwei Templates, um schonmal
die vorhanden Operatoren (fuer alle Klassen mit Inserter/Extractor) zu
verwenden:

  ostream &ObjectStream::out() { return i_out; }
  istream &ObjectStream::in()  { return i_in; }

  template <class T>
  inline ObjectStream &operator<< (ObjectStream &os, T const &t)
  {
    os.out() << t;
    return os;
  }

  template <class T>
  inline ObjectStream &operator>> (ObjectStream &os, T &t)
  {
    os.in() >> t;
    return os;
  }

It's magic, aber das reicht schonmal, um die vorhanden Operatoren fuer
'[io]stream' zu benutzen! Wenn auch noch die Manipulator verwendet
werden muessen, braucht man vielleicht noch ein paar Templates mehr,
aber die sind nach genau dem selben Stil zu schreiben, wie die beiden
oben. Die 'inline's sind uebrigens nicht fuer Effizienz, sondern um
Probleme mit der Instantiierung zu vermeiden.

Fall 2, wenn er denn tatsaechlich eintreten sollte, ist vielleicht noch
schoener, weil man die Member 'in()' und 'out()' nicht braucht,
andererseits ist man vermutlich trotzdem gut beraten, sie zur
Verfuegung zu stellen... Der Unterschied zu Fall 1 ist lediglich, dass
man wirklich Member hat (also so, wie es anscheinend gefordert ist):

  template <class T>
  inline ObjectStream &ObjectStream::operator<< (T const &t)
  {
    i_out << t;
    return *this;
  }

  template <class T>
  inline ObjectStream &ObjectStream::operator>> (T &t)
  {
    i_in >> t;
    return *this;
  }

Selbstverstaendlich muss man die Member passend in der Klasse
deklarieren und fuer Manipulatoren gilt das selbe wie im Fall 1.

Fall 3: "Das waer ihr Preis gewesen..." heisst es hier, denn das ist
die Niete (und bei dem was ich aus Deinem Artikel lese und dem Stand
der Compiler, das was der Fall ist). Je nachdem, ob "etc." in der
Spezifikation fuer "built-in Typen" oder fuer "alle Typen mit
Extractor/Insertor" steht, ist diese "Loesung" praktikabel oder eben
gerade nicht: Da die Operatoren einen 'ObjectStream' liefern sollen,
kann man sie nicht erben, sondern muss einen Wrapper schreiben. Da sie
Member sein sollen, muessen sie in die Klasse und da keine
Member-Templates unterstuetzt werden, muss man jeden Operator explizit
schreiben, so trivial er auch ist: Im Prinzip genau das, was beim Fall
2 passiert, nur statt 'T' jeden einzelnen Typ, z.B.:

  inline ObjectStream &ObjectStream::operator<< (char c)
  {
    i_out << c;
    return os;
  }

  inline ObjectStream &ObjectStream::operator>> (char &c)
  {
    i_in >> c;
    return os;
  }

"etc.", fuer die eingebauten Typen immerhin schon 16 mal (das ist die
Anzahl der entsprechenden Operatoren, die ich fuer 'ostream' gezaehlt
habe; allerdings sind nicht alle nur fuer die built-in Typen). Fuer
alle Typen mit Inserter/Extractor (diesen Fall werde ich im folgenden
als "Super-GAD", "groesster anzunehmender Designfehler" bezeichnen;
warum siehe unten), beliebig viele!

Falls der Super-GAD eingetreten ist, tun sich alle Beteiligten einen
grossen Gefallen, wenn die Spezifikation, etwa zum Fall 1 hin,
geaendert wird: Fuer Dich heisst das, dass Du nicht jetzt schonmal
massenhaft dieser trivialen Operatoren schreiben musst (auch wenn man
das zu einem Makro macht, heisst das, dass man massenhaft Typnamen
eintasten muss). Fuer die Benutzer ist es aber auch von Vorteil, wenn
dieser Ansatz nicht benutzt wird: Fuer jede neue Klasse mit
Extractor/Inserter muesste ein neues Paar von Funktionen hinzugefuegt
werden. Das bedeutet, dass ALLE Files, in denen 'ObjectStream' benutzt
wird, recompiliert werden muessen (nicht wirklich, aber alles andere
fuehrt normalerweise zu einem nicht handhabbaren Pfusch). Das ist nicht
akzeptabel, insbesondere, da es eine bessere Loesung gibt (etwa Fall 1,
bzw., IMHO, noch besser ganz ohne 'ObjectStream'). Daher halte ich,
falls wirklich dieser Fall da ist, es fuer einen Designfehler, wie er
kaum noch groesser sein koennte. Im uebrigen halt ich Fall 3, den nicht
Super-GAD Teil, (und auch, wenn man ganz ehrlich ist, Fall 2) fuer eine
GAD, halt einen, bei dem man den Schaden begrenzen kann (im Gegensatz
zum Super-GAD, wo der Designfehler unbegrenzt viel Arbeit nach sich
zieht).

Wie dem auch sein, alle anderen Operatoren lassen sich zu
'ObjectStream' hinzufuegen, wie man gerade lustig ist: Als Member oder
als nicht-Member. Wenn man sie als nicht-Member hinzufuegt, sollte man
sich ueberlegen, ob man sie nicht gleich zu 'ostream' bzw. 'istream'
hinzufuegt: Warum einschraenken, wenn man es auch allgemein schreiben
kann...

Das fuehrt mich nochmal zurueck, zu dem was ich bereits in meinem
vorherigen Artikel geschrieben habe: Spezifikation hin, Spezifikation
her. Egal. Wenn die Spezifikation Mist ist und es keine wirklich guten
Grund gibt, sie beizubehalten, dann muss sie eben geaendert werden.
Ein Grund wie "Das steht jetzt aber schon in der Spezifikation" ist
uebrigens kein wirklich guter Grund:  Designfehler muessen raus, so
schnell wie moeglich (nicht "koste es was es wolle", denn das tut es,
wenn der Designfehler drin bleibt :-). Damit man mit einem Designfehler
lebt, muss es schon wirklich triftige Gruende geben, denn meist sind
die Kosten hoeher, Problem aufgrund schlechten Designs zu umgehen, als
das Design zu aendern. Insbesondere, wenn noch keiner oder wenig Code
existiert. Da Du Dich nicht dazu geaeussert hast, warum 'ObjectStream'
noetig ist (ausser, dass es in der Spezifikation steht, was fuer mich
ein Ungrund ist, wenn es Argumente dagegen gibt), gehe ich jetzt davon
aus, dass 'ObjectStream' nur dazu da ist, 'os << object' bzw. 'os >>
object' ('os' wieder ein 'ObjectStream' und 'object' ein 'Object' oder
so) samt Verwandten zu ermoeglichen. Das ist z.B. ein ganz schlechter
Grund und ein grober Schnitzer im Design => Spezifikation aendern. Das
macht man anders:

  istream &operator>> (istream &in, Object &o)
  {
    // ... was fuer 'Object' noetig ist
    return in;
  }

  ostream &operator<< (ostream &out, Object const &o)
  {
    // ... was fuer 'Object' noetig ist
    return out;
  }

Naemlich so.

: Gibt es eine andere Art, die vordefinierten Operatoren <<, >> und die
: File-spezifischen Eigenschaften von fstream wiederzuverwenden und 
: neue Stream-Operatoren hinzuzufuegen, als von einer Stream-Klasse
: zu vererben?

Sicher. Siehe oben. Das ist ein Beispiel, wie man es machen kann. Man
kann auch die file-spezifischen Eigenschaften von 'fstream' verwenden,
ohne 'fstream' zu verwenden: Alles was an 'fstream' file-spezifisch
ist, steckt in der Klasse 'filebuf'.  Alles was 'fstream' (bzw. nach
Standard ja nur '[io]fstream') macht, ist seiner Basisklasse bzw.
seinen Basisklassen einen Pointern zu einem 'filebuf' beim
Konstruktoraufruf mitzugeben (die vier Service-Funktionen sind schnell
selbstgebaut). Das koennte man z.B. verwenden, wenn man binaer anstatt
formatiert schreiben will. Allerdings muesste man dann die ganzen
anderen Operatoren wirklich selbst schreiben, da es im Standard keine
Methode gibt, Objekte binaer zu schreiben...
--
dietmar.kuehl@uni-konstanz.de
http://www.informatik.uni-konstanz.de/~kuehl
I am a realistic optimist - that's why I appear to be slightly pessimistic
Re: Probleme beim Vererben von fstream
#1872
Author: Thomas Holubar
Date: Fri, 22 Mar 1996 00:05
39 lines
1818 bytes
Hallo Bernhard !

Ich mache es mir einmal einfach und gehe nicht explizit auf Dein Beispiel ein,
sondern gebe ein Beispiel zur Erzeugung einer "streamable class". Ich hoffe es
bringt Dich weiter:

Zunaechst: ein ueberladener stream-Operator _muss_ eine "friend-function" sein.
Ein Ausgabeoperator sieht dann in etwa so aus:

ostream & operator<< (ostream &os, Object &obj)
{
	os << "Objekt-atom1";
	os << "Objekt-atom2";
	...
	os << "Objekt-atom3";	// Objekt-atome sind z.B. die Teile einer 
				// Struktur etc.
	return os;		// Die stream-Referenz weiterreichen
}

Eine zugehoerige Klasse waere dann in etwa wie folgt zu definieren:

class AnyClass
{
	// evtl. Daten hier
	friend ostream & operator<< (ostream &, AnyClass &);
     public:
	// Konstruktoren etc.
};

Wenn Du das entsprechend auf Dein Problem hin modifizierst muesste es klappen.

Viel Erfolg!

Thomas
-- 
Thomas Holubar  / finger holubar@rz.uni-frankfurt.de for PGP key
                / eMail: holubar@stud.uni-frankfurt.de
                / WWW  : http://www.uni-frankfurt.de/~holubar
"Win95: from the people who brought you 'edlin' !" (seen on USENET)
Re: Probleme beim Vererben von fstream
#1877
Author: Harald Fuchs
Date: Fri, 22 Mar 1996 17:53
20 lines
1495 bytes
Thomas Holubar <holubar@informatik.uni-frankfurt.de> writes:

> [deleted]

> Zunaechst: ein ueberladener stream-Operator _muss_ eine
> "friend-function" sein.

Nein.  Eine externe Funktion, die auf private Member zugreift, muß
eine Friend-Funktion sein.  Das ist für einen Stream-Operator aber
nicht unbedingt der Fall:

  ostream& operator<< (ostream& s, const complex& x) {
    return s << x.re() << "+" << x.im() << 'i';
  }

muß kein Friend sein, da re() und im() wohl ohnehin public sind.

> "Win95: from the people who brought you 'edlin' !" (seen on USENET)

Ist "edlin" wirklich so furchtbar, wie alle behaupten? :-)
Re: Probleme beim Vererben von fstream
#1887
Author: Thomas Holubar
Date: Sun, 24 Mar 1996 04:00
81 lines
3290 bytes
hf@colibri.de (Harald Fuchs) wrote:
>Thomas Holubar <holubar@informatik.uni-frankfurt.de> writes:
>
>> [deleted]
>
>> Zunaechst: ein ueberladener stream-Operator _muss_ eine
>> "friend-function" sein.
>
>Nein.  Eine externe Funktion, die auf private Member zugreift, muß
>eine Friend-Funktion sein.  Das ist für einen Stream-Operator aber
>nicht unbedingt der Fall:
>
Ok, mit meinem "muss friend-function sein" habe ich mich nicht
richtig ausgedrueckt. Allerdings wuerde ich doch _sehr_ empfehlen
stream-Operatoren als "friend" zu deklarieren.

Der Unterschied zwischen

class Unkonventionell
{
..
ostream & operator<< (ostream &);
..
};

und

class Normal
{
..
friend ostream & operator<< (ostream &, Normal &);
..
};

liegt darin, dass erstere zu einer wahrscheinlich nicht gewollten
Verhaltensweise fuehrt (was natuerlich m/reine Spekulation ist):

Man betrachte folgende Anweisung:

cout << A << B << C;
// entspricht: ((cout << A) << B) << C;

Dies wuerde "normalerweise" zu folgender Evaluation fuehren:
1. (cout << A) // schreibt A nach cout, liefert cout
2. (cout << B) // " B " " "
3. cout << C   // " C " " "

Mit einer als friend deklarierten Funktion klappt das auch ganz wunderbar,
nicht aber mit der unkonventionellen Variante.
Diese sieht naemlich eigentlich so aus:
ostream & operator<< (Unkonventionell *, ostream &);
                        ^
                        |
                     versteckter "this"-Zeiger

Abgesehen davon, dass "this" ein Zeiger und keine Referenz ist sind die
Parameter hier auch in einer "unkonventionellen" Reihenfolge (was natuerlich
Definitionssache ist).

Um die gleiche Ausgabe wie in obigem Beispiel zu erhalten muesste ich jetzt
also wie folgt codieren:

C << (B << (A << cout));

Obwohl das sicherlich wunderbar funktioniert (hab's allerdings noch nicht
selbst probiert) bleibe ich doch lieber bei der "konventionellen" Methode ;-)

>
>> "Win95: from the people who brought you 'edlin' !" (seen on USENET)
>
>Ist "edlin" wirklich so furchtbar, wie alle behaupten? :-)

Wenn alle es behaupten kann es doch gar nicht falsch sein - oder? ;-)
(Ausserdem ist das "Niedermachen" von M$-Software doch Mega-In und wer
will sich denn schon selbst outen indem er/sie da nicht mitmacht :-))

-- 
Thomas Holubar  / finger holubar@rz.uni-frankfurt.de for PGP key
                / eMail: holubar@stud.uni-frankfurt.de
                / WWW  : http://www.uni-frankfurt.de/~holubar
"Win95: from the people who brought you 'edlin' !" (seen on USENET)
Re: Probleme beim Vererben von fstream
#1891
Author: Dietmar Kuehl
Date: Sun, 24 Mar 1996 18:49
71 lines
2809 bytes
Hi,
Thomas Holubar (holubar@informatik.uni-frankfurt.de) wrote:
: hf@colibri.de (Harald Fuchs) wrote:
: >Thomas Holubar <holubar@informatik.uni-frankfurt.de> writes:
: >
: >> [deleted]
: >
: >> Zunaechst: ein ueberladener stream-Operator _muss_ eine
: >> "friend-function" sein.
: >
: >Nein.  Eine externe Funktion, die auf private Member zugreift, muß
: >eine Friend-Funktion sein.  Das ist für einen Stream-Operator aber
: >nicht unbedingt der Fall:
: >
: Ok, mit meinem "muss friend-function sein" habe ich mich nicht
: richtig ausgedrueckt. Allerdings wuerde ich doch _sehr_ empfehlen
: stream-Operatoren als "friend" zu deklarieren.

: Der Unterschied zwischen

: class Unkonventionell
: {
: ..
: ostream & operator<< (ostream &);
: ..
: };

: und

: class Normal
: {
: ..
: friend ostream & operator<< (ostream &, Normal &);
: ..
: };

: liegt darin, dass erstere zu einer wahrscheinlich nicht gewollten
: Verhaltensweise fuehrt (was natuerlich m/reine Spekulation ist):

[deleted]

Du hast offensichtlich den Einwand von Harald Fuchs nicht verstanden.
Er sagt NICHT, dass der Inserter ein Member sein soll, sondern er sagt,
dass es nicht notwendigerweise ein 'friend' sein muss: Wenn der
Inserter nicht auch 'private' oder 'public' member zugreifen muss, dann
braucht er auch kein 'friend zu sein. z.B. kann man eine 'public'
Funktion 'print(ostream &)' definieren, die vom Inserter aufgerufen
wird. Es gibt dann ueberhaupt keinen Grund, den Inserter einen 'friend'
sein zu lassen und trotzdem kann man die gewohnte Syntax zum schreiben
in ein 'ostream' beibehalten. Hier ist ein Codebeispiel, dass das
demonstriert:

  #include <iostream.h>

  class foo
  {
  private:
    int val;
  public:
    foo(int v): val(v) {}
    ostream &print(ostream &out) const { return out << "foo::val(" << val << ")"; }
  };

  ostream &operator<< (ostream &out, foo const &f) { return f.print(out); }

An dem Inserter ist nichts unkonventionell und trotzdem ist er kein
'friend'.
--
dietmar.kuehl@uni-konstanz.de
http://www.informatik.uni-konstanz.de/~kuehl
I am a realistic optimist - that's why I appear to be slightly pessimistic
Re: Probleme beim Vererben von fstream
#1915
Author: Nico Josuttis
Date: Tue, 26 Mar 1996 12:50
55 lines
2291 bytes
In article <4j2hdo$lrk@zeus.rbi.informatik.uni-frankfurt.de>,
Thomas Holubar  <holubar@informatik.uni-frankfurt.de> wrote:
>hf@colibri.de (Harald Fuchs) wrote:
>>Thomas Holubar <holubar@informatik.uni-frankfurt.de> writes:
>>
>>> [deleted]
>>
>>> Zunaechst: ein ueberladener stream-Operator _muss_ eine
>>> "friend-function" sein.
>>
>>Nein.  Eine externe Funktion, die auf private Member zugreift, muß
>>eine Friend-Funktion sein.  Das ist für einen Stream-Operator aber
>>nicht unbedingt der Fall:
>>
>Ok, mit meinem "muss friend-function sein" habe ich mich nicht
>richtig ausgedrueckt. Allerdings wuerde ich doch _sehr_ empfehlen
>stream-Operatoren als "friend" zu deklarieren.
>

NEIN, das wuerde ich NICHT, denn Friend-Funktionen koennen nicht
virtuell sein. Wer Friend-Funktionen verwendet, sorgt deshalb
dafuer, dass oeffentliche Vererbung seiner Klasse
u.U. nicht richtig funktioniert.
Zumindest sofern Vererbung nicht ausgeschlossen ist, sollte deshalb
der Operator global implementiert eine Elementfunktion des
zweiten Operanden aufrufen:

class XXX {
  public:
    ...
    virtual void printOn (ostream& strm) const;
    virtual void scanFrom (istream& strm);
};

inline ostream& operator<< (ostream& strm, const XXX& x)
{
    x.printOn (strm);
    return strm;
}
inline istream& operator>> (istream& strm, const XXX& x)
{
    x.scanFrom (strm);
    return strm;
}


Da diese Technik keine Probleme macht, wuerde ich diese
grundsaetzlich _sehr_ empfehlen.
Siehe auch in meinem C++-Buch Kapitel 3.6.

-- 
Nico                             address: BREDEX GmbH, Nicolai Josuttis
email:   nico@bredex.de                   Fallersleber-Tor-Wall 23
phone:   +49 531 24330-0                  D-38100 Braunschweig
fax:     +49 531 24330-99                 Germany
Thread Navigation

This is a paginated view of messages in the thread with full content displayed inline.

Messages are displayed in chronological order, with the original post highlighted in green.

Use pagination controls to navigate through all messages in large threads.

Back to All Threads