[C++] ostream, <<, wchar_t* und operator-overloading

Programmiersprachen, APIs, Bibliotheken, Open Source Engines, Debugging, Quellcode Fehler und alles was mit praktischer Programmierung zu tun hat.
Antworten
kaiserludi
Establishment
Beiträge: 467
Registriert: 18.04.2002, 15:31

[C++] ostream, <<, wchar_t* und operator-overloading

Beitrag von kaiserludi »

Moin.

Ich portiere gerade eine Lib von char* und ANSI auf wchar_t* und UTF16.
Dabei habe ich unter anderem eine Strinklasse vorliegen.
Diese hat, als sie noch char* benutzt hat, reibungslos mit ostream zusammengearbeitet, macht jetzt mit wchar_t aber Probleme.
Da die Klasse selbst ziemlich umfangrech ist (quasi eine Komplette C++ Reimplementation der Java Stringklasse + noch ein wenig mehr) habe ich mal zu Testzwecken alles rausgeschmissen, was für die Reproduktion des Issues unwichtig ist.
Es verbleibt diese zierliche kleine Klasse:

Code: Alles auswählen

class MyString 
{
public:
	MyString(void);
	MyString(const char* Value);
	MyString(const wchar_t* Value);
	MyString(const MyString& Value);
	~MyString();

	operator wchar_t*(void) const;
	operator const wchar_t*(void) const;

protected:
	wchar_t* Buffer;
	unsigned int BufferLen;
	unsigned int Length;

	void GetBuffer(unsigned int MaxStrLen);
};

Code: Alles auswählen

inline void MyString::GetBuffer(unsigned int MaxStrLen)
{
	BufferLen = MaxStrLen;
	//Buffer = new char[BufferLen + 1];
	Buffer = (wchar_t*)MALLOC((BufferLen+1)*sizeof(wchar_t));
}

MyString::MyString(void): Buffer(NULL), BufferLen(0), Length(0)
{
	const wchar_t* Value = L"";
	GetBuffer(Length = WCSLEN(Value)); 
	WCSCPY(Buffer, Value);
}

MyString::MyString(const char* Value): Buffer(NULL), BufferLen(0), Length(0)
{
	DEBUG_ASSERT(Value);
	int len = STRLEN(Value);
	len++;
	wchar_t* newBuf = (wchar_t*)MALLOC(sizeof(wchar_t)*len);
	STRTOWSTR(const_cast<char*>(Value), newBuf, len);
	GetBuffer(Length=len); 
	WCSCPY(Buffer, newBuf);
	FREEIF(newBuf);
}

MyString::MyString(const wchar_t* Value): Buffer(NULL), BufferLen(0), Length(0)
{
	DEBUG_ASSERT(Value);
	GetBuffer(Length=WCSLEN(Value));
	WCSCPY(Buffer, Value);
}

MyString::MyString(const MyString& Value): Buffer(NULL), BufferLen(0), Length(0)
{
	GetBuffer(Length = Value.Length);
	WCSCPY(Buffer, Value.Buffer);
}

MyString::~MyString() 
{
	FREE(Buffer);
}

MyString::operator wchar_t*(void) const
{
	return Buffer;
}

MyString::operator const wchar_t*(void) const
{
	return Buffer;
}
Folgender Code funktioniert problemlos:

Code: Alles auswählen

MyString testStr(L"moep");
std::wcout << L"test - " << (const wchar_t*)testStr << std::endl;
Lasse ich den Cast allerdings weg, gibt er mir statt dem Stringinhalt dessen Speicheradresse aus:

Code: Alles auswählen

MyString testStr(L"moep");
std::wcout << L"test - " << testStr << std::endl;
Mit char* statt wchar_t hat das ganze auch ohne Cast funktioniert.

Debugging ergibt nun folgendes:
Der Code springt in den Operator operator const wchar_t*(void) const und sollte entsprechend einen const wchar_t* zurückbekommen, welcher zum Aufruf von ostreams operator<< Überladung für const wchar_t* führen sollte. Das ist mit dem cast auch so der Fall, für const char* ist es auch ohne cast der Fall, für const wchar_t* ohne cast allerdings wird seltsamerweise ostreams operator<< Überladung für const void* aufgerufen, was erklärt, wieso die Speicheradresse ausgegeben wird. Die Frage ist nun, was läuft das schief, dass er ohne expliziten Cast nicht die const wchar_t* Überladung aufruft? Übergeber ich direkt einen const wchar_t*, funktioniert es auch, nur der ungecastete Rückgabewert meines operators wird irgendwie zum const void*
"Mir ist auch klar, dass der Tag, an dem ZFX und Developia zusammengehen werden der selbe Tag sein wird, an dem DirectGL rauskommt."
DirectGL, endlich ist es da
:)

