fire v0.19.0
Framework for sImulation and Reconstruction of Events
|
Factory to dynamically create objects derived from a specific prototype class. More...
#include <Factory.h>
Public Types | |
using | PrototypeMaker = PrototypePtr(*)(PrototypeConstructorArgs...) |
the signature of a function that can be used by this factory to dynamically create a new object. More... | |
Public Member Functions | |
template<typename DerivedType > | |
uint64_t | declare () |
register a new object to be constructible More... | |
PrototypePtr | make (const std::string &full_name, PrototypeConstructorArgs... maker_args) |
make a new object by name More... | |
Factory (Factory const &)=delete | |
delete the copy constructor | |
void | operator= (Factory const &)=delete |
delete the assignment operator | |
Static Public Member Functions | |
static Factory & | get () |
get the factory instance More... | |
Private Member Functions | |
Factory ()=default | |
private constructor to prevent creation | |
Static Private Member Functions | |
template<typename DerivedType > | |
static PrototypePtr | maker (PrototypeConstructorArgs... args) |
make a new DerivedType returning a PrototypePtr More... | |
Private Attributes | |
std::unordered_map< std::string, PrototypeMaker > | library_ |
library of possible objects to create | |
Factory to dynamically create objects derived from a specific prototype class.
This factory is a singleton class meaning it cannot be created by the user.
Prototype | the type of object that this factory creates. This should be the base class that all types in this factory derive from. |
PrototypePtr | the type of pointer to the object By defeault, we use std::unique_ptr for good memory management but std::shared_ptr is another alternative provided by the standard libraries. The only requirement for this type is that it acts as a pointer to the Prototype class and can be constructed with a pointer to the derived class. |
PrototypeConstructorArgs | parameter pack of arguments to pass to the object constructor. |
The factory itself works in two steps.
Using an unnamed namespace defines the variables inside it as having internal linkage and as implicitly static. Having internal linkage allows us to have repeat variable names across different source files. Being static means that the variable is guaranteed to be constructed during library load time.
This if we put the following code in the source file for a class deriving from our prototype, it will be declared to the factory during library load.
The details of how this is handled is documented in Storage Class Specifiers.
Using the factory effecitvely can be done in situations where many classes all follow the same design structure, but have different implementations for specific steps. In order to reflect this "same design structure", we define an abstract base class for all of our derived classes from which to inherit. This abstract base class is our "prototype".
Below is a rudimentary example that shows you the basics of this class.
This LibraryEntry
prototype class satisfies our requirements. It also defines a helpful "declaration" macro for derived classes to use.
Here are a few example derived classes.
Since the DECLARE_LIBRARYENTRY
macro defines a function that is decorated with a compiler attribute causing the function to be called at library-load time, the registration of our various library entries is automatically done before the execution of main
(or after if the loadLibrary function is used). For simplicity, let's compile these sources files together with a main defined below.
Compiling these files together into the fave-things
executable would then lead to the following behavior.
using fire::factory::Factory< Prototype, PrototypePtr, PrototypeConstructorArgs >::PrototypeMaker = PrototypePtr (*)(PrototypeConstructorArgs...) |
the signature of a function that can be used by this factory to dynamically create a new object.
This is merely here to make the definition of the Factory simpler.
|
inline |
register a new object to be constructible
We insert the new object into the library after checking that it hasn't been defined before.
DerivedType | object type to declare |
|
inlinestatic |
get the factory instance
Using a static function variable gaurantees that the factory is created as soon as it is needed and that it is deleted before the program completes.
|
inline |
make a new object by name
We look through the library to find the requested object. If found, we create one and return a pointer to the newly created object. If not found, we raise an exception.
Exception | if the input object name could not be found |
The arguments to the maker are determined at compiletime using the template parameters of Factory.
[in] | full_name | name of class to create, same name as passed to declare |
[in] | maker_args | parameter pack of arguments to pass on to maker |
|
inlinestaticprivate |
make a new DerivedType returning a PrototypePtr
Basically a copy of what std::make_unique
or std::make_shared
do but with the following changes:
This is where we required that PrototypePtr has the same behavior as STL smart pointers. The PrototypePtr class must be able to be constructed from a pointer to a derived class and must take ownership of the new object.
DerivedType | type of derived object we should create |
[in] | args | constructor arguments for derived type construction |