问题
I am familiar with C++ RTTI, and find the concept interesting.
Still there exist a lot of more ways to abuse it than to use it correctly (the RTTI-switch dread comes to mind). As a developer, I found (and used) only two viable uses for it (more exactly, one and a half).
Could you share some of the ways RTTI is a viable solution to a problem, with example code/pseudo-code included?
Note: The aim is to have a repository of viable examples a junior developer can consult, criticize and learn from.
Edit: You'll find below code using C++ RTTI
// A has a virtual destructor (i.e. is polymorphic)
// B has a virtual destructor (i.e. is polymorphic)
// B does (or does not ... pick your poison) inherits from A
void doSomething(A * a)
{
// typeid()::name() returns the "name" of the object (not portable)
std::cout << "a is [" << typeid(*a).name() << "]"<< std::endl ;
// the dynamic_cast of a pointer to another will return NULL is
// the conversion is not possible
if(B * b = dynamic_cast<B *>(a))
{
std::cout << "a is b" << std::endl ;
}
else
{
std::cout << "a is NOT b" << std::endl ;
}
}
回答1:
Acyclic Visitor (pdf) is a great use of it.
回答2:
How about the boost::any object!
This basically uses the RTTI info to store any object and the retrieve that object use boost::any_cast<>.
回答3:
You can use RTTI with dynamic_cast to get a pointer to a derived class in order to use it to call a fast, type specialized algorithm. And instead of using the virtual methods through the base class, it will make direct and inlined calls.
This sped things up for me a lot using GCC. Visual Studio didn't seem to do as well, it may have a slower dynamic_cast lookup.
Example:
D* obj = dynamic_cast<D*>(base);
if (obj) {
for(unsigned i=0; i<1000; ++i)
f(obj->D::key(i));
}
} else {
for(unsigned i=0; i<1000; ++i)
f(base->key(i));
}
}
回答4:
I cant say I've ever found a use for in in real life but RTTI is mentioned in Effective C++ as a possible solution to multi-methods in C++. This is because method dispatch is done on the dynamic type of the this
parameter but the static type of the arguments.
class base
{
void foo(base *b) = 0; // dynamic on the parameter type as well
};
class B : public base {...}
class B1 : public B {...}
class B2 : public B {...}
class A : public base
{
void foo(base *b)
{
if (B1 *b1=dynamic_cast<B1*>(b))
doFoo(b1);
else if (B2 *b2=dynamic_cast<B2*>(b))
doFoo(b2);
}
};
回答5:
I worked on an aircraft simulation once, that had what they (somewhat confusingly) referred to as a "Simulation Database". You could register variables like floats or ints or strings in it, and people could search for them by name, and pull out a reference to them. You could also register a model (an object of a class descended from "SimModel"). The way I used RTTI, was to make it so you could search for models that implement a given interface:
SimModel* SimDatabase::FindModel<type*>(char* name="")
{
foreach(SimModel* mo in ModelList)
if(name == "" || mo->name eq name)
{
if(dynamic_cast<type*>mo != NULL)
{
return dynamic_cast<type*>mo;
}
}
return NULL;
}
The SimModel base class:
class public SimModel
{
public:
void RunModel()=0;
};
An example interface might be "EngineModel":
class EngineModelInterface : public SimModel
{
public:
float RPM()=0;
float FuelFlow()=0;
void SetThrottle(float setting)=0;
};
Now, to make a Lycoming and Continental engine:
class LycomingIO540 : public EngineModelInterface
{
public:
float RPM()
{
return rpm;
}
float FuelFlow()
{
return throttleSetting * 10.0;
}
void SetThrottle(float setting)
{
throttleSetting = setting
}
void RunModel() // from SimModel base class
{
if(throttleSetting > 0.5)
rpm += 1;
else
rpm -= 1;
}
private:
float rpm, throttleSetting;
};
class Continental350: public EngineModelInterface
{
public:
float RPM()
{
return rand();
}
float FuelFlow()
{
return rand;
}
void SetThrottle(float setting)
{
}
void RunModel() // from SimModel base class
{
}
};
Now, here's some code where somebody wants an engine:
.
.
EngineModelInterface * eng = simDB.FindModel<EngineModelInterface *>();
.
.
fuel = fuel - deltaTime * eng->FuelFlow();
.
.
.
Code is pretty pseudo, but I hope it gets the idea across. One developer can write code that depends on having an Engine, but as long as it has something that implements the engine interface, it doesn't care what it is. So the code that updates the amount of fuel in the tanks is completely decoupled from everything except the FindModel<>() function, and the pure virtual EngineModel interface that he's interested in using. Somebody a year later can make a new engine model, register it with the SimulationDatabase, and the guy above who updates fuel will start using it automatically. I actually made it so you could load new models as plugins (DLLs) at runtime, and once they are registered in the SimulationDatabase, they could be found with FindModel<>(), even though the code that was looking for them was compiled and built into a DLL months before the new DLL existed. You could also add new Interfaces that derive from SimModel, with something that implements them in one DLL, something that searches for them in another DLL, and once you load both DLLs, one can do a FindModel<>() to get the model in the other. Even though the Interface itself didn't even exist when the main app was built.
Parenthetically, RTTI doesn't always work across DLL boundaries. Since I was using Qt anyway, I used qobject_cast
instead of dynamic_cast
. Every class had to inherit from QObject (and get moc'd), but the qobject meta-data was always available. If you don't care about DLLs, or you are using a toolchain where RTTI does work across DLL boundaries (type comparisons based on string comparisons instead of hashes or whatever), then all of the above with dynamic_cast
will work just fine.
回答6:
I use it in a class tree which serializes to a XML file. On the de-serialization, the parser class returns a pointer to the base class which has a enumeration for the type of the subclass (because you don't know which type it is until you parse it). If the code using the object needs to reference subclass specific elements, it switches on the enum value and dynamic_cast's to the subclass (which was created by the parser). This way the code can check to ensure that the parser didn't have an error and a mismatch between the enum value and the class instance type returned. Virtual functions are also not sufficient because you might have subclass specific data you need to get to.
This is just one example of where RTTI could be useful; it's perhaps not the most elegant way to solve the problem, but using RTTI makes the application more robust when using this pattern.
回答7:
Sometimes static_cast
and C-style casts just aren't enough and you need dynamic_cast
, an example of this is when you have the dreaded diamond shaped hierarchy (image from Wikipedia).

struct top {
};
struct left : top {
int i;
left() : i(42) {}
};
struct right : top {
std::string name;
right() : name("plonk") { }
};
struct bottom : left, right {
};
bottom b;
left* p = &b;
//right* r = static_cast<right*>(p); // Compilation error!
//right* r = (right*)p; // Gives bad pointer silently
right* r = dynamic_cast<right*>(p); // OK
回答8:
Use cases I have in my projects (if you know any better solution for specific cases, please comment):
The same thing as 1800 INFORMATION has already mentioned:
You'll need a
dynamic_cast
for theoperator==
oroperator<
implementation for derived classes. Or at least I don't know any other way.If you want to implement something like
boost::any
or some other variant container.In one game in a
Client
class which had astd::set<Player*>
(possible instances areNetPlayer
andLocalPlayer
) (which could have at most oneLocalPlayer
), I needed a functionLocalPlayer* Client::localPlayer()
. This function is very rarely used so I wanted to avoid to clutterClient
with an additional local member variable and all the additional code to handle this.I have some
Variable
abstract class with several implementations. All registeredVariable
s are in somestd::set<Variable*> vars
. And there are several builtin vars of the typeBuiltinVar
which are saved in astd::vector<BuiltinVar> builtins
. In some cases, I have aVariable*
and need to check if it is aBuiltinVar*
and insidebuiltins
. I could either do this via some memory-range check or viadynamic_cast
(I can be sure in any case that all instances ofBuiltinVar
are in this vector).I have a gridded collection of
GameObject
s and I need to check if there is aPlayer
object (a specializedGameObject
) inside one grid. I could have a functionbool GameObject::isPlayer()
which always returns false except forPlayer
or I could use RTTI. There are many more examples like this where people often are implementing functions likeObject::isOfTypeXY()
and the base class gets very cluttered because of this.This is also sometimes the case for other very special functions like
Object::checkScore_doThisActionOnlyIfIAmAPlayer()
. There is some common sense needed to decide when it actually make sense to have such a function in the base class and when not.Sometimes I use it for assertions or runtime security checks.
Sometimes I need to store a pointer of some data in some data field of some C library (for example SDL or what not) and I get it somewhere else later on or as a callback. I do a
dynamic_cast
here just to be sure I get what I expect.I have some
TaskManager
class which executes some queue ofTask
s. For someTask
s, when I am adding them to the list, I want to delete otherTask
s of the same type from the queue.
回答9:
I used RTTI when doing some canvas-based work with Qt several years ago. It was darn convenient when doing hit-tests on objects to employ RTTI to determine what I was going to do with the shape I'd 'hit'. But I haven't used it otherwise in production code.
回答10:
I'm using it with Dynamic Double Dispatch and Templates. Basically, it gives the ability to observe/listen to only the interesting parts of an object.
来源:https://stackoverflow.com/questions/238452/c-rtti-viable-examples