Proxys und const-correctness
-
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.
-
Hi,
ich habe mir Deine Lösung jetzt nochmal genauer angeschaut und muss sagen: die ist genial.
Ich wäre nie darauf gekommen das so zu lösen, das ist äußerst geschickt. Wie kommt man auf so was? Das scheint jedenfalls zu erfüllen, was ich gerne haben möchte, es erscheint nur eben wenig intuitiv, dass man einen ConstProxy, der nicht brav erbt, als Argument für ein Interface angeben kann. Aber als Objekt, das nur sicherstellt, dass die Initialisierung über ein konstantes Objekt erfolgt, ist es dann doch wieder irgendwie intuitiv.
Man muss jedoch auch noch operator const Interface* überladen, nicht jeder mag Referenzen. Aber sonst scheint das okay zu sein.
Dankeschön
-
Find die Idee auch interessant. Weiß nicht, ob ich das jemals brauche, werd ich mir aber für alle Fälle merken.
-
Kurze Rückfrage:
Die Lösung impliziert jetzt, dass
const Proxy/ConstProxy
das als const qualifiziert, was bezogen auf das referenzierte (eigentliche!) Objekt const ist, richtig?Wenn ich also den Verweis ändere, sodass der Proxy auf ein anderes Objekt "zeigt", dann ist das so gesehen auch eine const Aktion. Und dann sollte der Zeiger doch eigentlich mutable sein, genau so wie ein Setter für den Verweis dann const ist.
Wenn ich hier nicht durch mutable trenne, kann ich den Verweis auf das Objekt eben nicht mehr ändern, was ja z.B. beim const_iterator auch geht. Wobei ich hier auch nochmal rückfragen möchte, was
const iterator kann auch das Objekt, auf das er verweist, verändern.
zu bedeuten hat. Das ist doch eben der Witz, dass das nicht geht?
-
Eisflamme schrieb:
Die Lösung impliziert jetzt, dass
const Proxy/ConstProxy
das als const qualifiziert, was bezogen auf das referenzierte (eigentliche!) Objekt const ist, richtig?Richtig, const Proxy und ConstProxy sind das gleiche.
Das muss aber so sein, weil dein
Interface
das so erfordert.Wenn ich also den Verweis ändere, sodass der Proxy auf ein anderes Objekt "zeigt", dann ist das so gesehen auch eine const Aktion. Und dann sollte der Zeiger doch eigentlich mutable sein, genau so wie ein Setter für den Verweis dann const ist.
Du vermischst die Ebenen, wie ich schon mal gesagt habe.
Es gibt
iterator const iterator const_iterator const const_iterator
Der Proxy ist das, auf das der Iterator zeigt. *iterator -> Proxy, *const_iterator -> ConstProxy. Der Proxy ist unmittelbar mit dem Interface verbunden. Sobald das Interface selbst State hat, geht das nicht mehr.
struct Interface { int data; // hier expliziter State, denkbar wäre auch impliziter };
Wenn dein Proxy den Pointer ändert, ändert sich das Verhalten, nicht aber data. Zudem können Invarianten verletzt werden (z.B.: Wenn einmal Interface.empty()==true, dann soll immer gelten Interface.empty()==true).
Mag sein, dass das bei dir nicht zutrifft, aber ich finde das den naheliegendsten Weg. Der Pointer darf nicht verändert werden. Das wird durch die Vererbung erzwungen.
Ein Proxy, der den Pointer ändert, hätte auch Probleme mit dem const. Ich sehe keine Lösung, wie das mit const correctness gelöst werden könnte.
Daher würde ich eine zweite Ebene der Indirektion vorschlagen.
typedef Proxy* ProxyPtr; typedef ConstProxy* ConstProxyPtr; // hier ev. einen schlanken Wrapper um implizit in Interface const* konvertiert werden zu können
Jetzt hast du
ProxyPtr // pointer/interface veränderbar const ProxyPtr // nur interface veränderbar ConstProxyPtr // nur pointer veränderbar const ConstProxyPtr // nichts veränderbar
-
Also was ich nach dem Lesen deines Posts als Problem meiner mutable-Lösung ansehe, ist dass ein const Proxy trotzdem sein Bezugsobjekt ändern kann, was ja nicht korrekt ist.
Bei der Version mit der zweiten Indirektionsebene verstehe ich nicht, wie ich den Pointer verändern kann. Ich nutze den Proxy ja nicht so, dass ich sagen kann:
ProxyPtr p; p = &actualObject1; // jetzt will ich den Zeiger ändern p = &actualObject2;
oder wie hast Du Dir das syntaktisch gedacht?
-
Kleine Anmerkung:
Eisflamme schrieb:
ConstProxy(const Interface& actualObject) : actualObject(&actualObject)
Vorsicht sei hier geboten, wenn sowas ähnliches tatsächlich in deinem Code vorkommt. Wenn ich deinen Konstruktor mit einem temp. Objekt aufrufe
ConstProxy cp(ActualObject());
dann lebt das temporäre Objekt solange, wie die Referenz darauf. Dh, dein Zeiger verweist mitunter auf einen möglicherweise toten Bereich. Ergo, nehme niemals die Adresse von const& Variablen.
-
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).
-
Hm, ich weiß nicht... An der Semantik des Proxys wird doch klar, dass man dort keine temporären Objekte reingeben darf. Oder nimmt irgendwer an, dass der Proxy eine Kopie des Parameters erstellt? So funktionieren die aber nicht.
Klar, syntaktisch merkt man bei einem Zeigerargument noch eher, dass es eine "Referenz" ist, die langlebig sein soll, aber eigentlich ist das mehr Interpretation als wirklich hinter einem Zeiger (vs. eine Referenz) steckt, finde ich.
-
Eisflamme schrieb:
Hm, ich weiß nicht... An der Semantik des Proxys wird doch klar, dass man dort keine temporären Objekte reingeben darf. Oder nimmt irgendwer an, dass der Proxy eine Kopie des Parameters erstellt? So funktionieren die aber nicht.
Klar, syntaktisch merkt man bei einem Zeigerargument noch eher, dass es eine "Referenz" ist, die langlebig sein soll, aber eigentlich ist das mehr Interpretation als wirklich hinter einem Zeiger (vs. eine Referenz) steckt, finde ich.
Ich habe mir nicht im Detail angeschaut, was nun die endgütlige Fassung von deinem Design ist, es war nur ein allgemeiner Hinweis auf ein potentielles Leck
Der Rest liegt in deinen Händen. Ich weiß ja nicht, ob dieses "Idiom" sonstwo in deinem Code auftaucht. Jedes potentielle Risiko verringert die Nachhaltigkeit der Software. Potentielle Risiken minimieren, Nachhaltigkeit maximieren.
-
Schon richtig, ja, werde ich beizeiten Mal überdenken. Bisher weiß ich halt immer, was mit dem Argument geschieht, daher passiert es mir nicht, dass dort temporäre Objekte übergeben werden.
Umso länger ich drüber nachdenke, umso berechtigter finde ich den Einwand jedoch. Dankeschön
Edit:
Noch offene Frage zur Übersicht:Eisflamme schrieb:
Also was ich nach dem Lesen deines Posts als Problem meiner mutable-Lösung ansehe, ist dass ein const Proxy trotzdem sein Bezugsobjekt ändern kann, was ja nicht korrekt ist.
Bei der Version mit der zweiten Indirektionsebene verstehe ich nicht, wie ich den Pointer verändern kann. Ich nutze den Proxy ja nicht so, dass ich sagen kann:
ProxyPtr p; p = &actualObject1; // jetzt will ich den Zeiger ändern p = &actualObject2;
oder wie hast Du Dir das syntaktisch gedacht?
-
Ä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)?