Proxys und const-correctness
-
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*
-
knivil:
Ich habe C++-Programmierstil mit Java-Programmierstil anhand deines Interface/Proxy/ConstProxy-Beispiels. Voellig emotionslos.
Moment, meinst Du, Du hältst meinen Code für C++- mit Java-Stil gemischt (oder auch nur letzteres) oder dass Du es selbst so nutzt? Ich gehe Mal vom ersteren aus.
Und dann ist das eben für diesen Teil ein Stil, den man auch in Java so nutzen würde. Aber das heißt ja nicht gleichzeitig, dass es schlecht ist oder der Code dadurch dequalifiziert wird. Es gibt in meinen Augen an der Stelle keine bessere Lösung für meine Praxis. Und dass GoF-Patterns nicht immer optimal für C++ sind, ist doch ein alter Hut. Nicht immer impliziert aber eben "manchmal eben doch".
Zudem werden Proxys in C++ ja durchaus eingesetzt. Das Einzige, was hier stört, ist ja, dass ich noch ein Interface drüberpappe. Dann müsste man ja sagen, dass dynamische Polymorphie java-like ist? Oder Interfaces? Also basierend auf den Aussagen hier komme ich zu keiner konsistenten Definition von "java-like", die ich halt bräuchte, wenn das so schlimm wäre, dass man es auf gar keinen Fall in C++-Code einsetzen darf (wirkt für mich so, als wäre das hier ständig gefordert, vielleicht vertue ich mich da).
Ok, aber zum Thema: Wer ein const Interface* erwartet, kann den ConstProxy erhalten, dafür ist der
const Interface* operator()
doch da. Somit verhält sich ConstProxy wie ein Erbe von Interface. Daher wird is_a quasi simuliert. Besser gesagt, wird "ConstProxy is a const Interface" simuliert (na ja, jedenfalls für Referenz- und Zeigerparameter). Geschieht nur nicht klassisch erstklassig durch Vererbung. Aber Vererbung würde ja auch sagen "ConstProxy is an Interface" und das ist ja keine korrekte Aussage. Eigentlich bräuchte man so was wie " : public const Interface" oder irgendwo anders einen Qualifizierer, aber da C++ das nicht anbietet (hätte ja auch wieder Probleme), muss man es anders tun und facepalms Lösung scheint das so weit doch zu tun.
-
Hi,
nach wie vor an einer Antwort interessiert.
Mir ist jetzt jedoch auch aufgefallen, dass das Interface mir nichts bringt *schäm*. Warum?
Weil ich an der Stelle, wo Proxy ODER eigentliches Objekt übergeben werden, ein logischer Konflikt vorliegt. Denn wie soll man bitte gestalten, dass man entweder Proxy oder eigentliches Objekt erhält? Ein Proxy ist ja kopierbar, er verweist ja nur auf das Objekt bzw. leitet alles weiter. Die Range selbst aber nicht. Wenn ich also ein Interface* habe, muss ich höllisch aufpassen, da dort auf keinen Fall ein Zeiger auf einen Proxy reinkommen darf, weil man den Proxy ja ständig by value übergibt, da die Kopie günstig und erwünscht ist.
Jetzt habe ich dämlicherweise gemerkt, dass es echt schnell passieren kann, dass man eben so einen Zeiger übergibt.
Insofern schmeiß ich das Interface jetzt in der Tat weg...
Designproblem bleibt jedoch der Falll, dass man entweder Proxy oder Referenz auf Objekt (ohne Proxy-Indirektion) übergeben kann... Das habe ich jetzt über die Indirektion gelöst, dass es eine Struktur Occurence gibt, die sowohl Zeiger auf ActualObject als auch ein Objekt (ohne Zeiger natürlich) des Proxys hat. Wenn der Zeiger nullptr ist, bedient man sich als Anwender dann dem Proxy...
Echt unschön, hat jemand eine Idee, wie ich das besser löse? Man könnte einen Proxy machen, der einfach alles weiterleitet, diesen und den eigentlichen Proxy von einer gemeinsamen Basis ableiten lassen und dann gezwungen werden immer einen der beiden Proxys (wovon der eine ja quasi nur Zeiger ist) zu übergeben... aber das ist doch auch eklig und ich muss wieder Polymorphie nutzen
-
Eisflamme schrieb:
Ein Proxy ist ja kopierbar, er verweist ja nur auf das Objekt bzw. leitet alles weiter.
Das passiert, wenn man sich nicht an meine Hinweise hält.
Der Fehler:
Man muss jedoch auch noch operator const Interface* überladen, nicht jeder mag Referenzen. Aber sonst scheint das okay zu sein.
Proxy = Interface = Noncopyable = zeigt immer aufs gleiche Objekt = Referenz
Du kannst von mir aus den operator& überladen, damit er ein const Interface* zurückgibt. Aber Proxy=Interface, nicht Proxy=Interface*
Ich finde es aber toll, dass du es auch ohne Vererbung gelöst hast. Das ist nämlich weitaus die bessere Variante.
-
Na ja, es waren viele Hinweise und ich hatte ja auch einige Fragen dazu, wie ich es dann anwenden soll, weil ich es nicht ganz verstand.
Aber ich glaube, wir haben hier auch eine andere Vorstellung vom Proxy an sich. Jedenfalls brauche ich den Proxy (oder wie man ihn dann gerne nennen würde) bei mir für einen Zugriff auf ein bereits bestehendes Objekt, das sich eben in einem Baum befindet. Ein sich in einem Baum befindliches Klassenobjekt hat bei mir Verhaltensunterschiede zu einer solchen Klasse, die nicht in einem Baum abgelegt ist.
Daher will ich eben so ein Proxy-Objekt nutzen, über das man das im Baum befindliche Objekt unter Berücksichtigung der Phänomene - da besagtes Objekt im Baum ist - ändern kann. Die Klasse des eigentlichen Objekts soll aber unberührt bleiben, weil man die eben auch außerhalb vom Baum nutzen kann.
Daher fand ich so einen "Proxy"-Ansatz ganz chic, die eigentliche Klasse bleibt unberührt und für andere Kontexte nutzbar, sobald die Elemente im Baum vorkommen, nutzt man aber den Proxy für den Zugriff.
Nur ist der Proxy dann eben Referenz auf ein bestehendes Objekt. Wie würdest Du das denn lösen?