Was tun wenns nix zu löschen gibt?
-
hustbaer schrieb:
DStefan schrieb:
Ich finde es nicht nachvollziehbar, dass der Versuch, ein nicht existierendes Element zu löschen als Fehler gewertet werden soll. Ganz gleich ob mit assert() oder Exception. Wie kommt ihr darauf, dass dies ein "Programmierfehler" ist? Ich meine ihr definiert das einfach, und diese Festlegung finde ich fragwürdig.
Da "wir" in diesem Fall die Rolle des Library-Designers übernehmen, können "wir" das einfach so entscheiden.
Wenn die Library sagt "du darst erase() nur für Elemente aufrufen die auch wirklich drinnen sind", dann ist es eben ein Programmierfehler, erase() mit Elementen aufzurufen die nicht drinnen sind.
Du, der du aus der Rolle des User-Programmierers argumentierst, musst dich einfach damit abfinden - das ist in der Realität auch nicht anders. Oder verbringst du deine Tage und Nächte damit, gewisse Dinge in gewissen Libraries fragwürdig zu finden, und dich darüber aufzuregen dass es anders besser gewesen wäre?
Das ist ja alles ganz nett, nur beantwortet es meine Frage nicht. Wie kommt man auf den Gedanken, das Löschen eines Elements als Fehler zu werten, das nicht in der Collection vorhanden ist?
Indem ich mich gegen diese Festlegung wende, argumentiere ich keineswegs aus der Sicht des Users/Kunden. Im Gegenteil: Ich frage, warum unsere Containerklasse darauf bestehen soll, einen solchen Löschversuch als Fehler zu behandeln, wenn doch durch schlichtes Ignorieren des Aufrufs die Postcondition eingehalten werden kann. Diese Frage bleibt "innen", sie sieht sich nur den Container an, nicht den Kunden.
Hingegen die Feststellung, dass auf diese Weise "Programmierfehler" vermieden werden können: Das ist ein Argument, das nur funktioniert, wenn man von "außen", also aus der Sicht des Users argumentiert. Das Design von removeItem() ist klar und sauber, wenn nach dem Aufruf das Item nicht mehr in der Collection ist. Ganz gleich, ob es vorher drin war oder nicht. Die Argumente für eine Exception betreffen den Container überhaupt nicht, sondern nur denjenigen, der ihn einsetzt.
hustbaer schrieb:
Natürlich sollte man als Library-Designers Vor- und Nachteile der verschiedenen Möglichkeiten abwägen, und nicht willkürlich irgendwas wählen. Genau das passiert in diesem Thread, in Form dieser Diskussion. Was passt dir nun bitte daran nicht?
Genau das mache ich doch: Vor- und Nachteile abwägen. Ich diskutiere. Nur dass ich eben anderer Meinung bin als du. Hast du den Eindruck, dass ich nicht abwäge? Willkürlich wähle? Das würde mich doch sehr wundern.
hustbaer schrieb:
Was der Vorteil der "assert(vorhanden)" Variante ist kann ich dir sagen: es führt dazu, dass der User-Programmierer darüber nachdenken muss, was es denn nun bedeutet, wenn er an einer bestimmten Stelle was löschen will, was garnicht da ist. Nämlich spätestens dann, wenn das assert() das erste mal fliegt. Und wie dieser Fall korrekt zu behandeln ist. Und das vermeidet oft Programmierfehler.
Ignoriert die Funktion den Fall einfach, wird er nicht gezwungen darüber nachzudenken. Otto-Normal-User-Programmierer wird dann auch nicht weiter darüber nachdenken. In einigen Fällen wird aber "einfach ignorieren" nicht korrekt sein. Und das nennt man dann einen Fehler. Nen?
Nein! Im Ernst, was ist denn das für ein Argument? Eine Klasse verhält sich so und so (und meiner Meinung nach ohne objektiven Grund), um den Kunden zu "anständigem" Verhalten zu erziehen?!!
Erstens: Wohin führt das? Wenn ich dieses Argument ernst nehme, wie muss ich meine Klassen designen, damit sie ihm genügen? Ich müsste wohl sagen: "Ja, was du hier tun willst, dass kann meine Klasse schon tun. Aber der Kontext, in dem du das tust, die Absicht, die du verfolgst kann ich nicht gut heißen." Das ist absurd!
Zweitens: Du drängst dem Kunden des Containers hier deine Auffassung von "Programmierfehler" auf. Ich habe in einem anderen Beitrag schon geschrieben, dass dies gar nicht so ausgemachte Sache ist. Es muss nicht zwingend ein Programmierfehler sein, nicht vorhandene Items löschen zu wollen. Wenn ich removeItem() so designe, dann zwinge ich den "Programmierfehler" herbei. Und noch einmal: Ich tue das ohne Not!
hustbaer schrieb:
Man kann auch find() verwenden, welches im Fall "nicht vorhanden" einfach end() zurückliefert, und sonst einen Iterator auf das gefundene Element. So kann man dann ohne verdoppeltem Aufwand das Element löschen, nämlich indem man den Iterator an die erase() Funktion übergibt.
Klar kann man das. Aber das war hier ja nicht die Frage. Ich habe einfach das Ausgangsposting wörtlich genommen: removeItem() erhält ein Item als Parameter, keinen Iterator. Ich meine, die Diskussion, ob das sinnvoll ist, gehört in einen anderen Thread. Und würde wohl auch mehr Wissen über den Container erfordern.
Stefan.
-
Es ist doch nicht allein die Frage, ob Post/Pre-Conditions verletzt werden, wie eine Schnittstelle aussehen soll.
Fehlervermeidendes oder wenigstens fehlersuchfreundliches Design gehört zu C++ dazu und sollte in Sachen Stil auch immer eines der wichtigsten Ziele sein.
Ob der User eine Zeile mehr tippen muß, ist dagegen recht egal.
Und immer wieder bieten Klassen dies oder jenes nicht an, weil der User damit nur Mist bauen würde. Wenn ich Deinen Stil weitertreibe bekommt eine verkettete Liste einen T& operator[](size_t index).
-
volkard schrieb:
Es ist doch nicht allein die Frage, ob Post/Pre-Conditions verletzt werden, wie eine Schnittstelle aussehen soll.
Nein, es ist z.B. auch die Frage, ob eine Klassen macht, was sie soll. Eine Klasse (meine ich) soll ihre Pre- und Postconditions eisern schützen. Und sie soll das tun, was sie "sagt", dass sie tut - und damit meine ich nicht bloß die Dokumentation.
Eine Klasse soll aber nicht die Umstände kontrollieren wollen, unter denen sie eingesetzt wird. Sie muss ihre eigene Integrität wahren, aber nicht die Integrität des Programms (oder so).
Wenn mir danach wäre, auf einer std::list auch ohne op[] massenweise "Index-Zugriffe" (du weißt schon) auszuführen, was würdest du sagen? Programmierfehler? Zumindest aber Anwendungsfehler. Mein Programm funktioniert aber es ist vielleicht Scheiße langsam.
Und wenn std::list ohne großen Aufwand feststellen könnte, dass der Kunde einen Programmierfehler macht - ich weiß, ich mute deiner Vorstellungskraft einiges zu
- sollte die Collection dann eine Exception werfen?Ich denke nein. Eine solche Klasse tut nicht, was sie soll.
volkard schrieb:
Und immer wieder bieten Klassen dies oder jenes nicht an, weil der User damit nur Mist bauen würde. Wenn ich Deinen Stil weitertreibe bekommt eine verkettete Liste einen T& operator[](size_t index).
Bitte, ich habe nicht mehr Verhalten vorgeschlagen, sondern weniger. Ich wollte nicht mehr Aufwand treiben, sondern weniger.
Naja - den Rest habe ich ja schon oben gesagt.Stefan.
-
Nexus schrieb:
@ hustbaer:
Meinst du zufällig das von Stepanov?Jopp, genau das, danke!
Geht los ab Seite 134. Bzw. bei "Lecture 13. Iterators".Habs zwar nicht vollständig gelesen und bin mir dementsprechend auch nicht sicher, ob die von dir erwähnte Thematik dort angesprochen wird, aber das wurde hier zumindest ein paar Mal genannt.
Lies es (ganz)

