views:

198

answers:

5

I have a token class that looks something like this:

class Token
{
 public:
   typedef enum { STRTOK, INTTOK } Type;
   virtual bool IsA(Type) = 0;
}

class IntTok : public Token
{
   int data;
 public:
   bool IsA(Type t) { return (t == INTTOK); }
   int GetData() { return data; }
}

IntTok newToken;
if ( newToken.IsA(Token::INTTOK )
{
  //blah blah
}

So essentially I have to have every subclass defined in the Token class; which doesn't turn out that bad because there are very few subclasses and I can't imagine them changing. But still, it's ugly, kludgy and less "correct" than identifying subclasses using a dynamic cast. However:

IntTok newToken;
IntTok* tmpTokenTest = dynamic_cast<IntTok*>(&newToken);
if ( tmpTokenTest != NULL )
{
  //blah blah
}

Is also pretty kludgy. Particularly when I have to string them together in a large, nested if.

So which would you use? Is there another solution to this problem?

Note: I know that I'll have to cast them to get at their respective data anyways, but

  1. I won't be casting them until right before I use their function, so it feels cleaner and
  2. I test their type far more often then I use their data.

Note2: Not indicated in the code above is that these tokens are also a linked list. That makes templating difficult(a Token<int> may point to a Token<string>, etc). Which is why I need a Token class as a parent to begin with.

+3  A: 

Just use virtual functions instead to do what you want. Instead of this:

if(newToken.IsA(Token::INTTOK))
{
    // do stuff with ((IntTok*)&newToken)->GetData()
}

Do this:

class Token
{
public:
    ...
    virtual void doTypeDependentStuff() {}  // empty default implementation
}

class IntTok : public Token
{
public:
    ...
    void doTypeDependent()
    {
        // do stuff with data
    }
}
Adam Rosenfield
It's not really a 'do' situation. These tokens are parsed by other objects, so I just need to see what type they are and get their stored value.
Whaledawg
@Whaledawg: You have control over those "other objects," right? So you make *those* objects call the virtual functions. If you think about it, *any time* you write "if (x.IsA(INTTOK)) { XYZ; }" you could instead write a virtual function, do_XYZ(), and call it as "x.do_XYZ();".
j_random_hacker
A: 

So essentially I have to have every subclass defined in the Token class

Can you explain why?

Is it really necessary to cast? Polymorphic functions can be put to use.

Or, maybe you can have a templated Token class (with default behavior for some) and specialize for the remaining.

dirkgently
A: 

That's a nasty one, though I would be more likely to go with the version of using RTTI.

Weren't new C++ compilers (I've last tried in VC 6.0 when it wasn't really supported) supposed the typeid operator so you wouldn't need a full dynamic cast?

Uri
I've never heard of the typeid operator :/
Whaledawg
Section 5.2.8 of the C++ standard. There is even a Wikipedia entry on it:http://en.wikipedia.org/wiki/TypeidThat being said, not sure if it's supported.
Uri
+3  A: 

Visitor pattern, indeed.

class TokenVisitor {
public:
    virtual ~TokenVisitor() { }
    virtual void visit(IntTok&) = 0;
    virtual void visit(StrTok&) = 0;
};

class Token {
 public:
   virtual void accept(TokenVisitor &v) = 0;
};

class IntTok : public Token {
   int data;
 public:
   virtual void accept(TokenVisitor &v) {
       v.visit(*this);
   }
   int GetData() { return data; }
};

Then just implement the visitor interface and call

token->accept(myVisitor);

Control will be given to the Visitor, which then can do the appropriate action(s). If you need to have the variable locally and of the right type - then however you will hardly get around down-casting it. But i think driving control to specific implementations using virtual functions often is a good way to solve it.

Johannes Schaub - litb
<i>If you need to have the variable locally and of the right type - then however you will hardly get around down-casting it.</i>I don't think I can. I need to get data out of the tokens and even with a visitor the return type needs to be statically handled in c++. :/
Whaledawg
yeah that's what i meant. When you use the visitor, the visitor will be called out-of-context. if you want to have the variable locally - that's impossible without determining the type and branching accordingly.
Johannes Schaub - litb
even boost's visitors for boost::variant can't do that :) the visitor methods all have to have a common return type. however, what makes it so that you can't process your stuff in the visitor methods? can you please elaborate?
Johannes Schaub - litb
Well the way it currently works is a Lexer creates this token list and passes it to the parser, which depending on the type and content will build the parse tree. I might be able to make the parser a visitor, I'll have to think about it.
Whaledawg
though iirc, the usual way this is done is with that enum stuff, and use some automaton (i believe a pushdown automaton or so) doing something with those tokens. but i've never done it myself :/
Johannes Schaub - litb
Yes, this is definitely the most general/scalable way of doing it, though possibly overkill in this case -- probably a few virtual functions would do the trick (see Adam Rosenfield's answer). BTW, I think you would invoke the visitor as "token->accept(myVisitor);", not "token->visit(myVisitor);".
j_random_hacker
oops, i see. thanks j_random_hacker
Johannes Schaub - litb
j_random_hacker, well the problem with having the virtual function in the token classes do it is that the operation possible do not have much to do with the token class itself. for example, consider a debug dump of his tree, or searching for some symbol in his tree. He can do that all with visitors
Johannes Schaub - litb
without having the functionality hard-coded in the token classes, as Whaledawg explains in comment to another answer.
Johannes Schaub - litb
it's similar to the free function / member function idea: adding a virtual function for each possible scenario will soon clutter the interface
Johannes Schaub - litb
+2  A: 

Might i suggest using Boost::Variant, which is basically the union of multiple types (an object of type variant can hold any object of type Ti ( 1 <= i <= n ) ).

Using this, you won't have to use inheritance.

See there for more information.

Benoît