Proxys und const-correctness



  • 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 bei ConstProxy 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, denn ConstProxy 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.

    Liegst voll daneben mit deiner Aussage



  • 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.

    Liegst voll daneben mit deiner Aussage

    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. 🙂



  • 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
    

Anmelden zum Antworten