Proxys und const-correctness



  • Eisflamme schrieb:

    Ja, das wäre perfekt! Wieso klappt es denn nicht?

    Weil ich

    void someMethod() // ja, hier
        {
            // mache irgendwas
            actualObject->someMethod();
        }
    

    So deklariere, dass bei ConstProxy ja auf eine non-const Methode von Interface zugreife - über einen const -Zeiger.
    Es geht ganz leicht, aber hässlich zu lösen:

    #include <type_traits>
    
    template<typename ValueT,
             typename Pointer = ValueT*,
             typename Reference = ValueT&>
    class ProxyBase : public Interface
    {
    	using internal_pointer = typename std::remove_const<typename std::remove_pointer<Pointer>::type>::type*;
    
    public:
    
    	using value_type = ValueT;
    	using reference = Reference;
    
        ProxyBase(reference actualObject)
            : actualObject( const_cast<internal_pointer>(&actualObject) ) {}
    
        void someMethod()
        {
            actualObject->someMethod();
        }
    
        void someOtherMethod() const
        {
            actualObject->someOtherMethod();
        }
    
    private:
        internal_pointer actualObject;
    };
    
    using Proxy = ProxyBase<Interface>;
    using ConstProxy = ProxyBase<Interface const> const;
    

    Laut Standard wahrscheinlich erlaubt, sehe ich gleich mal nach.

    Eisflamme schrieb:

    Schließlich ist eine Instanz ConstProxy selbst ja nicht (notwendigerweise) const.

    Doch, bei mir schon.

    Und dann kriegt man es auch nicht hin, dass es einen CompileTime-Error gibt, wenn man die aufrufen möchte.

    Doch! Siehe oben. 🙂



  • Ist auch deswegen Mist, weil ich dann über einen non-const ConstProxy tatsächlich die non-const-Methode aufrufen und sogar nutzen kann! ConstProxy darf ruhig non-const sein, denn man darf das Bezugsobjekt ändern (und mutable ist semantisch nicht korrekt, der Proxy ist ja tatsächlich nicht konstant, sein Zustand entspricht dem Objekt, auf das er verweist, finde ich).



  • Antwort statt Edit deines alten Posts bei Bezug auf meinen neuen wäre übersichtlicher btw. 😉

    Also konstanter Proxy und konstantes Bezugsobjekt sind für mich zwei verschiedene paar Schuhe, siehe Analogie zu const_iterator und iterator.



  • Also konstanter Proxy und konstantes Bezugsobjekt sind für mich zwei verschiedene paar Schuhe, siehe Analogie zu const_iterator und iterator.

    Was geht an

    using const Proxy = Interface*;
    using const Proxy = Interface const*;
    

    nicht, abgesehen davon, dass es dir nicht genug Java-like ist?



  • also ohne das const in using const

    typedef Interface* Proxy;
    typedef Interface const* ConstProxy;
    


  • Problem nicht Mal ansatzweise verstanden/versucht zu verstehen... Lies nochmal in Ruhe meinen OP und am Besten auch, was ein Proxy ist (Proxys nutzt man auch in C++, aber das scheinst Du nicht zu kapieren 🤡)



  • Eisflamme schrieb:

    Problem nicht Mal ansatzweise verstanden/versucht zu verstehen... Lies nochmal in Ruhe meinen OP und am Besten auch, was ein Proxy ist (Proxys nutzt man auch in C++, aber das scheinst Du nicht zu kapieren 🤡)

    Das einzige was nicht geht, ist das Methoden wrappen, genau.

    Also bauen wir das nach und erhalten die Semantik der Pointer.

    struct Proxy {
      Proxy(Interface* i) : i(i) {}
      Interface* i;
      void someMethod() { /*log*/ i->someMethod(); }
      void someOtherMethod() { /*log*/ i->someOtherMethod(); }
    };
    struct ConstProxy {
      Interface const* i;
      ConstProxy(Interface const* i) : i(i) {}
      ConstProxy(Proxy p) : i(p.i) {}
      void someOtherMethod() { /*log*/ i->someOtherMethod(); }
    };
    

    Proxys werden in C++ praktisch immer ohne Vererbung verwendet.



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


Anmelden zum Antworten