Run Time Type Information (RTTI) and typeid()

RTTI in Dynamic Scripting Systems

How can I use RTTI to implement a dynamic scripting system that interacts with C++ objects?

Illustration representing computer hardware

Implementing a dynamic scripting system that interacts with C++ objects using RTTI involves creating a bridge between the scripting language and C++ types. Here's an approach to create such a system:

Define a base class for scriptable objects:

#include <any>
#include <string>
#include <typeinfo>
#include <vector>

class Scriptable {
 public:
  virtual ~Scriptable() = default;
  virtual const std::type_info& getType() const {
    return typeid(*this);
  }
  virtual std::any invokeMethod(
    const std::string& method,
    const std::vector<std::any>& args
  ) = 0;
};

Implement a script engine class:

#include <any>
#include <string>
#include <typeinfo>
#include <unordered_map>
#include <vector>
#include <exception>
#include <stdexcept>

class Scriptable {/*...*/}; class ScriptEngine { public: void registerObject( const std::string& name, Scriptable* obj ) { objects_[name] = obj; } std::any callMethod( const std::string& objName, const std::string& method, const std::vector<std::any>& args ) { auto it = objects_.find(objName); if (it == objects_.end()) { throw std::runtime_error( "Object not found: " + objName ); } return it->second->invokeMethod(method, args); } private: std::unordered_map< std::string, Scriptable*> objects_; };

Create scriptable C++ classes:

#include <any>
#include <iostream>
#include <string>
#include <typeinfo>
#include <unordered_map>
#include <vector>
#include <exception>
#include <stdexcept>

class Scriptable {/*...*/};
class ScriptEngine {/*...*/}; class ScriptableMonster : public Scriptable { public: ScriptableMonster(const std::string& name) : name_(name), health_(100) {} std::any invokeMethod( const std::string& method, const std::vector<std::any>& args ) override { if (method == "getName") { std::cout << "Name: "; return name_; } else if (method == "getHealth") { std::cout << "Health: "; return health_; } else if (method == "takeDamage") { std::cout << "Taking Damage: "; if (args.size() == 1) { int damage = std::any_cast<int>(args[0]); health_ -= damage; return damage; } } throw std::runtime_error( "Method not found: " + method ); } private: std::string name_; int health_; };

Implement a simple script interpreter:

#include <any>
#include <iostream>
#include <sstream>
#include <string>
#include <typeinfo>
#include <unordered_map>
#include <vector>
#include <exception>
#include <stdexcept>

class Scriptable {/*...*/};
class ScriptEngine {/*...*/};
class ScriptableMonster : public Scriptable {/*...*/}; class ScriptInterpreter { public: ScriptInterpreter(ScriptEngine& engine) : engine_(engine) {} void execute(const std::string& script) { std::istringstream iss(script); std::string line; while (std::getline(iss, line)) { executeLine(line); } } private: void executeLine(std::string line) { line = trim(line); if (line.empty()) return; std::istringstream iss(line); std::string objName, method; iss >> objName >> method; std::vector<std::any> args; std::string arg; while (iss >> arg) { if (arg.find_first_not_of("0123456789") == std::string::npos) { args.push_back(std::stoi(arg)); } else { args.push_back(arg); } } try { std::any result = engine_.callMethod( objName, method, args ); if (result.type() == typeid(int)) { std::cout << std::any_cast<int>(result) << std::endl; } else if (result.type() == typeid(std::string)) { std::cout << std::any_cast<std::string>(result) << std::endl; } } catch (const std::exception& e) { std::cerr << "Error: " << e.what() << std::endl; } } // Remove any excess spaces from the start // or the end of the script std::string trim(const std::string& str) { const char* whitespace = " \t\n\r\f\v"; size_t start = str.find_first_not_of(whitespace); size_t end = str.find_last_not_of(whitespace); return ( start == std::string::npos || end == std::string::npos) ? "" : str.substr(start, end - start + 1); } ScriptEngine& engine_; };

Use the scripting system:

#include <any>
#include <string>
#include <typeinfo>
#include <vector>
#include <unordered_map>
#include <stdexcept>
#include <iostream>
#include <sstream>

class Scriptable {/*...*/};
class ScriptEngine {/*...*/};
class ScriptableMonster : public Scriptable {/*...*/};
class ScriptInterpreter {/*...*/}; int main() { ScriptEngine engine; ScriptableMonster dragon("Smaug"); engine.registerObject("dragon", &dragon); ScriptInterpreter interpreter(engine); std::string script = R"( dragon getName dragon getHealth dragon takeDamage 20 dragon getHealth )"; interpreter.execute(script); }
Name: Smaug
Health: 100
Taking Damage: 20
Health: 80

This system uses RTTI in several ways:

  1. The Scriptable base class uses typeid to provide type information for derived classes.
  2. The std::any class uses type erasure and RTTI internally to store and retrieve values of any type.
  3. The script interpreter uses RTTI to determine the type of the result and print it accordingly.

This approach provides a flexible way to expose C++ objects to a simple scripting system. It can be extended to support more complex scripting languages, additional types, and more sophisticated method invocation mechanisms.

Remember that while this system is flexible, it relies heavily on runtime type checking and method dispatch, which can impact performance. For performance-critical applications, you might consider alternative approaches like code generation or compile-time reflection techniques.

This Question is from the Lesson:

Run Time Type Information (RTTI) and typeid()

Learn to identify and react to object types at runtime using RTTI, dynamic casting and the typeid() operator

Answers to questions are automatically generated and may not have been reviewed.

This Question is from the Lesson:

Run Time Type Information (RTTI) and typeid()

Learn to identify and react to object types at runtime using RTTI, dynamic casting and the typeid() operator

A computer programmer
Part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Free, unlimited access

This course includes:

  • 124 Lessons
  • 550+ Code Samples
  • 96% Positive Reviews
  • Regularly Updated
  • Help and FAQ
Free, Unlimited Access

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Screenshot from Warhammer: Total War
Screenshot from Tomb Raider
Screenshot from Jedi: Fallen Order
Contact|Privacy Policy|Terms of Use
Copyright © 2024 - All Rights Reserved