automatischer funktions wrapper



  • ich will etwas erreichen, wofuer ich schon ein paar loesungen gefunden habe, aber keine ist wirklich zufriedenstellend. deswegen dachte ich, hat einer von euch vielleicht einen eleganten weg im kopf.

    ich habe gewissermassen 3 stufen

    daten -> wrapper -> nutzer
    

    der nutzer kennt immer den daten container. z.b. container koennte sein

    class Woerter
    {
    public:
    string Leangstes();
    string Kuerzestes();
    string Lustigstes();
    string AmAnfangVomDuden();
    ...
    };
    

    nutzer koennte sowas sein wie

    class StatistikDrucker
    {
    public:
    template<class T>
    void Run(T& rDaten)
    {
    cout << rDaten.LustigstesWort();
    }
    };
    

    wrapper ist nun etwas was die daten zwischendurch modifiziert z.b.

    template<class T>
    class Translator
    {
    };
    template<class T>
    class ToUpperCase
    {
    };
    template<class T>
    class LetterCounter
    {
    };
    

    nun moechte ich z.B. StatistikDrucker Run aufrufen mit Translator drumrum

    {
    ...
    Woerter W;
    StatistikDrucker Drucker;
    Drucker.Run(Translator(W));
    

    und im besten fall wuerde Translator einfach alle funktionen zur verfuegung stellen die Woerter auch hat und fuer Drucker wuerde das transparent sein.

    meine loesungen sind
    - manuel alle moeglichen accessors im Wrapper nachcoden
    - in "woerter" bei jedem return eine virtuelle funktion aufrufen {return Eval(wort);} das die translators implementieren muessen und von wort ableiten
    - es iterierbar zu machen, entweder ueber operator[] oder variadic template magic
    - ...

    es waere nett wenn die loesung non-intrusive waere und ohne virtual calls auskommt. template magic ist mir recht.

    vielleicht stehe ich nur auf dem schlauch und nach dem abschicken des posts hab ich die loesung 😃



  • Was verstehst du unter non-intrusive? Sowohl Woerter als auch StatistikDrucker sollen nicht verändert werden?
    Dann bleibt wohl nur die 1. von dir genannte Möglichkeit, also manuell eine Proxy-Klasse zu implementieren.

    Wobei du pro Container natürlich nur eine brauchst, die verschiedenen Implementierungen unterscheiden sich dann ja nur in der "Eval" Funktion, welche du als Template-Parameter an den WoerterProxy übergeben kannst:

    template <
        class W,  // der konkrete Wörter-Container
        class X   // die Transformation (Funktor)
        >
    class WoerterProxy
    {
    public:
        WoerterProxy(W w, X x) : w(w), x(x) {}
    
        string Leangstes() { return x(w.Leangstes()); }
        string Kuerzestes() { return x(w.Kuerzestes()); }
        string Lustigstes() { return x(w.Lustigstes()); }
        string AmAnfangVomDuden() { return x(w.AmAnfangVomDuden()); }
        // ...
    
        // Bzw. wenn das Schema immer das selbe ist
    #define DECLARE_WORD_PROXY_FN(name) \
        string name() { return x(w.name()); }
    
        DECLARE_WORD_PROXY_FN(Traurigstes)
        DECLARE_WORD_PROXY_FN(AmDudenEnde)
        // ...
    
    private:
        W w;
        X x;
    };
    

    Sonst wäre wohl die einfachste Variante entweder alle Container zu modifizieren oder alle Nutzer - je nachdem was weniger Aufwand ist.

    Wirklich elegante Lösung fällt mir jetzt keine ein. Ausser natürlich wenn man den Containern irgendwie allen ein einheitliches und möglichst schlankes Interface verpassen kann. Dann kommt man auch mit einem einzigen schlanken Proxy nach dem Beispiel oben aus. Aber da das dermassen naheliegen ist ... schätze ich dass du die Container nicht dermassen abändern möchtest.

    rapso schrieb:

    - in "woerter" bei jedem return eine virtuelle funktion aufrufen {return Eval(wort);} das die translators implementieren muessen und von wort ableiten

    Ja, geht. Aber wieso virtual? Wenn du "woerter" abänderst, dann kannst du auch gleich ein Template draus machen, und Eval als Template-Parameter übergeben. Wobei der Aufwand den du dadurch hast vermutlich auch nicht massgeblich geringer ist als mit einer manuell geschriebenen WoerterProxy Klasse - also von daher kein echter Vorteil.



  • auf den ersten Blick kommt mir das Decorator-Pattern in den Sinn.

    Oder mit Policy-Klassen, die das jeweilige Verhalten der Woerter-Klasse beeinflussen.



  • Fragen:

    1. Soll die Modifikation einmalig sein, oder besteht ggf. die Anforderung, mehrere Modifikationen hintereinander zu verschachteln?

    2. Sind die zu verwendenden Modifikationen zur Laufzeit alle bekannt und unveränderbar (wie z.B. der StatistikDrucker, der hier nur die Funktion "Lustigstes()" aufruft)?

    3. Wird der selbe Container mal mit der einen, mal mit der anderen Modifikation benötigt?

    Wenn 1. = einmalig und 2. = fix und 3. = nein, hier ein Ansatz mit Policy-Klassen:

    class Translator
    {
    protected:
        string modify( const string& source )
        {
            string translated;
            // translate source -> translated
            return translated;
        }
    };
    
    class UpperCase
    {
    protected:
        string modify( const string& source )
        {
            string upper;
            // make source upper case -> upper
            return upper;
        }
    };
    
    // Der Container, der über das Template mit der gewünschten Modifikation erstellt werden kann:
    
    template <class Mod>
    class Woerter : private Mod
    {
    public:
    string Leangstes();
    string Kuerzestes();
    string LustigstesWort()
    {
        string res;
        for( /*... über alle Wörter*/ ;; )
        {
            // vor jedem Vergleich: hier wird der eingebackene Modifikator aufgerufen:
            // (this ist hier keine Pointer-Dereferenzierung, der Compiler braucht das)
            res = this->modify( "someText" );
        }
        return res;
    }
    
    string AmAnfangVomDuden();
    };
    
    // Der Aufruf wäre dann etwa so:
    {
        Woerter<Translator> W;
        StatistikDrucker Drucker;
        Drucker.Run(W);
    }
    

    Das coole ist, dass die Policy-Klassen sich ähnlich wie Funktoren anfühlen, aber zur Compilezeit fix mit "eingebacken" werden. Das ist auch beim Ansatz von hustbaer so. Der Unterschied ist, dass man nicht alle Funktionsaufrufe 1:1 nachdefinieren muss, sondern die Funktionen rufen selbst die eingebackene Modifikation auf.

    Im Übrigen würde ich dennoch die Abhängigkeiten vermindern, also:

    1. Eine Klasse, die nur die Daten selbst hält (also den puren Container, basierend auf vector, set usw.)
    2. Klassen / Funktionen, die die Modifikationen vornehmen (Translator, UpperCase usw.)
    3. Die Auswahlfunktionen /-funktoren (Laengestes, Kürzestes, Lustigstes usw).

    Man kann die Policy-Klassen auch in den unter 1. genannten Container einbacken, und die Auswahlfunktion arbeitet dann z.B. auf einem Container<Translator>, der sich wie ein normaler Container anfühlt, die Wörter aber bereits übersetzt zurückgibt.



  • hustbaer schrieb:

    Was verstehst du unter non-intrusive? Sowohl Woerter als auch StatistikDrucker sollen nicht verändert werden?

    jup, die klassen mit denen umgegangen wird sollten ohne modifikation veraendert werden.

    Dann bleibt wohl nur die 1. von dir genannte Möglichkeit, also manuell eine Proxy-Klasse zu implementieren.

    Wobei du pro Container natürlich nur eine brauchst, die verschiedenen Implementierungen unterscheiden sich dann ja nur in der "Eval" Funktion, welche du als Template-Parameter an den WoerterProxy übergeben kannst:

    template <
        class W,  // der konkrete Wörter-Container
        class X   // die Transformation (Funktor)
        >
    class WoerterProxy
    {
    public:
        WoerterProxy(W w, X x) : w(w), x(x) {}
    
        string Leangstes() { return x(w.Leangstes()); }
        string Kuerzestes() { return x(w.Kuerzestes()); }
        string Lustigstes() { return x(w.Lustigstes()); }
        string AmAnfangVomDuden() { return x(w.AmAnfangVomDuden()); }
        // ...
    
        // Bzw. wenn das Schema immer das selbe ist
    #define DECLARE_WORD_PROXY_FN(name) \
        string name() { return x(w.name()); }
    
        DECLARE_WORD_PROXY_FN(Traurigstes)
        DECLARE_WORD_PROXY_FN(AmDudenEnde)
        // ...
    
    private:
        W w;
        X x;
    };
    

    hmm, ja so wuerde das ohne virtual funzen. muest jede "Woerter" class so ein connector anbieten. hmm

    Sonst wäre wohl die einfachste Variante entweder alle Container zu modifizieren oder alle Nutzer - je nachdem was weniger Aufwand ist.

    nicht wirklich moeglich, die dinge werden per template uebergeben und es kann wirklich unmengen an permutationen geben (es sind nicht nur die 3 stufen, sondern koennen 10 und mehr sein und jede stufe hat ihre n-moeglichkeiten.

    Wirklich elegante Lösung fällt mir jetzt keine ein. Ausser natürlich wenn man den Containern irgendwie allen ein einheitliches und möglichst schlankes Interface verpassen kann. Dann kommt man auch mit einem einzigen schlanken Proxy nach dem Beispiel oben aus. Aber da das dermassen naheliegen ist ... schätze ich dass du die Container nicht dermassen abändern möchtest.

    so hab ich angefangen, mit dem "uberinterface", aber immer wenn eine neue Woerter-class dazu kommt, brauch ich mehr funktionen und alle vorherigen Woerter-classes muessen entsprechend ergaenzt werden... ziemlich unschoen und unmaintainable.

    rapso schrieb:

    - in "woerter" bei jedem return eine virtuelle funktion aufrufen {return Eval(wort);} das die translators implementieren muessen und von wort ableiten

    Ja, geht. Aber wieso virtual? Wenn du "woerter" abänderst, dann kannst du auch gleich ein Template draus machen, und Eval als Template-Parameter übergeben. Wobei der Aufwand den du dadurch hast vermutlich auch nicht massgeblich geringer ist als mit einer manuell geschriebenen WoerterProxy Klasse - also von daher kein echter Vorteil.



  • minastaros schrieb:

    auf den ersten Blick kommt mir das Decorator-Pattern in den Sinn.

    laut wiki ist das ein anderer name fuer wrapper pattern, was ich ja schon gerne implementieren will, nur fehlt der wirklich elegante weg, leider.

    Oder mit Policy-Klassen, die das jeweilige Verhalten der Woerter-Klasse beeinflussen.[/quote]wenn ich das policy pattern verstanden habe, ist es noch nicht so ganz was ich brauche.



  • minastaros schrieb:

    1. Soll die Modifikation einmalig sein, oder besteht ggf. die Anforderung, mehrere Modifikationen hintereinander zu verschachteln?

    es gibt beliebig viele modifikationen die zudem komplexer sein koennen. z.b. koennte ein wrapper mit 3 verschiedenen "woertern" objekten initialisiert werden und transparent fuer den "drucker" immer die returns der 3 objekten konkatenieren oder wenn es zahlen waeren dann beliebige formeln ausfuehren.

    es gibt ein generisches "pipeline" objekt in das per template params verschiedene permutationen an "woertern" "wrappern" "printern" etc. uebergeben werden. nicht alle "woerter" passen zu allen "printern", aber das setup ist so das grundsaetzlich passende paare gemacht werden (sollte sonst nicht kompilieren).
    nur "wrapper", da dieser auf den elementen und nicht auf der ganzen klasse arbeitet, wollte komplett orthogonal sein (bzw die ganzen wrapper stages die es geben koennte).

    2. Sind die zu verwendenden Modifikationen zur Laufzeit alle bekannt und unveränderbar (wie z.B. der StatistikDrucker, der hier nur die Funktion "Lustigstes()" aufruft)?

    ist template magic, also compiletime bekannt. die magic wird ueber eine recht grosse switch-case tablet selektiert.
    das problem ist, dass manche wrapper nichts machen und komplett wegoptimiert werden koennen. manche sind komplexer, aber dafuer sind z.b. woerter und drucker trivial und rufen den komplexen wrapper nur einmal auf.
    es ist auch legitim dass der printer nur eine sehr kleine auswahl an funktionen von "woerter" nutzt und dann sollten nicht pauschal alle woerter einmal konvertiert werden. auf der anderen seite gehe ich mal davon aus, wenn zweimal dieselbe funktion aufgerufen wird, der compiler das merkt und den wrapper nur einmal laufen laesst und sich das resultat merkt.

    3. Wird der selbe Container mal mit der einen, mal mit der anderen Modifikation benötigt?

    ja, container koennen sehr ueberbesetzt sein. solange das templatisiert ist, hoffe ich, dass unbenoetigte dinge wegoptimiert werden.

    Wenn 1. = einmalig und 2. = fix und 3. = nein, hier ein Ansatz mit Policy-Klassen:

    class Translator
    {
    ...
        for( /*... über alle Wörter*/ ;; )
        {
            // vor jedem Vergleich: hier wird der eingebackene Modifikator aufgerufen:
            // (this ist hier keine Pointer-Dereferenzierung, der Compiler braucht das)
            res = this->modify( "someText" );
        }
        return res;
    }
    ...};
    
    // Der Aufruf wäre dann etwa so:
    {
        Woerter<Translator> W;
        StatistikDrucker Drucker;
        Drucker.Run(W);
    }
    

    ...

    hmm, eure ideen sind schon besser als virtual aufzurufen, aber dennoch intrusive. wie gesagt, wrapper koennte ventuell ein wenig komplexer sein also nur 1:1 umwandlung.

    z.b.

    FreundlicheWoerter W0;
    NeutraleWoerter W1;
    AggressiveWoerter W2;
    StatistikDrucker Drucker;
    Drucker.Print(Interpolator(W0,W1,W2,fFreundlichkeit),"text");
    

    hab gehoft da gibt es noch was neues im C++11 oder 14 was das erleichtert haette.

    vielleicht muss ich doch jeden eintrag von woerter als variadic template argument dem container uebergeben, der davon ableitet und somit das interface anbietet, dann koennte so ein generischer container als parameter auch den wrapper bekommen mit dem es jeden returnwert kapselt.

    viele geistesblitze aber keiner macht es hell fuer mich zZ. hmm.

    danke soweit und bin weiter fuer ideen offen 🙂



  • Wäre es möglich die Schnittstellen der Container-Klassen so zu modifizieren, dass die diversen Zugriffe nicht über verschiedene Methoden, sondern verschiedene Parameter einer einzigen, überladenen Methode angeboten werden?

    Also quasi

    class Woerter
    {
    public:
        struct ErstesTag {};
        struct LetztesTag {};
        struct WortAnStelleParameter { int Stelle; };
    
        string Zugriff(ErstesTag);
        string Zugriff(LetztesTag);
        string Zugriff(WortAnStelleParameter params);
        // ...
    };
    

    Das erlaubt verschiedenen Container-Klassen mit ganz unterschiedlichen Interfaces mit einem einzigen einfachen Proxy-Template einzuwickeln:

    // Hier ohne Template-Parameter für die eigentliche Funktion des Proxies, aber das ginge natürlich genau so
    template <class C>
    class ToUpperProxy
    {
    public:
        ToUpperProxy(C c) : c(c) {}
    
        template <class P>
        string Zugriff(P&& p)
        {
            string res = c.Zugriff(forward<P>(p));
            // ... make res upper case ...
            return res;
        }
    
    private:
        C c;
    };
    

    Spezialbehandlung für einzelne "Methoden" (Parameter-Typen) wäre dabei auch einfach möglich indem man non-template Overloads von Zugriff definiert (oder auch nur weitere, speziellere Zugriff-Templates).

    Mit der selben Technik könnte man natürlich auch Modifikationen an den Containern ermöglichen falls das nötig sein sollte. Bzw. durch " auto duck typing" (EDIT: ich meine den Returnwert von Zugriff() /EDIT) könnte man sogar diverse Modifikationen an beliebigen Typen vornehmen - so lange halt alle wie eine Ente gehen und quaken.



  • rapso schrieb:

    ist template magic, also compiletime bekannt. die magic wird ueber eine recht grosse switch-case tablet selektiert.

    Widerspricht sich das nicht ein wenig? Gerade wenn etwas zur compile time bekannt ist, dürfte doch die Auswahl der zu verwendenden Funktionalität über irgendwelche Typ-Entscheidungen erfolgen können, also templatisierte Klassen und/oder Überladung von Funktionen (wie von hustbear skizziert)?

    Switch-case dagegen ist ja eine Laufzeit-Entscheidung...

    Kann sein, dass ich Deine Anwendung noch nicht ganz durchschaut habe, aber die Aussage oben wirft ein Fragezeichen.



  • minastaros schrieb:

    rapso schrieb:

    ist template magic, also compiletime bekannt. die magic wird ueber eine recht grosse switch-case tablet selektiert.

    Widerspricht sich das nicht ein wenig?

    code sagt mehr als 1024 worte

    template<class Printer,class Data>
    void Output(Printer& rPrinter,const Data& rData,ELanguage Language)
    {
      switch(Language)
      {
         case ELanguage_Arabic:rPrinter<<TranslatorArabic(rData)<<std::endl;break;
         case ELanguage_Albania:rPrinter<<TranslatorAlbania>(rData)<<std::endl;break;
         case ELanguage_Alien:rPrinter<<TranslatorAlien>(rData)<<std::endl;break;
    ...
      };
    }
    

    Gerade wenn etwas zur compile time bekannt ist, dürfte doch die Auswahl der zu verwendenden Funktionalität über irgendwelche Typ-Entscheidungen erfolgen können, also templatisierte Klassen und/oder Überladung von Funktionen (wie von hustbear skizziert)?

    Switch-case dagegen ist ja eine Laufzeit-Entscheidung...

    die moeglichkeiten/permutationen sind bekannt, die selektierung eines typen passiert aber zur laufzeit aufgrund von daten.

    Kann sein, dass ich Deine Anwendung noch nicht ganz durchschaut habe, aber die Aussage oben wirft ein Fragezeichen.

    die anwendung ist durchaus komplexer als oben zu sehen, aber es sollte vermitteln was mit den templates passiert. (komplexer im sinne dass es nicht nur "Language" ist, sondern eine maske aus verschiedenen bitflags die | wurden.



  • hustbaer schrieb:

    Wäre es möglich die Schnittstellen der Container-Klassen so zu modifizieren, dass die diversen Zugriffe nicht über verschiedene Methoden, sondern verschiedene Parameter einer einzigen, überladenen Methode angeboten werden?...

    ueber den tag bin ich zufaellig auch auf die idee gekommen dass die methode gleichbleiben sollte und ich ueber den typen spezialisiere.

    mein kleiner test:

    #include <string>
    #include <iostream>
    
    //data container
    template<class... TElements>
    class CData : public TElements...
    {
    public:
    	CData(TElements&&... rElements):TElements(rElements)...{}
    };
    
    //parameter types instead of member functions
    class CName
    {
    	std::string Name;
    public:
    	CName(const char* pName):Name(pName){}
    	std::string Value()const{return Name;}
    };
    
    class CProfession
    {
    	std::string Profession;
    public:
    	CProfession(const char* pProfession):Profession(pProfession){}
    	std::string Value()const{return Profession;}
    };
    
    class CAge
    {
    	std::string Age;
    public:
    	CAge(const char* pAge):Age(pAge){}
    	std::string Value()const{return Age;}
    };
    
    //printer
    class CConsole
    {
    public:
    	template<class T>
    	static void Print(const T& rPerson)
    	{
    //static_cast to select member accessor-function
    		std::cout<<"Name: "<<static_cast<CName>(rPerson).Value()<<std::endl;
    		std::cout<<"Job:  "<<static_cast<CProfession>(rPerson).Value()<<std::endl;
    		std::cout<<"Age:  "<<static_cast<CAge>(rPerson).Value()<<std::endl;
    		std::cout<<"----------------------------"<<std::endl;
    	}
    
    };
    
    //transformation
    template<class T>
    class CRandomize3
    {
    	const T& m_rElement0;
    	const T& m_rElement1;
    	const T& m_rElement2;
    public:
    	CRandomize3(const T& rElement0,const T& rElement1,const T& rElement2):
    	m_rElement0(rElement0),
    	m_rElement1(rElement1),
    	m_rElement2(rElement2)
    	{
    
    	}
    
    //intercept "static cast" for ANY typecast
    	template<class T>
    	operator T()const
    	{
    		const size_t Idx=rand()%3;
    		return static_cast<T>(Idx==0?m_rElement0:Idx==1?m_rElement1:m_rElement2);
    	}
    };
    
    typedef CData<CName,CProfession,CAge> tdPerson;
    void Test()
    {
    	tdPerson Person0("Bob","Clerk","16");
    	tdPerson Person1("Max","Pilot","32");
    	tdPerson Person2("Udo","Robot","64");
    	CConsole::Print(Person0);
    	CConsole::Print(Person1);
    	CConsole::Print(Person2);
    	for(size_t a=0;a<16;a++)
    		CConsole::Print(CRandomize3<tdPerson>(Person0,Person1,Person2));
    }
    

    mich verblueft

    template<class T>
    	operator T()const
    

    ich haette nicht gedacht dass das geht, echt genial dass c++ das erlaubt, denn das dispatched auf den function call (hab bisher nur mit VS2013 getestet).

    das ist zwar ziemlich mit c++11 kanonen auf cast-spatzen zu feuern, aber das macht die sache wirklich komplett non-intrusive. Wobei ich dann die seltsame art des container declaration habe (siehe typedef). Irgendwie grenzt das an intrusive, weil ich den container vorgebe. Ich bevorzuge fast schon deine loesung, wegen simplicity.

    hmm... vielleicht finden wir noch einen weg das zu verbessern (ist aber schon 100mal besser als mein hand permutieren, danke jungs ! 🙂 )



  • Einige Gedanken dazu:

    1. Ich sehe noch nicht den Vorteil darin, ein CData mit verschiedenen Klassen als Template-Parameter zu erstellen, die dann jeweils Basisklasse werden, und die man später mit einem cast wieder auf die gewünschte Klasse eingrenzt. So viele casts sind eher ein Zeichen für ein falsches Design!
    Warum also nicht ein ganz normales struct/class Person, die einfach die Member name, profession und age hat?
    OK, man spart sich mit deiner Lösung den Constructor, und eine solche Klasse ist schnell mit einem typedef erstellt. Nachteil: Man muss dafür jede Elementklasse umständlich definieren.
    Könnte dann Sinn ergeben, wenn man ganz viele verschiedene CData hat, die sich aus einer begrenzten Sammlung von Elementklassen auf verschiedenen Weise zusammensetzen.

    Andererseits hat dieses CData keinen weiteren Vorteil: Bei jedem Zugriff auf ein "Element" (in diesem Fall die entsprechende Basisklasse) musst du ein - zur Compilezeit bekanntes - cast einsetzen, also gewinnst du keine Flexibilität, wenn zwar die Funktionen alle gleich Value() heißen, das cast davor jedoch individuell sein muss.
    Dein Printer z.B. verlangt, dass das übergebene, ansonsten beliebige T zumindest eine Basisklasse CName, CProfession und CAge hat. Das geht mit der Person-Klasse aber genauso: In meiner Version unten müssen eben mindestens die Member name, profession und age vorhanden sein. Beide Klassen kann man auch nicht polymorph mit anderen mischen. Daher: ich sehe den Vorteil nicht.

    2. Wieso verwenden CName, CProfession usw. ein const char*, wenn sie doch ein Member std::string enthalten? Da kannst du das auch im Constructor so angeben.

    3. Der operator T ist insofern seltsam, als dass man das, was du tun willst, auch mit einer ganz normalen Funktion machen kann. Warum also den Aufwand einer Klasse? Zudem sagt der operator T nichts aus; es wird im allgemeinen von solchen impliziten Typumwandlungen abgeraten, weil man zunächst nicht sieht, was das Ding überhaupt tut. Wenn schon, ist wie für Functoren der operator() üblich.

    4. Das template <class T> vor diesem Operator ist unnötig, denn die Klasse selbst hat schon ein <class T>. Meckert dein Compiler da nicht? (gcc tut's)

    5. Du rufst mit deiner Variante den Constructor CRandomize3 16x auf. Wenn du statt dessen den operator() verwendest, kannst du vor der Schleife einmalig ein "Functor"-Objekt erstellen und in der Schleife nur den random-Teil mit () aufrufen.

    6. Dein "Zufalls"generator erzeugt immer die gleiche Folge von Zahlen; du solltest ihn noch initialisieren mit srand((unsigned)time(NULL));

    7. Nur eine sehr persönliche Meinung: ich halte es für eine Unsitte, vor jede Klasse ein C zu schreiben (ich weiß, Microsoft macht das...). Warum? [Edit: ist keine ungarische Notation, sondern wohl aus den MFC, daher gelöscht]. Zweitens kann dir jede gute IDE farbig darlegen, was eine Klasse, was eine Variable, was ein Member usw. ist. Drittens liest das Auge Wörter nicht Buchstabe für Buchstabe, sondern als Ganzes, vor allem jedoch den Anfang und das Ende. Wenn der Anfang jetzt immer ein C ist, muss es zur Unterscheidung mindestens auch den zweiten Buchstaben lesen. Der Begriff fällt also nicht so schnell ins Auge als wenn er kein C davor hätte, folglich ist das Lesen und damit Verstehen und die Fehlersuche von Programmen schwieriger. Ein Randomize ist schneller gefunden als ein CRandomize , weil das Auge nach Wörtern mit "R" sucht.

    Hier eine Variante:

    struct Person
    {
        Person( const std::string& n, const std::string p, const std::string& a ) :
            name( n ), profession( p ), age( a ) {}
    
        std::string name;
        std::string profession;
        std::string age;
    };
    
    //printer
    class CConsole
    {
    public:
        template<class T>
        static void Print(const T& rPerson)
        {
            std::cout<<"Name: " << rPerson.name       << std::endl;
            std::cout<<"Job:  " << rPerson.profession << std::endl;
            std::cout<<"Age:  " << rPerson.age        << std::endl;
            std::cout<<"----------------------------"<<std::endl;
        }
    
    };
    
    //transformation
    template<class T>
    const T& randomize3(const T& rElement0,const T& rElement1,const T& rElement2)
    {
        const size_t Idx=rand()%3;
        return Idx==0 ? rElement0 :
               Idx==1 ? rElement1 :
                        rElement2;
    }
    
    int main()
    {
        srand((unsigned)time(NULL));
    
        Person Person0("Bob","Clerk","16");
        Person Person1("Max","Pilot","32");
        Person Person2("Udo","Robot","64");
        CConsole::Print(Person0);
        CConsole::Print(Person1);
        CConsole::Print(Person2);
        for(size_t a=0;a<16;a++)
            CConsole::Print(randomize3<Person>(Person0,Person1,Person2));
    }
    


  • @minastaros
    Die Cast-Geschichte finde ich persönlich auch komisch. Aber ich kennen den genauen Anwendungsfall von rapso nicht, daher denke ich: er wird das schon passend entscheiden.

    Davon abgesehen ist die Idee mit dem komisch anmutenden Accessor (hier als Conversion-Operator implementiert, man könnte aber genau so nen Getter mit Tag-Dispatching verwenden) die, die Schnittstelle so hinzubekommen dass man einen einfachen, universell verwendbaren Proxy dafür schreiben kann.

    Mit deiner Variante geht das nicht, da gäbe es keine Möglichkeit die drei Strings name, profession und age über einen Proxy zu modifizieren, ohne alle drei im Proxy explizit aufzulisten. Wenn es nicht nur Person gibt, sondern noch zig andere Klassen die jeweils zig Properties haben, dann wird das ... lästig.

    Wenn das Interface aber Tag-Dispatching verwendet, dann geht das sehr einfach - vorausgesetzt man will alle Strings mit der gleichen Transformation modifizieren.


Anmelden zum Antworten