"According to the C++ standard, it's "undefined". That's a technical term that means, in theory, anything can happen: the program can crash, or keep running but generate garbage results, or send Bjarne Stroustrup an e-mail saying how ugly you are and how funny your mother dresses you." :shock:[/size]
Benutzeravatar
Krishty
Establishment
Beiträge: 8250
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [C++] ostream, <<, wchar_t* und operator-overloading

Beitrag von Krishty »

kaiserludi hat geschrieben:MyString::operator wchar_t*(void) const
{
return Buffer;
}
Ist dir klar, dass dort eine const-Methode eine non-const-Referenz zum Objektinhalt zurückgibt – dass du mit dieser Methode also Strings verändern kannst, selbst, wenn sie const deklariert sind? Nimm das hintere const mal weg. (Ich würde sogar die komplette Funktion kippen, weil man so z.B. moep[5] = 0; schreiben und damit Attribute wie die String-Länge kaputt machen kann.)
kaiserludi hat geschrieben:Da die Klasse selbst ziemlich umfangrech ist (quasi eine Komplette C++ Reimplementation der Java Stringklasse + noch ein wenig mehr) habe ich mal zu Testzwecken alles rausgeschmissen, was für die Reproduktion des Issues unwichtig ist.
Auch noch andere Konvertierungsoperatoren?
Vergiss am besten Konvertierungs-operator-Methoden. Man kann sie (noch) nicht explicit deklarieren und hat deshalb dauernd Ärger mit ihnen. Nenn die Funktion, die einen Zeiger zurückgibt, wie bei ::std::string c_str() und überlad den ::std::ostream & operator << (::std::ostream &, MyString const &).

Das Problem ist hier, dass die Überladung von ::std::ostream::operator <<, die wchar_t const * akzeptieren würde, ein Template ist. Die Überladung für void const * ist hingegen keins. Wenn der Compiler prüft, welche Überladung für MyString geeignet sein könnte, prüft er zuerst die nicht-Template-Version mit void const * (da nicht-Template-Überladungen immer bevorzugt werden) und erkennt, dass eine Konvertierung von MyString zu wchar_t const * und damit zu void const * möglich ist und gibt sich damit zufrieden – du kommst also um einen überladenen operator << nicht herum.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
kaiserludi
Establishment
Beiträge: 467
Registriert: 18.04.2002, 15:31

Re: [C++] ostream, <<, wchar_t* und operator-overloading

Beitrag von kaiserludi »

Nein, die Klasse hat zwar eine Menge Operatorüberladungen, aber keine weiteren für Konvertierungsoperatoren außer den beiden oben erwähnte.

Eine Methode cstr() gibts sogar schon (mit der funktioniert ostream übrigens auch).

Wenn ich ::std::ostream & operator << (::std::ostream &, MyString const &) deklariere, wird die Klasse nicht mehr für Plattformen kompilieren, die keine Standardbibliothek implementiert haben (und genau wegen dieser Plattformen nutzt die Lib überhaupt ihre eigene Stringklasse anstatt die aus der Standardbiobliothek zu verwenden), deswegen wollte ich das bisher vermeiden.
Aber gut, ich kanns ja in Plattform-#ifdefs packen, machen wir bei einer Menge andere Sachen sowieso schon (z.B. um Namespaces verwenden zu können, die nicht von allen Zielplattformen unterstützt werden).

