To point or not to point -- Designfrage
-
Tachyon schrieb:
Wie stellst Du es denn klar, wenn ich Fragen darf?
Es bedarf keiner gesonderten Klarstellung. Ich habe gar kein Verlangen, 0 zu übergeben.
-
StefanBo schrieb:
Simon2 schrieb:
Für mich ist ein Zeiger immer ein Hinweis darauf, dass auch 0 übergeben werden und/oder der Verweis "verschoben" werden darf
Genau deshalb frage ich ja.
Mich interessiert, wie andere das sehen, also welches Verhalten angenommen wird, wenn im Parameter ein Zeiger oder eine Referenz auftaucht.Nehmen wir Folgendes an:
std::list<const Item*> _cont; void AddItem( const Item& item ) { _cont.push_back( &item ); }Ich finde das zeigt, dass so was hier fast schon erwünscht ist:
AddItem( Item( ... ) );Daher einfach die Verständnisfrage.
Ich finde die Regel, immer Referenzen zu übergeben, außer man benötigt Pointer-Eigenschaften, nützlich und wende sie möglichst immer an.
Nach meinem Verständnis ist allerdings die Möglichkeit, bei Übergabe von Pointern ohne Weiteres einen Verweis statt eine Kopie zu übernehmen, auch eine Pointer-Eigenschaft. Aus diesem Grunde würde ich nie AddItem() wie im Beispiel implementieren, sondern immer mit einem Pointer als Parameter. Dass kein Nullpointer übergeben werden darf, würde ich in diesem Fall dokumentieren und vielleicht noch die Funktion in TakeItem() (oder so) umbenennen.
Stefan.
-
Ich halt mich an die Regel: "wenn die Funktion die Daten veraendert, wird ein Pointer uebergeben".
Die Logik dahinter ist, dass beim Verwenden der Funktionen sofort sehe, was die Funktion macht:
do_something(&foo); // foo kann womoeglich veraendert werden do_something(foo); // foo wird nicht veraendertWenn kein Pointer uebergeben wird gehe ich davon aus dass entweder eine Kopie oder eine const Referenz uebergeben wird. Bei AddItem wuerde ich einen Pointer uebergeben. Ein Aufruf wie "addItem(foo)" schaut fuer mich so aus, als wuerde eine KOPIE von foo uebergeben, in Wirklichkeit ist dem ja aber nicht so.
Das "ich kann auch null uebergeben"-Argument find ich nicht so wichtig. In der Regel kann ich vom Funktionsnamen ableiten, ob null uebergeben werden kann oder nicht. Und in nicht-performance-kritischen Funktionen kann ich die Parameter auch von Hand auf null testen und reagieren (einfach ignorieren, oder ein assert() oder eine Exception... je nachdem was mehr Sinn macht).
-
Blue-Tiger schrieb:
Ich halt mich an die Regel: "wenn die Funktion die Daten veraendert, wird ein Pointer uebergeben"....
Das heißt, "non-const-Referenzen" kommen bei Dir in Parameterlisten gar nicht vor?
Blue-Tiger schrieb:
...In der Regel kann ich vom Funktionsnamen ableiten, ob null uebergeben werden kann oder nicht. ...
Kannst Du mal ein Beispiel geben? Ich kann mir gerade keinen "organischen" Funktionsnamen vorstellen, bei dem diese Eigenschaft eines bestimmten Parameters deutlich wird.
Die "Ich-sehe-direkt-beim-Call-ob-ein-Parameter-geändert-wird"-Strategie greift mir persönlich zu kurz (eigentlich heißt sie ja: "Ich-beurteile-Veränderbarkeit-ohne-Funktionsdeklaration"-Strategie
).1.) Es gibt viel zu viele Ausnahmen - z.B.
struct A { //.... tausend Attribute int* a; int operator&(); //.... tausend weitere Attribute }; A a; f(a); // hoppla: hier sehe ich gar nicht mehr, dass A::a verändert werden könnte g(&a); // hoppla: Hier wird nur ein int übergeben und nichts verändertAußerdem ist das eine Eigenschaft der Funktion und hängt weniger der übergebenen Variablen. Deswegen hängt man sowieso (und zu Recht) an der Funktionsdeklaration.
2.) Für die Frage der Veränderbarkeit gibt es das klar definierte Konstrukt "const" ... und das sollte man IMHO auch verwenden.
Gruß,
Simon2.
-
Simon2 schrieb:
Blue-Tiger schrieb:
Ich halt mich an die Regel: "wenn die Funktion die Daten veraendert, wird ein Pointer uebergeben"....
Das heißt, "non-const-Referenzen" kommen bei Dir in Parameterlisten gar nicht vor?
mal von swap abgesehen kommen bei mir non-const-Referenzen in Parameterlisten eigentlich auch nicht vor.
Simon2 schrieb:
Blue-Tiger schrieb:
...In der Regel kann ich vom Funktionsnamen ableiten, ob null uebergeben werden kann oder nicht. ...
Kannst Du mal ein Beispiel geben? Ich kann mir gerade keinen "organischen" Funktionsnamen vorstellen, bei dem diese Eigenschaft eines bestimmten Parameters deutlich wird.
Ich würde viel eher gerne wissen, welchen Funktionen man natürlich 0 übergeben dürfen soll! Ich finde, kein indert, delete, remove, free, print, compare, find, unique, sort oder wasauchimmer muß erwarten, 0 übergeben zu bekommen.
Die "Ich-sehe-direkt-beim-Call-ob-ein-Parameter-geändert-wird"-Strategie greift mir persönlich zu kurz (eigentlich heißt sie ja: "Ich-beurteile-Veränderbarkeit-ohne-Funktionsdeklaration"-Strategie
).Das Gegenteil ist der Fall! Beim Aufrufer sehe ich sofort und ohne Funktionsdeklaration, ob es bei diesem Aufruf Veränderungsprobleme geben kann. Die Regel ist nicht für den Schreiber da, sondern für den Leser. Und den Fehlersucher/Fehlervermeider.
1.) Es gibt viel zu viele Ausnahmen - z.B.
struct A { //.... tausend Attribute int* a; int operator&(); //.... tausend weitere Attribute }; A a; f(a); // hoppla: hier sehe ich gar nicht mehr, dass A::a verändert werden könnte g(&a); // hoppla: Hier wird nur ein int übergeben und nichts verändertVielleicht müßte a in Wirklichkeit pa heißen. Vielleicht müßte das Attribut private sein und es einen const-Getter geben. Wer weiß?
Außerdem ist das eine Eigenschaft der Funktion und hängt weniger der übergebenen Variablen. Deswegen hängt man sowieso (und zu Recht) an der Funktionsdeklaration.
Und es ist wichtig genug, beim Aufrufer zu stehen! Ganz im Gegensatz zur NullKannSeinBarkeit, die mangels Existenz auch nicht so arg relevant ist.
2.) Für die Frage der Veränderbarkeit gibt es das klar definierte Konstrukt "const" ... und das sollte man IMHO auch verwenden.
Das klingt wie "Wenn der Herr geollt hätte, daß der Mensch fliegt, hätte er ihm Flügel wachsen lassen.". Unser Vorgehen unterstützt const doch.
-
volkard schrieb:
...
Simon2 schrieb:
Kannst Du mal ein Beispiel geben? Ich kann mir gerade keinen "organischen" Funktionsnamen vorstellen, bei dem diese Eigenschaft eines bestimmten Parameters deutlich wird.
Ich würde viel eher gerne wissen, welchen Funktionen man natürlich 0 übergeben dürfen soll! ...
Das ist ja interessant.
Irgendwie beantwortet das meine Frage nicht.

