基于《Modern C++ Design》的模板类层级Object Factory注册问询
Got it, let's break down how to handle template class hierarchy registration for your Object Factory built from Modern C++ Design—I’ve spent plenty of time tinkering with these patterns, so let’s dive in.
First, let’s align on the core setup you’re working with: a singleton factory that maps identifiers to creator functions, returning pointers to your Base class. The tricky part with template classes is that every instantiation (like Derived<int> vs Derived<std::string>) is a distinct type, each needing its own factory registration entry.
1. Core Problem: Registering Template Specializations
For non-template derived classes, you’d typically use a static registration object (like the book’s RegisterInFactory helper) that hooks into the singleton factory on initialization. But template classes need a way to trigger this registration automatically for every unique specialization.
Solution: Template Registration Helpers
Adapt the book’s static registration pattern to work with templates by creating a templated helper struct. Here’s a simplified implementation:
// object_factory.h (simplified core) class ObjectFactory { public: using Creator = Base* (*)(); void Register(const std::string& type_id, Creator creator) { creator_map_[type_id] = creator; } Base* Create(const std::string& type_id) { auto it = creator_map_.find(type_id); return it != creator_map_.end() ? it->second() : nullptr; } private: std::unordered_map<std::string, Creator> creator_map_; }; // Singleton wrapper (Meyers pattern to avoid init order issues) template <typename T> class Singleton { public: static T& Instance() { static T instance; return instance; } private: Singleton() = default; ~Singleton() = default; Singleton(const Singleton&) = delete; Singleton& operator=(const Singleton&) = delete; }; using SingletonBaseFactory = Singleton<ObjectFactory>; // Templated registration helper template <typename Derived> struct TemplateRegistrar { TemplateRegistrar(const std::string& type_id) { SingletonBaseFactory::Instance().Register(type_id, []() -> Base* { return new Derived(); // Replace with smart pointers in production! }); } };
Now, hook this helper into your template-derived class by declaring a static instance of it:
// Example template-derived class template <typename T> class Derived : public Base { // Static registrar to auto-register the specialization static const TemplateRegistrar<Derived<T>> registrar; public: // ... your class implementation ... }; // Define the registrar with a unique ID per specialization template <typename T> const TemplateRegistrar<Derived<T>> Derived<T>::registrar( "Derived<" + GetTypeName<T>() + ">" ); // Helper for readable, portable type names (avoids compiler-specific typeid output) template <typename T> std::string GetTypeName() { return typeid(T).name(); } // Specialize for common types to make IDs human-readable template <> std::string GetTypeName<int>() { return "int"; } template <> std::string GetTypeName<std::string>() { return "std::string"; }
2. Handling Deep Template Hierarchies
If you have nested template classes (e.g., template <typename T> class MidDerived : public Base, and template <typename T> class FinalDerived : public MidDerived<T>), the pattern stays identical. Each specialization of FinalDerived gets its own static registrar, which registers a creator function for that exact type.
The key rule here: every distinct instantiated type—even if it’s a template specialization—needs a unique entry in the factory’s creator map.
3. Avoiding Static Initialization Order Issues
A common pitfall with this pattern is the static initialization order fiasco, where your registrar tries to register with the factory before the factory singleton is initialized. Using the Meyers singleton pattern (local static instance in Instance()) fixes this, since the factory is initialized on first access—guaranteeing it exists when the registrar runs.
4. Alternative: Type-Safe Factory for Templates
If you don’t need to create objects from runtime strings (e.g., config files), you can make the factory type-safe by using type_info hashes instead of string IDs:
// Enhanced ObjectFactory with type-safe registration class ObjectFactory { public: template <typename Derived> void Register() { const auto type_hash = typeid(Derived).hash_code(); creator_map_[type_hash] = []() -> Base* { return new Derived(); }; } template <typename Derived> Derived* Create() { const auto type_hash = typeid(Derived).hash_code(); auto it = creator_map_.find(type_hash); return it != creator_map_.end() ? static_cast<Derived*>(it->second()) : nullptr; } private: std::unordered_map<size_t, Creator> creator_map_; }; // Updated registrar for type-safe setup template <typename T> const struct TypeSafeRegistrar { TypeSafeRegistrar() { SingletonBaseFactory::Instance().Register<Derived<T>>(); } } Derived<T>::registrar; // Usage (no string IDs needed!) Derived<int>* my_obj = SingletonBaseFactory::Instance().Create<Derived<int>>();
This approach eliminates manual ID management and reduces human error, though it gives up the ability to create objects from runtime string identifiers.
内容的提问来源于stack exchange,提问作者Davide

