-
Notifications
You must be signed in to change notification settings - Fork 0
Moduler systems using macros
We are now trying to design a modular system for well... modules.
-
Module: This is the interface that defines the contract for all modules. It contains pure virtual functions that must be implemented by any module. It ensures that all modules adhere to a common interface, making it easier to manage them in a centralized way.
-
AModule: This is an abstract class that implements
IModule
and provides default implementations for some or all of the methods defined inIModule
. This class can implement common behavior that is shared across all modules, reducing code duplication. -
WindowModule, InputModule, etc.: These are concrete classes that inherit from
AModule
and implement the specific behavior for each module. They can override methods fromAModule
to provide module-specific behavior. -
REGISTER_MODULE Macro: This macro helps in registering a module along with its dependencies in the
ModuleRegistry
. It makes the registration process easier and less error-prone by automating the creation of registrar objects and instances of the modules. -
ModuleRegistry: This is a singleton class that manages all the modules in the system. It keeps track of all registered modules and their dependencies, and provides methods to initialize, update, and clean up modules in the correct order based on their dependencies.
classDiagram
class IModule {
+virtual InitializeModule() : void = 0
+virtual UpdateModule() : void = 0
+virtual CleanUpModule() : void = 0
}
class AModule {
+InitializeModule() : void
+UpdateModule() : void
+CleanUpModule() : void
}
class WindowModule {
+InitializeModule() : void
+UpdateModule() : void
+CleanUpModule() : void
}
class InputModule {
+InitializeModule() : void
+UpdateModule() : void
+CleanUpModule() : void
}
class ModuleRegistry {
-modules : map<string, IModule*>
+Instance() : ModuleRegistry&
+GetModule<T>() : T*
+RegisterModule<T>(T*) : void
+InitializeModules() : void
+UpdateModules() : void
+CleanUpModules() : void
}
IModule <|-- AModule
AModule <|-- WindowModule
AModule <|-- InputModule
ModuleRegistry "1" o-- "*" IModule : manages
Module Registry
All right using Macros for this makes no sense in this case cause it does requrie to make it so that we can easily create a namespced instance and try and create a module.
first, let's break something important down. namespace area that will create a struct # operator this is the Stringifier operator. using the methadoly of
#define REGISTER_MODULE(ModuleClass, ModuleName, ...) \
namespace { \
// Creating a on stack struct registere
struct ModuleClass##Registrar { \
// creating a constructor for it \
ModuleClass##Registrar() { \
// initializing the input for the class we are trying to create \
static std::shared_ptr<ModuleClass> ModuleName = std::make_shared<ModuleClass>(); \
// registering it
ModuleRegistry::Instance().RegisterModule(ModuleName, {__VA_ARGS__}); \
} \
}; \
// then creating an instance of it to actually call the constructor.
static ModuleClass##Registrar ModuleClass##registrar; \
}
The problems with this approach.
-
Static Initialization Order: Be cautious when relying on static initialization order, especially when you have multiple translation units (source files) using this technique. The order of initialization of these static variables is not guaranteed and can lead to subtle bugs. It may work in simple cases but become problematic as your project grows.
-
Multiple Declarations: Each translation unit (source file) that includes this macro will generate a separate instance of the
ModuleClass##Registrar
. If you include this macro in multiple source files, you'll end up with multiple instances of the registrar, which could be unexpected behaviour. // can be fixed with inline static? SOl . By adding theinline
keyword, you ensure that theModuleClass##registrar
variable is defined only once across all translation units. This will prevent the multiple-declaration issue and ensure that the registration happens once.
Keep in mind that the use of inline
for variables has a different meaning compared to functions. In this context, it ensures that the variable is defined only once, and the linker will merge all instances into a single one.
-
Namespace Pollution: This approach creates anonymous namespaces for each module, which may lead to unnecessary namespace pollution in your codebase.
-
Clarity: The approach may not be immediately obvious to someone reading your code, so it's essential to document it clearly for anyone working on the project.
-
Error Handling: This approach doesn't provide a straightforward way to handle errors during module registration. If module creation or registration fails, there's no mechanism to report or handle the error gracefully.