Methoden mit bool-Parametern und "Lesbarkeit" im Kontext
-
lolz schrieb:
Mir fiel kein wirklich guter Titel für das Thema ein..es geht um folgendes:
ich habe oftmals Parameter welche vom Typ bool sind, z.B. ob Vollbild verwendet werden soll oder Fenstermodus. Mit einem Parameter bool useFullscreen ist die Beudeutung des Parameters klar, wenn man die Parameterliste der Methode anschaut.
Aber wird die Methode irgendwo im Programm aufgerufen so liest man dann nur ein window.setMode(true) und ohne den Namen des Parameters zu kennen lässt sich damit wenig anfangen.
Bei solchen Fällen habe ich schon oft darüber nachgedacht ein enum zu benutzen, welches zwei Symbole hat mit denen man auch im Kontext die Bedeutung feststellen kann.Wie handhabt ihr das? Ich habe bisher meist trotzdem die bool Parameter verwendet, aber in einigen Fällen auch schonmal ein enum benutzt.
In C nehme ich ein integer und definiere Flags.
#define USE_FULLSCREEN ( 1 << 0 ) #define USE_BORDERS ( 1 << 1 ) if( myStruct.Flags & USE_FULLSCREEN ) ...In C++ erstelle ich eine Klasse MyClassFlags und leite davon MyClass ab.
class MyClassFlags { private: unsigned int Flags; protected: MyClassFlags( unsigned int flags ) : Flags( flags ) {} public: static MyClassFlags const UseFullscreen; static MyClassFlags const UseBorders; MyStructFlags & operator |= ( MyStructFlags & addFlags ) { return ; MyStructFlags & operator & ( MyStructFlags & checkFlags ); <...> }; class MyClass : public MyClassFlags {}; MyClass myClass; myClass |= myClassFlags::UseFullscreen; if( myClass & myClassFlags::UseFullScreen ) ...Die Lösung hat jedoch den Nachteil, dass man damit unter Umständen Abhängigkeiten aufbauen kann, die in C++ nicht mehr beschreibbar sind. Die Funktion des Programms wird damit abhängig von der Reihenfolge der Objektfiles beim Linkeraufruf.
Um das zu vermeiden ist es u.U. erforderlich Flags über statische Funktionen abzufragen:
MyClassFlags & MyClassFlags::UseFullScreen( void ) { static MyClassFlags result( MyClassFlags::UseFullScreenId ); return result; } <...> myClass |= myClassFlags::UseFullscreen(); if( myClass & myClassFlags::UseFullScreen() ) ...
-
Xin schrieb:
In C nehme ich ein integer und definiere Flags.
...
In C++ erstelle ich eine Klasse MyClassFlags und leite davon MyClass ab.wieso macht du's in C++ so umständlich, wenn die C-version dort auch funktionieren würde

-
Undertaker schrieb:
Xin schrieb:
In C nehme ich ein integer und definiere Flags.
...
In C++ erstelle ich eine Klasse MyClassFlags und leite davon MyClass ab.wieso macht du's in C++ so umständlich, wenn die C-version dort auch funktionieren würde

Die C-Version ist nicht typsicher, auf Typsicherheit lege ich sehr großen Wert.
Frage ich USE_FULLSCREEN auf ein anderenObjektyp->Flags ab, kommt ein Ergebnis dabei rum, dass aber u.U. gar nicht so gewünscht ist. Gibt es mehrere Klassen, kann man sich schnell mal vertun: Ist BUTTON_USE_BORDER != WINDOW_USE_BORDER und man vertut sich, kann man stundenlang USE_BORDER lesen, ohne zu bemerken, dass ein BUTTON kein WINDOW ist.
Der Mehraufwand für viele Klassen rentiert sich sobald man den ersten Fehler gesucht hat.
Noch wichtiger: es gibt die Sicherheit, dass man keinen derartigen Fehler übersehen haben kann.
Je weniger Fehler ein Programm haben kann, desto weniger muss ich bedenken, um einen Fehler zu finden.
-
Wenn du schon eine Klasse schreibst, wieso nicht die Enum-Klasse von Java?
class MeinEnum { public: static MeinEnum modiEins = new MeinEnum(1); static MeinEnum modiZwo = new MeinEnum(2); static MeinEnum modiDrei = new MeinEnum(3); protected: MeinEnum(int modi) { this->modi = modi; } private: int modi; } // ... { barMethod(MeinEnum::modiEins); }Naja, eigentlich ist die Regel doch klar: Wenn man 100% sicher ist, das es nur 2 Modis sind, dann reicht ein bool. Man muss dann einfach nur die Methode richtig bennen: setModal() oder setVisible()
Btw, wieso nicht einfach ein enum nehmen? Die sind doch auch typsicher, oder?
-
DEvent schrieb:
Btw, wieso nicht einfach ein enum nehmen? Die sind doch auch typsicher, oder?
Nicht wirklich:
enum T { bla, blup }; void foo(int mode) { } foo(7);
-
Xin schrieb:
Undertaker schrieb:
Xin schrieb:
In C nehme ich ein integer und definiere Flags.
...
In C++ erstelle ich eine Klasse MyClassFlags und leite davon MyClass ab.wieso macht du's in C++ so umständlich, wenn die C-version dort auch funktionieren würde

Die C-Version ist nicht typsicher, auf Typsicherheit lege ich sehr großen Wert.
ach so, deshalb.

-
DEvent schrieb:
Wenn du schon eine Klasse schreibst, wieso nicht die Enum-Klasse von Java?
Ich programmiere C++.

DEvent schrieb:
Naja, eigentlich ist die Regel doch klar: Wenn man 100% sicher ist, das es nur 2 Modis sind, dann reicht ein bool.
Yupp, aber trotzdem kann ein Flag helfen.
OpenWindow( "Titel", WindowFlag::FullScreen );liest sich einfach besser als
OpenWindow( "Titel", true );und macht Kommentierung überflüssig.
OpenWindow( "Titel", /* Fullscreen? */ true );DEvent schrieb:
Btw, wieso nicht einfach ein enum nehmen? Die sind doch auch typsicher, oder?
enums sind in C++ einfach ints und befinden sich nichtmals in einem eigenen Namensraum. Man könnte genauso gut #defines nehmen.
In der Beziehung schaue ich leicht neidisch in Richtung C#.
-
OpenWindow (title = "Titel", fullscreen = true);Ist aber schon noch um einiges cooler

Xin schrieb:
DEvent schrieb:
Btw, wieso nicht einfach ein enum nehmen? Die sind doch auch typsicher, oder?
[...]Man könnte genauso gut #defines nehmen.
Das stimmt so nicht.
enum A { a, b }; enum B { x, y }; void foo (A x) {} int main () { foo (0); foo (x); }liefert folgende wunderschöne Compilerfehler:
test.cpp: In function 'int main()': test.cpp:9: error: invalid conversion from 'int' to 'A' test.cpp:9: error: initializing argument 1 of 'void foo(A)' test.cpp:10: error: cannot convert 'B' to 'A' for argument '1' to 'void foo(A)'Das enum's nicht besonders toll sind will ich nicht bestreiten, aber so mies muss man sie auch nicht machen.
-
.filmor schrieb:
Xin schrieb:
[...]Man könnte genauso gut #defines nehmen.
Das stimmt so nicht.
enum A { a, b }; enum B { x, y }; void foo (A x) {} int main () { foo (0); foo (x); }liefert folgende wunderschöne Compilerfehler:
test.cpp: In function 'int main()': test.cpp:9: error: invalid conversion from 'int' to 'A' test.cpp:9: error: initializing argument 1 of 'void foo(A)' test.cpp:10: error: cannot convert 'B' to 'A' for argument '1' to 'void foo(A)'Das enum's nicht besonders toll sind will ich nicht bestreiten, aber so mies muss man sie auch nicht machen.
Wow, das muss ich morgen nochmal ausprobieren. Als ich das letzte Mal enum nutzte, wurden die einfach als int verbraten. Darum habe ich sie eigentlich seitdem ignoriert.
-
Xin schrieb:
Ich programmiere C++.

dachte ich haette ein C++ Beispiel gebracht?

-
@Xin: Die Verwirrung um verschiedene USE_BORDER's kann dir mit deiner Flag-Klasse genauso passieren wie mit Bit-Arithmetik

@Shade:
Shade Of Mine schrieb:
DEvent schrieb:
Btw, wieso nicht einfach ein enum nehmen? Die sind doch auch typsicher, oder?
Nicht wirklich:
enum T { bla, blup }; void foo(int mode) { } foo(7);Ja, wenn die Funktion einen int erwartet, kann sie natürlich einen beliebigen int-Wert bekommen. Wenn du die Definition änderst auf
void foo(T mode), wird sich dein C++ Compiler zu recht beschweren, daß er eine explizite Typumwandlung benötigt (wenn es um C geht, hast du allerdings recht).
-
CStoll schrieb:
Ja, wenn die Funktion einen int erwartet, kann sie natürlich einen beliebigen int-Wert bekommen. Wenn du die Definition änderst auf
void foo(T mode), wird sich dein C++ Compiler zu recht beschweren, daß er eine explizite Typumwandlung benötigt (wenn es um C geht, hast du allerdings recht).enum T { bla, blup }; void foo(T mode) { } foo(bla | blup); //errorman ist auf int festgelegt wenn man enums sinnvoll verwenden will. in ganz wenig ausnahmefällen kann man ein foo(T mode) machen, aber in den meisten Fällen braucht man int - denn wozu flags wenn ich sie nicht kombinieren kann?
-
Also eine Lösung, die das Problem löst, wird wohl schwieriger sein. Entweder du hast eine implizite Umwandlung von int nach Flag (wie bei C enum's), dann kann der Compiler aber nicht sicherstellen, daß die übergebenen int's legal sind - oder du hast keine solche Umwandlung (wie bei C++ enum's), dann funktionieren diese Flag-Kombinationen üblicherweise nicht. Am besten ist da immer noch die Flag-Klasse von Xin, allerdings als eigenständige Klasse und nicht als Basis.
*grübelt* enum's sind doch eigenständige Typen - also wäre es auch erlaubt, die Bit-Operatoren für einen eigenen enum zu überladen:
enum Flag {...}; Flag operator|(Flag r, Flag l) { return (Flag)((int)r|(int)l); } ...
-
Jetzt noch den einen Slash durch ein Klammer auf ersetzt und es würde tatsächlich gehen, ja.
-
CStoll schrieb:
@Xin: Die Verwirrung um verschiedene USE_BORDER's kann dir mit deiner Flag-Klasse genauso passieren wie mit Bit-Arithmetik

C++ kann die Typen WindowFlags und ButtonFlags problemlos unterscheiden.
Und da WindowFlags keinen Operator & anbietet, der auf ButtonFlags reagiert, wird der Compiler meckern.CStoll schrieb:
Am besten ist da immer noch die Flag-Klasse von Xin, allerdings als eigenständige Klasse und nicht als Basis.
Was spricht dagegen die Klasse als Basis zu nutzen. Klappt hier wunderbar.
Die Klasse benötige ich nur, um WindowFlags von Windows zu unterscheiden und WindowFlags von integers, wenn ich Operatoren überlade.
-
Wozu hast du eigentlich
MyClass : MyClassFlags { }, Xin?
-
wie wärs mit
window open title: "title" fullscreen: true.
ach moment das is ja die verbotene sprache

-
DEvent schrieb:
Wozu hast du eigentlich
MyClass : MyClassFlags { }, Xin?
Wenn ich die Frage richtig verstehe...?
Ich mache die Ableitung, weil mein Objekt Flags beinhaltet. Statt eine "hat ein" Beziehung zu machen und eine public-Variable Flags einzuführen, mache ich eine "ist ein" Beziehung daraus, um mir Schreibarbeit zu sparen.
Die Sache ist ungefährlich, weil es sonst nichts gibt, was sich für MyClassFlags interessiert und so muss ich nicht immer betonen, dass ich mit den Flags des Objektes arbeite.Ich will ja wissen, ob das Fenster Fullscreen ist ( "ist ein" Fullscreen-Fenster)
if( MyWindow & WinFlgs::Fullscreen ) ...und nicht ob die Flags Fullscreen signalisieren. ("hat ein" Fullscreen-Fenster zu sein)
if( MyWindow->Flags & WinFlgs::Fullscreen ) ...Ich finde das semantisch logischer und ich muss halt immernoch weniger tippen

-
Und man verwirrt den Anwender des Codes ungemein. Vorallem wenn man nachher noch eine neue Flagvariante einführt, die zB das backbuffering oder so betrifft
dann wird alles sehr sehr chaotisch.schreibarbeit darf _nie_ ein grund sein etwas zu machen.
-
Xin schrieb:
DEvent schrieb:
Wozu hast du eigentlich
MyClass : MyClassFlags { }, Xin?
Wenn ich die Frage richtig verstehe...?
Ich mache die Ableitung, weil mein Objekt Flags beinhaltet. Statt eine "hat ein" Beziehung zu machen und eine public-Variable Flags einzuführen, mache ich eine "ist ein" Beziehung daraus, um mir Schreibarbeit zu sparen.
Die Sache ist ungefährlich, weil es sonst nichts gibt, was sich für MyClassFlags interessiert und so muss ich nicht immer betonen, dass ich mit den Flags des Objektes arbeite.Ich will ja wissen, ob das Fenster Fullscreen ist ( "ist ein" Fullscreen-Fenster)
if( MyWindow & WinFlgs::Fullscreen ) ...und nicht ob die Flags Fullscreen signalisieren. ("hat ein" Fullscreen-Fenster zu sein)
if( MyWindow->Flags & WinFlgs::Fullscreen ) ...Ich finde das semantisch logischer und ich muss halt immernoch weniger tippen

Sowas funktioniert aber nur mit öffentlicher Vererbung (aber die ist für die Konstallation KlassenFlag - Klasse wenig sinnvoll) oder im Inneren deiner Klassenmethoden (aber dann gibt es keinen Aufwand-Unterschied zwischen 'Flags&WinFlgs::Fullscreen' und '*this&WinFlgs::Fullscreen'). Und du solltest mal darüber nachdenken, was "ist-ein" bzw. "hat-ein" im OOP-Kontext eigentlich bedeutet (Tip: Die Beziehungen beziehen sich auf die beteiligten Klassen).