Zu MyString::operator wchar_t*(void) const:
const Methode heißt doch nur, dass die Methode selbst ihre Member nicht verändert, was der Operator doch auch nicht tut, sonst würde sich der Compiler beschweren. Was die Methode nun zurückgibt, ist ja egal, denn das const dient doch nur dazu, dem Aufrufer zu sagen: "Du kannst mich beruhigt aufrufen, ich kann dein Objekt gar nicht verändern." Ob der Aufrufer es später selbst verändert, bleibt ja ihm überlassen, allerdings gebe ich zu, dass der Aufrufer ohne die Implementation zu sehen, natürlich nicht wissen kann, dass er da einen Zeiger auf den Originalinhalt zurückbekommt, nicht auf eine Kopie. So gesehen ist das wohl tatsächlich unschön. Das der Operator überhaupt existiert, liegt daran, dass die C-Schicht der Lib auch in ihren readonly-Funktionen keine const Parameter hat und ich mir durch diesen Operator für nicht const Objekte erspare, noch mehr zu casten im C++ wrapper und dennoch readonly Parameter in der API mit const kennzeichnen kann.
"Mir ist auch klar, dass der Tag, an dem ZFX und Developia zusammengehen werden der selbe Tag sein wird, an dem DirectGL rauskommt."
DirectGL, endlich ist es da
:)

"According to the C++ standard, it's "undefined". That's a technical term that means, in theory, anything can happen: the program can crash, or keep running but generate garbage results, or send Bjarne Stroustrup an e-mail saying how ugly you are and how funny your mother dresses you." :shock:[/size]
Benutzeravatar
Aramis
Moderator
Beiträge: 1458
Registriert: 25.02.2009, 19:50
Echter Name: Alexander Gessler
Wohnort: 2016
Kontaktdaten:

Re: [C++] ostream, <<, wchar_t* und operator-overloading

Beitrag von Aramis »

Was die Methode nun zurückgibt, ist ja egal, denn das const dient doch nur dazu, dem Aufrufer zu sagen: "Du kannst mich beruhigt aufrufen, ich kann dein Objekt gar nicht verändern.
Falsch rum. const verschafft nicht dem Aufrufer die Sicherheit dass er das Objekt nicht veraendern wird, sondern dem Objekt die Gewissheit dass der Aufrufer seinen Zustand nicht veraendern kann solange es const ist.

Was du zurueckgibst ist syntaktisch egal, semantisch ist es aber eine Todsuende dadurch einen Member jederzeit, auch wenn der Aufrufer nur ein const–Objekt zu Verfuegung hat, veraenderbar zu machen. Der gesamte Nutzen von const waere zunichte gemacht.
Benutzeravatar
Krishty
Establishment
Beiträge: 8250
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [C++] ostream, <<, wchar_t* und operator-overloading

Beitrag von Krishty »

Aramis hat geschrieben:
Was die Methode nun zurückgibt, ist ja egal, denn das const dient doch nur dazu, dem Aufrufer zu sagen: "Du kannst mich beruhigt aufrufen, ich kann dein Objekt gar nicht verändern.
Falsch rum. const verschafft nicht dem Aufrufer die Sicherheit dass er das Objekt nicht veraendern wird, sondern dem Objekt die Gewissheit dass der Aufrufer seinen Zustand nicht veraendern kann solange es const ist.
Ich würde – wie kaiserludi – sagen, dass const dazu dient, Funktionen zu markieren, deren Benutzung keine logische Wirkung nach ziehen kann. Für das Objekt ist das nur indirekt von Belang, denn wenn eine Methode etwas tun muss, muss sie es unabhängig davon, ob das Objekt nun verändert wird oder nicht, tun.

Sein Denkfehler ist nur: Wir sprechen hier von dem logischen Zustand eines Objekts, nicht von dem bitweisen Zustand seiner Daten. Auch, wenn der Zeiger im String durch die Rückgabe nicht verändert wird, ist ein Aufruf der Funktion geeignet, von dem String verwaltete Daten zu manipulieren und damit seinen logischen Zustand zu verändern, bzw. im Beispiel von moep[5] = 0; inkonsistent zu machen. const bedeutet, dass der String unter allen Umständen gleich bleibt, nicht, dass der Zeiger immer derselbe bleibt. Es wäre z.B. perfekt const-correct, wenn der String bei jedem Aufruf der Funktionen einen neuen Zeiger allokieren und die alten Daten rüberkopieren würde, weil das abstrakte Objekt „String“ gleich bliebe.

Ein weiteres bekanntes Beispiel für diesen Unterschied ist eine Datenbank mit einem Cache – frage ich einen Wert aus der Datenbank ab, ändert sich der logische Zustand nicht. Da der abgefragte Wert im Cache landet und der Zeitstempel des letzten Zugriffs aktualisiert wird, kann es aber gut sein, dass sich der bitweise Zustand der Instanz ändert, obwohl der Logische gleich bleibt.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Aramis
Moderator
Beiträge: 1458
Registriert: 25.02.2009, 19:50
Echter Name: Alexander Gessler
Wohnort: 2016
Kontaktdaten:

