Rewrite uses-allocator construction explanation and add an example that shows the difference a uses_allocator vs non-uses allocator compatible types.

This commit is contained in:
Ion Gaztañaga
2025-12-19 22:20:08 +01:00
parent db73a97ff9
commit feb074e876
2 changed files with 137 additions and 30 deletions

View File

@@ -3452,36 +3452,6 @@ The object construction API allows constructing a type `T` in a managed memory s
[endsect]
[section:uses_allocator Uses-allocator protocol for object construction]
Since Boost 1.91, [*Boost.Interprocess] uses [*Boost.Container]'s
extended [*uses-allocator construction] utilities
(see [@https://www.boost.org/doc/libs/latest/doc/html/container/cpp_conformance.html Boost.Container])
so that constructing objects that use [*Boost.Interprocess] allocators is simplified. As a result a user:
* Does not longer need to explicitly pass allocator arguments (or `segment_manager*` arguments that are convertible
to allocators) when constructing an object and its subobjects.
* [*Boost.Inteprocess] allocators' `construct` methods, in cooperation with [*Boost.Container] containers
and uses-allocator utilities, take advantage of the protocol to automatically propagate the state
of the allocator to the uses_allocator compatible types stored in those containers.
* Containers of containers automatically propagate the allocator recursively without the user explicitly needing to do so.
More formally, a type `T` is compatible with the [*Boost.Interprocess] `uses_allocator` protocol for a managed segment `MS` if:
* Defines `T::allocator_type` as one of [*Boost.Interprocess] allocator types and
uses one of the following conventions when constructed from an argument list `args...`
* The leading-allocator convention: `T` is constructible as `T(allocator_arg, alloc, args...)` -->
* The trailing-allocator convention `T` is constructible as `T(args..., alloc)`.
If a managed memory segment's construction utility takes the arguments `par1, par2` to construct a type
named `MyType`, then `MyType` instances are constructed as follows:
* If `MyType` does not support the uses-allocator protocol, it calls `MyType(par1, par2)`
* Otherwise, if the constructor is viable, it calls `MyType(arg0, par1, par2)`
* Otherwise, if the constructor is viable, it calls `MyType(allocator_arg, uses_segment_manager, par1, par2)`
[endsect]
[section:named Named Object construction function family]
When constructing objects in a managed memory segment (managed shared memory,
@@ -3657,6 +3627,39 @@ Here is an example showing this functionality:
[endsect]
[section:uses_allocator Uses-allocator protocol for object construction]
Since Boost 1.91, [*Boost.Interprocess] uses [*Boost.Container]'s
extended [*uses-allocator construction] utilities
(see [@https://www.boost.org/doc/libs/latest/doc/html/container/cpp_conformance.html Boost.Container])
so that constructing objects that use [*Boost.Interprocess] allocators is simplified. As a result a user:
* Does not longer need to explicitly pass allocator arguments (or `segment_manager*` arguments that are convertible
to allocators) when constructing an object and its subobjects.
* [*Boost.Inteprocess] allocators' `construct` methods, in cooperation with [*Boost.Container] containers
and uses-allocator utilities, take advantage of the protocol to automatically propagate the state
of the allocator to the uses_allocator compatible types stored in those containers.
* Containers of containers, all using [*Boost.Interprocess] allocators, automatically propagate the allocator
state recursively without the user explicitly needing to do so.
More formally, a type `T` is compatible with the [*Boost.Interprocess] implicit allocator
propagation protocol for a managed segment instance `ms` if the following conditions are fulfilled
when a user tries a `ms.construct<T>` or `ms.construct_it<T>` operation with argument list `args...`:
* `T::allocator_type` is one of [*Boost.Interprocess] allocator types (a void allocator fulfills this requirement)
* `T` can be constructed with one of the following conventions:
* The leading-allocator convention: `T` is constructible as `T(allocator_arg, allocator_type, args...)` -->
* The trailing-allocator convention `T` is constructible as `T(args..., allocator_type)`.
See the following example to see the difference between a type that does follow the uses-allocator
protocol (`UAType`) and another that does not (`Type`). Note how when constructing `UAType` using
the managed memory construction functions, the allocator is implicitly passed in constructors:
[import ../example/doc_uses_allocator_construction.cpp]
[doc_uses_allocator_construction]
[endsect]
[endsect]
[section:managed_memory_segment_advanced_features Managed Memory Segment Advanced Features]

View File

@@ -0,0 +1,104 @@
//////////////////////////////////////////////////////////////////////////////
//
// (C) Copyright Ion Gaztanaga 2025-2025. Distributed under the Boost
// Software License, Version 1.0. (See accompanying file
// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// See http://www.boost.org/libs/interprocess for documentation.
//
//////////////////////////////////////////////////////////////////////////////
#include <boost/interprocess/detail/workaround.hpp>
//[doc_uses_allocator_construction
#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/allocators/allocator.hpp>
#include <boost/container/vector.hpp>
#include <boost/container/uses_allocator_fwd.hpp> //for allocator_arg_t
//<-
#include "../test/get_process_id_name.hpp"
//->
using namespace boost::interprocess;
typedef managed_shared_memory::segment_manager seg_mngr_t;
typedef allocator<void, seg_mngr_t> valloc_t;
struct Type //Requires explicit allocator arguments
{
typedef allocator<Type, seg_mngr_t> vec_alloc_t;
boost::container::vector <Type, vec_alloc_t> m_vec; //Recursive vector
float m_v1;
int m_v2;
Type(valloc_t va) //0-arg + alloc constructor
: m_vec(va), m_v1(), m_v2() {}
Type(std::size_t size, valloc_t va) //1-arg + alloc constructor
: m_vec(vec_alloc_t(va)) //We can't use vector(size_type, allocator_type) as Type
, m_v1(), m_v2() //has no default constructor. Forced to one-by-one initialization.
{ for(std::size_t i = 0; i != size; ++i) m_vec.emplace_back(va); }
Type (valloc_t va, float v1, int v2) //allocator + 2-arg constructor
: m_vec(vec_alloc_t(va)) //We can't use vector(size_type, allocator_type) as Type
, m_v1(v1), m_v2(v2) //has no default constructor. Forced to one-by-one initialization.
{ for(std::size_t i = 0; i != 2; ++i) m_vec.emplace_back(va); }
};
struct UAType //Uses-Allocator compatible type
{
typedef allocator<UAType, seg_mngr_t> vec_alloc_t;
boost::container::vector <UAType, vec_alloc_t> m_vec; //Recursive vector
float m_v1;
int m_v2;
typedef valloc_t allocator_type; //Signals uses-allocator construction is available
UAType(valloc_t va) //0 explicit args + allocator
: m_vec(vec_alloc_t(va)), m_v1(), m_v2() {}
UAType( boost::container::allocator_arg_t
, allocator_type a , std::size_t size) //1 explicit arg + leading-allocator convention
: m_vec(size, vec_alloc_t(a)), m_v1(), m_v2() {}
UAType(float v1, int v2, allocator_type a) //2 explicit args + trailing-allocator convention
: m_vec(vec_alloc_t(a)), m_v1(v1), m_v2(v2) {}
};
int main ()
{
//<-
//Remove shared memory on construction and destruction
struct shm_remove
{
shm_remove() { shared_memory_object::remove(test::get_process_id_name()); }
~shm_remove(){ shared_memory_object::remove(test::get_process_id_name()); }
} remover;
(void)remover;
//->
managed_shared_memory ms(create_only, test::get_process_id_name(), 65536);
// 1 arg + allocator: Requires explicit allocator argument
// Type(size_type, valloc_t) called
Type *ptype1 = ms.construct< Type >(anonymous_instance)(3u, ms.get_allocator<void>());
// allocator + 2 arg: Requires explicit allocator-convertible argument (segment_manager*)
// Type(valloc_t, float, int) called
Type *ptype2 = ms.construct< Type >(anonymous_instance)(ms.get_segment_manager(), 0.0f, 0);
// 1 explicit arg + implicit leading allocator:
// UAType(allocator_arg_t, allocator_type, std::size_t) called
UAType *pua1 = ms.construct<UAType>(anonymous_instance)(3u);
// 2 explicit args + implicit trailing allocator:
// UAType(float, int, allocator_type) called
UAType *pua2 = ms.construct<UAType>(anonymous_instance)(0.0f, 0);
//<-
ms.destroy_ptr(ptype1);
ms.destroy_ptr(ptype2);
ms.destroy_ptr(pua1);
ms.destroy_ptr(pua2);
//->
return 0;
}
//]