LDMX Software
framework::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 (const std::string &derived_type_name)
 register a new object to be constructible
 
virtual std::optional< PrototypePtr > make (const std::string &full_name, PrototypeConstructorArgs... maker_args)
 make a new object by name
 
 Factory (Factory const &)=delete
 delete the copy constructor
 
void operator= (Factory const &)=delete
 delete the assignment operator
 
 Factory ()=default
 default constructor that does nothing
 
virtual ~Factory ()=default
 default destructor that is virtual for Warehouse override
 

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
 

Detailed Description

template<typename Prototype, typename PrototypePtr, typename... PrototypeConstructorArgs>
class framework::Factory< Prototype, PrototypePtr, PrototypeConstructorArgs >

Factory to dynamically create objects derived from a specific prototype class.

This factory should be a singleton class so its copy constructor and assignment operator have been deleted.

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

  • 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. In order to reduce the amount of boilerplate code, the derived class declaration can be done with the FACTORY_REGISTRATION macro.
  2. The factory creates any of the registered classes and returns a pointer to it in the form of a prototype-class pointer.

Usage

Using the factory effectively 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.

// LibraryEntry.h
#ifndef LIBRARYENTRY_H
#define LIBRARYENTRY_H
// 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
DECLARE_FACTORY(LibraryEntry, std::shared_ptr<LibraryEntry>);
}; // LibraryEntry
#endif // LIBRARYENTRY_H
// LibraryEntry.cxx
#include "LibraryEntry.hpp"
DEFINE_FACTORY(LibraryEntry);
Header holding Factory class and supporting macros.
#define DECLARE_FACTORY(...)
This macro is used in the public portion of your prototype class declaration.
Definition Factory.h:377
#define DEFINE_FACTORY(classtype)
This should go into an implementation file for your prototype class.
Definition Factory.h:411

Example Derived Classes

Here are a few example derived classes.

// Book.cxx
#include "LibraryEntry.hpp"
namespace library {
class Book : public LibraryEntry {
public :
virtual std::string name() override {
return "Where the Red Fern Grows";
}
};
}
FACTORY_REGISTRATION(LibraryEntry, library::Book);
#define FACTORY_REGISTRATION(prototype, derived)
Register a new class with a specific factory.
Definition Factory.h:469
// Podcast.cpp
#include "LibraryEntry.hpp"
namespace library {
namespace audio {
class Podcast : public LibraryEntry {
public :
virtual std::string name() override {
return "More Rim and AB";
}
};
}
}
FACTORY_REGISTRATION(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";
}
};
}
}
FACTORY_REGISTRATION(LibraryEntry, library::audio::Album);

Executable

Since the FACTORY_REGISTRATION macro defines a function whose result produces a static variable, the function is called at library-load time, the registration of our various library entries is automatically done before the execution of main (or after if the libraries are dynamically loaded with dlopen). 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]};
auto entry_ptr{LibraryEntry::Factory::get().make(full_cpp_name)};
if (entry_ptr) {
std::cout << entry_ptr.value()->name() << std::endl;
} else {
std::cerr << "ERROR: could not create a " << full_cpp_name << 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
More Rim and AB
$ fave-things library::audio::Album
Kind of Blue
$ fave-things library::DoesNotExist
ERROR: could not create a library::DoesNotExist

Definition at line 181 of file Factory.h.

Member Typedef Documentation

◆ PrototypeMaker

template<typename Prototype , typename PrototypePtr , typename... PrototypeConstructorArgs>
using framework::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 189 of file Factory.h.

Member Function Documentation

◆ declare()

template<typename Prototype , typename PrototypePtr , typename... PrototypeConstructorArgs>
template<typename DerivedType >
uint64_t framework::Factory< Prototype, PrototypePtr, PrototypeConstructorArgs >::declare ( const std::string & derived_type_name)
inline

register a new object to be constructible

We insert the new object into the library without checking if we are overwriting anything.

Note
The derived_type_name argument is what should be used as an input to Factory::make. Most commonly, this is just the fully-specificed C++ class name which is what is done with the FACTORY_REGISTRATION macro, but it could be something else if the user wishes to use this function directly instead of the macro.
Template Parameters
DerivedTypeobject type to declare
Parameters
[in]derived_type_namename of DerivedType to be used in the mapping
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 211 of file Factory.h.

211 {
212 library_[derived_type_name] = &maker<DerivedType>;
213 return reinterpret_cast<std::uintptr_t>(&library_);
214 }
std::unordered_map< std::string, PrototypeMaker > library_
library of possible objects to create
Definition Factory.h:281
static PrototypePtr maker(PrototypeConstructorArgs... args)
make a new DerivedType returning a PrototypePtr
Definition Factory.h:275

References framework::Factory< Prototype, PrototypePtr, PrototypeConstructorArgs >::library_, and framework::Factory< Prototype, PrototypePtr, PrototypeConstructorArgs >::maker().

◆ make()

template<typename Prototype , typename PrototypePtr , typename... PrototypeConstructorArgs>
virtual std::optional< PrototypePtr > framework::Factory< Prototype, PrototypePtr, PrototypeConstructorArgs >::make ( const std::string & full_name,
PrototypeConstructorArgs... maker_args )
inlinenodiscardvirtual

make a new object by name

We look through the library to find the requested object. If found, we create one, and return it wrapped in a std::optional. If not found, we return std::nullopt.

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 (or null)

Reimplemented in framework::FactoryWithWarehouse< Prototype, PrototypePtr, PrototypeConstructorArgs >.

Definition at line 233 of file Factory.h.

234 {
235 auto lib_it{library_.find(full_name)};
236 if (lib_it == library_.end()) {
237 return std::nullopt;
238 }
239 return lib_it->second(maker_args...);
240 }

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

◆ maker()

template<typename Prototype , typename PrototypePtr , typename... PrototypeConstructorArgs>
template<typename DerivedType >
static PrototypePtr framework::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 275 of file Factory.h.

275 {
276 return PrototypePtr(
277 new DerivedType(std::forward<PrototypeConstructorArgs>(args)...));
278 }

Referenced by framework::Factory< Prototype, PrototypePtr, PrototypeConstructorArgs >::declare().

Member Data Documentation

◆ library_

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

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