Common Preprocessor Macros#
Preprocessor macros are a C/C++ convention used to simplify code. Using #define
we set some keyword to expand to a larger piece of code. These macros can also take arguments. For example:
#define HELLO std::cout << "Hello world!" << std::endl;
#define PRINT(...) std::cout << __VARGS__ << std::endl;
MasterType#
Defined in apps/integrated_abm/Master_Type.h
MasterType is where we decide which template types are used throughout POLARIS, as well as store a few global variables like scenario and network. All Implementations in POLARIS have a MasterType template parameter, which allows them to refer back to this type and use all of the other classes throughout POLARIS.
Within the code, MasterType may be referred to simply as MT
or tMT
which expand to MasterType
and typename MasterType
respectively.
Global Variables#
A few variables within POLARIS are globally accessible but require MasterType to cast into a useable form. We use macros to simplify this and avoid retyping casts throughout the code:
#define global_scenario (reinterpret_cast<typename MasterType::scenario_type*>(_global_scenario))
#define global_network (reinterpret_cast<typename MasterType::network_type*>(_global_network))
#define global_demand (reinterpret_cast<typename MasterType::demand_type*>(_global_demand))
#define global_person_logger (reinterpret_cast<typename MasterType::person_data_logger_type*>(_global_person_logger))
Implementation Macros#
In general, within POLARIS, all our classes are sub-classed from one main parent class called Polaris_Component.
template <typename MasterType = NULLTYPE,
typename InheritanceList = NULLTYPELIST,
typename ObjectType = Data_Object>
class Polaris_Component : public ObjectType
As can be seen, this is a templated class with three template parameters
The MasterType from which all other type declarations can be obtained
The inheritance tree of this class (as a list of types)
The actual type of the object (Data or Execution)
When declaring such a sub-classes, they also need to be declared with appropriately named template types. These template types are used in the definition of the CRTP framework and have to be named and populated exactly.
template <typename MasterType, typename InheritanceList = NULLTYPELIST, typename DerivedType = void>
struct MyClass
: public Polaris_Component<MasterType, TypeList<NT, TypeList<MyClass<MT>, NT>>, Execution_Object>
{...};
When defining methods a similar signature is required (but without the defaults)
template <typename MasterType, typename InheritanceList, typename DerivedType>
int MyClass<MasterType, InheritanceList, DerivedType>::_my_method()
To make this process easier and less error-prone, a number of helper macros are defined.
// implementation - This is used when declaring any class or struct that inherits
// from Polaris_Component (any where up the chain)
template <tMT, typename InheritanceList = NULLTYPELIST, typename DerivedType = void>
// implement - This is used to add the correct template types when defining any method on such a class
template <tMT, typename InheritanceList, typename DerivedType>
// IMPLTYPE(TYPE) - This is the actual full type of the sub-class including its templates.
TYPE<MasterType, InheritanceList, DerivedType>
// INHERIT(TYPE) - This is used to add another class to the inheritance list (which is stored as a type
// using template magic) when declaring the inherited parent class
typename Append<InheritanceList, TYPE<MasterType>>::Result
// INHERIT_IMPL(TYPE) - This is used when declaring classes that inherit indirectly from Polaris_Component.
// For example, when A inherits from B which inherits from Polaris_Component.
// NOTE: it actually fills in TWO template slots, both InheritanceList and DerivedType
INHERIT(TYPE), IMPLTYPE(TYPE)
These macros allow the above examples to be simplified greatly, the method definitions in particular to get much shorter.
implementation struct MyClass : public Polaris_Component<MT, INHERIT(MyClass), Execution_Object>
{...};
implement int IMPLTYPE(MyClass)::_my_method()
Member variable macros#
Classes and Structs in C++ have options for data and functions to be private, protected, or public. It is good practice to keep data protected and define public functions for accessing it, so that the data can be kept track of easily when debugging, so we define a few macros to make this easier.
t_data#
t_data is used for all basic and local data:
class Foo {
t_data(int, my_variable);
}
This macro defines a protected int
member variable called _my_variable
and a getter (int my_variable()
) and setter (void my_variable(int val)
) method.
t_object#
t_data
is most useful when the data being represented is sufficiently simple as defines a getter which returns by value. When we are storing more complex datatype this can be a significant performance penalty - t_object
addresses this by defining a getter which returns by refenence .
class Foo {
t_object(vector<int>, lots_of_ints);
}
Const-ness
Because the reference is not const - it can be used to modify the member variable directly
Debug Messages#
MasterType has a Logger class, explained in more depth HERE. For debugging, we usually use macros to simplify code writing and make these messages especially visible when reviewing errors that appear in POLARIS. For example:
DEBUG_WARNING("Variable should never exceed 1, is at " << variable);
Warning levels are explained in the Logging page.
There is also THROW_EXCEPTION
which prints a message to the Logger at ERROR level, then throws and abort() call. This kills the function with a SIGABRT signal (the only time this should happen, if you see it in your stacktrace) so be careful using it.