![]() |
Home | Libraries | People | FAQ | More |
Note: The following features are common to all managed memory segment classes, but we will use managed shared memory in our examples. We can do the same with memory mapped files or other managed memory segment classes.
Boost.Interprocess' managed memory segments allows a varied object construction styles:
The object construction API allows constructing a type T
in a managed memory segment instance ms
as:
ms.construct<T>("Name"/unique_instance/anonymous_instance)(args...))
T is constructed
as T(args...)
N objects.
Elements of an array can be constructed:
ms.construct<T>("Name"/unique_instance/anonymous_instance)[N])(args...)
T(args...)
it
ms.construct_it<T>("Name"/unique_instance/anonymous_instance)[N])(it)
T(*(it++))
When constructing objects in a managed memory segment (managed shared memory, managed mapped files...) associated with a name, the user has a varied object construction family to "construct" or to "construct if not found". Boost.Interprocess can construct a single object or an array of objects.
//!Allocates and uses-allocator constructs an object of type MyType (throwing version). MyType *ptr = managed_memory_segment.construct<MyType>("Name") (par1, par2...); //!Allocates and uses-allocator constructs an array of objects of type MyType (throwing version). //!Each object receives the same parameters. MyType *ptr = managed_memory_segment.construct<MyType>("Name")[count](par1, par2...); //!Tries to find a previously created object. If not present, allocates //!and uses-allocator constructs an object of type MyType (throwing version) MyType *ptr = managed_memory_segment.find_or_construct<MyType>("Name") (par1, par2...); //!Tries to find a previously created object. If not present, allocates and //!uses-allocator constructs an array of objects of type MyType (throwing version). Each object //!receives the same parameters (par1, par2, ...) MyType *ptr = managed_memory_segment.find_or_construct<MyType>("Name")[count](par1, par2...); //!Allocates and uses-allocator constructs an array of objects of type MyType (throwing version) //!Each object receives parameters returned with the expression (*it1++, *it2++,... ) MyType *ptr = managed_memory_segment.construct_it<MyType>("Name")[count](it1, it2...); //!Tries to find a previously created object. If not present, allocates and uses-allocator constructs //!an array of objects of type MyType (throwing version). Each object receives //!parameters returned with the expression (*it1++, *it2++,... ) MyType *ptr = managed_memory_segment.find_or_construct_it<MyType>("Name")[count](it1, it2...); //!Tries to find a previously created object. Returns a pointer to the object and the //!count (if it is not an array, returns 1). If not present, the returned pointer is 0 std::pair<MyType *,std::size_t> ret = managed_memory_segment.find<MyType>("Name"); //!Destroys the created object, returns false if not present bool destroyed = managed_memory_segment.destroy<MyType>("Name"); //!Destroys the created object via pointer managed_memory_segment.destroy_ptr(ptr);
All these functions have a non-throwing version, that is invoked with an additional parameter std::nothrow. For example, for simple object construction:
//!Allocates and uses-allocator constructs an object of type MyType (no throwing version) MyType *ptr = managed_memory_segment.construct<MyType>("Name", std::nothrow) (par1, par2...);
Sometimes, the user doesn't want to create class objects associated with a name. For this purpose, Boost.Interprocess can create anonymous objects in a managed memory segment. All named object construction functions are available to uses-allocator construct anonymous objects. To allocate an anonymous objects, the user must use "boost::interprocess::anonymous_instance" name instead of a normal name:
MyType *ptr = managed_memory_segment.construct<MyType>(anonymous_instance) (par1, par2...); //Other construct variants can also be used (including non-throwing ones) ... //We can only destroy the anonymous object via pointer managed_memory_segment.destroy_ptr(ptr);
Find functions have no sense here, since anonymous objects have no name. We can only destroy the anonymous object via pointer.
Sometimes, the user wants to emulate a singleton in a managed memory segment. Obviously, as the managed memory segment is constructed at run-time, a user must construct and destroy this object explicitly. But how can a user be sure that the object is the only object of its type in the managed memory segment? This can be emulated using a named object and checking if it is present before trying to create one, but all processes must agree in the object's name, that can also conflict with other existing names.
To solve this, Boost.Interprocess offers a "unique object" creation in a managed memory segment. Only one instance of a class can be created in a managed memory segment using this "unique object" service (you can create more named objects of this class, though) so it makes easier the emulation of singleton-like objects across processes, for example, to design pooled, shared memory allocators. The object can be searched using the type of the class as a key.
// Uses-allocator constructs a unique (segment-wide) instance of MyType MyType *ptr = managed_memory_segment.construct<MyType>(unique_instance) (par1, par2...); // Find it std::pair<MyType *,std::size_t> ret = managed_memory_segment.find<MyType>(unique_instance); // Destroy it managed_memory_segment.destroy<MyType>(unique_instance); // Other construct and find variants can also be used (including non-throwing ones) //...
// We can also destroy the unique object via pointer MyType *ptr = managed_memory_segment.construct<MyType>(unique_instance) (par1, par2...); managed_shared_memory.destroy_ptr(ptr);
The find function obtains a pointer to the only object of type T that can be created using this "unique instance" mechanism.
One of the features of named/unique allocations/searches/destructions is
that they are atomic. Named allocations
use the recursive synchronization scheme defined by the internal mutex_family typedef defined of the memory
allocation algorithm template parameter (MemoryAlgorithm).
That is, the mutex type used to synchronize named/unique allocations is defined
by the MemoryAlgorithm::mutex_family::recursive_mutex_type type. For shared memory,
and memory mapped file based managed segments this recursive mutex is defined
as interprocess_recursive_mutex.
If two processes can call:
MyType *ptr = managed_shared_memory.find_or_construct<MyType>("Name")[count](par1, par2...);
at the same time, but only one process will create the object and the other will obtain a pointer to the created object.
Raw allocation using allocate() can be called also safely while executing
named/anonymous/unique allocations, just like when programming a multithreaded
application inserting an object in a mutex-protected map does not block other
threads from calling new[] while the map thread is searching the place where
it has to insert the new object. The synchronization does happen once the
map finds the correct place and it has to allocate raw memory to construct
the new value.
This means that if we are creating or searching for a lot of named objects, we only block creation/searches from other processes but we don't block another process if that process is inserting elements in a shared memory vector.
Once an object is constructed using construct<> function family, the programmer can
obtain information about the object using a pointer to the object. The programmer
can obtain the following information:
Here is an example showing this functionality:
#include <boost/interprocess/managed_shared_memory.hpp> #include <cassert> #include <cstring> class my_class { //... }; int main() { using namespace boost::interprocess; //Remove shared memory on construction and destruction struct shm_remove { shm_remove() { shared_memory_object::remove("MyName"); } ~shm_remove(){ shared_memory_object::remove("MyName"); } } remover; managed_shared_memory managed_shm(create_only, "MyName", 10000*sizeof(std::size_t)); //Construct objects my_class *named_object = managed_shm.construct<my_class>("Object name")[1](); my_class *unique_object = managed_shm.construct<my_class>(unique_instance)[2](); my_class *anon_object = managed_shm.construct<my_class>(anonymous_instance)[3](); //Now test "get_instance_name" function. assert(0 == std::strcmp(managed_shared_memory::get_instance_name(named_object), "Object name")); assert(0 == std::strcmp(managed_shared_memory::get_instance_name(unique_object), typeid(my_class).name())); assert(0 == managed_shared_memory::get_instance_name(anon_object)); //Now test "get_instance_type" function. assert(named_type == managed_shared_memory::get_instance_type(named_object)); assert(unique_type == managed_shared_memory::get_instance_type(unique_object)); assert(anonymous_type == managed_shared_memory::get_instance_type(anon_object)); //Now test "get_instance_length" function. assert(1 == managed_shared_memory::get_instance_length(named_object)); assert(2 == managed_shared_memory::get_instance_length(unique_object)); assert(3 == managed_shared_memory::get_instance_length(anon_object)); managed_shm.destroy_ptr(named_object); managed_shm.destroy_ptr(unique_object); managed_shm.destroy_ptr(anon_object); return 0; }
Since Boost 1.91, Boost.Interprocess uses Boost.Container's extended uses-allocator construction utilities (see Boost.Container) so that constructing objects that use Boost.Interprocess allocators is simplified. As a result a user:
segment_manager*
arguments that are convertible to allocators) when constructing an object
and its subobjects.
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.
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:
T
is constructible as T(allocator_arg, allocator_type, args...) -->
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:
#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 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 () { managed_shared_memory ms(create_only, "MyName", 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); return 0; }