boolscher Operator



  • HEZ schrieb:

    Unkontrolliert vergleichbar? Bei bool könnte doch da nicht viel passieren? Gibt ja schließlich nur zwei Zustände, wenn man jetzt den Operator mit einer anderen bool-Variable vergleicht, ist es ja nicht so, als ob eine Übereinstimmung besteht, obwohl sich die Inhalte widersprechen...

    Das ist nicht das Problem. Das Problem ist, dass ziemlich viele Dinge implizit in bool konvertierbar sind. Damit bastelt dir der Compiler aus den wildesten Konstrukten, die sonst womöglich eine Fehlermeldung erzeugen würden, irgendetwas hin, das diesen Operator benutzt.

    operator void* ... ich stehe ja eigentlich nicht so auf void-Zeiger... da lässt sich doch noch viel mehr Wahnsinn zuweisen?

    Mit einem void* kannst du (implizit) nicht viel mehr machen als ihn auf != 0 zu prüfen. Zuweisen kannst du da sowieso nichts. 7H3 N4C3R hat mit seinem Einwand recht.



  • Hmm du kannst mal überlegen, wieviel implizit nach void* und wieviel implizit nach bool castet - und wie sich das dann auf die Vergleichbarkeit deiner Klasse auswirkt. 😉
    Mit operator bool() ist deine Klasse (zwar nicht sinnvoll aber trotzdem) mit so ziemlich allen eingebauten Datentypen vergleichbar. Willst Du das wirklich?

    Edit: Hm da war MFK schneller 🙂



  • Naja, ich kann jetzt zwar nicht behaupten, dass ich die Begründung verstehe, aber wenn ihr euch einig seid, muss es ja stimmen. 😃 Irgenwann werde ich es nachvollziehen können und bis dahin werde ich mich einfach an diese Anweisung halten. (Habe ich bei "Public-Variablen sind böse" auch einfach gemacht und später kapiert.)



  • Habe es jetzt so umgesetzt:

    TToolTnc::WZ_Var::operator void*()
    {
      if(this->VarName.IsEmpty() && this->Value.IsEmpty())
        return NULL;
      else 
        return this;
    }
    

    Habs jetzt direkt aus dem Code genommen und kein Beispiel gebastelt, weil man wohl erkennen kann, worums geht.

    Nun, es erscheint mir so ein wenig komisch, schließlich liegt das Objekt ja nicht tatsächlich auf NULL. Aber wenn ich stattdessen versuche, einem Objekt NULL zuzuweisen, damit der Operator korrekte Werte zurückgibt, bekomme ich nen Konvertierungsfehler. Jetzt könnte ich da auch einen Zuweisungsoperator hinwurschteln, aber dann könnte man schon wieder jeden Müll zuweisen.

    Wo liegt denn diesmal mein Denkfehler?



  • HEZ schrieb:

    Aber wenn ich stattdessen versuche, einem Objekt NULL zuzuweisen, damit der Operator korrekte Werte zurückgibt,

    Wozu "korrekte Werte"? void* ist hier nur eine Krücke, damit du eine möglichst kontrollierte Umwandlung nach bool hinbekommst. Der Zeiger ist nur für Vergleiche mit 0 vorgesehen, nicht, um damit auf das Objekt zuzugreifen. Der Zeiger muss daher auch nichts mit der Adresse des Objekts zu tun haben. Du musst nicht this zurückgeben, jeder andere von 0 verschiedene Zeiger tut's auch.



  • Na gut. Danke!



  • HEZ schrieb:

    operator void* ... ich stehe ja eigentlich nicht so auf void-Zeiger... da lässt sich doch noch viel mehr Wahnsinn zuweisen?

    Nunja, eigentlich kannst du nur andere Zeiger damit vergleichen. Du kannst es aber auch vollkommen idiotensicher machen. Dazu nimmst du eine simple Klasse, zB:

    class safe_bool
    {
    public:
    	typedef void (safe_bool::*type)();
    	static type value(bool source)
    	{
    		return source ? &safe_bool::non_null : NULL;
    	}
    private:
    	void non_null()
    	{
    	}
    };
    

    Die Verwendung sieht dann wie folgt aus:

    operator safe_bool::type() const
    {
    	return safe_bool::value(!this->VarName.IsEmpty() || !this->Value.IsEmpty());
    }
    


  • [cpp]
    class safe_bool
    {
    public:
    typedef void (safe_bool::*type)();
    [cpp]
    sag mir ma, wie groß sizeof type ist. nicht schätzen. miss mal.

    void non_null()
    {
    }

    diese funktion wird leider generiert, weil da jemand von die adresse gezogen hat. ich würde lieber reinterpret_cast nehmen als sowas.
    warum nicht this? das liegt doch eh im register cx und mit mov ax,cx breicht man nur einen takt, um this als returnwert zu haben.

    außerdem würde ich nen zeiger auf nen typen zurückgeben. auf struct NoType{}; vielleicht. der ist dann eh nur mit 0 vergleichbar, aber das macht nix.


  • Mod

    richtig: messen
    folgendes kleine program zum demonstrieren:

    #include <iostream>
    struct foo
    {
    	void bar() const {}
    	typedef void (foo::*safe_bool)() const;
    	struct bar2 {};
    	static const bar2 b;
    	operator bool() const { return m < RAND_MAX / 2; }
    //	operator const void*() const { return m < RAND_MAX / 2 ? this : 0; }
    //	operator const bar2*() const { return m < RAND_MAX / 2 ? &b : 0; }
    //	operator safe_bool() const { return m < RAND_MAX / 2 ? &foo::bar : 0; }
    	int m;
    };
    const foo::bar2 foo::b;
    unsigned rdtsc() { __asm rdtsc }
    int main()
    {
    	using namespace std;
    	foo a;
    	srand( rdtsc() );
    	a.m = rand();
    	if ( a )
    		cout << "true";
    	else
    		cout << "false";
    }
    

    das ganze compiliert mit vc++ 7.1 mit allen optimierungenergibt:

    21: 	a.m = rand();
    0040101B E8 70 29 00 00   call        rand (403990h) 
        22: 	if ( a )
    00401020 3D FF 3F 00 00   cmp         eax,3FFFh 
    00401025 7D 13            jge         main+2Ah (40103Ah) 
        23: 		cout << "true";
    00401027 68 28 C1 40 00   push        offset string "true" (40C128h)
    

    mit operator const void*:

    21: 	a.m = rand();
    0040101C E8 7F 29 00 00   call        rand (4039A0h) 
        22: 	if ( a )
    00401021 33 C9            xor         ecx,ecx 
    00401023 3D FF 3F 00 00   cmp         eax,3FFFh 
    00401028 0F 9D C1         setge       cl   
    0040102B 8D 14 24         lea         edx,[esp] 
    0040102E 83 E9 01         sub         ecx,1 
    00401031 85 CA            test        edx,ecx 
    00401033 74 14            je          main+39h (401049h) 
        23: 		cout << "true";
    00401035 68 28 C1 40 00   push        offset string "true" (40C128h)
    

    mit operator const bar2*

    21: 	a.m = rand();
    0040101B E8 80 29 00 00   call        rand (4039A0h) 
        22: 	if ( a )
    00401020 33 C9            xor         ecx,ecx 
    00401022 3D FF 3F 00 00   cmp         eax,3FFFh 
    00401027 0F 9D C1         setge       cl   
    0040102A 83 E9 01         sub         ecx,1 
    0040102D F7 C1 1C C1 40 00 test        ecx,offset foo::b (40C11Ch) 
    00401033 74 13            je          main+38h (401048h) 
        23: 		cout << "true";
    00401035 68 28 C1 40 00   push        offset string "true" (40C128h)
    

    und mit operator safe_bool:

    21: 	a.m = rand();
    0040102B E8 80 29 00 00   call        rand (4039B0h) 
        22: 	if ( a )
    00401030 33 C9            xor         ecx,ecx 
    00401032 3D FF 3F 00 00   cmp         eax,3FFFh 
    00401037 0F 9D C1         setge       cl   
    0040103A 83 E9 01         sub         ecx,1 
    0040103D F7 C1 00 10 40 00 test        ecx,offset foo::bar (401000h) 
    00401043 74 13            je          main+38h (401058h) 
        23: 		cout << "true";
    00401045 68 28 C1 40 00   push        offset string "true" (40C128h)
    

    nun sieht das mit einem anderen compiler sicher anders aus.
    bleibt festzuhalten: nur operator bool() wird optimal übersetzt - das problem ist hier aber, dass der compiler sich nicht bewusst ist, dass benannte objekte niemals die adresse 0 haben können. das lässt sich heilen - allerdings nicht gänzlich standardkonform (da pointer auch trapvalues haben können):
    mit

    operator const void*() const { return m < RAND_MAX / 2 ? (const void*)1 : 0; }
    

    erhalte ich:

    31: 	a.m = rand();
    0040101B E8 70 29 00 00   call        rand (403990h) 
        32: 	if ( a )
    00401020 3D FF 3F 00 00   cmp         eax,3FFFh 
    00401025 7D 13            jge         main+2Ah (40103Ah) 
        33: 		cout << "true";
    00401027 68 28 C1 40 00   push        offset string "true" (40C128h)
    

    wunderbar. das ganze geht auch für memberfuntionpointer:

    operator safe_bool() const
    	{
    		union
    		{
    			unsigned x;
    			safe_bool result;
    		} result = { m < RAND_MAX / 2 };
    		return result.result;
    	}
    
    31: 	a.m = rand();
    0040101B E8 70 29 00 00   call        rand (403990h) 
        32: 	if ( a )
    00401020 3D FF 3F 00 00   cmp         eax,3FFFh 
    00401025 7D 13            jge         main+2Ah (40103Ah) 
        33: 		cout << "true";
    00401027 68 28 C1 40 00   push        offset string "true" (40C128h)
    

    sehr schön. da jetzt auch bezug auf bar::foo() mehr genommen wird spart man diese 16 byte (1 byte function, 15 byte für ausrichtung des einsprungpunktes, so sinnlos das hier auch ist) auch noch ein.

    fazit: mit ein paar tricks kann jede form optimalen code liefern. memberfunctionspointer sind dann aber am sichersten, denn auch mit bar* kann man noch viele unsinnige sachen wie pointerarithmetik betreiben (es sei denn, bar ist ein unvollständiger typ, aber dann muss man auch wieder einen hack wie oben benutzen, um ein bar* zurückliefern zu können). ausserdem ist sowohl bar* als auch void* mit jedem anderen void* pointer vergleichbar.



  • groovemaster schrieb:

    Nunja, eigentlich kannst du nur andere Zeiger damit vergleichen. Du kannst es aber auch vollkommen idiotensicher machen. Dazu nimmst du eine simple Klasse, zB:

    Also nehmen wir mal eine Klasse MeinStream, deren Zustand mit operator safe_bool überprüfbar ist, und eine Klasse Farbe, deren safe_bool "true" ergibt, wenn die Farbe nicht unsichtbar ist, und wir bekommen

    void doBockmist(MeinStream& a, Farbe& b)
    {
        if (a == b) ...;
    }
    

    Boost bietet doch afaik eine safe_bool-Bibliothek an - ich würde mich einfach mal darauf verlassen, dass das da einigermaßen vernünftig gelöst ist.



  • safe_bool überprüfbar ist, und eine Klasse Farbe, deren safe_bool "true" ergibt, wenn die Farbe nicht unsichtbar ist, und wir bekommen

    void doBockmist(MeinStream& a, Farbe& b)
    {
        if (a == b) ...;
    }
    

    das ist auch sinnvoller code.
    der sagt "wenn a den gleichen gültigkeitswert hat wie b". null probleme und sinnvoll.
    es gibt darum, daß man nicht bei
    cout<<a; vom rechner "true" erzählt kriegt, sondern das richtige "ätsch, ich kann gar keinen MeinStream ausgeben".



  • So kann mans natürlich auch interpretieren. Ich finde das eine sehr verwirrende Benutzung des operator==, andererseits würde ich auch nie auf die Idee kommen, irgendeine spezielle Eigenschaft meines Objekts per if (obj) abzufragen. Ich bin dann eher der "bool isXyz() const"-Typ, glaube ich.



  • operator void schrieb:

    So kann mans natürlich auch interpretieren. Ich finde das eine sehr verwirrende Benutzung des operator==, andererseits würde ich auch nie auf die Idee kommen, irgendeine spezielle Eigenschaft meines Objekts per if (obj) abzufragen. Ich bin dann eher der "bool isXyz() const"-Typ, glaube ich.

    naja, jeder wird wohl irgendwann sowas gemacht haben:

    Object* p=foo();
    if(p){
        ....
    }
    

    wieso dann nicht auch so?

    SmartPointer<Object> p=foo();
    if(p){
        ....
    }
    


  • Mache ich, weil es geht und idiomatisch ist. Im Prinzip halte ichs da aber eher mit Java/C# 🙂



  • OMG, ich hatte wohl übersehen, dass ich im Assembler-Forum bin. 😮 🙄
    Vielleicht hätte ich dazu schreiben sollen, dass es für Leute, die im Mikrobereich optimieren, andere Möglichkeiten gibt. Das Beispiel war einzig und allein für Codesicherheit gedacht.

    volkard schrieb:

    sag mir ma, wie groß sizeof type ist

    Wozu? Stört dich der Member-Funktionszeiger? Den kann man natürlich noch eliminieren.

    class safe_bool
    {
    private:
    	struct closed
    	{
    	};
    public:
    	typedef void (*type)(closed);
    	static type value(bool source)
    	{
    		return source ? &safe_bool::non_null : NULL;
    	}
    private:
    	static void non_null(closed)
    	{
    	}
    };
    

    volkard schrieb:

    warum nicht this?

    Weil ich in einer Non-Memberfunktion nunmal kein this habe. Ich bin aber schon gespannt auf ein Beispiel von dir.



  • groovemaster schrieb:

    volkard schrieb:

    sag mir ma, wie groß sizeof type ist

    Wozu? Stört dich der Member-Funktionszeiger?

    8 oder 12 bytes. nicht 4, und damit schmerzhaft.

    Den kann man natürlich noch eliminieren.

    class safe_bool
    {
    private:
    	struct closed
    	{
    	};
    public:
    	typedef void (*type)(closed);
    	static type value(bool source)
    	{
    		return source ? &safe_bool::non_null : NULL;
    	}
    private:
    	static void non_null(closed)
    	{
    	}
    };
    

    der vorteil deines member-zeugs war wohl, daß man die sehr schwer aus versehen casten kann.
    einen möglichen fehler sehe ich sogar. man hantiert mit zeigern und placement-new. und das will nen void*. und man steckt nen safe_bool rein.
    wird der aktuelle normale funktionszeiger nicht von allein in nen void* konvertiert?

    volkard schrieb:

    warum nicht this?

    Weil ich in einer Non-Memberfunktion nunmal kein this habe. Ich bin aber schon gespannt auf ein Beispiel von dir.[/quote]
    satic zu nehmen ist neu.
    mir scheint, reinterpret_cast muß her.



  • volkard schrieb:

    der vorteil deines member-zeugs war wohl, daß man die sehr schwer aus versehen casten kann.

    Yep.

    volkard schrieb:

    einen möglichen fehler sehe ich sogar. man hantiert mit zeigern und placement-new. und das will nen void*. und man steckt nen safe_bool rein.
    wird der aktuelle normale funktionszeiger nicht von allein in nen void* konvertiert?

    An das implizite Casten nach void* hab ich jetzt beim zweiten Beispiel gar nicht mal gedacht. Aber ein weiterer Grund, weshalb ich wohl beim Member-Funktionszeiger bleiben werden.

    volkard schrieb:

    volkard schrieb:

    warum nicht this?

    Weil ich in einer Non-Memberfunktion nunmal kein this habe. Ich bin aber schon gespannt auf ein Beispiel von dir.

    satic zu nehmen ist neu.
    mir scheint, reinterpret_cast muß her.

    Ich bin immer noch auf Code von dir gespannt. Ich habe es nicht geschafft, gültigen Code mit this zu schreiben, da dies auch mein erster Ansatz war. Allerdings habe ich nicht allzu viel Zeit darin investiert, um sagen zu können, alle Fälle abgecheckt zu haben.


Anmelden zum Antworten