fire v0.19.0
Framework for sImulation and Reconstruction of Events
fire::factory::Factory< Prototype, PrototypePtr, PrototypeConstructorArgs > Class Template Reference

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 Factoryget ()
 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, PrototypeMakerlibrary_
 library of possible objects to create
 

Detailed Description

template<typename Prototype, typename PrototypePtr = std::unique_ptr<Prototype>, typename... PrototypeConstructorArgs>
class fire::factory::Factory< Prototype, PrototypePtr, PrototypeConstructorArgs >

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.

Template Parameters
Prototypethe type of object that this factory creates. This should be the base class that all types in this factory derive from.
PrototypePtrthe 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.
PrototypeConstructorArgsparameter pack of arguments to pass to the object constructor.

Terminology

  • Factory: An object that has a look-up table between class names and pointers to functions that can create them
  • Maker: A function that can create a specific class
  • Prototype: An abstract base class from which derived classes can be used

Design

The factory itself works in two steps.

  1. All of the different derived classes "declare" themselves so that the factory knowns how to create them. This registration is done by providing their type and the name they should be referred to by.
  2. The factory creates any of the registered classes and returns a pointer to it in the form of a prototype-class pointer.

Declaration

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.

// MyDerived.cpp
// MyDerived inherits from MyPrototype
namespace {
.declare<MyDerived>();
}
static Factory & get()
get the factory instance
Definition: Factory.h:251
uint64_t declare()
register a new object to be constructible
Definition: Factory.h:275

The details of how this is handled is documented in Storage Class Specifiers.

Usage

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.

A Prototype LibraryEntry

This LibraryEntry prototype class satisfies our requirements. It also defines a helpful "declaration" macro for derived classes to use.

// LibraryEntry.hpp
#ifndef LIBRARYENTRY_HPP
#define LIBRARYENTRY_HPP
// we need the factory template
#include "Factory/Factory.hpp"
// this class is our prototype
class LibraryEntry {
public:
// virtual destructor so we can dynamically create derived classes
virtual ~LibraryEntry() = default;
// pure virtual function that our derived classes will implement
virtual std::string name() = 0;
// the factory type that we will use here
}; // LibraryEntry
// a macro to help with registering our library entries with our factory
#define DECLARE_LIBRARYENTRY(CLASS) \
namespace { \
auto v = ::LibraryEntry::Factory::get().declare<CLASS>() \
}
#endif // LIBRARYENTRY_HPP
Factory to dynamically create objects derived from a specific prototype class.
Definition: Factory.h:231
Factory()=default
private constructor to prevent creation

Example Derived Classes

Here are a few example derived classes.

// Book.cpp
#include "LibraryEntry.hpp"
namespace library {
class Book : public LibraryEntry {
public :
virtual std::string name() final override {
return "Where the Red Fern Grows";
}
};
}
DECLARE_LIBRARYENTRY(library::Book)
// Podcast.cpp
#include "LibraryEntry.hpp"
namespace library {
namespace audio {
class Podcast : public LibraryEntry {
public :
virtual std::string name() final override {
return "538 Politics Podcast";
}
};
}
}
DECLARE_LIBRARYENTRY(library::audio::Podcast)
// Album.cpp
#include "LibraryEntry.hpp"
namespace library {
namespace audio {
class Album : public LibraryEntry {
public :
virtual std::string name() final override {
return "Kind of Blue";
}
};
}
}
DECLARE_LIBRARYENTRY(library::audio::Album)

Executable

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.

// main.cxx
#include "LibraryEntry.hpp"
int main(int argc, char* argv[]) {
std::string full_cpp_name{argv[1]};
try {
auto entry_ptr{LibraryEntry::Factory::get().make(full_cpp_name)};
std::cout << entry_ptr->name() << std::endl;
} catch (const std::exception& e) {
std::cerr << "ERROR: " << e.what() << std::endl;
}
}
T endl(T... args)
int main(int argc, char *argv[])
definition of fire executable
Definition: fire.cxx:40
T what(T... args)

Compiling these files together into the fave-things executable would then lead to the following behavior.

$ fave-things library::Book
Where the Red Fern Grows
$ fave-things library::audio::Podcast
538 Politics Podcast
$ fave-things library::audio::Album
Kind of Blue
$ fave-things library::DoesNotExist
ERROR: An object named library::DoesNotExist has not been declared.

Member Typedef Documentation

◆ PrototypeMaker

template<typename Prototype , typename PrototypePtr = std::unique_ptr<Prototype>, typename... PrototypeConstructorArgs>
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.

Member Function Documentation

◆ declare()

template<typename Prototype , typename PrototypePtr = std::unique_ptr<Prototype>, typename... PrototypeConstructorArgs>
template<typename DerivedType >
uint64_t fire::factory::Factory< Prototype, PrototypePtr, PrototypeConstructorArgs >::declare ( )
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.

Note
This uses the demangled name of the input type as the key in our library of objects. Using the demangled name effectively assumes that all of the libraries being loaded were compiled with the same compiler version. We could undo this assumption by having the key be an input into this function.
Template Parameters
DerivedTypeobject type to declare
Returns
value to define a static variable to force running this function at library load time. It relates to variables so that it cannot be optimized away.

◆ get()

template<typename Prototype , typename PrototypePtr = std::unique_ptr<Prototype>, typename... PrototypeConstructorArgs>
static Factory & fire::factory::Factory< Prototype, PrototypePtr, PrototypeConstructorArgs >::get ( )
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.

Returns
reference to single Factory instance

◆ make()

template<typename Prototype , typename PrototypePtr = std::unique_ptr<Prototype>, typename... PrototypeConstructorArgs>
PrototypePtr fire::factory::Factory< Prototype, PrototypePtr, PrototypeConstructorArgs >::make ( const std::string full_name,
PrototypeConstructorArgs...  maker_args 
)
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.

Exceptions
Exceptionif the input object name could not be found

The arguments to the maker are determined at compiletime using the template parameters of Factory.

Parameters
[in]full_namename of class to create, same name as passed to declare
[in]maker_argsparameter pack of arguments to pass on to maker
Returns
a pointer to the parent class that the objects derive from.

◆ maker()

template<typename Prototype , typename PrototypePtr = std::unique_ptr<Prototype>, typename... PrototypeConstructorArgs>
template<typename DerivedType >
static PrototypePtr fire::factory::Factory< Prototype, PrototypePtr, PrototypeConstructorArgs >::maker ( PrototypeConstructorArgs...  args)
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:

  1. constructor arguments defined by the Factory and not here
  2. return type is a base pointer and not a derived pointer

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.

Template Parameters
DerivedTypetype of derived object we should create
Parameters
[in]argsconstructor arguments for derived type construction

The documentation for this class was generated from the following file: