Run Time Type Information (RTTI) and typeid()

RTTI in Logging and Debugging

How can I use RTTI to implement a robust logging system that provides detailed type information for debugging?

Illustration representing computer hardware

Implementing a robust logging system using RTTI can greatly enhance debugging by providing detailed type information. Here's an approach to create such a system:

Define a base Loggable class:

#include <typeinfo>
#include <string>

class Loggable {
 public:
  virtual ~Loggable() = default;
  virtual std::string getLogInfo() const {
    return std::string("Type: ") +
      typeid(*this).name();
  }
};

Implement a Logger class:

#include <typeinfo>
#include <string>
#include <chrono>
#include <iostream>

class Loggable {/*...*/} class Logger { public: enum class LogLevel { DEBUG, INFO, WARNING, ERROR }; static void log( LogLevel level, const Loggable& obj, const std::string& message ) { std::ostringstream oss; oss << getCurrentTimestamp() << " [" << getLevelString(level) << "] " << obj.getLogInfo() << " - " << message; std::cout << oss.str() << std::endl; } static std::string getCurrentTimestamp() { using namespace std::chrono; auto now = system_clock::now(); auto time = system_clock::to_time_t(now); std::stringstream ss; ss << std::put_time( std::localtime(&time), "%Y-%m-%d %H:%M:%S" ); return ss.str(); } static std::string getLevelString( LogLevel level) { switch (level) { case LogLevel::DEBUG: return "DEBUG"; case LogLevel::INFO: return "INFO"; case LogLevel::WARNING: return "WARNING"; case LogLevel::ERROR: return "ERROR"; default: return "UNKNOWN"; } } };

Create loggable classes:

#include <typeinfo>
#include <string>
#include <chrono>
#include <iostream>

class Loggable {/*...*/}
class Logger {/*...*/} class Player : public Loggable { public: Player(const std::string& name, int health) : name_(name), health_(health) {} std::string getLogInfo() const override { std::ostringstream oss; oss << Loggable::getLogInfo() << ", Name: " << name_ << ", Health: " << health_; return oss.str(); } void takeDamage(int Damage) { // ... } private: std::string name_; int health_; }; class Item : public Loggable { public: Item(const std::string& name, int value) : name_(name), value_(value) {} std::string getLogInfo() const override { std::ostringstream oss; oss << Loggable::getLogInfo() << ", Name: " << name_ << ", Value: " << value_; return oss.str(); } private: std::string name_; int value_; };

Implement a template function for logging types that do not inherit from Loggable:

#include <typeinfo>
#include <string>
#include <chrono>
#include <iostream>

class Loggable {/*...*/}
class Logger {/*...*/}
class Player : public Loggable {/*...*/}
class Item : public Loggable {/*...*/} template <typename T> void logValue( Logger::LogLevel level, const T& value, const std::string& message ) { std::ostringstream oss; oss << "Type: " << typeid(value).name() << ", Value: " << value; std::cout << Logger::getCurrentTimestamp() << " [" << Logger::getLevelString(level) << "] " << oss.str() << " - " << message << std::endl; }

Use the logging system:

#include <typeinfo>
#include <string>
#include <chrono>
#include <iostream>

class Loggable {/*...*/}
class Logger {/*...*/}
class Player : public Loggable {/*...*/}
class Item : public Loggable {/*...*/}
void logValue(/*...*/) {/*...*/} int main() { Player player("Alice", 100); Logger::log(Logger::LogLevel::INFO, player, "Player created"); Item sword("Excalibur", 1000); Logger::log(Logger::LogLevel::DEBUG, sword, "Item acquired"); player.takeDamage(20); Logger::log(Logger::LogLevel::WARNING, player, "Player took damage"); int score = 5000; logValue(Logger::LogLevel::INFO, score, "Current score"); std::string gameState = "Level 2"; logValue(Logger::LogLevel::DEBUG, gameState, "Game state changed"); try { throw std::runtime_error("Game crash"); } catch (const std::exception& e) { Logger::log(Logger::LogLevel::ERROR, player, std::string("Exception: ") + e.what()); } }
2024-06-24 03:54:24 [INFO] Type: class Player, Name: Alice, Health: 100 - Player created
2024-06-24 03:54:24 [DEBUG] Type: class Item, Name: Excalibur, Value: 1000 - Item acquired
2024-06-24 03:54:24 [WARNING] Type: class Player, Name: Alice, Health: 100 - Player took damage
2024-06-24 03:54:24 [INFO] Type: int, Value: 5000 - Current score
2024-06-24 03:54:24 [DEBUG] Type: class std::string, Value: Level 2 - Game state changed
2024-06-24 03:54:24 [ERROR] Type: class Player, Name: Alice, Health: 100 - Exception: Game crash

This logging system uses RTTI in several ways:

  1. The base Loggable class uses typeid to provide the type name of derived classes.
  2. The logValue template function uses typeid to get type information for non-Loggable types.
  3. The Logger class provides a consistent interface for logging both Loggable and non-Loggable types.

Benefits of this approach:

  • Detailed type information: The system provides the actual type of objects being logged, which is especially useful for polymorphic types.
  • Extensibility: New Loggable classes can easily be added by inheriting from the Loggable base class and overriding the getLogInfo method.
  • Flexibility: The system can log both Loggable objects and primitive types.
  • Consistency: All log entries include timestamps, log levels, and type information.

Considerations:

  • Performance: RTTI operations have a runtime cost. In performance-critical sections, you might want to disable detailed logging or use compile-time alternatives.
  • Readability: The typeid().name() output is implementation-defined and might not be human-readable. You might need to use a demangling function for better readability.
  • Memory usage: Storing detailed type information for all objects might increase memory usage. Consider implementing a way to control logging verbosity.

To further enhance this system, you could:

  • Add file output capabilities to the Logger class.
  • Implement log filtering based on log levels or object types.
  • Use a thread-safe logging mechanism for multi-threaded applications.
  • Integrate with a more comprehensive logging framework like spdlog or Boost.Log.

By using RTTI in your logging system, you can create a powerful debugging tool that provides detailed type information at runtime, greatly aiding in the development and maintenance of complex C++ applications.

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:

  • 125 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