Als Zeiger designter Proxy - Implementierung



  • Hi,

    ich habe mir jetzt Mal einen Proxy gebastelt, der sich wie ein Zeiger verhält. D.h. man kann über operator-> die Funktionen aufrufen und über operator= das Bezugsobjekt ändern. const-correctness klappt auch sowohl auf Ebene des Proxys als auch auf Ebene der eigentlichen Klasse. Proxy als Zeiger impliziert natürlich, dass es sich um einen Proxy handelt, bei dem das eigentliche Objekt bereits irgendwo im Speicher besteht und der Proxy nur darauf verweist, nicht aber besitzend ist. Kann man sich drüber streiten, ob man das so will, ich finde es praktisch.

    operator* habe ich mir hingegen gespart, weil mein Proxy nicht ausgetrickst können werden soll. Wenn jemand einen Proxy erhält, dann muss er auch über die Methoden gehen, die er kriegt, und soll es nicht umgehen können.

    Ich habe es noch etwas abstrahiert: Man kann mit beliebigen Proxyimplementierungen für alle möglichen Klassen Proxys mit oben genannten Eigenschaften generieren (der Schritt ist möglicherweise nicht besonders hilfreich, ist aber praktisch, wenn man noch Operatoren hinzufügen möchte oder so).

    #include <iostream>
    #include <type_traits>
    
    // ===================================================
    // Proxybasis: Kann zur Erzeugung unterschiedlicher Proxys verwendet werden
    
    template<typename ProxyImpl> 
    class ProxyBase 
    { 
    	typedef typename ProxyImpl::type ActualClass;
    
    	ProxyImpl proxyImpl;
    
    public: 
    	ProxyBase(ActualClass& someClass) : proxyImpl(someClass) {} 
    
    	// Proxyobjekt durch Zuweisung veränderbar
    	ProxyBase<ProxyImpl>& operator=(ActualClass* someClass)
    	{
    		proxyImpl.someClass = someClass;
    		return *this;
    	}
    
        // const_cast ist hier sauber, da alle non-const Methoden ohnehin nicht funktionieren, wenn ProxyImpl::type const ist;
        // und es ist notwendig, weil ein const Proxy die Semantik hat, dass man über -> das Objekt verändern darf
    	ProxyImpl* operator->() const {return const_cast<ProxyImpl*>(&proxyImpl);}
    };
    
    // ===================================================
    // Anwendung der Proxybasis zur Proxyerzeugung
    
    // Eigentliche Klasse
    class SomeClass
    {
    public:
    	void foo() {std::cout << "foo\n";}
    	void bar() const {std::cout << "bar\n";}
    } sc;
    
    // Implementierung des Proxys; muss type definieren und ctor haben, der type akzeptiert
    template<typename T>
    struct SomeClassProxyImpl 
    { 
    	typedef T type;
    
    	type* someClass; 
    	SomeClassProxyImpl(type& someClass) : someClass(&someClass) {} 
    
    	void foo() {someClass->foo();} 
    	void bar() const {someClass->bar();} 
    };
    
    // jetzt geht: 
    typedef ProxyBase<SomeClassProxyImpl<const SomeClass>> ConstSomeClassProxy; 
    typedef ProxyBase<SomeClassProxyImpl<SomeClass>> SomeClassProxy; 
    
    // und auch die vier Varianten 
    const ConstSomeClassProxy proxy1(sc); // Bezugsobjekt nicht austauschbar, nicht änderbar 
    ConstSomeClassProxy proxy2(sc); // Bezugsobjekt austauschbar, nicht änderbar 
    const SomeClassProxy proxy3(sc); // Bezugsobjekt nicht austauschbar, doch änderbar 
    SomeClassProxy proxy4(sc); // Bezugsobjekt austauschbar, und änderbar
    
    int main()
    {
    	// Proxy1
    	//proxy1 = &sc; // geht nicht
    	//proxy1->foo(); // geht nicht
    	proxy1->bar();
    
    	// Proxy 2
    	proxy2 = &sc;
    	//proxy2->foo(); // geht nicht
    	proxy2->bar();
    
    	// Proxy 3
    	//proxy3 = &sc; // geht nicht
    	proxy3->foo();
    	proxy3->bar();
    
    	// Proxy 4
    	proxy4 = &sc;
    	proxy4->foo();
    	proxy4->bar();
    
    	int x; std::cin >> x;
    }
    

    Kurz an dieser späten Stelle zur Motivation fürs Zeigerverhalten:
    Ich kann jetzt z.B. das hier tun:

    template<typename PointerType>
    void doSomething(PointerType p) {p->foo();}
    
    // mit:
    doSomething(&sc);
    // oder:
    doSomething(proxy4);
    

    und das freut mich. 🙂 Somit kann ich jetzt endlich austauschbar Zeiger auf Objekte und Proxys auf Objekte nutzen - wenn auch nur auf Compiletime-Basis.

    Habt ihr Verbesserungsvorschläge oder sonstige Anmerkungen?

    Vielen Dank und viele Grüße!



  • Das kompiliert mit MSVC10, aber wieso meckert Ideone in Zeile 44:
    expected nested-name-specifier before T
    ?



  • Weil es falsch ist, MSVC nun mal aber jeden Scheiß schluckt.



  • Und wie geht es richtig? 🙂



  • Eisflamme schrieb:

    Und wie geht es richtig? 🙂

    Ohne typename .

    typename-specifier:
    typename nested-name-specifier identifier
    typename nested-name-specifier [template] simple-template-id

    Den benutzt man nicht ohne nested-name-specifier.

    struct A
    {
    	using int_ = int;
    };
    
    int main()
    {
    	typename A::int_ i;
    
    	typename A a; // Compilerfehler
    }
    


  • Ah okay, dankesehr.

    Gäbe es denn eine Möglichkeit hier überhaupt einen nested-name specifier anzugeben? T ist ja nicht im global namespace und auf den Template-Space schaffe ich es nicht ohne Weiteres zuzugreifen.

    Das hier scheint nicht zu gehen:

    typedef typename SomeClassProxyImpl<T>::T type;
    

    Jedoch funktioniert:

    typedef T type1;
    typedef typename SomeClassProxyImpl<T>::type1 type2; // sorry, hier stand Müll vorher
    

    Leider muss ich mit dem alten Compiler arbeiten, using steht mir da ja nicht zur Verfügung. 😞



  • Eisflamme schrieb:

    Gäbe es denn eine Möglichkeit hier überhaupt einen nested-name specifier anzugeben?

    Nein, aber das brauchst du auch nicht. 😕

    Das hier scheint nicht zu gehen:

    typedef typename SomeClassProxyImpl<T>::T type;
    

    Natürlich nicht, T ist schließlich kein Member dieser Klasse.

    Leider muss ich mit dem alten Compiler arbeiten, using steht mir da ja nicht zur Verfügung. 😞

    alias-Deklarationen sind nur Syntaxzucker, die sind nicht irgendwie anders als typedef.

    A typedef-name can also be introduced by an alias-declaration. [...] It has the same semantics as if it were introduced by the typedef specifier.

    _________________________________________________________________________________

    typedef T type1; 
    typedef typename SomeClassProxyImpl<T>::type1 type2; // sorry, hier stand Müll vorher
    

    ...



  • Ja, war auch eher eine Interessensfrage. Aber ich merke, dass MSVC da Käse macht. Schade, dann muss man ja ständig noch mit nem Zweitcompiler testen, ob der Code auch wirklich standardkonform ist (oder es einfach wissen).

    Edit:
    Die zweite Variante habe ich gewählt, weil ich im Kopf hatte, dass MSVC dann bei späterer Verwendung von type2 für nested types kein typename mehr erforderte. Ergibt zwar keinen Sinn, aber ich meine gesehen zu haben, dass:

    typedef typename T type;
    

    dazu führt, dass

    typedef /* typename weg! */ type::nested_type;
    

    klappt, was Schreibarbeit erspart, wenn man viele nested_types typedefen will.

    Ergibt keinen Sinn, wenn man drüber nachdenkt, hatte aber meine ich Mal funktioniert, aber vielleicht habe ich das auch verwechselt. Jedenfalls bin ich erstmal belehrt. Danke 🙂


Anmelden zum Antworten