Aber mal auf Deine Frage:struct A { B* connectedWith; A(B* connectTo = 0) : connectedWith(connectTo) {} };Für mich steht aber auch nicht die "Nullbarkeit" im Mittelpunkt, sondern die "Umhängbarkeit" und die "Optionalität".
... und letztlich die (damit verbundene) lockerere Bindung zwischen 2 Objekten.volkard schrieb:
...Beim Aufrufer sehe ich sofort und ohne Funktionsdeklaration, ob es bei diesem Aufruf Veränderungsprobleme geben kann. ...
Die Wiederholung macht das Argument nicht besser - ich sage immer noch: Ohne Funktionsdeklaration wird man nur sehr (und für mich: zu) unzuverlässig etwas über die "Änderungspläne" einer Funktion herausbekommen.
volkard schrieb:
...Vielleicht müßte a in Wirklichkeit pa heißen. Vielleicht müßte das Attribut private sein und es einen const-Getter geben. Wer weiß?
...Welche Relevanz hat das für den Schreiber von f()?
(und wer sagt, was "...müsste.."? Das mit dem "pa" hast Du nicht ernst gemeint, oder?)volkard schrieb:
...Das klingt wie "Wenn der Herr geollt hätte, daß der Mensch fliegt, hätte er ihm Flügel wachsen lassen.". Unser Vorgehen unterstützt const doch.
Die Aussage ist aber genau anders herum: Wenn wir doch Flügel (const) haben - warum dann für Flugversuche mit den Armen (Pointer) wedeln?
Nochmal ein Beispiel:
T *************a; f(**********a); g(************a); h(&*******a);welche Funktion ändert was?
Was bedeutet eigentlich "ändert den Parameter"?
Das kann man mit "... * const * * const ...." exakt und nachvollziehbar ("wunderschön" verkneife ich mir mal in dem Zusammenang) festlegen.Gruß,
Simon2.
-
Simon2 schrieb:
Aber mal auf Deine Frage:
struct A { B* connectedWith; A(B* connectTo = 0) : connectedWith(connectTo) {} };Für mich steht aber auch nicht die "Nullbarkeit" im Mittelpunkt, sondern die "Umhängbarkeit" und die "Optionalität".
... und letztlich die (damit verbundene) lockerere Bindung zwischen 2 Objekten.Ich benutze keine Default-Parameter.
struct A { B* connectedWith; A() : connectedWith(0) {} void connect(B* connectTo) {connectedWith=cw;}//hier natürlich zeiger und 0 nicht erlaubt A() : connectedWith(B* connectTo);//hier natürlich zeiger und 0 nicht erlaubt };Umhängbarkeit deckt sich zum Teil mit der genannten Nichtkopierbarkeit und zwingt beide Lager zum Zeiger. Die Optionalität verwende ich eigentlich nur bei Such-Rückgaben. Verwendete. Ich glaube, das mache ich auch nicht mehr, sondern gebe Ranges zurück.
-
volkard schrieb:
...Ich benutze keine Default-Parameter. ...
Ich auch eher selten (hier nur der Vollständigkeit halber und zur besseren Schnittstellendoku) - spielt aber keine Rolle in diesem Zusammenhang.
volkard schrieb:
...Umhängbarkeit deckt sich zum Teil mit der genannten Nichtkopierbarkeit und zwingt beide Lager zum Zeiger. ...
Japp - das meinte ich.
Und weil es genau diese Situation ist, die einem zum Zeiger zwingt, würde ich auch genau diesen Zusammenhang damit ausdrücken wollen - und das hat nichts mit "Veränderbarkeit" zu tun.volkard schrieb:
...Ich glaube, das mache ich auch nicht mehr, sondern gebe Ranges zurück.
bei "find_first_element()" auch nicht unbedingt praktisch ...

Aber da sprechen wir wohl auch nicht von Parametern sondern von Rückgabewerten.Ich habe aber auch den Eindruck, dass Du hier diese Diskussion auf Dein spezielles Steckenpferd ("non-const Referenzen braucht/nimmt man nicht") ausweitest - die will ich nicht führen und die erwartet der Threadersteller auch nicht.
Wenn ich ihn recht verstehe, möchte er einen Überblick (also eher eine statistische Aussage) darüber, wieviele Leute seine Schnittstelle in welche Richtung interpretieren - und nicht die eine korrekte Antwort auf alle Fragen.Gruß,
Simon2.
-
Ich handhabe Schnittstellen übrigens ähnlich wie Simon2. Aber was mich interessiert, ist, wieso ihr keine/kaum Default-Parameter verwendet?
Auch betreffend Nullzeiger: Bei mir kamen schon solche Funktionen vor, wenn auch sehr selten:
T DoSomething(const S& input_param, U* output_param = 0);Also dass
output_paramgewisse Zusatzinformationen speichern könnte, die den Aufrufer im Normalfall nicht interessieren. Falls doch, übergibt man der Funktion ein zweites Argument. Erachtet ihr so eine Vorgehensweise als schlechten Stil?Ansonsten verwende ich Zeiger mit Möglichkeit zum Null-Sein in Schnittstellen, wenn ich optionale Attribute habe, also sowas wie
void Ship::SetTarget(Ship* Target);Hast du das wirklich nie, volkard, oder gehst du immer Umwege über zusätzliche Methoden?
-
Nexus schrieb:
T DoSomething(const S& input_param, U* output_param = 0);Also dass
output_paramgewisse Zusatzinformationen speichern könnte, die den Aufrufer im Normalfall nicht interessieren. Falls doch, übergibt man der Funktion ein zweites Argument. Erachtet ihr so eine Vorgehensweise als schlechten Stil?Nein, finde ich nicht. Weil man ja nur in einem Sonderfall für diese Daten interessiert. Ich denke mal, dass du hier auf einen Intersect Algorithmus anspielst. Dort finde ich das nämlich sehr passend. In erster Linie ist man ja nur mal daran interessiert, ob sich 2 Objekte schneiden. Dann möchte man vielleicht noch wissen, wo das genau passiert. Dann ist der Parameter genau passend. Würde ich auch so machen. (Habe ich glaube ich sogar auch mal).
-
Nexus schrieb:
Ansonsten verwende ich Zeiger mit Möglichkeit zum Null-Sein in Schnittstellen, wenn ich optionale Attribute habe, also sowas wie
void Ship::SetTarget(Ship* Target);Hast du das wirklich nie, volkard, oder gehst du immer Umwege über zusätzliche Methoden?
Ich kann mich jetzt nicht erinnern, sowas zu haben.
Ich nehme an, ich würdevoid Ship::SetTarget(Ship* Target);und
void Ship::ClearTarget();haben.
Ich habe ja außen schon ein if, und dann brauche ich innen nicht auch noch eins. Ich fürchte, sowas mutiert.
void Ship::SetTarget(Ship* Target) { if(Target==0) {//ClearTarget m_target=0; plan=HEALSELF|SEEKENEMY; } else {//SetTarget m_target=Target; plan=ATTACK; } }wird optimiert zu
void Ship::SetTarget(Ship* Target) { if(Target==0) {//ClearTarget plan=HEALSELF|SEEKENEMY; } else {//SetTarget plan=ATTACK; } m_target=Target; }
-
drakon schrieb:
Nein, finde ich nicht. Weil man ja nur in einem Sonderfall für diese Daten interessiert. Ich denke mal, dass du hier auf einen Intersect Algorithmus anspielst.
Danke für die Antwort. Stimmt, Intersection wäre ein gutes Beispiel, ich habe jetzt nicht einmal konkret daran gedacht.
volkard schrieb:
Ich habe ja außen schon ein if, und dann brauche ich innen nicht auch noch eins. Ich fürchte, sowas mutiert.
Innen braucht man ja nur eine If-Abfrage, wenn man mehr als eine
Target-Zuweisung macht. Was meinst du genau mit aussen?Bei einer einzelnen Funktion kann dafür die Anwendung leichter sein:
MyShip.SetTarget(AlliedShip.GetTarget());v.s.
Ship* Target = AlliedShip.GetTarget(); if (Target == 0) MyShip.ClearTarget(); else MyShip.SetTarget(Target);Hier würdest du ja kaum speziell abfragen (sowas wie
bool HasTarget() const), oder?
-
Nexus schrieb:
Was meinst du genau mit aussen?
Ich gehe davon aus, daß der Aufrufer weiß, ob SetTarget(0) oder SetTarget(echtesDing) gemacht wird.
Nexus schrieb:
Bei einer einzelnen Funktion kann dafür die Anwendung leichter sein
Jo, wäre denkbar. Aber ich kann mir nicht vorstellen, daß ich die leicher anwendbare Version übersehen hätte, wenn sowas bei mir vorgekommen wäre.
MyShip.SetTarget(AlliedShip.GetTarget());Da steckt evtl eine Bedeutungsüberladung drin, m_target!=0 heißt zugleich, daß das Schiff im Angriffsmodus ist. Ich schätze, davor hätte ich Angst und würde den Modus woanders scpeichern und genau dann, wenn Modus==ANGRIFF, dann ist m_target definiert. Evtl wäre m_target auch nur ein Attribut eines AngriffsPlan-Objekts und nicht immer optionales Schiffsattribut. Ich tue mir mit optionalen Attributen recht schwer.
-
Mir ist klar dass meine Konvention nur eine von vielen moeglichen ist, aber fuer mich hat sie bisher eigentlich immer funktioniert und erleichtert das Code-Lesen.

Allerdings gebe ich dir Recht, manchmal hantiert man mit rohen Pointern (eben weil man NULL benoetigt), und dann passierts, dass man Funktionen Pointern uebergibt, obwohl diese ihren Parameter gar nicht veraendern (War aber fuer mich Persoenlich noch nie ein Problem).Simon2 schrieb:
Blue-Tiger schrieb:
Ich halt mich an die Regel: "wenn die Funktion die Daten veraendert, wird ein Pointer uebergeben"....
Das heißt, "non-const-Referenzen" kommen bei Dir in Parameterlisten gar nicht vor?
In der Regel nicht, nein
Blue-Tiger schrieb:
...In der Regel kann ich vom Funktionsnamen ableiten, ob null uebergeben werden kann oder nicht. ...
Kannst Du mal ein Beispiel geben? Ich kann mir gerade keinen "organischen" Funktionsnamen vorstellen, bei dem diese Eigenschaft eines bestimmten Parameters deutlich wird.
addItem --> NULL ist kein Item (sonderne eben die Abwesenheit davon), also macht addItem(NULL) auch keinen Sinn.
processEntry --> NULL ist kein Entry, also macht processEntry auch keinen Sinn.
isValid --> da koennte NULL Sinn machen.
traverseTree -> dito (da Blaetter ja in der Regel mit null abgespeichert werden)
...Die "Ich-sehe-direkt-beim-Call-ob-ein-Parameter-geändert-wird"-Strategie greift mir persönlich zu kurz (eigentlich heißt sie ja: "Ich-beurteile-Veränderbarkeit-ohne-Funktionsdeklaration"-Strategie
).Solang sich jeder im Projekt an die Strategie haelt, funktionierts aber!
1.) Es gibt viel zu viele Ausnahmen - z.B.
struct A { //.... tausend Attribute int* a; int operator&(); //.... tausend weitere Attribute }; A a; f(a); // hoppla: hier sehe ich gar nicht mehr, dass A::a verändert werden könnte g(&a); // hoppla: Hier wird nur ein int übergeben und nichts verändertIch geb zu das ist mir noch nie passiert. Aber solang ich mich an die "Wenn die Funktion was aendert, wird als Pointer uebergeben" Regel halte, gaebe es den Aufruf f(a) nur dann, wenn f A::a nicht veraendert. Und operator& hab ich eigentlich noch nie ueberladen.
2.) Für die Frage der Veränderbarkeit gibt es das klar definierte Konstrukt "const" ... und das sollte man IMHO auch verwenden.
Wenn ich Code ueberfliege hab ich die Deklaration aber selten so genau im Kopf.
-
Hi Blue-Tiger,
Blue-Tiger schrieb:
Blue-Tiger schrieb:
Simon2 schrieb:
......In der Regel kann ich vom Funktionsnamen ableiten, ob null uebergeben werden kann oder nicht. ...
Kannst Du mal ein Beispiel geben? Ich kann mir gerade keinen "organischen" Funktionsnamen vorstellen, bei dem diese Eigenschaft eines bestimmten Parameters deutlich wird.
addItem --> NULL ist kein Item (sonderne eben die Abwesenheit davon), also macht addItem(NULL) auch keinen Sinn.
processEntry --> NULL ist kein Entry, also macht processEntry auch keinen Sinn.
isValid --> da koennte NULL Sinn machen.
traverseTree -> dito (da Blaetter ja in der Regel mit null abgespeichert werden)
...Also für mich ist das aber alles Andere als klar: 0 ist oft genug Stellvertreter für "ungültiges Objekt".
So wie string::npos auch keine "Position" ist und iterator::end() auch kein "Iterator" (im Sinne von "man kann mit ihnen alles machen, was man mit einem ... machen kann").Ich wäre bei den o.g. Funktionsnamen niemals darauf gekommen, dass (aufgrund der Namen) kein 0 erlaubt wäre - wo ist denn in dieser Hinsicht der Unterschied zwischen "addItem" und "isValidItem" ?
Außerdem setzt das eine intensive Auseinandersetzung mit den fachlichen Hintergründen voraus (wie z.B. dass bei einem Tree die Blätter mit 0 abgelegt werden) und ein gleiches Verständnis desselben voraus (Man könnte auch einen Tree implementieren, für die Blattobjekte von einem eigenen Typ sind - oder jedes Element ein entsprechendes Flag hat....).
Und selbst wenn das bei der Implementierung reichlich bekannter technischer Strukturen noch funktionieren mag, wird das Ganze bei komplexer Fachlichkeit nicht mehr intuitiv.
Mir scheint es in diesem Fall andersherum zu sein:
- Weil Du weißt/denkst/vermutest, dass 0 in diesem Zusammenhang nicht sinnvoll ist, ...
- ... findest Du auch die Funktionsnamen sprechend.
Aber für den Fall, in dem "jeder sowieso weiß, wie's funktioniert" braucht man gar keine Konvention.
Blue-Tiger schrieb:
...
Simon2 schrieb:
Die "Ich-sehe-direkt-beim-Call-ob-ein-Parameter-geändert-wird"-Strategie greift mir persönlich zu kurz (eigentlich heißt sie ja: "Ich-beurteile-Veränderbarkeit-ohne-Funktionsdeklaration"-Strategie
).Solang sich jeder im Projekt an die Strategie haelt, funktionierts aber!
Das wäre mir eine deutlich zu wackelige Angelegenheit - Projekte altern und wachsen, Mitarbeiter lernen und wechseln die Projekte, ...
Blue-Tiger schrieb:
...
Ich geb zu das ist mir noch nie passiert. Aber solang ich mich an die "Wenn die Funktion was aendert, wird als Pointer uebergeben" Regel halte, gaebe es den Aufruf f(a) nur dann, wenn f A::a nicht veraendert. ...Ich sehe immer noch nicht den Vorteil oder Mehrwert ggü. der Verwendung von const.
Blue-Tiger schrieb:
...
Und operator& hab ich eigentlich noch nie ueberladen....Ic auch nicht ... kann aber passieren und da hast Du als Programmierer von f() nicht unbedingt Einfluß drauf.
Noch "schlimmer" wird's ja dadurch, dass der Begriff "Pointer" ja ziemlich verwischt durch Iteratoren, Smart_Pointer, ... und natürlich durch die Möglichkeit, diese zu "schachteln".
Von welcher Form von "Veränderung" gehst Du denn aus beismart_pointer<A> a; smart_pointer<A>** b; f(a); g(&a); h(*a); i(*b); j(**b); k(&*b); ...?
Blue-Tiger schrieb:
...
...Wenn ich Code ueberfliege hab ich die Deklaration aber selten so genau im Kopf.Das ist IMHO das eigentliche Problem. Sich aber stattdessen eine "Eselsbrücke" zu bauen, die noch unsicherer ist, halte ich aber nicht für eine Lösung.
Andererseits sollte das in Zeiten morderne IDEs keine echte Schweirigkeit mehr darstellen - Maus drüber und schon siehst Du's.Gruß,
Simon2.
-
volkard schrieb:
Da steckt evtl eine Bedeutungsüberladung drin, m_target!=0 heißt zugleich, daß das Schiff im Angriffsmodus ist. Ich schätze, davor hätte ich Angst und würde den Modus woanders scpeichern und genau dann, wenn Modus==ANGRIFF, dann ist m_target definiert. Evtl wäre m_target auch nur ein Attribut eines AngriffsPlan-Objekts und nicht immer optionales Schiffsattribut. Ich tue mir mit optionalen Attributen recht schwer.
Okay. Ich finde optionale Attribute recht nützlich, hatte auch noch nie wirklich Probleme damit. Aber es kann hier natürlich heikel werden, wenn es sich um mehr als einen simplen Setter handelt. Vielleicht würde ich dann auch sowas wie die Angriffsplan-Variante bevorzugen, kommt auf die besonderen Umstände an. Ich hätte aber kein grundsätzliches Problem mit einer einzigen Methode.
Eine ähnliche Sache, bei der eine Entscheidung zwischen einer oder zwei Memberfunktionen fällig ist:
bool Button::SetVisible(bool Visible); // vs. void Button::Show(); void Button::Hide();Obwohl
Show()undHide()sprechender sind und in vielen Situationen direkt so vom User aufgerufen werden können, finde ichSetVisible()flexibler und würde wohl das einsetzen (ich gehe auch hier von einfachen Set-Funktionen aus). Falls sich herausstellt, dass man tatsächlich dauernd am explizittrueoderfalseübergeben ist und das nicht schön findet, kann man ja immer noch zwei globale Funktionstemplates schreiben, die dann auf alle Klassen anwendbar sind, welche eineSetVisible()-Methode bereitstellen.