Proxys und const-correctness
-
Ähnlich wie beim Iterator wollte ich jetzt also eine Klasse ConstProxy machen, wovon eine Klasse Proxy erbt. So weit, so gut.
Erbt denn const_iterator von iterator (bzw. andersrum)?
-
Habe schon einige Implementierungen gesehen, die das so machen, ja.
-
Nun, warum benutzt du dann nicht den gleichen Trick wie dort? Desweiteren gibt es auch noch const_cast, wenn du deinen ConstProxy zu einem Proxy verwandeln moechtest, d.h. du erbst einfach und fuegst non-const-Methoden hinzu und das referenzierte Objekt wird mittels const_cast gefuegig gemacht. Oder was ist mit: Einfach Proxy zu implementieren und ConstProxy davon erben lassen. ConstProxy reimplementiert alle non-const-Methoden dahingehend, dass sie alle eine runtime-Exception werfen. Bei entsprechenden Unit-Tests ist das so gut wie ein Fehler zur Compilezeit.
Umso länger ich drüber nachdenke, umso berechtigter finde ich den Einwand jedoch.
Denk bloss nich zu lange drueber nach. Ein Interface ohne Beschreibung (Spezifikation) ist nichts wert, auch wenn sprechende Namen verwendet wurden. Vor- und Nachbedingungen gehoeren in die Dokumentation. Wer sich an diese nicht haelt, hat Pech. Warum soll jede Fehlbenutzung im Code abgefagen werden?
-
Finde das Okay. Allein von der Semantik des Wortes proxy ist klar, dass die Lebenszeit des übergebenen Objektes länger sein muss als die des Proxies.
-
Lbenszeit des übergebenen Objektes länger sein muss
Wollte gerade länger/gleich hinzufügen, dann ist mir aufgefallen dass letzeres gar nicht möglich ist.
Nun, warum benutzt du dann nicht den gleichen Trick wie dort?
Es gibt da ein Problem. Denn wie facepalm richtig kommentiert hat, sollte ein ConstProxy tatsächlich nicht zu einem non-const Interface konvertierbar sein. Somit ist facepalms Lösung tatsächlich die einzig richtige.
-
Gerade ohn compiler runtergetippt. Sollte funktionieren:
template<class I> class ProxyBase { public: template<class> friend class ProxyBase; ProxyBase(I& actualObject) : actualObject(&actualObject){} //conversion ctor ProxyBase(ProxyBase<Interface> const& other):actualObject(other.actualObject); //conversion op= ProxyBase & operator=(ProxyBase<Interface> const& other){ actualObject = other.actualObject); return *this; } void someMethod() { // mache irgendwas actualObject->someMethod(); } void someOtherMethod() const { // mache irgendwas actualObject->someOtherMethod(); } private: I* actualObject; }; typedef ProxyBase<Interface> Proxy; typedef ProxyBase<Interface const> ConstProxy;
//edit nochmal kleinen eleganten Trick eingebaut und damit an template<> gespart...
-
Mal meine erste (bzw. zweite) Lösung angeguckt?
Deine ist übrigens falsch. Man kann über ein ConstProxy non-const Methoden aufrufen.
Dazu sollte das schon nicht einmal kompilieren, siehe erste Seite, ich hatte dieselben Probleme.
-
ConstProxy tatsächlich nicht zu einem non-const Interface konvertierbar sein
Nun, man kann nicht alles haben. Entweder ist der ConstProxy nicht zu einem Interface konvertierbar, dann darf er nicht erben. Oder er ist Erbe und muss non-const-Methoden reimplementieren, die dann eine Exception werfen. Waehle zwischen Compilezeit const und Laufzeitpolymorphie (Interface, Vererbung)! Ersteres ist C++-like, letzteres Java-like (und in Java gibt es kein const).
In CLOS kann man before- und after-Methoden angeben. Leider ist C++ nicht Lisp. Pech. Selbst habe ich noch nie eine Proxyklasse erbend von einem Interface in C++ gebraucht, weil a) const und non-const problematisch ist b) Proxies nur temporaere Objekte bisher waren. Wenn du was loggen moechtest, dann tu es! Wenn nicht, dann lass es. Keep it simple. Fuer mich riecht es mehr und mehr nach overengineering.
Ist wie mit Spielen: Write games, not engines! Das laesst sich auch allgemeiner formulieren: Write programs, not Frameworks!
-
Keine Ahnung, man muss halt aufpassen, wo man ein Interface drübersetzt und nicht. Bei Java sucht man nach Gründen das zu tun, man will es einfach überall und manch einer fühlt sich toll, wenn er so eine kreative Ader an den Tag legt, dass er es auch wirklich überall findet.
In meinem Fall erscheint es einfach angebracht. Ich bin im Refactoring dessen, was schon perfekt funktioniert hatte, aber nicht so toll erweiterbar war und nicht so super änderbar. Ich schaffe mir durch so ein Interface einfach die Flexibilität, die ich praktisch erprobt immer gewollt habe. Daher stellt es sich als optimale Lösung für mich heraus. Der Schlechtredner (damit meine ich nicht eine Person, sondern wirklich die Personifizierung der auf zu wenigen Infos basierenden Kritik) des Hauses kann jetzt sagen, dass der restliche Teil meiner Software Murks ist, wenn ich an dieser Stelle ein Interface brauche. Nur ist mit solchen Aussagen irgendwas Produktives anzufangen...?
Ich bin auch ein Gegner von Overengineering und kenne sowohl Leute als auch Software, die es übertreiben. Aber das andere Extrem ist einfach grundsätzlich jede Art von Interface zu verteufeln, weil es ja "java-like" sein könnte und daher grundsätzlich auch sein muss.
Das ist vor allem ohnehin das absolute Totschlagargument, weil es in C++ ja immer eine bessere Möglichkeit gibt. Es ist auch egal, ob die wirklich praktische Vorteile hat, Hauptsache ist, dass das in Java auf gar keinen Fall üblich ist.
knivil:
Man kann nicht alles haben. Aber was hast Du gegen facepalms Lösung? Die ist nah an allem dran (auch wenn ich die zweite Indirektion noch nicht verstehe, aber auch ohne ist es schon Mal ganz chic).Bei den restlichen Argumenten und Vorschlägen habe ich den Eindruck, dass wir das schon auf den vorherigen Seiten durchgekaut haben, von daher fällt mir kein neuer Kommentar mehr dazu ein. Trotzdem natürlich herzlichen Dank! Wenn ich dazu komme, schaue ich es mir nochmal genauer an, um sicherzustellen, dass es wirklich nichts Neues ist.
-
Aber was hast Du gegen facepalms Lösung?
Warum sollte Proxy von Interface erben, waehrend ConstProxy es nicht tut? Das ist inkonsistent. Deswegen wuerde ich Proxy ebenfalls nicht von Interface erben lassen. Ich hoffe du hast trotzdem verstanden, warum deine Anforderungen nicht vereinbar sind.
-
ConstProxy ist kein vollständiges Interface, weil es nur die const-Methoden unterstützt. Somit ist oberflächlich betrachtet erstmal nicht LSP verletzt.
Davon unabhängig löst es aber das Problem, dass const-correctness gewahrt ist. Und was stört es mich, wer wovon erbt, wenn ich es einsetzen kann? Höchstens verwirrend ist es, wenn man auf den Klassenkopf schaut und kein ": public Interface" sieht. Dafür gibt es dann eine Zeile Kommentar und gut ist. Was ist an der Inkonsistenz schlimm, außer dass es Inkonsistenz ist?
Edit: Also klar ist das nicht schön. Aber im Gegensatz zu all den anderen Nachteilen, die wir hier so hatten, empfinde ich das als ziemlich geringes Übel.
-
Und was stört es mich, wer wovon erbt, wenn ich es einsetzen kann?
Ach so, und ich dachte, du willst wissen, wie es in C++ ideomatisch geloest wird. Warum geht sonst der Thread schon ueber 6 Seiten.
Was ist an der Inkonsistenz schlimm, außer dass es Inkonsistenz ist?
Weil normalerweise ein Proxy auch ein ConstProxy ist, was durch eine is_a-Beziehung bzw. Vererbung ausgedrueckt wird. Am besten ist es wohl auf den ConstProxy zu verzichten.
-
Sone schrieb:
Mal meine erste (bzw. zweite) Lösung angeguckt?
Deine ist übrigens falsch. Man kann über ein ConstProxy non-const Methoden aufrufen.kannst du nicht. actualobjct ist const. oops.
und es funktioniert wunderbar:
vielleicht solltest du das nächstes mal die Funktionen nicht virtuell machen.
-
Was ist denn "C++-idiomatisch" wieder? Ja, ich weiß, was das ist, aber die Idiome haben Hintergründe und Gründe, weswegen man sie einsetzt. Die wollen nicht als Götzenbilder verehrt werden oder dass man nicht mehr drüber nachdenkt, dass es auch anders geht.
Wieso es nur einen Grund dafür geben kann, dass der Thread über sechs Seiten geht, verstehe ich nicht. Es gibt Lösungsvorschläge, über die diskutiert wird. Und zu jeder Lösung kommen von verschiedenen Seiten verschiedene Argumente, nicht nur von mir. Jedenfalls lässt sich eine Diskussion mit vielen Argumenten nicht auf einen Schlag beenden, weil halbgare oder Totschlagargumente auf den Tisch geworfen werden... Auf ConstProxy zu verzichten ist für mich einer der schlechteren Vorschläge, im Kopf hatte ich das ja auch schon, ist ja auch offensichtlich, stieß aber auf den ersten Seiten aber wohl nicht auf Zustimmung.
otze:
Die Sache ist, das kompiliert anscheinend ja nur, weil someMethod-Aufruf nur dann einen Compiler-Fehler gibt, wenn man es aufruft. Wenn man ein Interface drüberhaut, das ich ja benötige, dann wird die Methode virtual und somit wirft es auch anderweitig einen Compile-Time-Error und die Lösung funktioniert wieder nicht.Wozu ich das Interface brauche, habe ich ja nun häufig erläutert.
-
Eisflamme schrieb:
Wozu ich das Interface brauche, habe ich ja nun häufig erläutert.
Bislang hast du nur erläutert, das du glaubst ein explizites Interface zu brauchen. Das ist etwas anderes als zu erläutern, warum du das tust, musst du zugeben.
//edit Und so ein const_cast wie in der jetzigen Lösung ist doch ein Alarmsignal, das da was mit dem Interface nicht stimmt.
-
Ich habe erläutert, dass ich dynamische Polymorphie brauche, weil sich in manchen Programmteilen zur Laufzeit entscheidet, ob dort ein Proxy auf ein Objekt oder das eigentliche Objekt übergeben wird - was willst Du denn noch?
Zum Edit: Wie gesagt, es gibt nicht die perfekte Lösung. Wenn die Lösung, die sonst überall passt und die meisten Vorteile zu ergeben scheint, ein Alarmsignal feuert, ist das eben so, solange es nur ein Signal ist. Ohne das durchzudenken, reicht mir das nicht als Gegenargument, solange nicht eine bessere Lösung präsentiert wird...
Ist wie gesagt ein Abwägen von Vor- und Nachteilen. Ich teile nicht die Auffassung, dass irgendwo ein theoretisches Argument (Konsistenz, const-cast signalisiert schlechtes Design, Interfaces sind java-like und haben in C++ nichts zu suchen) so stark wiegt, dass es die praktischen Vorteile überkompensiert.
Ich gehe jetzt erstmal schlafen, gute Nacht
-
Ich habe erläutert, dass ich dynamische Polymorphie brauche, weil sich in manchen Programmteilen zur Laufzeit entscheidet, ob dort ein Proxy auf ein Objekt oder das eigentliche Objekt übergeben wird - was willst Du denn noch?
Das verstehe ich schon. Leider kannst du ConstProxy mit der Loesung von facepalm nicht als Ersatz fuer Proxy oder das eigentliche Objekt benutzen, weil ConstProxy nicht Teil der Vererbungshierarchie (is_a Beziehung) ist.
weil halbgare oder Totschlagargumente auf den Tisch geworfen werden...
Die Argumentation mit is_a ist nicht halbgar.
Zur Erklaerung: Ich habe C++-Programmierstil mit Java-Programmierstil anhand deines Interface/Proxy/ConstProxy-Beispiels. Voellig emotionslos. Ich habe auch diesen Thread angefuehrt, warum GoF-Patterns nicht immer die beste Wahl fuer C++ sind: http://www.c-plusplus.net/forum/319393-20 . Das hat aber nichts mit dir zu tun.
-
Sone schrieb:
Ergo, nehme niemals die Adresse von const& Variablen.
Man sollte einfach nie davon ausgehen, dass eine const-Referenz langlebig ist. Und das führt hier dazu, dass der komplette Code so nicht funktioniert, sondern man einen Zeiger nehmen muss - sowohl für die interne Referenzierung als auch für die gesamte Schnittstelle (um Inkonsistenz zu vermeiden).
Naja...
Ich kann mir jetzt nicht vorstellen wie man sich damit in genau dieser Situation ins Knie schiessen kann.Das Interface wird wohl kaum eine konkrete Klasse mit einem conversion-ctor sein, und das konkrete Objekt vermutlich nicht kopierbar sein.
Gut, movebar wäre noch drinnen.Ganz Allgemein geb ich dir aber Recht: wieso sich auf nicht sehr sichere Annahmen darüber verlassen wie die Interface-Klasse und die konkrete Klasse eventuell aussehen, wenn man mit nem Zeiger viel besser kommunizieren kann was abgeht.
-
vielleicht solltest du das nächstes mal die Funktionen nicht virtuell machen.
Aber dann hast du ja gegen die Rahmenbedingungen verstoßen, habe ich gar nicht bemerkt. Wenn du die Methoden nicht virtuell hast, dann ist doch klar, dass der von mir zitierte Paragraph gültig wird und Methoden die nicht benutzt werden auch nicht instantiiert werden.
Dann ist die Lösung aber auch wieder falsch.
-
Sone schrieb:
Dann ist die Lösung aber auch wieder falsch.
hast ja recht *wuschelwuschel*