Jump to content
  • Advertisement
Sign in to follow this  
Zakwayda

rtti question

This topic is 5048 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hi, I have read on these boards and elsewhere that using rtti and dynamic_cast can be a symptom of bad design. I'm using both in a few places and am wondering if mine is a case of 'ok usage' or 'bad design'. Here's an example. I have a typical console system with commands and variables. All the console commands and variables are handled by one list, so that tasks like registering and unregistering, autocompletion, and matching can be handled uniformly for both types. Classes Cmd and Var inherit from a common base class, and the ConList class maintains an array of pointers to this base class. Obviously there are some functions which apply to one subtype and not the other. For example, variables can be toggled, but not commands. So when the console receives a toggle command, it tries to find an entry with the given name, then uses rtti to determine if it is in fact of type Var, and then dynamic_casts the pointer to a Var* so it can call the Var function Toggle(). So anyway, that's what I'm doing - seems fairly harmless. But I'm not very experienced in software engineering, so, is this bad design? And if so, what would be a better solution?

Share this post


Link to post
Share on other sites
Advertisement
You could store them in two different lists. You'd still have a common base class though as things like the autocompletion is the same for both types. You can also do selective searching based on the current state, for example, you must have a command first, followed by a variable.

Thus, no RTTI is required, or dynamic_cast either.

Skizz

Share this post


Link to post
Share on other sites
Thanks for the reply. Even though this is knd of a trivial example, I'd like to understand the solution you're proposing, as similar problems may pop up elsewhere.

So are you recommending maintaining three lists - a base class list, a cmd list, and a var list - and using whichever is appropriate? Or, just using a cmd list and a var list and traversing them both when performing generic functions like autocompletion?

Any other thoughts will also be welcome.

Share this post


Link to post
Share on other sites
On the flip side of the coin, rtti is not always bad programming practice.
I agree that in most games, the speed hit involved in rtti is probably too large to be practical, but if speed is not a big issue, rtti does make some programming tasks considerably easier.
Quote:
Original post by jyk
So are you recommending maintaining three lists - a base class list, a cmd list, and a var list - and using whichever is appropriate? Or, just using a cmd list and a var list and traversing them both when performing generic functions like autocompletion?

Just remember that there is a third case, a class which is both command and variable (I am thinking stream re-direction in unix shells here, basically any command whose result could be passed as a variable to another command).

SwiftCoder

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
jyk,

Have you looked into the vistor design pattern? This is a good way to do what you want to do without using any rtti or any other messy stuff. The link I provided explains it pretty well, but you can find more by googling it. And feel free to ask if you find it confusing.

Share this post


Link to post
Share on other sites
Quote:
Original post by jyk
Obviously there are some functions which apply to one subtype and not the other. For example, variables can be toggled, but not commands. So when the console receives a toggle command, it tries to find an entry with the given name, then uses rtti to determine if it is in fact of type Var, and then dynamic_casts the pointer to a Var* so it can call the Var function Toggle().


Here is a solution that might work for you: Add two functions, CanToggle and Toggle to the base class. It is kind of like RTTI but it distinguishes between functionality rather than type. That way the console code is no longer hard-wired to toggle only specific classes.

But I think the best solution is to maintain two lists as Skizz suggested.

Share this post


Link to post
Share on other sites
Cool - thanks for the replies! I will look into the visitor pattern as well as consider the other approaches suggested. Thanks again.

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
Quote:
Original post by JohnBolton
Here is a solution that might work for you: Add two functions, CanToggle and Toggle to the base class. It is kind of like RTTI but it distinguishes between functionality rather than type. That way the console code is no longer hard-wired to toggle only specific classes.


In a large and more complicated system this technique of throwing everything into one interface and having to make checks at runtime isn't a scalable solution. You're putting stuff in the interface which might do something but needs checking in case it doesn't.


#include <iostream>

class ConsoleEntity;
class Variable;
class Command;

class Visitor {
public:
virtual void Visit(ConsoleEntity* var) const {
//do nothing
//throw an exception
//whatever you want
}
//can't implement these here as don't have declarations of Variable and Command yet
//will implement it below
virtual void Visit(Variable* var) const;//calls Visit(ConsoleCommandBase* var) by default
virtual void Visit(Command* var) const;//calls Visit(ConsoleCommandBase* var) by default
};

class ConsoleEntity {
public:
void AutoComplete() {
std::cout << "Console Entity auto-completed" << std::endl;
}
virtual void Accept(const Visitor& visitor) {
visitor.Visit(this);
}
};

class Variable : public ConsoleEntity {
public:
void Toggle() {
std::cout << "Variable Toggled" << std::endl;
}
virtual void Accept(const Visitor& visitor) {
visitor.Visit(this);
}
};

class Command : public ConsoleEntity {
public:
void Execute() {
std::cout << "Command Executed" << std::endl;
}
virtual void Accept(const Visitor& visitor) {
visitor.Visit(this);
}
};

void Visitor::Visit(Variable* var) const {
ConsoleEntity* base = var;
Visit(base);
}
void Visitor::Visit(Command* cmd) const {
ConsoleEntity* base = cmd;
Visit(base);
}

class Toggle : public Visitor {
public:
virtual void Visit(Variable* var) const {
var->Toggle();
}
virtual void Visit(Command* cmd) const {
//call base Visitor::Visit(cmd)
//or do nothing, etc.
}
};

class Execute : public Visitor {
public:
virtual void Visit(Variable* var) const {
//do nothing, etc.
}
virtual void Visit(Command* cmd) const {
cmd->Execute();
}
};

class AutoComplete : public Visitor {
public:
virtual void Visit(ConsoleEntity* base) const {
base->AutoComplete();
}
};

int main() {
Variable v;
Command c;

Toggle t;
v.Accept(t);
c.Accept(t);

Execute e;
v.Accept(e);
c.Accept(e);

AutoComplete a;
v.Accept(a);
c.Accept(a);

//take pointers to base types
Visitor* visitor = &t;
ConsoleEntity* entity = &v;

entity->Accept(*visitor);//don't need to know types to call correct functions

return 0;
}


There are basically two calls to virtual functions to determine what function is finally called. E.g to get to call Toggle you call Visit on a base Console Entity class which determines whether it is a Command or Variable object. Then that object calls Accept on the Visitor using its own 'this' pointer. That determines which Accept function gets used.

Copy this into a new project and step through it to get a feel for what is happening.

There are probably different ways to use the Visitor Pattern in your case. I have made the Toggle, AutoComplete and Execute into Visitors but it may be that you can do something different.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!