Re: [C++] ostream, <<, wchar_t* und operator-overloading

Beitrag von Aramis »

Nun, zur Modellierung dieses Unterschieds gibt es ja in C++ mutable – aber es ist wohl klar dass es hier falsch waere, diesen Weg zu waehlen.
kaiserludi
Establishment
Beiträge: 467
Registriert: 18.04.2002, 15:31

Re: [C++] ostream, <<, wchar_t* und operator-overloading

Beitrag von kaiserludi »

So, habe da selbst auch noch mal drüber nachgedacht und bin zu dem Schluss gekommen, der operator wchar_t*(void) const fliegt komplett raus, nicht nur das const am Ende, es bleibt nur operator const wchar_t*(void) const erhalten. Ist tatsächlich ziemlich böse, wenn ich jedem darüber Schreibrechte auf die private-Member gebe, dann könnte ich auch gleich das const beim Rückgabewert von cstr() und den Buffer am besten gleich zum public member machen.
An den Stellen, wo ich tatsächlich den Member als nicht konstant brauche, mache ich das jetzt so:

Code: Alles auswählen

short Foobject::wrapFoo(const JString& str)
{
	return foo(const_cast<wchar_t*>(str.cstr()));
}
Dort weiß ich, foo() verändert den Wert nicht und hat bloß kein const in der API. Durch den expliziten cast kann man also nicht mehr versehentlich drauf rumschreiben, sondern wird drauf hingewiesen, das man hier aufpassen muss, was man tut.
"Mir ist auch klar, dass der Tag, an dem ZFX und Developia zusammengehen werden der selbe Tag sein wird, an dem DirectGL rauskommt."
DirectGL, endlich ist es da
:)

"According to the C++ standard, it's "undefined". That's a technical term that means, in theory, anything can happen: the program can crash, or keep running but generate garbage results, or send Bjarne Stroustrup an e-mail saying how ugly you are and how funny your mother dresses you." :shock:[/size]
Benutzeravatar
Krishty
Establishment
Beiträge: 8250
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [C++] ostream, <<, wchar_t* und operator-overloading

Beitrag von Krishty »

Übrigens kamen ich mit Aramis gerade auf eine Lösung des Problems betreffend operator <<, falls keine STL #includet ist:
Da ::std::ostream ein Template ist, muss auch der Operator ein Template sein. Sein Text wird also nur kompiliert werden, wenn tatsächlich mal in einen ostream geschrieben wird – und wenn man das tut, hat man logischerweise vorher <iostream> einbezogen.

Es reicht also, wenn du eine Forward Declaration vornimmst:

Code: Alles auswählen

namespace std {
    template<class _Elem, class _Traits> class basic_ostream;
}
und dann den Operator folgendermaßen definierst:

Code: Alles auswählen

template<
    class _Elem,
    class _Traits
> ::std::basic_ostream<_Elem, _Traits> & operator << (
    ::std::basic_ostream<_Elem, _Traits> & stream,
    MyString const & string
) {
    return stream << string.c_str();
}
Die Definition des Operators muss ins selbe Namespace wie MyString (Argument-dependent Lookup)!
(Für die Syntax keine Gewähr, da Code aus dem Kopf.)
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
kaiserludi
Establishment
Beiträge: 467
Registriert: 18.04.2002, 15:31

Re: [C++] ostream, <<, wchar_t* und operator-overloading

Beitrag von kaiserludi »

Deine aus dem Kopf-Syntax ist besser als du vermutest:
Nachdem ich den Methoennamen von c_str() auf cstr() angepasst habe (und den Klassennamen, MyString heißt nur die gekürzte Version der Klasse fürs Forum, um euch nicht mit unwichtigem Quellcode zu erschlagen), hat es auf Anhieb kompiliert, habs nur noch ein bischen umformatiert, damit es vom CodeStyle zum Rest des Codes passt:

Code: Alles auswählen

/* Summary
   Operator<<.

   Used to print the JString to a std::ostream.*/
template<class _Elem, class _Traits>
::std::basic_ostream<_Elem, _Traits>& operator<<(::std::basic_ostream<_Elem, _Traits>& stream, const JString& string)
{
	return stream << string.cstr();
}
Jetzt funktioniert die Ausgabe der Strings über wcout einwandfrei.

