conversion operator erzeugt zuviele Interpretationen von operator ==
-
@ Vollstrecker:
Habe das Beispiel jetzt mit einer künstlichen SString Klasse gemacht:
struct SString { char m_str[20]; SString() {;} operator char* () { return m_str; } friend bool operator == (const SString&, const SString&) { return 1; } friend bool operator == (const SString&, const char* ) { return 0; } }; int main() { char animal[] = "Fuchs"; char* p = animal; SString tier; SString tier2; if(tier == p) // hier selbe Fehlermeldung wie zuvor return 1; // aber siehe unten if(tier == tier2) // ok return 1; }Weiss nicht, ob ich das mit dem Rang einer Konvertierungssequenz recht verstanden habe. Intuitiv finde ich, dass operator ==(const SString&, const char
gewählt werden sollte, wenn die Argumente entsprechend sind. Und so macht es der Compiler ja auch, wenn kein Conversion Operator definiert ist.Aber Du hast mich darauf gebracht, auch die Umwandlung const - non-const als conversion zu berücksichtigen. Es stellt sich heraus, dass folgendes funktioniert (Veränderung zu oben ist die unterschiedliche Qualifikation von char* mit const):
operator const char* () { return m_str; } friend bool operator == (const SString&, const SString&) { return 1; } friend bool operator == (const SString&, const char* ) { return 0; }ODER
operator char* () { return m_str; } friend bool operator == (const SString&, const SString&) { return 1; } friend bool operator == (const SString&, char* ) { return 0; }also char* ist nun beide male gleichartig. Verstehen tu ich es noch nicht, aber zumindest kompiliert es jetzt.
-
Das Design is deshalb nich besser geworden. Das Template std::basic_string hat nicht umsonst keinen Konvertierungoperator sondern bietet hierzu die Methode c_str() an.
grüße
-
Was soll an dem Design schlecht sein? Wieso soll ein Konvertierungsoperator schlechter sein als ein c_str()? Schließlich besteht die Klasse nur aus einem char*, warum also keinen Konvertierungsoperator dafür einbauen?
Man kann die Klasse intuitiv benutzen und es ergibt sich kein Nachteil! Was soll an diesem Design schlecht sein
-
Badestrand schrieb:
Was soll an dem Design schlecht sein? Wieso soll ein Konvertierungsoperator schlechter sein als ein c_str()? Schließlich besteht die Klasse nur aus einem char*, warum also keinen Konvertierungsoperator dafür einbauen?
Man kann die Klasse intuitiv benutzen und es ergibt sich kein Nachteil! Was soll an diesem Design schlecht sein
An c_str() ist besser das man SString Objekte nicht "ausversehen" in char* umwandeln kann...
-
Ja, aber was ist dann an einem const char* Operator so schlecht?
-
Das man evtl. fehlerhafte Übergaben an Funktionen wegen der impliziter Konvertierung nicht erkennt.
-
Badestrand schrieb:
Ja, aber was ist dann an einem const char* Operator so schlecht?
Schlecht ist, dass er immer dort aufgerufen werden kann, wo die Sprache das vorsieht, und nicht nur an den Stellen, an die der Programmierer denkt, wenn er diesen Operator einführt. Ein Konvertierungsoperator, der in einen primitiven Typ konvertiert, macht alle Operationen, die für diesen primitiven Typ schon eingebaut sind, auch dem ursprünglichen Typ zugänglich. Das muss sehr schnell zu Mehrdeutigkeiten (im besten Falle) oder zu stillen Bedeutungsänderungen führen. Viele Konstrukte, die eigentlich falsch sind und diagnostiziert werden sollten, können so auf Umwegen doch noch legal werden.
In deinem Beispiel sollteif(tier == p)natürlich den selbstdefinierten Vergleichsoperator benutzen. Tatsächlich funktioniert das aber auch dann noch, wenn du gar keinen Vergleichsoperator definierst. Die Bedeutung des Ausdrucks tier==p hängt also nicht bloß von den Dingen, die explizit dastehen (also den Operanden und deren Typen und dem Vergleichsoperator) ab, sondern noch von etwas, was gar nicht dasteht, nähmlich ob und, wenn ja, welche Konvertierungsoperatoren/-konstruktoren definiert wurden. Das macht so einen einfachen Ausdruck kompliziert und das ist per se schlecht. Wie soll das erst werden, wenn wir etwas haben, was an sich schon sehr komplex ist.
-
Hm, ich erkenne immer noch keinen Nachteil... Aber nicht, dass ihr mich falsch versteht, ich habe keine besonderen Argumente für einen Konv.Op., mch interessiert das halt nur weil ich selber solche Klassen hab...
Bei einem char*-Op versteh ich das ja, dass da was falsch laufen kann, aber ein const char* darf man halt nicht verändern, also weiß ich nicht, was da in die Hose gehen sollte...
Jetzt am Beispiel der String-Klasse: Die Klasse ist ja nur ein Wrapper für den Typ char*, da die Daten an sich aber nur von der Klasse selber verändert werden dürfen (OOP halt), kann man eben nur einen const char* bekommen.
Wenn man einer Variable der Klasse an eine Funktion übergibt, die einen const char* erwartet, dann ist es doch gut so, die Funktion kann ja nix falsches mit anstellen.Habt ihr vielleicht ein (mehr oder weniger) konkretes Beispiel, wie es "gefährlich" werden könnte, bzw halt nicht im Sinne des Benutzers?
-
Gefährlich für den Benutzer kann es werden, wenn du beispielsweise den operator== vergisst. Bei einer "normalen" Klasse dürfte der Compiler sich beschweren, wenn du da zwei Objekte vergleichen willst, mit deinem Konversions-Operator bekommst du einfach Müll (char*-Vegleiche arbeiten auf den Adressen, für deine String-Klasse benötigst du vermutlich einen Inhalts-Vegleich). Und noch schlimmer wird das bei gemischten Objekten:
class string { public: string(const char*); operator const char*() const; friend operator==(const string& l, const string& r); } string ob1; char ob2[20]; if(ob1==ob2)... //soll ich ob1 per operator char* umwandeln und den eingebauten char* Vergleich durchführen? //oder soll ich ob2 per Ctor in einen string umwandeln und obigen op== verwenden?
-
class string { public: string(const char*); operator const char*() const; friend operator==(const string& l, const char* r); } string ob1; char ob2[20]; if(ob1==ob2)... string ob3 if (ob1==ob3) ...Wenn ich Compiler wäre, würde ich sowohl ob2 als auch ob3 in const char* umwandeln, dann müsste es ja funktionieren...
Der Compiler müsste ja alle in Frage kommenden Vergleichsobjekte (also char* char[], const char*, const char[]) in const char* umwandeln können und damit den Vergleichsoperator aufrufen können.
Würde auch funktionieren, wenn es nicht den eingebauten char* == char* Operator geben würde^^Oder hab ich da was falsch verstanden? Jedenfalls kommt es mir so vor, als würde es komplett ohne casten gehen..??.?
-
Das Problem ist das man sehr schnell Fehler macht, weil man die ganzen Möglichkeiten die sich, aufgrund impliziter Konvertierung, ergeben garnicht überblicken kann. Die Anzahl der Möglichkeiten ist bei konstanten Typen geringer, es existieren aber immer noch genug die dumme Fehlerquellen sind. Das schlimme ist die Fehlersuche, du wirst bei einem großen Projekt fluchen wenn du so einen Fehler suchen musst und Tage deines Lebens verschwendest diesen zu finden!
grüße
-
Wenn ich Compiler wäre, würde ich sowohl ob2 als auch ob3 in const char* umwandeln, dann müsste es ja funktionieren...
Ja, denke ich auch. (Allerdings betrachte ich den operator == (string&, string&) als den für die Klasse typischen Operator und den
operator == (string&, char*) als sekundäre Anpassung an externe char*'s.)Würde auch funktionieren, wenn es nicht den eingebauten char* == char* Operator geben würde^^
Oder hab ich da was falsch verstanden?
Beim dem Satz oben bekomme ich schon den Eindruck, dass Du vielleicht etwas falsch verstanden hast. Der eingebaute operator == (char*, char*) ist ja das, was in den diskutierten Situationen immer vermieden werden muss. Ich weiss nicht ob Du string und char* zu sehr vermischt. Du hattest da einige nicht ganz richtigen Formulierungen wie:
Schließlich besteht die Klasse nur aus einem char*, ...
Für den string-vergleich spielt es in der Tat keine so grosse Rolle, dass die Klasse auch noch andere Elemente enthält.
In meinen Beispielen habe ich statt des notwendigen Codes für den Vergleich immer nur return 0/1 geschrieben, nur um etwas kompilierbares zu haben. Ich hoffe, das hat niemanden verwirrt.
-
hgf schrieb:
In meinen Beispielen habe ich statt des notwendigen Codes für den Vergleich immer nur return 0/1 geschrieben, nur um etwas kompilierbares zu haben. Ich hoffe, das hat niemanden verwirrt.
Glaube nicht, dass du jemanden verwirrt hast

hgf schrieb:
Allerdings betrachte ich den operator == (string&, string&) als den für die Klasse typischen Operator [...]
Im Prinzip ja, aber IMO bräuchte man als zweiten Parameter ja gar kein string&, wenn der sowieso in ein const char* gecastet wird (werden kann). Deckt man halt ein größeres Feld mit dem Operator ab

hgf schrieb:
Für den string-vergleich spielt es in der Tat keine so grosse Rolle, dass die Klasse auch noch andere Elemente enthält.
Ich dachte immer, der Compiler benutzt die eingebauten Operatoren, wenn die Klasse nur aus einem Typ besteht...?
hgf schrieb:
Der eingebaute operator == (char*, char*) ist ja das, was in den diskutierten Situationen immer vermieden werden muss.
Das wollte ich ja auch gar nicht! Mir ging es ja nur um den Operator const char*. Also auch operator== (const char*, const char*). Gilt da das auch noch mit dem Vermeiden? Wenn ja, warum?
PS: Danke für die Antwort

-
Badestrand schrieb:
Das wollte ich ja auch gar nicht! Mir ging es ja nur um den Operator const char*. Also auch operator== (const char*, const char*). Gilt da das auch noch mit dem Vermeiden? Wenn ja, warum?
Weil auch dieser nur Zeiger vergleicht, und nicht Inhalte.
-
Badestrand schrieb:
hgf schrieb:
Allerdings betrachte ich den operator == (string&, string&) als den für die Klasse typischen Operator [...]
Im Prinzip ja, aber IMO bräuchte man als zweiten Parameter ja gar kein string&, wenn der sowieso in ein const char* gecastet wird (werden kann). Deckt man halt ein größeres Feld mit dem Operator ab

Nein, eine String-Klasse hat üblicherweise auch einen Ctor, der (const) char* entgegennimmt - und dann ist es letztlich egal, ob die Umwandlung von char* nach string bei der Parameter-Übergabe passiert oder im Inneren des op==.
hgf schrieb:
Für den string-vergleich spielt es in der Tat keine so grosse Rolle, dass die Klasse auch noch andere Elemente enthält.
Ich dachte immer, der Compiler benutzt die eingebauten Operatoren, wenn die Klasse nur aus einem Typ besteht...?
Falsch gedacht. Der Compiler benutzt die eingebauten Operatoren immer, wenn er in der Lage ist, deine Klasse in einen Build-in Typ umzuwandeln (und nichts "besseres" zur Verfügung steht). Sprich: Sobald deine Klasse 'string' einen op char* anbietet, denkt er zumindest darüber nach, die Vergleiche auf char*-Ebene durchzuführen.
-
LordJaxom schrieb:
Badestrand schrieb:
Das wollte ich ja auch gar nicht! Mir ging es ja nur um den Operator const char*. Also auch operator== (const char*, const char*). Gilt da das auch noch mit dem Vermeiden? Wenn ja, warum?
Weil auch dieser nur Zeiger vergleicht, und nicht Inhalte.
Wenn ich den Operator überschreibe (bzw halt op== string&, const char*), vergleicht er ja eben nicht mehr die Zeiger. Sonst bräuchte ich den Operator ja nicht...
CStoll schrieb:
Nein, eine String-Klasse hat üblicherweise auch einen Ctor, der (const) char* entgegennimmt - und dann ist es letztlich egal, ob die Umwandlung von char* nach string bei der Parameter-Übergabe passiert oder im Inneren des op==.
Achso, na gut... Aber verbraucht die Umwandlung in ein (bzw Erstellung eines) string-Objekt(es) dann nicht mehr Laufzeit als ein Cast? Den Cast könnte man ja inlinen, dann muss ja nur das Objekt direkt auf den Stack gepusht werden. Wenn das Objekt erstellt wird, muss es ja auf dem Stack erzeugt werden (was ja nicht viel kostet), aber dann wird der Konstruktor aufgerufen, der String kopiert, übergeben und dann der Destruktur ausgeführt... Das dürfte der Compiler ja eigentlich nicht weg-optimieren.
Und was meinst du mit "Umwandlung [...] im Inneren des op=="? Im Inneren des op musst du ja nicht nach string umwandeln. In dem Fall kann man doch einfach strcmp aufrufen oder eine ähnliche Funktion durchlaufen lassen
Nach wie vor: Ich sehe immer noch keinen Nachteil, in eine String-Klasse den operator const char* zu implementieren :xmas2:
-
Badestrand schrieb:
LordJaxom schrieb:
Badestrand schrieb:
Das wollte ich ja auch gar nicht! Mir ging es ja nur um den Operator const char*. Also auch operator== (const char*, const char*). Gilt da das auch noch mit dem Vermeiden? Wenn ja, warum?
Weil auch dieser nur Zeiger vergleicht, und nicht Inhalte.
Wenn ich den Operator überschreibe (bzw halt op== string&, const char*), vergleicht er ja eben nicht mehr die Zeiger. Sonst bräuchte ich den Operator ja nicht...
Du hast theoretisch vier verschiedene Kombinationsmöglichkeiten für den Vergleich - String==String, String==char*, char*==String und char*==char*. Die letzte Kombination kannst du nicht beeinflussen, die ersten beiden fängst du mit den eigenen Operatoren ab - die dritte Kombination bereitet Probleme, weil du sie nicht implementiert hast (und für den Compiler sind die Alternativen "ersten Parameter per Ctor in String umwandeln" und "zweiten Parameter per Cast-Operator in char* umwandeln" gleich gut).
Achso, na gut... Aber verbraucht die Umwandlung in ein (bzw Erstellung eines) string-Objekt(es) dann nicht mehr Laufzeit als ein Cast? Den Cast könnte man ja inlinen, dann muss ja nur das Objekt direkt auf den Stack gepusht werden. Wenn das Objekt erstellt wird, muss es ja auf dem Stack erzeugt werden (was ja nicht viel kostet), aber dann wird der Konstruktor aufgerufen, der String kopiert, übergeben und dann der Destruktur ausgeführt... Das dürfte der Compiler ja eigentlich nicht weg-optimieren.
Erst solltest du dafür sorgen, daß dein Programm funktioniert, danach kannst du dir überlegen, wo du Optimierungen benötigen könntest

Und was meinst du mit "Umwandlung [...] im Inneren des op=="? Im Inneren des op musst du ja nicht nach string umwandeln. In dem Fall kann man doch einfach strcmp aufrufen oder eine ähnliche Funktion durchlaufen lassen

Ich war von meinem üblichen Vorgehen ausgegangen - und da versuche ich normalerweise, Code-Dopplungen zu vermeiden.
Nach wie vor: Ich sehe immer noch keinen Nachteil, in eine String-Klasse den operator const char* zu implementieren :xmas2:
Der Punkt ist: Dieser Operator wird überall eingesetzt, wo der Compiler denkt, daß er passen könnte - selbst wenn dabei im konkreten Anwendungsfall absoluter Müll rauskommt. Und diese Situationen wirst du nur schwer überblicken können (z.B. in einer if-Bedingung (
if(str)..- Vergleich "str==""" vergessen - egal) oder bei delete (String* pstr=new String;...;delete*pstr;- * zu viel - egal) - diese "egal"-Situationen schluckt der Compiler anstandslos, aber im laufenden Betrieb bekommst du Probleme). Eine Methode ala c_str() wird dagegen nur dann verwendet, wenn du es explizit angibst.
-
Badestrand schrieb:
Nach wie vor: Ich sehe immer noch keinen Nachteil, in eine String-Klasse den operator const char* zu implementieren :xmas2:
Dann hast du wohl nich richtig zugehört, bzw willst es einfach nicht verstehen.
-
Zugehört hab ich jedenfalls

Werde ne Weile drüber nachdenken, auf jeden Fall ein dickes Danke für die Argumente und Geduld!