Well I've got a problem, that is more theoretical than practital. I
need to know benefits of RTTI. I see another way of doing it...
class A {
public:
~virtual A() {}
enum Type { X, Y, Z };
Type GetType() { return type_; }
private:
Type type_;
}
class B : public A { ... }
and now if I want to know what type of class it is I just call a
GetType method.
I'd be really pleased with any advice. Thanks.
There are a couple answers, mostly what other people have said. I'll
just mention them to contrast what I'm going to say:
1. Your design is potentially faulty -- this is applicable in either
case
2. If you KNOW you have a fixed set of types, this can work okay --
but it's increased maintanence
3. So if you must use this design, RTTI is usually better
I'll just mention an interesting case where this doesn't necessarily
apply: generated code. If your class heirarchy is generated, then
maintenance isn't an issue, and an approach similar to this can have a
couple useful benefits.
I'm working with a C++ front end now where the classes that represent
nodes of the abstract syntax tree (AST) are generated from a compact
description like the following (abbreviated):
class Statement {
-> S_if;
-> S_for;
-> S_while;
}:
This would get expanded to:
class Statement { ... };
class S_if : public Statement { ... };
class S_for : public Statement { ... };
class S_while : public Statement { ... };
(In reality, the AST language allows you to specify data and function
members, but I'm leaving them out for simplicity.)
But it also generates essentially what you name. Statement contains:
enum Kind { S_IF, S_FOR, S_WHILE, NUM_KINDS }:
virtual Kind kind() const = 0;
Now this already has an unexpected benefit: while debugging in GDB, if
you have a Statement object s, you can see what kind it is by saying
"print s->kind()". I'm no GDB expert, but I don't know a way to do this
in general.
But there's more. It also generates more functions in Statement:
bool isS_if() const;
S_if* asS_if() const;
S_if* isS_if() const;
The former acts as you'd expect. The last acts exactly as dynamic_cast,
though I would guess faster. The second contains an assert(isS_if())
before returning. (There are also const versions of all of these.)
So depending on what you're doing, you have a syntactically cleaner way
of getting the downcast pointer than dynamic_cast.
This arose because the author was working in C++ before stuff was
standardized (let alone reliably implemented in compilers), though I
think he still prefers to use it, probably because of the cleaner
syntax.
Again, this is really only a good idea if you have auto-generated code.
In this example, the entire AST heirarchy comes out of the AST language
specs, so this isn't an issue.
-----
But this is a good place to ask this question, still working off of the
above. So I've got an AST, and I have classes that implement a visitor
interface that works like the GoF visitor. Except that my visitor only
has functions of the form like visitStatement, visitExpression, etc.
rather than visitS_if(), visitS_for(), visitE_funCall(), etc. This
means that my visit statement is more or less a "switch(s->kind()) case
S_IF: ..." type of thing.
I know the typical way that you're supposed to refactor this sort of
design smell (type tags) is by changing it to virtual methods. In this
case, there is a virtual function (e.g.) Statement::traverse(Visitor&)
each subclass implements to visit its children. (So
S_if::traverse(Visitor& vis) calls vis->visitStatement(this) then calls
traverse on the statements representing the branches.) I could see
changing this to call, say, visitS_if() instead of visitStatement().
However, suppose I can't do this. (As is pretty much the case;
implementing this would require changing the astgen program, and I
don't want to get into its internals.) Are there any better designs
that jump out?
In this case (again because the list of classes is very static) I don't
think that this is a big deal, and it's only mildly ugly, and I can't
come up with anything better, but it still smells a bit.
Evan