LDMX Software
Public Types | Public Member Functions | Static Public Member Functions | Private Member Functions | Static Private Member Functions | Private Attributes | List of all members
simcore::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.
 

Public Member Functions

template<typename DerivedType >
uint64_t declare ()
 register a new object to be constructible
 
PrototypePtr make (const std::string &full_name, PrototypeConstructorArgs... maker_args)
 make a new object by name
 
template<class UnaryFunction >
void apply (UnaryFunction f) const
 Apply the input UnaryFunction to each entry in the inventory.
 
 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
 

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
 

Private Attributes

std::unordered_map< std::string, PrototypeMakerlibrary_
 library of possible objects to create
 
std::vector< PrototypePtr > warehouse_
 warehouse of objects that have already been created
 

Detailed Description

template<typename Prototype, typename PrototypePtr, typename... PrototypeConstructorArgs>
class simcore::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 that the factory creates
PrototypeConstructorArgsparameter pack of arguments to pass to the object constructor.

Terminology

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 {
auto v = ::fire::factory::Factory<MyPrototype>::get()
.declare<MyDerived>();
}

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.h"
// 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
using Factory = ::fire::factory::Factory<LibraryEntry>;
}; // 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:197

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() 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() 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() 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;
}
}

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.

Definition at line 197 of file Factory.h.

Member Typedef Documentation

◆ PrototypeMaker

template<typename Prototype , typename PrototypePtr , typename... PrototypeConstructorArgs>
using simcore::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.

Definition at line 205 of file Factory.h.

Member Function Documentation

◆ apply()

template<typename Prototype , typename PrototypePtr , typename... PrototypeConstructorArgs>
template<class UnaryFunction >
void simcore::Factory< Prototype, PrototypePtr, PrototypeConstructorArgs >::apply ( UnaryFunction  f) const
inline

Apply the input UnaryFunction to each entry in the inventory.

UnaryFunction is simply passed dirctly to std::for_each so look there for requirements upon it.

Definition at line 283 of file Factory.h.

283 {
284 std::for_each(warehouse_.begin(), warehouse_.end(), f);
285 }
std::vector< PrototypePtr > warehouse_
warehouse of objects that have already been created
Definition Factory.h:326

References simcore::Factory< Prototype, PrototypePtr, PrototypeConstructorArgs >::warehouse_.

Referenced by simcore::Simulator::beforeNewRun(), simcore::ReSimulator::produce(), simcore::Simulator::produce(), and simcore::RunManager::setupPhysics().

◆ declare()

template<typename Prototype , typename PrototypePtr , typename... PrototypeConstructorArgs>
template<typename DerivedType >
uint64_t simcore::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.

Definition at line 241 of file Factory.h.

241 {
242 std::string full_name{boost::core::demangle(typeid(DerivedType).name())};
243 library_[full_name] = &maker<DerivedType>;
244 return reinterpret_cast<std::uintptr_t>(&library_);
245 }
std::unordered_map< std::string, PrototypeMaker > library_
library of possible objects to create
Definition Factory.h:323

References simcore::Factory< Prototype, PrototypePtr, PrototypeConstructorArgs >::library_.

◆ get()

template<typename Prototype , typename PrototypePtr , typename... PrototypeConstructorArgs>
static Factory & simcore::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

Definition at line 217 of file Factory.h.

217 {
218 static Factory the_factory;
219 return the_factory;
220 }
Factory()=default
private constructor to prevent creation

Referenced by simcore::Simulator::beforeNewRun(), simcore::GammaPhysics::ConstructProcess(), simcore::RunManager::Initialize(), simcore::ReSimulator::produce(), simcore::Simulator::produce(), and simcore::RunManager::setupPhysics().

◆ make()

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

Definition at line 265 of file Factory.h.

266 {
267 auto lib_it{library_.find(full_name)};
268 if (lib_it == library_.end()) {
269 EXCEPTION_RAISE("SimFactory", "An object named " + full_name +
270 " has not been declared.");
271 }
272 warehouse_.emplace_back(lib_it->second(maker_args...));
273 return warehouse_.back();
274 }

References simcore::Factory< Prototype, PrototypePtr, PrototypeConstructorArgs >::library_, and simcore::Factory< Prototype, PrototypePtr, PrototypeConstructorArgs >::warehouse_.

Referenced by simcore::GammaPhysics::ConstructProcess(), simcore::RunManager::Initialize(), and simcore::RunManager::setupPhysics().

◆ maker()

template<typename Prototype , typename PrototypePtr , typename... PrototypeConstructorArgs>
template<typename DerivedType >
static PrototypePtr simcore::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

Definition at line 314 of file Factory.h.

314 {
315 return PrototypePtr(
316 new DerivedType(std::forward<PrototypeConstructorArgs>(args)...));
317 }

Member Data Documentation

◆ library_

template<typename Prototype , typename PrototypePtr , typename... PrototypeConstructorArgs>
std::unordered_map<std::string, PrototypeMaker> simcore::Factory< Prototype, PrototypePtr, PrototypeConstructorArgs >::library_
private

◆ warehouse_

template<typename Prototype , typename PrototypePtr , typename... PrototypeConstructorArgs>
std::vector<PrototypePtr> simcore::Factory< Prototype, PrototypePtr, PrototypeConstructorArgs >::warehouse_
private

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