Interface (Abstrakte Basisklasse) aber mit verschieden Datentypen speichern



  • Hi!

    Ich hab einen kleinen Parser geschrieben und das geparste speichere ich in Tokens.

    Die Tokenklasse ist eine abstrakte Basisklasse von der ich verscheidene Typen ableite (StringToken, NumberToken,...). Die Tokenklasse schaut ungefähr so aus.

    class Token
    {
      private:
      TokenTyp typ_;
    
      public:
      enum TokenTyp {ttString, ttNumber, ...};
    
      Token(TokenTyp typ) : Typ_(typ) {};
      virtual ~Token() {};
      TokenType getType() { return typ_; };
    };
    

    Jetzt will ich in den abgeleiteten Tokens, je nach Typ, einen String, Integer, .. speichern. Kann ich das im Interface irgendwie festlegen, oder muss ich für jeden Typ in der abgeleiteten Klasse eine Membervariable machen. Das hat den Nachteil, dass ich wenn einer Funktion einen Token übergebe, jedesmal einen Cast machen muss, wenn ich auf den String zugreife. Das heißt ich muss gegen die Implementierung der Klasse entwickeln und nicht gegen das Interface.

    void test(Token *t)
    {
      if(t->getType()==Token::ttNumber)
      {
        TokenNumber* number = static_cast<TokenNumber*>(t);
        // Tu irgendwas
      }
    }
    

    Hat jemand sowas schon mal gemacht? Ich hab eicht keine Idee wie ich das am besten löse. In C würde ich das mit einem void-Pointer machen, aber dann kann ich ja gleich C programmieren. Ich will eine saubere Objektorientierte Lösung

    mfg bauerb



  • Hi,

    erst mal eine kleine Bemerkung, nach meiner Erfahrung solltest Du versuchen, auf das 'TokenType'-enum zu verzichten, da es eine überflüssige Redundanz darstellt, und stattdessen für jeden möglichen Tokentyp eine eigene 'Token'-Klasse schreiben (was Du wahrscheinlich eh machst, oder?).

    Zu Deinem Problem, Du musst an sich für jede Token-Art eine eigene Klasse mit entsprechender Member-Variable schreiben. Du kannst allerdings Templates als Abkürzung verwenden:

    template <typename T>
    class LiteralToken : public Token {
        T _value;
    
    public:
        LiteralToken(T const& v) : _value(v) { }
        ~LiteralToken() {}
        T const& value() const { return _value; }
    };
    
    // Und dann evtl. noch 'convenience':
    
    typedef LiteralToken<int> IntToken;
    typedef LiteralToken<std::string> StringToken;
    // …
    


  • wie wäre es, wenn du die funktion test() überlädst für alle arten von tokens? StringToken bekommt eine methode value() und gibt einen wert vom typ std::string zurück und NumberToken z.b. einen int.



  • Hi!

    Konrad Rudolph schrieb:

    [...]

    template <typename T>
    class LiteralToken : public Token {
        T _value;
    
    public:
        LiteralToken(T const& v) : _value(v) { }
        ~LiteralToken() {}
        T const& value() const { return _value; }
    };
    
    // Und dann evtl. noch 'convenience':
    
    typedef LiteralToken<int> IntToken;
    typedef LiteralToken<std::string> StringToken;
    // …
    

    Ja das gefällt mir schon ganz gut.

    besserwisser schrieb:

    wie wäre es, wenn du die funktion test() überlädst für alle arten von tokens? StringToken bekommt eine methode value() und gibt einen wert vom typ std::string zurück und NumberToken z.b. einen int.

    An das hab ich schon gedacht, aber meiner Meinung ist das auch noch nicht ganz das gelbe vom Ei.

    mfg bauerb



  • Wenn die Zahl der möglichen Tokentypen nicht allzu riesig ist, insbesondere wenn selten Tokentypen hinzukommen (mit Konrads LiteralToken<> geht das hier zum Beispiel nicht mehr, da die Anzahl der möglichen Typen beliebig ist), und wenn sie sich nicht signifikant in der Größe unterscheiden (was aber kein Killerkriterium ist) wäre es vielleicht eine Überlegung wert, das ganze statischer mit boost::variant zu programmieren.

    Die Tokentypen haben dann keine virtuellen Methoden und enthalten direkt die benötigten Informationen (wenn es überhaupt welche gibt, ansonsten einfach leere Structs).
    Der Tokenvektortyp kann dann wie folgt definiert werden:

    typedef boost::variant<StringToken, IntToken, LeftParen, RightParen etc.> token_type;
    typedef std::vector<token_type> token_vector_type;
    

    Das ganze zu erzeugen ist simpel, einfach vec.push_back(TokenTyp(tolle_geparste_informationen));.
    Das von dir beschriebene Problem wird mit einem visitor gelöst, siehe dazu die Boost-Doku.


Anmelden zum Antworten