Proxys und const-correctness
-
Und dein Problem
class Interface { public: void someMethod() = 0; void someOtherMethod() const = 0; }; class ConstProxy : public Interface
widerspricht OOP.
Das kann entweder Java-like durch
class ConstInterface { public: void someOtherMethod() const = 0; }; class Interface : ConstInterface { public: void someOtherMethod() const = 0; }; struct Proxy : Interface {...} struct ConstProxy : ConstInterface {...}
gelöst werden, oder eben mit Templates.
-
Mal davon abgesehen, dass das mit den typedefs nichts mehr zu hat: Aha, also den ganzen Code doppelt schreiben? Tolle Lösung... iterator erbt in manchen Implementierungen auch von const_iterator, um Mal die Analogie weiterzuverwenden. Das ist dann auch javalike für Dich?
Das Interface darüber brauche ich an anderer, hier im Thread nicht erwähnter Stelle. Ist ja schön, dass Du einfach sagst "das ist javalike und daher kacke", aber trotzdem ist das einfach eine Anforderung an die Lösung, die ich hier suche. Wenn Du das ignorierst, hilfst Du mir leider nicht.
Ich erläutere es trotzdem Mal: An anderer Stelle brauche ich das, weil dort entweder ein Proxy oder ein eigentliches Objekt übergeben wird und deren Methoden benutzt werden sollen. Ohne das Interface darüber muss die andere Klasse entweder zum Template werden oder ich brauche fixe Methoden für jede Art von Proxy. Dass zweiteres Käse ist, sollte klar sein. Und aus der Klasse ein Template machen, geht auch nicht, weil die Unterscheidung, ob Proxy oder Nicht-Proxy zur Laufzeit getroffen wird. Kurz: Ich brauche dynamische Polymorphie für meinen Proxy und daher das Interface.
Oder willst Du mir jetzt weißmachen, dass man dynamische Polymorphie ohne Vererbung umsetzen kann?
widerspricht OOP.
Erläuterung?
-
Vorschlag ohne Code-Duplikation:
struct Proxy : Interface{ Proxy(Interface* i) : i(i) {} Interface* i; void someMethod() { /*log*/ i->someMethod(); } void someOtherMethod() const { /*log*/ i->someOtherMethod(); } }; class ConstProxy { Proxy p; public: ConstProxy(Interface const* i) : p(const_cast<Interface*>(i)) {} void someOtherMethod() const { p->someOtherMethod(); } operator const Interface& () { return *i; } // imitiert "extends const Interface" };
Und ja, dein Vorhaben ist etwas widersprüchlich.
Also konstanter Proxy und konstantes Bezugsobjekt sind für mich zwei
verschiedene paar Schuhe, siehe Analogie zu const_iterator und iterator.class ConstProxy : public Interface // (1) class Proxy : public Interface (2)
(1): ConstProxy ist kein Interface, weil jedes Interface someMethod() implementiert haben muss. LSP und so.
(2) Proxy hat hier zwei Ebenen von const, die auf eine abgebildet wurde. Es ist nicht möglich, einen Proxy zu erstellen, der immer auf das gleiche Objekt zeigt, aber trotzdem someMethod aurufen kann. Hier bitte Zuständigkeiten trennen.
const iterator
kann auch das Objekt, auf das er verweist, verändern.
-
Wo ist bei der Kompositionsvariante der Vorteil ggü. Vererbung? Ja, ich halte mir die Vererbungshierarchie frei, aber die benötige ich doch sonst auch nicht. Imo dasselbe in grün.
Zu den Punkten (1) und (2) von Dir: Da hast Du Recht.
Wie löse ich denn jetzt, dass ich ConstProxy und Proxy haben möchte, aber ein Interface drüber sein soll? Ich könnte halt einfach auf die const-correctness verzichten bzw. nur non-const Access anbieten. Irgendwie muss ich hier immer zwischen diversen Übeln wählen.
-
class ConstProxy { Proxy p; public: ConstProxy(Interface const* i) : p(const_cast<Interface*>(i)) {} void someOtherMethod() const { p->someOtherMethod(); } operator const Interface& () { return *i; } // imitiert "extends const Interface" };
Diese Lösung ist immer noch falsch. Denn die Imitation ist immer noch keine echte Vererbung.
Ich würde Sagen, Eisflamme, nimm einfach die Lösung aus dem OP und implementiere die
someMethod
-Funktion einfach beiConstProxy
als ...Die Sache ist ja auch: Selbst wenn ich es irgendwie hinkriege ein instanzierbares ConstProxy zu erstellen, so hat dieses Methoden, die im Interface drin sind. Und dann kriegt man es auch nicht hin, dass es einen CompileTime-Error gibt, wenn man die aufrufen möchte.
private
.(1): ConstProxy ist kein Interface, weil jedes Interface someMethod() implementiert haben muss. LSP und so.
Also ist
Interface const
auch kein Interface?
Das Argument passt hier nicht, dennConstProxy
wrappt nur einen kleineren Teil, der, der für konstante Interface-Instanzen überhaupt erst nutzbar ist.
Das ist so gewollt.
Überhaupt funktioniert Vererbung nicht immer auf Basis des LSP.
-
Sone schrieb:
class ConstProxy { Proxy p; public: ConstProxy(Interface const* i) : p(const_cast<Interface*>(i)) {} void someOtherMethod() const { p->someOtherMethod(); } operator const Interface& () { return *i; } // imitiert "extends const Interface" };
Diese Lösung ist immer noch falsch. Denn die Imitation ist immer noch keine echte Vererbung.
Zeig einen Fall, wo das nicht geht.
(1): ConstProxy ist kein Interface, weil jedes Interface someMethod() implementiert haben muss. LSP und so.
Also ist
Interface const
auch kein Interface?Nein, aber Interface ist ein Interface const.
Überhaupt funktioniert Vererbung nicht immer auf Basis des LSP.
Doch, ausser sie ist private.
-
Oder protected. Und nein, auch wenn sie public ist, kann man in C++ damit Späße treiben wie man lustig ist.
Zeig einen Fall, wo das nicht geht.
void foo( Interface& i ); foo( meinconstproxy );
Hier wird nämlich nix protokolliert oder sonstwas. Also voll daneben.
-
Sone schrieb:
Zeig einen Fall, wo das nicht geht.
void foo( Interface& i ); foo( meinconstproxy );
Hier wird nämlich nix protokolliert oder sonstwas. Also voll daneben.
-
Was spricht gegen static_assert und template-isierte methoden für die Const-Methoden?
-
facepalm schrieb:
Sone schrieb:
Zeig einen Fall, wo das nicht geht.
void foo( Interface& i ); foo( meinconstproxy );
Hier wird nämlich nix protokolliert oder sonstwas. Also voll daneben.
Erst behaupten dass man keine Vererbung braucht, und dann die Vererbung schnell dazuschummeln um zu "beweisen" dass man Recht gehabt hat. Ja, das macht Sinn.
-
Ich würde Sagen, Eisflamme, nimm einfach die Lösung aus dem OP und implementiere die someMethod-Funktion einfach bei ConstProxy als private
Ja, dankeschön. Das erscheint mir zurzeit als beste Lösung. LSP verletzen ist nicht schön, aber viele Dinge sind hier irgendwie nicht schön und mir fällt nichts Besseres ein.
Ich verfolge den Thread aber weiter. Falls jemand gute Vorschläge hat, werde ich sie lesen, schon Mal herzlichen Dank für all die Mühe.
-
Maxi schrieb:
Was spricht gegen static_assert und template-isierte methoden für die Const-Methoden?
Keine Polymorphie mehr?
LSP verletzen ist nicht schön, aber viele Dinge sind hier irgendwie nicht schön und mir fällt nichts Besseres ein.
Dann rechtfertigt sich diese Methode dadurch, dass sie der beste Kompromiss ist und damit die (mit dem vorliegenden Anforderungen) beste Lösung.
Was ich nicht verstehe, und was mal jemand erklären sollte: Wieso wird bei meiner ersten Lösung someMethod eigentlich instantiiert (und bringt daher die Fehlermeldung)? Laut §14.7.1/2 sollte doch - solange man es nicht irgendwie nutzt - die Semantik darin nicht überprüft werden (ich nutze GCC 4.8.1)?
-
Na ja, ist etwas Aufwand rauszufinden, ob eine virtuelle Methode wirklich nicht verwendet wird, oder? Könnte mir vorstellen, dass der Fehler non-virtual nicht auftritt.
-
Eisflamme schrieb:
Na ja, ist etwas Aufwand rauszufinden, ob eine virtuelle Methode wirklich nicht verwendet wird, oder? Könnte mir vorstellen, dass der Fehler non-virtual nicht auftritt.
Ganz vergessen, das stimmt. Sobald die Funktion virtuell ist, gelten andere Regeln.
Bspw.:It is unspecified whether or not an implementation implicitly instantiates a virtual member function of a class template if the virtual member function would not otherwise be instantiated.
Aber hier muss sie sowieso instantiiert werden.
-
hustbaer schrieb:
Erst behaupten dass man keine Vererbung braucht, und dann die Vererbung schnell dazuschummeln um zu "beweisen" dass man Recht gehabt hat. Ja, das macht Sinn.
- Ich behaupte immer noch, dass es dazu prinzipiell keine Vererbung braucht. Aber das gegebene Design und die gewünschte Funktionalität erfordern das hier. Ich hätte vielleicht einen vectorstd::function ins Interface reingenommen.
- Mein Verhalten war absolut korrekt. Schau mal, woher der Code kommt, den Sone mit "Diese Lösung ist immer noch falsch." kommentiert hat. Der entspricht abgesehen von Syntaxfehlern genau meiner ideone-Lösung.
Was ich nicht verstehe, und was mal jemand erklären sollte: Wieso wird bei meiner ersten Lösung someMethod eigentlich instantiiert (und bringt daher die Fehlermeldung)? Laut §14.7.1/2 sollte doch - solange man es nicht irgendwie nutzt - die Semantik darin nicht überprüft werden (ich nutze GCC 4.8.1)?
Du hast den Unterschied zwischen Compiletime und Runtime nicht verstanden.
Eisflamme schrieb:
Ja, dankeschön. Das erscheint mir zurzeit als beste Lösung. LSP verletzen ist nicht schön, aber viele Dinge sind hier irgendwie nicht schön und mir fällt nichts Besseres ein.
ActualObject a; ConstProxy p(a); static_cast<Interface&>(p).someMethod(); // Runtime-error
Was spricht gegen meine Lösung? ( http://ideone.com/9cmUr7 ) Da hast du absolute Sicherheit.
-
Wenn jemand ein Interface erwartet, kann ich ihm meinen ConstProxy übergeben, das geht bei Dir nicht.
Streng genommen sollte man ConstProxy ja auch gar nicht einem Interface-Parameter übergeben können, sondern nur einem const Interface-Parameter. Aber das lässt sich wohl nicht bewerkstelligen.
Ansonsten hat Dein ConstProxy wegen Komposition statt Vererbung in erster Linie mehr Schreibaufwand, weil ich alle Aufrufe weiterleiten muss. Der Nachteil ist nicht groß, aber der obige eben schon eher.
-
facepalm schrieb:
Du hast den Unterschied zwischen Compiletime und Runtime nicht verstanden.
Doch.
Dir muss ich aber sowieso nichts beweisen.- Mein Verhalten war absolut korrekt. Schau mal, woher der Code kommt, den Sone mit "Diese Lösung ist immer noch falsch." kommentiert hat. Der entspricht abgesehen von Syntaxfehlern genau meiner ideone-Lösung.
Stimmt. Anderes Beispiel, diesmal ein wenig rustikaler:
template<typename T> struct Bla { static_assert( std::is_base_of<Interface, T>::value, "Dieses Klassentemplate funktioniert nur mit Interface-erbenden Klassen" );
Außerdem ist deine Lösung so oder so hässlich - das doppelte Aufschreiben aller
const
-qualifizierten Methoden ist völlig überflüssig und nervt bei größeren Klassen enorm. Das geht viel schöner.
-
Eisflamme schrieb:
Wenn jemand ein Interface erwartet, kann ich ihm meinen ConstProxy übergeben, das geht bei Dir nicht.
Das ist ja gerade das Feature. Man kannes nur in ein
const Interface&
konvertieren.Streng genommen sollte man ConstProxy ja auch gar nicht einem Interface-Parameter übergeben können, sondern nur einem const Interface-Parameter. Aber das lässt sich wohl nicht bewerkstelligen.
Genau das macht mein Proxy.
-
Sone schrieb:
static_assert( std::is_base_of<Interface, T>::value, "Dieses Klassentemplate funktioniert nur mit Interface-erbenden Klassen" );
That's the whole point. Es ist kein Interface, sondern ein const Interface. LSP ftw.
-
facepalm schrieb:
hustbaer schrieb:
Erst behaupten dass man keine Vererbung braucht, und dann die Vererbung schnell dazuschummeln um zu "beweisen" dass man Recht gehabt hat. Ja, das macht Sinn.
- Ich behaupte immer noch, dass es dazu prinzipiell keine Vererbung braucht. Aber das gegebene Design und die gewünschte Funktionalität erfordern das hier. Ich hätte vielleicht einen vectorstd::function ins Interface reingenommen.
- Mein Verhalten war absolut korrekt. Schau mal, woher der Code kommt, den Sone mit "Diese Lösung ist immer noch falsch." kommentiert hat. Der entspricht abgesehen von Syntaxfehlern genau meiner ideone-Lösung.
OK. Du hast Recht.
Hmpf.