small doc improvements

This commit is contained in:
Jean-Louis Leroy
2025-10-19 16:29:17 -04:00
parent 98319c4797
commit 9ae95df2af
14 changed files with 63 additions and 128 deletions

View File

@@ -1,77 +0,0 @@
## inplace_vptr
### Synopsis
Defined in <boost/openmethod/inplace_vptr.hpp>.
```c++
namespace boost::openmethod {
template<class Class, class Policy = BOOST_OPENMETHOD_DEFAULT_REGISTRY>
class inplace_vptr {
protected:
inplace_vptr();
~inplace_vptr();
friend auto boost_openmethod_vptr(const Class& obj) -> vptr_type;
};
template<class Class, class Base, class... MoreBases>
class inplace_vptr {
protected:
inplace_vptr();
~inplace_vptr();
friend auto boost_openmethod_vptr(const Class& obj) -> vptr_type;
// if sizeof(MoreBases...) > 0
};
} // namespace boost::openmethod
```
### Description
`inplace_vptr` is a CRTP class template that embeds and manages a vptr across a
class hierarchy.
If `Class` has no `Bases`, `inplace_vptr` adds a `boost_openmethod_vptr` private
member to `Class`. In either case, it sets the vptr to the v-table of `Class`
from `Policy`. It also creates a `boost_openmethod_vptr` friend function that
takes a a `const Class&` and returns the embedded vptr.
If `Class` has more than one base, the `boost_openmethod_vptr` friend
function is also created. It returns one of the embedded vptrs (it doesn't
matter which one, as they all have the same value). This is to resolve
ambiguities
As part of its implementation, `inplace_vptr` may also declare one or two free
functions (`boost_openmethod_policy` and `boost_openmethod_bases`) at certain
levels of the hierarchy.
### Members
#### constructor
```c++
inplace_vptr();
```
Sets the vptr to the v-table for Class, obtained from `Policy`. If `Policy`
contains `indirect_vptr`, an additional level of indirection is added, thus
preserving the validity of the pointer across calls to `initialize`.
#### destructor
```c++
~inplace_vptr();
```
For each `Base`, sets the vptr to the v-table for that base.
#### Free Functions
```c++
auto boost_openmethod_vptr(const Class& obj) -> vptr_type;
```
Returns the vptr embedded in `obj`.

View File

@@ -4,6 +4,8 @@
// or copy at http://www.boost.org/LICENSE_1_0.txt)
// tag::content[]
// employee.cpp
#include "roles.hpp"
#include <boost/openmethod.hpp>

View File

@@ -4,6 +4,8 @@
// or copy at http://www.boost.org/LICENSE_1_0.txt)
// tag::content[]
// salesman.cpp
#include "roles.hpp"
#include <boost/openmethod.hpp>

View File

@@ -22,6 +22,5 @@ BOOST_OPENMETHOD(pay, (boost::openmethod::virtual_ptr<const Employee>), double);
BOOST_OPENMETHOD_DECLARE_OVERRIDER(
pay, (boost::openmethod::virtual_ptr<const Employee>), double);
#endif // ROLES_HPP
// end::content[]

View File

@@ -1,12 +1,12 @@
* xref:introduction.adoc[Introduction]
* xref:basics.adoc[Basics]
* xref:basics.adoc[Open-Methods 101]
* xref:performance.adoc[Performance]
* xref:smart_pointers.adoc[Smart Pointers]
* xref:headers.adoc[Headers]
* xref:namespaces.adoc[Namespaces]
* xref:friendship.adoc[Friendship]
* xref:multiple_dispatch.adoc[Multiple Dispatch]
* Advanced Features
* xref:advanced_features.adoc[Advanced Features]
** xref:core_api.adoc[Core API]
** xref:registries_and_policies.adoc[Registries and Policies]
** xref:custom_rtti.adoc[Custom RTTI]

View File

@@ -1,6 +1,6 @@
:exampledir: ../example
## Basics
## Open-Methods 101
An _open-method_ is a free-standing function that takes one or more _virtual_
_parameters_. When it is called, it forwards to an _overrider_ selected from a
@@ -11,7 +11,8 @@ with one virtual parameter is equivalent to a virtual function - with one big
difference: it exists outside of classes.
A virtual parameter is in the form `virtual_ptr<Class>`. It is a pointer-like
class that points to an instance of `Class`.
class that points to an instance of `Class`. `virtual_ptr` is defined in the library's main namespace,
`boost::openmethod`.
To create an open-method that implements the `postfix` operation, we use the
xref:BOOST_OPENMETHOD.adoc[BOOST_OPENMETHOD] macro:

View File

