Files
openmethod/doc/custom_rtti.adoc
Jean-Louis Leroy 5e0fa8ee4b inception
2025-03-08 15:31:25 -05:00

164 lines
4.9 KiB
Plaintext

## Custom RTTI
Stock policies use the `std_rtti` implementation of `rtti`. Here is its full
source:
[source,c++]
----
struct std_rtti : rtti {
template<typename T>
static type_id static_type() {
return reinterpret_cast<type_id>(&typeid(T));
}
template<typename T>
static type_id dynamic_type(const T& obj) {
return reinterpret_cast<type_id>(&typeid(obj));
}
template<class Stream>
static void type_name(type_id type, Stream& stream) {
stream << reinterpret_cast<const std::type_info*>(type)->name();
}
static std::type_index type_index(type_id type) {
return std::type_index(*reinterpret_cast<const std::type_info*>(type));
}
template<typename D, typename B>
static D dynamic_cast_ref(B&& obj) {
return dynamic_cast<D>(obj);
}
};
----
* `static_type` is used by class registration, by `virtual_ptr`{empty}'s "final"
constructs, and to format error and trace messages. `T` is not restricted to
the classes that appear as virtual parameters. This function is required.
* `dynamic_type` is used to locate the v-table for an object. This function is
usually required. If only the `virtual_ptr` "final" constructs are used, or
if `boost_openmethod_vptr` is provided for all the classes in the policy, it
can be omitted.
* `type_name` writes a representation of `type` to `stream`. It is used to format
error and trace messages. `Stream` is a lighweight version of `std::ostream`
with reduced functionality. It only supports insertion of `const char*`,
`std::string_view`, pointers and `std::size_t`. This function is optional;
if it is not provided, "type_id(_type_)" is used.
* `type_index` returns an object that _uniquely_ identifies a class. Some forms
of RTTI (most notably, C++'s `typeid` operator) do not guarantee that the
type information object for a class is unique within the same program. This
function is optional; if not provided, `type` is assumed to be unique, and
used as is.
* `dynamic_cast_ref` casts `obj` to class `D`. `B&&` is either a lvalue reference
(possibly cv-qualified) or a rvalue reference. `D` has the same reference
category (and cv-qualifier if applicable) as `B`. This function is required
only in presence of virtual inheritance.
Consider a custom RTTI implementation:
[source,c++]
----
struct Animal {
Animal(unsigned type) : type(type) {
}
virtual ~Animal() = default;
unsigned type;
static constexpr unsigned static_type = 1;
};
struct Cat : Animal {
Cat() : Animal(static_type) {
}
static constexpr unsigned static_type = 2;
};
// ditto for Dog
----
This scheme has an interesting property: its type ids are monotonically
allocated in a small, dense range. Thus, we don't need to hash them. We can use
them as indexes in the table of vptrs.
This time we are going to replace the default policy globally. First we need to
define the custom RTTI facet. We must _not_ include
`<boost/openmethod/core.hpp>` or any header that includes it yet.
Here is the facet implementation:
[source,c++]
----
include::{examplesdir}/custom_rtti.cpp[tag=facet]
----
This facet is quite minimal. It does not support virtual inheritance. It would
not produce good error or trace messages, because types would be represented by
their integer ids.
This time we create a policy from scratch. For that we use the `basic_policy`
CRTP template:
[source,c++]
----
include::{examplesdir}/custom_rtti.cpp[tag=policy]
----
Now we can include the "core" header and write the example:
[source,c++]
----
include::{examplesdir}/custom_rtti.cpp[tag=example]
----
This programs compiles even if standard RTTI is disabled.
## Deferred RTTI
In the previous example, the RTTI system assigns types id statically. It is more
common to allocate them using a global counter, manipulated by static
constructors. This is a problem, because `static_type` is used by class
registration. It may read the custom type ids _before_ they are have been
initialized.
The solution is to add the `deferred_static_rtti` facet to the policy; it defers
reading the type information until `initialize` is called.
This time let's support virtual inheritance as well. First the domain classes:
[source,c++]
----
include::{examplesdir}/deferred_custom_rtti.cpp[tag=classes]
// ditto for Dog
----
The rtti facet is the same, with one more function:
[source,c++]
----
struct custom_rtti : bom::policies::rtti {
// as before
include::{examplesdir}/deferred_custom_rtti.cpp[tag=dynamic_cast_ref]
};
----
Finally, the policy contains an additional facet - `deferred_static_rtti`:
[source,c++]
----
struct custom_policy
: bom::policies::basic_policy<
custom_policy, custom_rtti,
bom::policies::deferred_static_rtti, // <-- additional facet
bom::policies::vptr_vector<custom_policy>> {};
----
The example is the same as in the previous section.