"Dynamische" Klasse



  • Hi,
    der Titel ist leider nicht sehr aussagekräftig, aber ich wusste nicht, wie ich es besser benennen soll. Also, mein Problem ist folgendes:
    Ich habe eine Klasse, die ein Zeichen repräsentiert. Dies kann entweder eine Ganzzahl, eine Kommazahl oder ein Operator wie beispielsweise +,- u.s.w. sein.
    Mittels Enum wird der aktuelle Status, also ob es eine Zahl oder Operator ist, festgehalten. Hier einmal der Aufbau:

    class Token
    {
        public:
            enum Type
            {
                Int,
                Decimal,
                Operator,
                Unknown
            };
            /** Default constructor */
            Token():
                m_Type(Unknown)
            {}
            Token(long val)
            {
                Set(val);
            }
            Token(double val)
            {
                Set(val);
            }
            Token (boost::shared_ptr<IOperator> op)
            {
                Set(op);
            }
            /** Default destructor */
            ~Token() {}
            Type m_Type;
            void Set(long val)
            {
                m_Type = Int;
                m_IntVal = val;
            }
            void Set(double val)
            {
                m_Type = Decimal;
                m_DecVal = val;
            }
            void Set(boost::shared_ptr<IOperator> op)
            {
                m_Type = Operator;
                m_Operator = op;
            }
        protected:
        private:
            long m_IntVal;
            double m_DecVal;
            boost::shared_ptr<IOperator> m_Operator;
    };
    

    Mittels Überladung von Set wird also der Wert übergeben und der aktuelle Zustand vom token geändert.
    Was ich nun gerne hätte, wäre eine Möglichkeit mit nur einer ( eventuell überladenen) Get-Methode den aktuellen Wert zu bekommen. Je nach Zustand also die Ganzzahl, die Kommazahl oder den Operator. Dazu müsste die entsprechende Methode das Typ-Enum überprüfen und je nachdem einen entsprechenden Rückgabewert haben. Dies ist so allerdings nicht möglich. Habt ihr nicht eine Idee, wie man möglichst über eine Zugriffsmöglichkeit abhängig vom zustand des Tokens an den korrespondierenden Wert kommen könnte?
    Grüße Gate



  • Dein Ansatz scheint mir ein wenig merkwürdig. Müsstest du nicht wissen, was für ein Zeichen kommt, wenn du damit weiterarbeitest? Denn entweder könntest du drei Get-Methoden anbieten und bei einem falschen Typen eine Exception werfen.

    Oder du vereinheitlichst die drei Typen so, dass du mit ihnen gemeinsam arbeiten kannst. Vielleicht würde hier bei eine gemeinsame polymorphe Basisklasse helfen. Ansonsten wäre Boost.Variant ein Ansatz.



  • Gate schrieb:

    Ich habe eine Klasse, die ein Zeichen repräsentiert. Dies kann entweder eine Ganzzahl, eine Kommazahl oder ein Operator wie beispielsweise +,- u.s.w. sein.
    Mittels Enum wird der aktuelle Status, also ob es eine Zahl oder Operator ist, festgehalten.

    selber nach dem typ zu switchen ist immer ein zeichen dafür, daß du veransäumt hast, den unsichtbaren enum namens vptr zu benutzen mithilfe des wortes virtual. und du brauchst am ende gar kein getWert, weches von einem unsissenden teil des programms aufgerufen wird, wie der main, sondern innerhalb der optimier, compilier- oder auswert-funktionen, und dort reicht Token* foo(). "Mehr effektiv C++ programmieren" kann da hilreich sein, denn zu diesem behufe bieten sich diverse techniken zum double dispatching an.
    viel spaß.



  • Hallo und danke für die Antworten soweit.

    Nexus schrieb:

    Dein Ansatz scheint mir ein wenig merkwürdig. Müsstest du nicht wissen, was für ein Zeichen kommt, wenn du damit weiterarbeitest? Denn entweder könntest du drei Get-Methoden anbieten und bei einem falschen Typen eine Exception werfen.

    Das Problem ist, dass ich leider nicht weiß um welches Zeichen es sich gerade handelt. Zur Verdeutlichung:

    class PlusOp : public IOperator
    {
        public:
        PlusOp():IOperator("Plus","+",1)
        {}
        Token Eval(const Token& lhs, const Token& rhs) //Hier bekomme ich lediglich Tokens 
        { //und weiß nicht, was sie eigentlich darstellen
            double Result = 0;
            if( lhs.IsNumber() && rhs.IsNumber() ) //Daher hier die Überprüfung
            {
                switch( lhs.m_Type ) //Und dann den Wert extrahieren
                {
                    case Token::Int:
                        Result = lhs.GetInt();
                    case Token::Decimal:
                        Result = lhs.GetDecimal();
                }
                switch( rhs.m_Type )
                {
                    case Token::Int:
                        Result += rhs.GetInt();
                    case Token::Decimal:
                        Result += rhs.GetDecimal();
                }
                return Token(Result);
            }
            return Token();
        }
    };
    

    Wie gesagt, dass funktioniert, ist aber recht umständlich und sicher nicht die elgeganteste Möglichkeit.

    volkard schrieb:

    selber nach dem typ zu switchen ist immer ein zeichen dafür, daß du veransäumt hast, den unsichtbaren enum namens vptr zu benutzen mithilfe des wortes virtual. und du brauchst am ende gar kein getWert, weches von einem unsissenden teil des programms aufgerufen wird, wie der main, sondern innerhalb der optimier, compilier- oder auswert-funktionen, und dort reicht Token* foo(). "Mehr effektiv C++ programmieren" kann da hilreich sein, denn zu diesem behufe bieten sich diverse techniken zum double dispatching an.
    viel spaß.

    Danke für den Hinweis, aber ich kann dir leider nicht ganz folgen. Hättest du eventuelle ein kleines Beispiel zur Verdeutlichung?

    Grüße Gate



  • Polymorphie wird wohl das beste sein. Hast du dich schon mit Vererbung und virtuellen Funktionen beschäftigt? Dann könntest du eine abstrakte Basisklasse Token schreiben, die das Interface bereitstellt. In den abgeleiteten Klassen (konkrete Tokens) implementierst du die Funktionalität. Im Idealfall sollte es dann möglich sein, nur über virtuelle Funktionen die spezifische Funktionalität der drei Klassen zu vereinheitlichen. Falls du das nicht schaffen solltest, gibt es auch noch dynamic_cast .

    class Token
    {
        public:
            virtual void Set(const Token& NewToken) = 0;
            virtual const Token& Get() const = 0;
    
            ...
            virtual ~Token();
    };
    
    class IntegerToken : public Token
    {
        ...
    };
    
    class DecimalToken : public Token
    {
        ...
    };
    
    class Operator : public Token
    {
        ...
    };
    


  • Nexus schrieb:

    Polymorphie wird wohl das beste sein. Hast du dich schon mit Vererbung und virtuellen Funktionen beschäftigt? Dann könntest du eine abstrakte Basisklasse Token schreiben, die das Interface bereitstellt. In den abgeleiteten Klassen (konkrete Tokens) implementierst du die Funktionalität.

    Ja das wäre kein Problem, so habe ich auch die Operatorklasse aufgebaut. Allerdings gibt es da noch ein Problem:

    Nexus schrieb:

    Im Idealfall sollte es dann möglich sein, nur über virtuelle Funktionen die spezifische Funktionalität der drei Klassen zu vereinheitlichen. Falls du das nicht schaffen solltest, gibt es auch noch dynamic_cast .

    class Token
    {
        public:
            virtual void Set(const Token& NewToken) = 0;
            virtual const Token& Get() const = 0;
    
            ...
            virtual ~Token();
    };
    
    class IntegerToken : public Token
    {
        ...
    };
    
    class DecimalToken : public Token
    {
        ...
    };
    
    class Operator : public Token
    {
        ...
    };
    

    Ich weiß nicht wie ich dann an die konkreten Daten kommen kann. Wie bereits erwähnt kann ein Token ja beispielsweise einen operator oder eine Zahl enthalten. In deinem Beispiel hast du die Get-Methode mit einem Token als Rückgabewert definiert, wobei ich da ja eigentlich schon den konkreten Typ bräuchte. Das gleiche gilt ja dann entsprechend für die abgeleiteten Klassen. Wenn ich nicht übersehe, dann komme ich ja so auch nicht an die Daten.



  • Ich gebe eine Const-Referenz auf ein Token zurück. Diese muss zwangsläufig auf eine abgeleitete Klasse verweisen, da die Basisklasse abstrakt ist.

    Wie gesagt, im Idealfall bestünde gar kein Anlass, von aussen zu erkennen, ob es sich um ein Operator oder eine Zahl handelt. Allerdings ist das teilweise nicht trivial umzusetzen, weswegen ich auch dynamic_cast erwähnt habe. Das führt aber nicht selten zu hässlichen Fallunterscheidungen und schlechtem Laufzeitverhalten. Ich empfehle es nicht, aber möchte dir trotzdem dessen Funktionsweise zeigen:

    const Token& t = MyToken.Get();
    
    try
    {
        const Operator& o = dynamic_cast<const Operator&>(t);
        // Token war ein Operator, arbeite damit weiter.
    }
    catch (std::bad_cast&)
    {
        // kein Operator.
    }
    

    Von daher solltest du dir überlegen, ob sich die Schnittstelle gegen aussen nicht vereinheitlichen liesse und du mit virtuellen Funktionen arbeiten könntest. Welche Operationen sollen denn mit den Tokens möglich sein? Das übliche Syntax-Auswerten?

    Vielleicht lohnt sich der Aufwand aber auch nicht und man kommt mit herkömmlichen Fallunterscheidungen weiter. Hängt stark davon ab, wie dein Kontext aussieht.

    Edit: Hmm, ich merke gerade, ich hab mir das zum Teil zu wenig überlegt, zum Beispiel auch die Rückgabe sollte in der Form nicht so da sein. Nicht ganz einfach, aber ich lass mir das bei Gelegenheit nochmals durch den Kopf gehen... Vielleicht kennt auch jemand anders gerade eine gute Möglichkeit.



  • Vorschlag: Die Auswertung des Tokens scheint mir ein klassischer Fall für die Anwendung des "Visitor"-Patterns zu sein.

    Dazu müßte man eine Klasse "TokenVisitor" definieren, die für jedes mögliche Token eine Auswertungsmethode bereitstellt. Eine Instanz dieser Klasse wird an Token übergeben und das Token (das ja selbst am besten weiß, welchen Wert es besitzt) ruft dann die entsprechende Methode auf. 🙂

    Gruß,

    VV



  • Nexus schrieb:

    Welche Operationen sollen denn mit den Tokens möglich sein? Das übliche Syntax-Auswerten?

    Vielleicht lohnt sich der Aufwand aber auch nicht und man kommt mit herkömmlichen Fallunterscheidungen weiter. Hängt stark davon ab, wie dein Kontext aussieht.

    Ja, hauptsächlich soll das Token halt ein Zeichen repräsentieren und Zugriff auf Selbiges ermöglichen. Beispielsweise wenn dieser Ausgangsstring vorhanden ist: "1234+67" -> Token T1(Int), Token T2(Operator), Token T3(Int)
    Und dann der Zugriff auf die eigentlichen Daten:
    T1.Get()//gibt den integer 1234 zurück
    T2.Get()//gibt den Operator + zurück
    T3.Get()//gibt den Integer 67 zurück
    So funktioniert es bis jetzt ja leider nicht, aber das wäre quasi das Wunschziel. Ich denke mal, ich werde den Tipp von VirtualVoid mit dem VisitorPattern einmal ausprobieren. Muss ich mich mal einlesen, kannte es bis jetzt nur vom Namen her.
    Vielen Dank aber schonmal für eure Tipps.
    Grüße Gate



  • Das Visitor-Pattern scheint interessant. Sowas Ähnliches kommt ja auch in Boost.Variant vor, vielleicht kannst du dir das mal anschauen...


Anmelden zum Antworten