@@ -50,7 +50,7 @@ struct std_rtti : rtti {
This template is required.
* `static_type` is called during class registration, by
`virtual_ptr`{empty}'s "final" constructs.
`virtual_ptr`{empty}s "final" constructs.
It is also called to set `bad_call::method`.
This function is required.

View File

@@ -2,30 +2,27 @@
## Headers
Typically, `BOOST_OPENMETHOD` go in headers, while
`BOOST_OPENMETHOD_CLASSES` and `BOOST_OPENMETHOD_OVERRIDE` go in
Typically, `BOOST_OPENMETHOD` is used in headers, while
`BOOST_OPENMETHOD_CLASSES` and `BOOST_OPENMETHOD_OVERRIDE` are used in
implementation files.
Let's use a payroll application as an example.
We have two roles: `employee` and `salesman`, and a `pay` method that
computes the monthly pay of an employee.
We want to override and call `pay` from from multiple translation units, so
we put it in a header:
Let's use a payroll application as an example. We have two roles: `Employee` and
`Salesman`, and a `pay` method that computes the monthly pay of an employee. We
want to override and call `pay` from from multiple translation units, so we put
it in a header:
[source,c++]
----
include::{example}/1/roles.hpp[tag=content]
----
`BOOST_OPENMETHOD` _defines_ an inline function, so it can be called only
once in a translation unit.
The include guards see to this.
`BOOST_OPENMETHOD` _defines_ an inline function, so it can be called only once
in a translation unit. The include guards see to this.
Let's write the override for "just" employees - they get a fixed salary.
`BOOST_OPENMETHOD_OVERRIDE` _adds_ an overrider to the method.
We don't want to add the same multiple times; that would create an
ambiguity.
Thus it should go in an implementation file:
`BOOST_OPENMETHOD_OVERRIDE` _adds_ an overrider to the method. We don't want to
add the same multiple times; that would create an ambiguity. Thus it should go
in an implementation file:
[source,c++]
----

View File

@@ -8,8 +8,9 @@ xref:headers.adoc[Headers] section.
xref:BOOST_OPENMETHOD.adoc[BOOST_OPENMETHOD] defines a method in the current
namespace. xref:BOOST_OPENMETHOD_OVERRIDE.adoc[BOOST_OPENMETHOD_OVERRIDE] works
_across_ namespaces. Overriders are not required to be in the same namespace as
the method they override. The macro adds the overrider a method that can be
called with the same arguments, possibly located via argument dependant lookup.
the method they override. The macro adds the overrider to a method that can be
called with the same arguments as the overrider, possibly located via argument
dependant lookup.
Overrider containers are added to the current namespace. It follows that the
same method can have overriders in several different container, in different
@@ -45,5 +46,5 @@ namespace:
[source,c++]
----
include::{example}/3/salesman.cpp[tag=content]
include::{example}/4/salesman.cpp[tag=content]
----

View File

@@ -47,16 +47,15 @@ This compiles to (variable names are shortened for readability):
mov rdx, rsi
mov rcx, qword ptr [rip + vptr_vector_vptrs]
mov rdi, qword ptr [rcx + 8*rdi]
mov rcx, qword ptr [rip + fn+88]
mov rcx, qword ptr [rip + postfix::fn+88]
mov rcx, qword ptr [rdi + 8*rcx]
mov rsi, rax
jmp rcx # TAILCALL
----
This is quite a few instructions more. Upon closer examination, we see that many
are memory reads, independent of one another; they can be thus executed in
parallel. For example, the first three instructions would execute simultaneously
on a modern CPU.
are memory reads, independent of one another; they can thus be executed in
parallel. For example, the first three instructions can execute simultaneously.
llvm-mca estimates a throughput of 4 cycles per dispatch. However, the
difference is amortized by the time spent passing the arguments and returning
@@ -97,9 +96,9 @@ jmp rax # TAILCALL
`virtual_ptr` arguments are passed through the method call, to the overrider,
which can use them to make further method calls.
A program designed with open-methods in mind should use `virtual_ptr`{empty}'s
A program designed with open-methods in mind should use `virtual_ptr`{empty}s
in place of plain pointers or references, as much as possible. Here is the Node
example, rewritten to use `virtual_ptr`{empty}'s thoughout:
example, rewritten to use `virtual_ptr`{empty}s thoughout:
[source,c++]
----

View File

@@ -22,7 +22,7 @@ parameters:
* xref:#std_unique_ptr[`boost/openmethod/interop/std_unique_ptr.hpp`] to use
`std::unique_ptr` in virtual parameters.
*The remaining headers are for advanced use*.
*The headers below are for advanced use*.
## Pre-Core Headers

View File

@@ -113,7 +113,7 @@ across modules.
### Indirect Vptrs
`initialize` rebuilds the v-tables in the registry. This invalidates all the
`virtual_ptr`{empty}'s, and also the v-table pointers stored in objects by
`virtual_ptr`{empty}s, and also the v-table pointers stored in objects by
cpp:inplace_vptr_base[], related to that registry. This is seldom an issue, as
most programs that dynamically load shared libraries do so at the very beginning
of their execution.
@@ -125,7 +125,7 @@ that has the same policies as `default_registry`, plus `indirect_vptr`. We can
use it to override the default registry, for example using a compiler
command-line switch (`-DBOOST_OPENMETHOD_DEFAULT_REGISTRY=indirect_registry`).
Here is an example of a program that carries `virtual_ptr`{empty}'s across
Here is an example of a program that carries `virtual_ptr`{empty}s across
`initialize` calls:
[source,c++]

View File

@@ -2,9 +2,9 @@
## Smart Pointers
If we want maximum performance, we want to use `virtual_ptr`{empty}s in place of
ordinary pointers or references. However, many designs want to use smart
pointers for lifetime management; think of `std::unique_ptr`, `std::shared_ptr`,
or `boost::intrusive_ptr`.
ordinary pointers or references. However, we may also want to use smart pointers
for lifetime management; think of `std::unique_ptr`, `std::shared_ptr`, or
`boost::intrusive_ptr`.
Does it mean that we have to choose between the performance of `virtual_ptr` and
the convenience of `std::unique_ptr`? Or carry around both types of smart
@@ -17,20 +17,18 @@ Node>>` both point to a `const Node`; the former uses a plain `const Node*`
while the latter uses a `std::unique_ptr<const Node>`. Both carry the same
v-table pointer.
Smart `virtual_ptr`{empty}'s automatically convert to their non-smart, "plain"
Smart `virtual_ptr`{empty}s automatically convert to their non-smart, "plain"
counterparts - e.g. from `virtual_ptr<std::unique_ptr<const Node>>` to
`virtual_ptr<const Node>`. Methods and overriders typically use plain
`virtual_ptr`{empty}'s, although it is not always the case. For example,
`virtual_ptr`{empty}s, although it is not always the case. For example,
consider a `transpose` method for matrices. If the matrix is symmetric, the
overrider should return its argument. This can be implemented by passing a
`virtual_ptr<std::shared_ptr<const Matrix>>` to the method.
The reverse conversion, from plain to smart, does not exist, because it is
unsafe.
A smart `virtual_ptr` can be constructed from a corresponding smart pointer, but
not directly from a plain reference or pointer, because it has the potential to
accidentally create smart pointers.
The reverse conversion, from plain to smart, does not exist, because it would
have the potential to accidentally create smart pointers. Likewise, a smart
`virtual_ptr` can be constructed from a smart pointer, but not directly from a
plain reference or pointer.
The library provides aliases for standard smart pointers:
@@ -39,8 +37,9 @@ The library provides aliases for standard smart pointers:
- `shared_virtual_ptr<Class>` is an alias for `virtual_ptr<std::shared_ptr<Class>>`
The standard library provides `std::make_unique` and `std::make_shared` to
create smart pointers. They are convenient, robust, and, in the case of
`std::shared_ptr`, more efficient. OpenMethod provides counterparts:
create smart pointers. They are convenient, robust in presence of exceptions,
and, in the case of `std::shared_ptr`, more efficient. OpenMethod provides
these counterparts:
- `make_unique_virtual_ptr<Class>(...)`
@@ -49,7 +48,16 @@ create smart pointers. They are convenient, robust, and, in the case of
Since these functions create the object, they know its exact type with
certainty. Thus they don't need to perform a hash table lookup to find the
appropriate v-table; they simply read it from a static variable. As a
consequence, they don't even require `Class` to be polymorphic.
consequence, they don't require `Class` to be polymorphic.
The aliases and the `make_*` functions are aliased in `namespace
boost::openmethod::aliases`, making it convenient to import constructs that are
likely to be used together.
Smart `virtual_ptr`{empty}s are implemented in their own headers, found in the
`interop` subdirectory. For example, support for `std::unique_ptr` is provided
in `<boost/openmethod/interop/std_unique_ptr.hpp>`. `<boost/openmethod.hpp>`
does not include smart pointer headers, so they must be included explicitly.
Here is a variation of the AST example that uses dynamic allocation and unique
pointers:

View File

@@ -70,9 +70,9 @@ jmp qword ptr [rax + 8*rcx] # TAILCALL
### `inplace_vptr`
cpp:inplace_vptr_base[] and cpp:inplace_vptr_derived[] automate the creation and
management of embedded vptrs. They are both are
management of embedded vptrs. They are both
https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern[CRTP]
classes.
mixin classes.
`inplace_vptr_base` is used at the root of a hierarchy. It defines a vptr member
variable, and a `boost_openmethod_vptr` friend function that returns its value -
@@ -80,6 +80,10 @@ just like in the previous example. `inplace_vptr_derived` is used in derived
classes. Their constructors set the vptr member to point to v-table for the
class.
WARNING;; `inplace_vptr_derived` must be used at each level of the hierarchy,
and reflect the actual inheritance relationships. Otherwise, the vptrs will not
be set correctly, and the wrong overriders may be called.
[source,c++]
----
include::{example}/3/virtual_ptr_alt.cpp[tag=content]
@@ -95,6 +99,5 @@ can be listed after the class being defined.
The destructor of `inplace_vptr_derived` set the bases' vptrs back to the
v-table for the bases, just like what C++ does for its native vptrs.
WARNING;; `inplace_vptr_derived` must be used at each level of the hierarchy,
and reflect the actual inheritance relationships. Otherwise, the vptrs will not
be set correctly, and the wrong overriders may be called.
`inplace_vptr_base` and `inplace_vptr_derived` are aliased in `namespace
boost::openmethod::aliases`.