Methods are scoped in a registry. A method can only reference classes in the same registry. If a class is used as a virtual parameter in methods using different registries, it must be registered with each of them.
Class templates use_classes, method, virtual_ptr, and macros
BOOST_OPENMETHOD and
BOOST_OPENMETHOD_CLASSES, take an additional
argument, a registry class, which defaults to default_registry. The
default registry can be overridden by defining the macroprocessor symbol
BOOST_OPENMETHOD_DEFAULT_REGISTRY
before including <boost/openmethod/core.hpp>. The value of the symbol is
used as a default template parameter for use_classes, method, virtual_ptr,
and others. Once the core header has been included, changing
BOOST_OPENMETHOD_DEFAULT_REGISTRY has no effect.
A registry has a collection of policies. Each policy belongs to a policy
category. A registry may contain at most one policy of each category. Policies
control how type information is obtained, how vptrs are acquired, how errors are
handled and reported, etc. While the behavior of initialize can be
customized via options, policies are primarily involved in method dispatch.
Policies are placed in the boost::openmethod::policies namespace.
default_registry contains the following policies:
| policy category | policy | role |
|---|---|---|
rtti |
std_rtti |
provides type information for classes and objects |
type_hash |
fast_perfect_hash |
hashes type id to an index in a vector |
vptr |
vptr_vector |
stores vptrs in an indexed collection |
error_handler |
default_error_handler |
calls an overridable handler function |
output |
stderr_output |
prints diagnostics to |
if
BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS
is defined, default_registry also contains the runtime_checks policy. This
enables extra validations during method dispatch, which can detect missing class
registrations that could not be caught by initialize.
The library provides another predefined registry: indirect_registry. It is
useful when shared libraries are dynamically loaded at runtime, and add methods
and overriders across program and shared library boundaries. See the section
about shared libraries.
Registries can be created from scratch, using the registry template. Here is
the definition of default_registry, copied from
<boost/openmethod/registry.hpp>:
struct default_registry
: registry<
policies::std_rtti,
policies::fast_perfect_hash, policies::vptr_vector,
policies::default_error_handler,
policies::stderr_output
#ifdef BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS
,
policies::runtime_checks
#endif
> {
};
When defining a new registry, it is recommended to define a new class, derived
from registry<…>, rather than via a typedef, which would create excessively
long symbol names and make debugging harder.
That class is a convenience, not the registry’s identity. Everything a registry
owns - the class and method lists, the dispatch tables, the state of every
stateful policy - is keyed on the registry<…> specialization the class
derives from, which is what its registry_type member aliases. Two classes
built from the same policies, in the same order, are therefore the same
registry, and share everything:
struct animals : registry<policies::std_rtti, policies::vptr_map<>> {};
struct vehicles : registry<policies::std_rtti, policies::vptr_map<>> {};
// the policy lists are identical, so this is one registry, not two
static_assert(std::is_same_v<animals::registry_type, vehicles::registry_type>);
This is worth watching for when the purpose of a second registry is to isolate a set of methods from another, since such a registry would naturally be given the same policies as the first. Registering a class or a method in either would then register it in both. To keep them apart, give each one a policy of its own:
// a policy in a category of its own, carrying nothing but a number
struct marker_category {
using category = marker_category;
};
template<int N>
struct marker final : marker_category {
template<class Registry>
struct fn {};
};
struct animals : default_registry::with<marker<1>> {};
struct vehicles : default_registry::with<marker<2>> {};
static_assert(!std::is_same_v<animals::registry_type, vehicles::registry_type>);
The order of the policies matters. When initialize runs, it calls each
policy’s initialize in the order the policies appear in the registry, from
left to right; finalize calls each policy’s finalize in the reverse
order. A policy that depends on another policy having been initialized must
therefore be listed after its dependency. In particular, vptr_vector reads
the state of the type_hash policy, so the type_hash policy must come before
vptr_vector — as in default_registry above, where fast_perfect_hash
precedes vptr_vector. This particular requirement is enforced with a
static_assert.
A registry can also be created by copying an existing registry’s policies,
using the with and without nested templates. For example,
indirect_registry is a tweak of default_registry:
struct indirect_registry : default_registry::with<policies::indirect_vptr> {};
Policies are implemented as unary
Boost.MP11
quoted metafunctions. A policy is an ordinary class that contains a nested
class template fn, which is instantiated by the registry, passing itself as
the single template argument. The reason for this mechanism is to allow policies
to have static data members, which each registry must have its own set of.
vptr_vector, for example, stores v-table pointers in a std::vector. If it is
used in two different registries, it needs to use two different vectors, one for
each registry.