Run Time Type Information (RTTI) and typeid()

RTTI in Factory Pattern Implementation

How can I use RTTI to implement a factory pattern that creates objects based on runtime type information?

Illustration representing computer hardware

Using RTTI (Run-Time Type Information) to implement a factory pattern can provide a flexible way to create objects based on runtime type information.

This approach can be particularly useful when you need to create objects dynamically based on type information that's only available at runtime. Here's how you can implement this:

Basic Factory Pattern with RTTI

First, let's define a base class and some derived classes:

#include <iostream>

class Product {
 public:
  virtual ~Product() = default;
  virtual void Use() const = 0;
};

class ConcreteProductA : public Product {
 public:
  void Use() const override {
    std::cout << "Using ConcreteProductA\n";
  }
};

class ConcreteProductB : public Product {
 public:
  void Use() const override {
    std::cout << "Using ConcreteProductB\n";
  }
};

Now, let's create a factory class that uses RTTI:

#include <iostream>
#include <unordered_map>
#include <typeindex>
#include <functional>

class Product {/*...*/};
class ConcreteProductA : public Product {/*...*/};
class ConcreteProductB : public Product {/*...*/}; class Factory { public: template <typename T> void RegisterProduct() { creators_[std::type_index(typeid(T))] = []() { return std::make_unique<T>(); }; } std::unique_ptr<Product> CreateProduct( const std::type_info& type) { auto it = creators_ .find(std::type_index(type)); if (it != creators_.end()) { return it->second(); } return nullptr; } private: std::unordered_map< std::type_index, std::function< std::unique_ptr<Product>()>> creators_; };

Here's how we can use this factory:

#include <iostream>
#include <unordered_map>
#include <typeindex>
#include <functional>

class Product {/*...*/};
class ConcreteProductA : public Product {/*...*/};
class ConcreteProductB : public Product {/*...*/};
class Factory {/*...*/}; int main() { Factory factory; factory.RegisterProduct<ConcreteProductA>(); factory.RegisterProduct<ConcreteProductB>(); auto productA = factory.CreateProduct( typeid(ConcreteProductA)); if (productA) { productA->Use(); } auto productB = factory.CreateProduct( typeid(ConcreteProductB)); if (productB) { productB->Use(); } }
Using ConcreteProductA
Using ConcreteProductB

Advanced Factory Pattern with RTTI

We can extend this pattern to create objects based on string identifiers, which can be useful when working with configuration files or network messages:

#include <iostream>
#include <unordered_map>
#include <typeindex>
#include <functional>
#include <memory>
#include <string>
#include <stdexcept>

class Product {/*...*/};
class ConcreteProductA : public Product {/*...*/};
class ConcreteProductB : public Product {/*...*/}; // Advanced Factory class using RTTI class AdvancedFactory { public: template<typename T> void RegisterProduct(const std::string& name) { creators_[name] = []() { return std::make_unique<T>(); }; type_info_[name] = &typeid(T); } std::unique_ptr<Product> CreateProduct( const std::string& name) { auto it = creators_.find(name); if (it != creators_.end()) { return it->second(); } throw std::runtime_error( "Unknown product: " + name ); } // Method to demonstrate RTTI usage void PrintProductType(const std::string& name) { auto it = type_info_.find(name); if (it != type_info_.end()) { std::cout << "Product '" << name << "' is of type: " << it->second->name() << std::endl; } else { std::cout << "Unknown product: " << name << std::endl; } } private: std::unordered_map<std::string, std::function< std::unique_ptr<Product>()>> creators_; std::unordered_map< std::string, const std::type_info*> type_info_; }; int main() { AdvancedFactory factory; // Register products factory.RegisterProduct<ConcreteProductA>( "ProductA"); factory.RegisterProduct<ConcreteProductB>( "ProductB"); try { // Create and use ProductA auto productA = factory.CreateProduct( "ProductA"); productA->Use(); factory.PrintProductType("ProductA"); // Create and use ProductB auto productB = factory.CreateProduct( "ProductB"); productB->Use(); factory.PrintProductType("ProductB"); // Try to print type of an unregistered product factory.PrintProductType("UnknownProduct"); // Try to create an unregistered product auto unknownProduct = factory.CreateProduct( "UnknownProduct"); } catch (const std::exception& e) { std::cout << "Error: " << e.what() << std::endl; } }
Using ConcreteProductA
Product 'ProductA' is of type: class ConcreteProductA
Using ConcreteProductB
Product 'ProductB' is of type: class ConcreteProductB
Unknown product: UnknownProduct
Error: Unknown product: UnknownProduct

Considerations and Best Practices

Error Handling: Consider throwing exceptions or using std::optional for better error handling:

std::unique_ptr<Product> CreateProduct(
  const std::string& name) {
  auto product = /* ... creation logic ... */;
  if (!product) {
    throw std::runtime_error(
      "Unknown product: " + name
    );
  }
  return product;
}

Thread Safety: If your factory will be used in a multi-threaded environment, consider adding thread safety:

#include <mutex>

class ThreadSafeFactory {
 public:
  template <typename T>
  void RegisterProduct(const std::string& name) {
    std::lock_guard<std::mutex> lock(mutex_);
    // Registration logic...
  }

  std::unique_ptr<Product> CreateProduct(
    const std::string& name) {
    std::lock_guard<std::mutex> lock(mutex_);
    // Creation logic...
  }

 private:
  std::mutex mutex_;
  // Other members...
};

Performance: RTTI operations can be relatively expensive. If performance is critical, consider alternatives like enum-based factories or compile-time factories using templates.

Extensibility: This pattern allows for easy addition of new product types without modifying existing code:

class ConcreteProductC : public Product {
 public:
  void Use() const override {
    std::cout << "Using ConcreteProductC\n";
  }
};

// In client code:
factory.RegisterProduct<ConcreteProductC>(
  "ProductC"
);

Reflection: For more advanced scenarios, you might want to combine this with a simple reflection system:

class Reflectable {
 public:
  virtual std::string GetClassName() const = 0;
};

class ReflectableProduct
  : public Product, public Reflectable {
  // ...
};

// Then in your factory:
std::unique_ptr<Product> CreateProduct(
  const std::string& name) {
  auto product = /* ... creation logic ... */;
  if (auto reflectable =
    dynamic_cast<Reflectable*>(product.get())) {
    std::cout << "Created product of class: "
      << reflectable->GetClassName() << "\n";
  }
  return product;
}

By using RTTI in this way, you can create a flexible and extensible factory pattern that can create objects based on runtime type information.

This approach is particularly useful in scenarios where the types of objects to be created are not known at compile-time or when you need to create objects based on external input or configuration.

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