Yes, there are several alternatives to RTTI for identifying types at runtime in performance-critical systems. These alternatives often provide better performance at the cost of some flexibility or additional implementation effort. Here are some common approaches:
Use a virtual function to return a type identifier. This is faster than RTTI and doesn't require additional memory for type information.
enum class MonsterType { Base, Dragon, Goblin };
class Monster {
public:
virtual ~Monster() = default;
virtual MonsterType getType() const {
return MonsterType::Base;
}
};
class Dragon : public Monster {
public:
MonsterType getType() const override {
return MonsterType::Dragon;
}
};
class Goblin : public Monster {
public:
MonsterType getType() const override {
return MonsterType::Goblin;
}
};
void handleMonster(const Monster& monster) {
switch (monster.getType()) {
case MonsterType::Dragon:
std::cout << "Handling a Dragon\n";
break;
case MonsterType::Goblin:
std::cout << "Handling a Goblin\n";
break;
default:
std::cout << "Handling a generic Monster\n";
}
}
Use tag classes to dispatch to the correct function at compile-time. This approach is fast and doesn't require virtual functions.
struct MonsterTag {};
struct DragonTag : MonsterTag {};
struct GoblinTag : MonsterTag {};
class Monster {
public:
virtual ~Monster() = default;
virtual MonsterTag getTag() const {
return MonsterTag{};
}
};
class Dragon : public Monster {
public:
DragonTag getTag() const override {
return DragonTag{};
}
};
class Goblin : public Monster {
public:
GoblinTag getTag() const override {
return GoblinTag{};
}
};
void handleMonster(
const Monster& monster, MonsterTag
) {
std::cout << "Handling a generic Monster\n";
}
void handleMonster(
const Monster& monster, DragonTag
) {
std::cout << "Handling a Dragon\n";
}
void handleMonster(
const Monster& monster, GoblinTag
) {
std::cout << "Handling a Goblin\n";
}
void processMonster(const Monster& monster) {
handleMonster(monster, monster.getTag());
}
Use CRTP to implement static polymorphism, avoiding the need for virtual functions and RTTI.
template<typename Derived>
class Monster {
public:
void process() {
static_cast<Derived*>(this)->processImpl();
}
};
class Dragon : public Monster<Dragon> {
public:
void processImpl() {
std::cout << "Processing a Dragon\n";
}
};
class Goblin : public Monster<Goblin> {
public:
void processImpl() {
std::cout << "Processing a Goblin\n";
}
};
template<typename T>
void handleMonster(Monster<T>& monster) {
monster.process();
}
Use type erasure to store type-specific behavior without virtual functions or RTTI.
#include <functional>
class Monster {
public:
template<typename T>
Monster(T&& obj) :
object_(new T(std::forward<T>(obj))),
process_([](void* obj) {
static_cast<T*>(obj)->process();
})
{}
void process() {
process_(object_);
}
private:
void* object_;
std::function<void(void*)> process_;
};
struct Dragon {
void process() {
std::cout << "Processing Dragon\n";
}
};
struct Goblin {
void process() {
std::cout << "Processing Goblin\n";
}
};
void handleMonster(Monster& monster) {
monster.process();
}
Each of these alternatives has its own trade-offs. The virtual function approach is simple but still incurs the cost of virtual function calls. Tag dispatch and CRTP provide compile-time polymorphism but can lead to code bloat. Type erasure provides runtime flexibility without RTTI but involves more complex implementation.
Choose the approach that best fits your specific performance requirements and design constraints. In many cases, these alternatives can provide significant performance improvements over RTTI in critical code paths.
Answers to questions are automatically generated and may not have been reviewed.
typeid()
Learn to identify and react to object types at runtime using RTTI, dynamic casting and the typeid()
operator