Design-Problem mit Templates



  • Moin. Ich bin gerade schwer beschäftigt, mir ein kleines GUI-Framework zu basteln. Ich komme zu dem Schluss: ich HASSE HASSE HASSE templates...

    Die Basis-Daten:
    Das ganze GUI-System ist in sich geschlossen. Nach außen sind nur zwei Klassen bekannt: die Basis-Klasse** Ctl von der alle Steuerelemente abgeleitet werden und die Zentralklasse UIManager die auch als Klassenfabrik fungiert.
    Die Steuerelement-Eigenschaften werden nicht als Member-Variablen deklariert sondern sind in Objekten vom Typ
    template <class T> CtlProperty : public CtlProperty0 gekapselt.
    Alle Eigenschaften werden in einer Liste abgelegt (
    list<CtlProperty0*> Ctl::m_lstProperties **).
    Von außen Zugriff hat man über die template-Methode

    template <class T> Ctl::Get () const
    

    bzw. entsprechende Set-Methode.
    Für z.B.** Get() **gibts jetzt folgende Varianten:

    1.: downcast

    template <class T>
    T Ctl::Get (CtlPropertyType type) const
    {
        CtlProperty<T>* pTmp = dynamic_cast<CtlProperty<T>*> (m_lstProperties.find (type));
        if (NULL == pTmp)
            throw Error ("template-Parameter falsch. ätschbätsch");
        else return pTmp->GetValue();
    }
    

    Vorteil: Typsicher
    Nachteil: Zu restriktiv. ein als int initialisiertes Property kann man sich nicht z.B. als long geben lassen, man muss immer genau den richtigen Datentyp für jede Eigenschaft im Auge behalten.
    a) Fehleranfällig
    b) an einer Stelle ein echtes Design-Problem (=UITheme-Klasse komplett umbauen. *grrrr*)

    Variante 2: void*

    Ich spar mir jetz das Listing...
    Vorteil: alle Nachteile aus Variante1 lösen sich in Wohlgefallen auf.
    Nachteil: ZEIGER AUF VOID - cast... super-hyper-Typ-unsicher.

    Variante 3: stringstream
    Mein bisheriges Konzept.
    Ein kompliziertes Gewurste mit Speichern als String und hin und her casten mit std::stringstream plus noch weiteren von CtlProperty0 abgeleiteten Klassen für Zeiger und Objekte.
    Typunsicher, viel zu zeitaufwändig.

    Ich brauche ein Mittelding. sowas muss funktionieren:

    Ctl* pTxt = m_pUIManager->CreateCtl (CtlTypes::TextBox);
    pTxt->Set<int> (CtlPropTypes::Value, 3);
    long l = pTxt->Get<long> (CtlPropTypes::Value);
    

    Und das sollte einen Compiler-Fehler generieren (oder irgendwas um zur Laufzeit darauf reagieren zu können)

    pTxt->Set<Ctl*> (CtlPropTypes::Parent, pWnd);
    int i = pTxt->Get<int> (CtlPropTypes::Parent);
    

    Kann mir jemand mit nem Gedankenblitz aushelfen?



  • Hier würde mich auch eine lösung interessieren. Hab ein ähnliches Problem unter .Net. Gelöst hab ichs aktuell mit object Objekten. Welches wohl einen void pointer gleichkommen würde. Ich kann jetzt nicht genau sagen wie sich die void ptr beim Cast verhalten. Im .net sollte dadurch das meist abgedeckt sein. Falls die Klassen nicht übereinstimmen geht der Cast mit einer Exception schief. Sofern sowas ähnliches auch mit void ptr machbar ist. Wäre das gar nicht mehr so typunsicher wie man es sich vorstellt. Obwohl die Lösung immer noch nicht wirklich das wahre ist.



  • Mit dem Entscheid, Basisklassenzeiger und enum als Objekt-ID zu verwenden, verlierst du statische Typsicherheit und somit ist eine Prüfung zur Compilezeit unmöglich.

    Für die Property-Klasse fände ich es zweckmässig, ähnliche Eigenschaften zusammenzupacken, z.B. in eine Klasse IntegerProperty für Ganzzahlen oder StringProperty für Strings. Damit legt man sich nicht auf einen einzelnen Typ fest und gewinnt Flexibilität. Man könnte in die Klasse implizite Konvertierungsoperatoren zu den jeweiligen Typen (z.B. long , int , short respektive const char* , std::string ) einbauen, vielleicht sogar als Funktionstemplates.

    Deine Variante 1 ist im Prinzip nicht schlecht, man könnte sie folgendermassen ausbauen:

    class Property  { /* ... */ };
    class IntegralProperty : public Property { /* ... */ };
    class StringProperty : public Property { /* ... */ };
    
    template <class PropertyClass>
    PropertyClass& Ctl::Get(PropertyType identifier) const
    {
        Property* basePtr = m_lstProperties.find(identifier);
    
        return dynamic_cast<PropertyClass&>(*basePtr);
    }
    
    int main()
    {
        Ctl* myControl = /* ... */;
        long value =  myControl->Get<IntegralProperty>(CtlPropTypes::Value);
    }
    

    Hierbei wirft dynamic_cast bei Fehlschlag eine std::bad_cast -Exception. Da der dynamische Downcast bei einem korrekten Programm nicht fehlschlägt, aber dennoch relativ viel Rechenzeit verbraucht, würde ich stattdessen static_cast im Release- und dynamic_cast im Debug-Modus einsetzen (oder gleich boost::polymorphic_downcast verwenden).

    Schön wäre natürlich, wenn das Get() -Funktionstemplate gleich erkennen könnte, zu welcher Klasse eine Property-ID wie CtlPropTypes::Value gehört. Auch das ist möglich, mit einer relativ einfachen Typ-Metafunktion. Das Makro dient der Übersichtlichkeit:

    #define PROPERTY_ID_TO_CLASS(ID, CLASS) \
    template <>                             \
    struct IdToProperty<ID>                 \
    {                                       \
    	typedef CLASS Type;                 \
    };
    
    template <typename T>
    struct IdToProperty;
    
    PROPERTY_ID_TO_CLASS(CtlPropTypes::Value, IntegralProperty)
    PROPERTY_ID_TO_CLASS(CtlPropTypes::Parent, IntegralProperty)
    PROPERTY_ID_TO_CLASS(CtlPropTypes::Text, StringProperty)
    

    Angewandt:

    template <CtlPropTypes::Type Identifier> // CtlPropTypes::Type ist dein enum
    typename IdToProperty<Identifier>::Type& Ctl::Get() const
    {
        Property* basePtr = m_lstProperties.find(Identifier);
    
        return dynamic_cast<typename IdToProperty<Identifier>::Type&>(*basePtr);
    }
    
    int main()
    {
        Ctl* myControl = /* ... */;
        long value = myControl->Get<CtlPropTypes::Value>();
    }
    

    Möglicherweise sind noch ein paar Fehler drin, ich hoffe aber, das Prinzip ist verständlich. Ansonsten frage einfach nach. Fedaykin, auf C# wird das aufgrund fehlender Metaprogrammierung wohl nicht 1:1 übertragbar sein, aber da kann man sicher mit Reflection etwas Ähnliches hinbekommen.



  • Ich komme zu dem Schluss: ich HASSE HASSE HASSE templates...

    Warum benutzt du sie dann? Andere GUI-Bibliotheken kommen auch ohne aus.


Anmelden zum Antworten