btw.: Schade, dass es ein template werden musste, jetzt hat der eine Operator comment für die Dokugeneration und Implementation im Header stehen, während es für alle anderen Methoden und Operatoren in der Klasse in der Sourcedatei steht, aber da lässt sich wohl nichts machen.

argument dependend lookup:
Interessant, heißt das, wenn ich Zwei Klassen habe Foo und Foo2 und einen Operator Foo operator+(const Foo& Lsh, const Foo2& Rsh);, dann dürfen Foo und Foo2 nicht in verschiedenen Namespaces sein?
Könnte ich nicht einfach Foo operator+(const Foo& Lsh, const NamespaceOfFoo2::Foo2& Rsh); schreiben (oder auch ein using namespace einsetzen) und dann würde es funktionieren?

Auf jeden Fall vielen Dank euch beiden, das Problem ist gelöst :)
"Mir ist auch klar, dass der Tag, an dem ZFX und Developia zusammengehen werden der selbe Tag sein wird, an dem DirectGL rauskommt."
DirectGL, endlich ist es da
:)

"According to the C++ standard, it's "undefined". That's a technical term that means, in theory, anything can happen: the program can crash, or keep running but generate garbage results, or send Bjarne Stroustrup an e-mail saying how ugly you are and how funny your mother dresses you." :shock:[/size]
Benutzeravatar
Aramis
Moderator
Beiträge: 1458
Registriert: 25.02.2009, 19:50
Echter Name: Alexander Gessler
Wohnort: 2016
Kontaktdaten:

Re: [C++] ostream, <<, wchar_t* und operator-overloading

Beitrag von Aramis »

Interessant, heißt das, wenn ich Zwei Klassen habe Foo und Foo2 und einen Operator Foo operator+(const Foo& Lsh, const Foo2& Rsh);, dann dürfen Foo und Foo2 nicht in verschiedenen Namespaces sein?
Doch! Aber der operator+ sollte in einem der beiden Namespaces – oder global – deklariert sein. Der Compiler geht die Namespaces aller beteiligten Typen durch und sammelt dabei alle Kandidaten. Danach nimmt er - gemaess den ueblichen und nicht gerade einfachen Regeln zur Overload Resolution – den Besten bzw. wirft einen Fehler, wenn er keinen findet oder zwei Ueberladungen gleich passend sind.

(D.h. uebrigens auch dass eine via ADL gefundene Funktion fuer den Compiler genauso „gut” ist wie eine direkt auffindbare – einer der Gruende weshalb ADL auch mal gerne fuer unerklaerliche Fehler sorgt).
Benutzeravatar
Krishty
Establishment
Beiträge: 8250
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [C++] ostream, <<, wchar_t* und operator-overloading

Beitrag von Krishty »

Doch, es würde funktionieren, weil der Compiler erst in Foo nach dem Operator sucht, dann im Namespace von Foo und dann im Namespace von Foo2. Es würde aber nicht mehr funktionieren, wenn der Operator in einem anderen Namespace läge als Foo und Foo2, z.B. im Globalen oder in einem übergeordneten Namespace von Foo2.

(Ganz sicher bin ich mir aber nicht – ich hasse ADL und meide deshalb die Konfrontation damit.)

Edit: Okay, mit dem globalen Namespace lag ich offenbar falsch.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
BeRsErKeR
Establishment
Beiträge: 689
Registriert: 27.04.2002, 22:01

Re: [C++] ostream, <<, wchar_t* und operator-overloading

Beitrag von BeRsErKeR »

kaiserludi hat geschrieben:btw.: Schade, dass es ein template werden musste, jetzt hat der eine Operator comment für die Dokugeneration und Implementation im Header stehen, während es für alle anderen Methoden und Operatoren in der Klasse in der Sourcedatei steht, aber da lässt sich wohl nichts machen.
Sofern du mit doxygen arbeitest ist das schon möglich. Die Doku einfach irgendwo in die Sourcedatei.

Code: Alles auswählen

/*! \fn template<class _Elem, class _Traits>
::std::basic_ostream<_Elem, _Traits>& operator<<(::std::basic_ostream<_Elem, _Traits>& stream, const JString& string)
    \brief Stream insertion operator for JString.

    A more detailed function description.

    \param stream Destination stream
    \param string String to insert
*/
Ohne Input kein Output.
Antworten