Ich kann zwar mit einigem was er schreibt nicht wahnsinnig viel Anfangen, aber alles in allem finde ich es ziemlich gut. Wenig theoretisches lala sondern relativ schön aus der Praxis. Manchmal übertreibt er es für meinen Geschmack ein wenig, aber wenn man das so liest, bekommt man finde ich eine gute Vorstellung davon, was man so beim Thema Library-Design alles falsch oder richtig machen kann
-
hustbaer schrieb:
Lies es (ganz)

Ich kann zwar mit einigem was er schreibt nicht wahnsinnig viel Anfangen, aber alles in allem finde ich es ziemlich gut. Wenig theoretisches lala sondern relativ schön aus der Praxis. Manchmal übertreibt er es für meinen Geschmack ein wenig, aber wenn man das so liest, bekommt man finde ich eine gute Vorstellung davon, was man so beim Thema Library-Design alles falsch oder richtig machen kann
Ja, werde ich tun, danke!
Ich habe mich bisher ein wenig mit dem dort erwähnten
swap()und dem drumherum befasst, aber das ist auch schon eine Weile her. Das hat mich da sehr interessiert, aber irgendwie hab ichs wohl wieder vernachlässigt. Naja, in nächster Zeit habe ich mir sowieso vorgenommen, mich theoretisch ein wenig weiterzubilden, da kommt das gerade recht...
-
@DStefan:
Guck mal.
Erstmal vorweg: ich persönlich finde die "return bool" Variante die "beste". Nur damit wir uns nicht misverstehen. Hab ich zwar schon geschrieben, aber vielleicht hast du das ja überlesen.Davon abgesehen bin ich aber der Meinung, dass der Standpunkt "remove(gibtsNicht) == Fehler" seine Berechtigung hat.
Es ist logisch gesehen halt einfach nicht möglich, etwas zu entfernen, was nicht da ist.
Die Frage ob "remove(gibtsNicht)" "OK" ist, ist IMO äquivalent zur Frage ob "delete NULL" "OK" ist. Der C++ Standard sagt in beiden Fällen "ja, ist OK". Was aber durchaus strittig ist, in beiden Fällen (hallo volkard :)).
"delete p", wenn "p" NULL ist, ist, meiner Meinung nach, logisch betrachtet Unfug. Praktisch, aber Unfug. Warum die "Hardliner" (hallo volkard :D) argumentieren dass es verboten werden sollte: du kannst ein Objekt welches nicht existiert, nicht löschen. Der Versuch es zu tun, deutet darauf hin, dass das Programm welches es tut, nicht weiss was es tut. = Programmierfehler.
Und von diesem Standpunkt aus betrachtet, macht IMO keine andere Definition von "remove" Sinn, als die, dass es ein Fehler ist, "remove" mit einem Item aufzurufen, welches garnicht in der Collection enthalten ist.
----
Bitte, ich habe nicht mehr Verhalten vorgeschlagen, sondern weniger. Ich wollte nicht mehr Aufwand treiben, sondern weniger.
Ansichtssache. Aus Sicht der Implementierung: ja, vielleicht. Aus Sicht der Interface-Definition: nein. "Entfernt das Element x, falls es vorhanden ist" ist "mehr" als "Entfernt das Element x".
-
DStefan schrieb:
Nein, es ist z.B. auch die Frage, ob eine Klassen macht, was sie soll. Eine Klasse (meine ich) soll ihre Pre- und Postconditions eisern schützen.
Gut und hustbaer wie auch volkard sagen, dass das
Itemvorhanden sein soll, die Precondition vonRemoveItemist. Und wenn die Precondition verletzt ist, dann soll man dem User dies schliesslich mitteilen, da man die Pre- und Postconditions eisern schützen muss, nicht?
Die Frage ist halt wirklich nur, ob man diese Precondition annehmen soll oder nicht?
Und da denke ich, kommt es sehr auf die Art der Funktion an. Ist zum Beispiel Item dervalue_typeder Containerklasse, dann kann man wohl davon ausgehen, dass die Precondition nicht gilt, da diese Funktion einfach alle Vorkommnisse von diesem Item-Wert löschen soll. Ähnlich wie dies auchstd::list<Item>::removetut.
Ist RemoveItem hier allerdings zum Beispiel eine interne Funktion der Klasse (private) und Item ein Container-Element, dann sollte man wohl die Precondition schon prüfen, ob Item auch wirklich zu diesem Container gehört. Dies könnte man auch auf eine öffentliche Funktion, welche einen Iterator annimmt verwenden. Wie dies durchaus gewisse Implementationen machen, nämlich einassert(false)aufrufen, wenn man anstd::list<X>::eraseoderstd::vector<X>::erasefalsche Iteratoren übergibt.Und es gibt wohl auch noch andere Möglichkeiten, wie man diese
RemoveItemFunktion verstehen kann
Wenn man allerdings die Art wählt, dass man keine Precondition nimmt, dann würde ich auch einen entsprechenden Rückgabewert empfehlen, aus dem einfachen Grund, dass man dem User dann helfen kann, sowas zu machen:
bool result = removeItem(item); assert(!result);Eine Library soll schliesslich auch Userfreundlich sein, also diesem helfen wo es nur geht.
Grüssli
-
Dravere schrieb:
DStefan schrieb:
Nein, es ist z.B. auch die Frage, ob eine Klassen macht, was sie soll. Eine Klasse (meine ich) soll ihre Pre- und Postconditions eisern schützen.
Gut und hustbaer wie auch volkard sagen, dass das
Itemvorhanden sein soll, die Precondition vonRemoveItemist. Und wenn die Precondition verletzt ist, dann soll man dem User dies schliesslich mitteilen, da man die Pre- und Postconditions eisern schützen muss, nicht?
Die Frage ist halt wirklich nur, ob man diese Precondition annehmen soll oder nicht?
Und da denke ich, kommt es sehr auf die Art der Funktion an. Ist zum Beispiel Item dervalue_typeder Containerklasse, dann kann man wohl davon ausgehen, dass die Precondition nicht gilt, da diese Funktion einfach alle Vorkommnisse von diesem Item-Wert löschen soll. Ähnlich wie dies auchstd::list<Item>::removetut.
Ist RemoveItem hier allerdings zum Beispiel eine interne Funktion der Klasse (private) und Item ein Container-Element, dann sollte man wohl die Precondition schon prüfen, ob Item auch wirklich zu diesem Container gehört. Dies könnte man auch auf eine öffentliche Funktion, welche einen Iterator annimmt verwenden. Wie dies durchaus gewisse Implementationen machen, nämlich einassert(false)aufrufen, wenn man anstd::list<X>::eraseoderstd::vector<X>::erasefalsche Iteratoren übergibt.Und es gibt wohl auch noch andere Möglichkeiten, wie man diese
RemoveItemFunktion verstehen kann
Wenn man allerdings die Art wählt, dass man keine Precondition nimmt, dann würde ich auch einen entsprechenden Rückgabewert empfehlen, aus dem einfachen Grund, dass man dem User dann helfen kann, sowas zu machen:
bool result = removeItem(item); assert(!result);Eine Library soll schliesslich auch Userfreundlich sein, also diesem helfen wo es nur geht.
Grüssli
Damit bin ich auf der ganzen Linie einverstanden. Ich bin seit dem ersten Posting davon ausgegangen, dass removeItem() ein Teil der öffentlichen Schnittstelle ist. Und ja, falls removeItem() fordert, dass das übergebene Item vorhanden ist (also eine solche Precondition festlegt), dann sollte es eine Exception geben - oder meinetwegen auch ein assert().
Bloß bin ich eben auch davon ausgegangen, dass diese Diskussion hier sich um die Frage dreht ob dies eine Precondition sein sollte. Ich dachte eigentlich, das sei klar zu sehen gewesen, aber vielleicht habe ich mich nicht gut genug ausgedrückt.
Der Vorschlag mit der Rückgabe eines bool liegt in sofern auf meiner Linie, als dies dem User die Möglichkeit gibt, seinerseits auf Programmierfehler zu prüfen - oder eben auch nicht. Nicht die Klasse definiert das Nichtvorhandensein des Items als Fehler, sondern der Kunde. Und ich meine, das ist die richtige Stelle dafür.
Stefan.
-
hustbaer schrieb:
Die Frage ob "remove(gibtsNicht)" "OK" ist, ist IMO äquivalent zur Frage ob "delete NULL" "OK" ist. Der C++ Standard sagt in beiden Fällen "ja, ist OK". Was aber durchaus strittig ist, in beiden Fällen (hallo volkard :)).
"delete p", wenn "p" NULL ist, ist, meiner Meinung nach, logisch betrachtet Unfug. Praktisch, aber Unfug. Warum die "Hardliner" (hallo volkard :D) argumentieren dass es verboten werden sollte: du kannst ein Objekt welches nicht existiert, nicht löschen. Der Versuch es zu tun, deutet darauf hin, dass das Programm welches es tut, nicht weiss was es tut. = Programmierfehler.
Nun ja - vermutlich kann man sich darüber wirklich den Mund fusselig diskutieren
Vielleicht verstehen wir auch unter "Programmierfehler" verschiedenes. Beziehungsweise wir beantworten die Frage verschieden, wer definieren soll, was ein Fehler ist und was nicht.Dein Punkt ist, dass man ein Objekt, das es nicht gibt, nicht löschen kann. Meiner ist, das man es nicht zu löschen braucht - um zu dem "versprochenen" Ergebnis zu kommen, dass das Objekt nach dem Aufruf nicht mehr existiert.
In diesem Sinne stimme ich deinem letzten Satz nicht zu. removeItem(gibtsNicht) und delete 0 deuten in meinen Augen nicht auf einen Fehler hin. Sie stellen lediglich einen bestimmten Status her, nämlich das es das Objekt nach dem Aufruf nicht mehr gibt.
Stefan.
-
hustbaer schrieb:
Ansichtssache. Aus Sicht der Implementierung: ja, vielleicht. Aus Sicht der Interface-Definition: nein. "Entfernt das Element x, falls es vorhanden ist" ist "mehr" als "Entfernt das Element x".
Außerdem ist "Entfernt das Element x" schneller.
Ach, da kommt mir ja gerade, daß ich in den letzten Tagen viel über pop() nachgedacht habe, die haben die Tendenz, sehr einfach zu sein.
void popFront(){ ++begin; }und es wäre wirklich eine Ungeheuerlichkeit, da ein if reinzumachen. Widersteht dem Drang, aus C++ ein zweites Java zu machen. Zero abstraction overhead ist die Devise.
auch assert, siehe Seite 41.
http://www.boostcon.com/site-media/var/sphene/sphwiki/attachment/2009/05/08/iterators-must-go.pdf
-
volkard schrieb:
hustbaer schrieb:
Ansichtssache. Aus Sicht der Implementierung: ja, vielleicht. Aus Sicht der Interface-Definition: nein. "Entfernt das Element x, falls es vorhanden ist" ist "mehr" als "Entfernt das Element x".
Außerdem ist "Entfernt das Element x" schneller.
Im Fall "Suchen, Finden, Löschen", um den es hier geht, nicht. Bzw. nur in recht esoterischen Spezialfällen.
BTW: Liest sich interessant. Muss man sehen ob's ein Video von dem Vortrag gibt. (Hast du vielleicht nen Link?)
-
hustbaer schrieb:
Außerdem ist "Entfernt das Element x" schneller.
Im Fall "Suchen, Finden, Löschen", um den es hier geht, nicht. Bzw. nur in recht esoterischen Spezialfällen.
da=suche() if(gefunden(da)) lösche(da);das if fällt jedesmal weg, unabhängig davon, wie suchen und löschen geht.
hustbaer schrieb:
BTW: Liest sich interessant. Muss man sehen ob's ein Video von dem Vortrag gibt. (Hast du vielleicht nen Link?)
Hab keinen Link.
-
volkard schrieb:
da=suche() if(gefunden(da)) lösche(da);das if fällt jedesmal weg, unabhängig davon, wie suchen und löschen geht.
Das stimmt so nicht. Man kann immer
lösche(suche(item))schreiben, weil lösche() das Argument ohnehin gegen end() testen muss, um korrekt zu arbeiten. Und suche(item) wird doch wohl end() liefern müssen, falls item nicht gefunden wird. Die obige Vorgehensweise ist also genau die, die man braucht, wenn man nicht darauf reagieren möchte, dass item nicht vorhanden ist.
Hingegen muss man zum Abfangen dieses "Programmierfehlers" schreiben:
da = suche(item); assert(da != end()); lösche(da);Und das ist mehr Aufwand.
Oder habe ich jetzt irgend etwas übersehen?
Stefan.
-
DStefan schrieb:
volkard schrieb:
da=suche() if(gefunden(da)) lösche(da);das if fällt jedesmal weg, unabhängig davon, wie suchen und löschen geht.
Das stimmt so nicht. Man kann immer
lösche(suche(item))schreiben, weil lösche() das Argument ohnehin gegen end() testen muss, um korrekt zu arbeiten. Und suche(item) wird doch wohl end() liefern müssen, falls item nicht gefunden wird. Die obige Vorgehensweise ist also genau die, die man braucht, wenn man nicht darauf reagieren möchte, dass item nicht vorhanden ist.
Hingegen muss man zum Abfangen dieses "Programmierfehlers" schreiben:
da = suche(item); assert(da != end()); lösche(da);Und das ist mehr Aufwand.
Oder habe ich jetzt irgend etwas übersehen?
Natürlich.
lösche muß doch nicht gegen end testen!
-
@volkard:
Achherrjeh, ja, natürlich fälle ein "if" weg.
Es fällt aber nichts weg, was wirklich ins Gewicht fallen würde.Ich meine ein "if" wird, im Vergleich dazu wie lange "suche()" braucht, ziemlich untergehen.