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:
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
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
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.
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