![]() |
Home | Libraries | People | FAQ | More |
Boost.Interprocess STL compatible allocators offer a STL compatible allocator interface and if they define their internal pointer typedef as a relative pointer, they can be used to place STL containers in shared memory, memory mapped files or in a user defined memory segment.
Originally, in C++98 and C++03, this pointer typedef was useless. As Scott Meyers mentions in his Effective STL book, Item 10, "Be aware of allocator conventions and restrictions":
Obviously, if any STL implementation ignores pointer typedefs, no smart pointer can be used as allocator::pointer. If STL implementations assume all allocator objects of the same type compare equal, it will assume that two allocators, each one allocating from a different memory pool are equal, which is a complete disaster.
STL containers that we want to place in shared memory or memory mapped files with Boost.Interprocess can't make any of these assumptions, so:
This was in theory fixed in C++11, when std::pointer_traits
utilities were introduced and standard library containers added support for
them. However, due to ABI concerns and low user demand, some implementations
still don't fully support fancy pointer types like
boost::interprocess:offset_ptr. Those implementations still
use raw pointers in some containers for internal data or iterator implementations.
This non-portable situation is described in "P0773R0: Towards meaningful fancy pointers"
Can we use offset fancy pointer types to enable safe sharing of memory regions?
Yes; but the requirements on vendors are unclear, and there are pitfalls for the programmer.
Scenario (A), offset pointers, has only one tenable solution, "continue to partly support."
![]() |
Important |
|---|---|
Since Boost.Container was created from Boost.Interprocess in 2011, this library has mantained several <boost/interprocess/containers/.hpp> headers for backwards compatibility. Those headers are now deprecated and will be removed in a future Boost version. Users should directly use Boost.Container headers |
Due to the described partial support from Standard Library implementations Boost.Interprocess highly recommends using Boost.Container containers, which are guaranteed to work with Boost.Interprocess
The following Boost.Container containers are compatible with Boost.Interprocess:
deque
devector
flat_map/multimap
flat_set/multiset
list
map/multimap
set/multiset
slist
small_vector
stable_vector
static_vector
string
vector
To place any of these containers in managed memory segments, we must define the allocator template parameter with a Boost.Interprocess allocator so that the container allocates the values in the managed memory segment. To place the container itself in shared memory, we construct it in the managed memory segment just like any other object with Boost.Interprocess:
#include <boost/container/vector.hpp> #include <boost/interprocess/allocators/allocator.hpp> #include <boost/interprocess/managed_shared_memory.hpp> 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; //A managed shared memory where we can construct objects //associated with a c-string managed_shared_memory segment(create_only,"MyName", 65536); //Alias an STL-like allocator of ints that allocates ints from the segment typedef allocator<int, managed_shared_memory::segment_manager> ShmemAllocator; //Alias a vector that uses the previous STL-like allocator typedef boost::container::vector<int, ShmemAllocator> MyVector; int initVal[] = {0, 1, 2, 3, 4, 5, 6 }; const int *begVal = initVal; const int *endVal = initVal + sizeof(initVal)/sizeof(initVal[0]); //Initialize the STL-like allocator const ShmemAllocator alloc_inst (segment.get_segment_manager()); //Construct the vector in the shared memory segment with the STL-like allocator //from a range of iterators MyVector *myvector = segment.construct<MyVector> ("MyVector")/*object name*/ (begVal /*first ctor parameter*/ ,endVal /*second ctor parameter*/ ,alloc_inst /*third ctor parameter*/); //Use vector as your want std::sort(myvector->rbegin(), myvector->rend()); // . . . //When done, destroy and delete vector from the segment segment.destroy<MyVector>("MyVector"); return 0; }
These containers also show how easy is to create/modify an existing container making possible to place it in shared memory.
Boost.Interprocess containers are placed in shared memory/memory mapped files, etc... using two mechanisms at the same time:
construct<>, find_or_construct<>... functions. These functions
place a C++ object in the shared memory/memory mapped file. But this
places only the object, but not the
memory that this object may allocate dynamically.
This means that to place any Boost.Interprocess container (including Boost.Interprocess strings) in shared memory or memory mapped files, containers must:
If you do the first two points but you don't use construct<> or find_or_construct<> you are creating a container placed
only in your process but that allocates
memory for contained types from shared memory/memory mapped file.
Let's see an example:
#include <boost/interprocess/managed_shared_memory.hpp> #include <boost/container/vector.hpp> #include <boost/container/string.hpp> #include <boost/interprocess/allocators/allocator.hpp> int main () { using namespace boost::interprocess; //Typedefs typedef allocator<char, managed_shared_memory::segment_manager> CharAllocator; typedef boost::container::basic_string<char, std::char_traits<char>, CharAllocator> MyShmString; typedef allocator<MyShmString, managed_shared_memory::segment_manager> StringAllocator; typedef boost::container::vector<MyShmString, StringAllocator> MyShmStringVector; //Open shared memory //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 shm(create_only, "MyName", 10000); //Create allocators CharAllocator charallocator (shm.get_segment_manager()); StringAllocator stringallocator(shm.get_segment_manager()); //This string is in only in this process (the pointer pointing to the //buffer that will hold the text is not in shared memory). //But the buffer that will hold "this is my text" is allocated from //shared memory MyShmString mystring(charallocator); mystring = "this is my text"; //This vector is only in this process (the pointer pointing to the //buffer that will hold the MyShmString-s is not in shared memory). //But the buffer that will hold 10 MyShmString-s is allocated from //shared memory using StringAllocator. Since strings use a shared //memory allocator (CharAllocator) the 10 buffers that hold //"this is my text" text are also in shared memory. MyShmStringVector myvector(stringallocator); myvector.insert(myvector.begin(), 10, mystring); //This vector is fully constructed in shared memory. All pointers //buffers are constructed in the same shared memory segment //This vector can be safely accessed from other processes. MyShmStringVector *myshmvector = shm.construct<MyShmStringVector>("myshmvector")(stringallocator); myshmvector->insert(myshmvector->begin(), 10, mystring); //Destroy vector. This will free all strings that the vector contains shm.destroy_ptr(myshmvector); return 0; }
When creating containers of containers, each container needs an allocator. To avoid using several allocators with complex type definitions, we can take advantage of the type erasure provided by void allocators and the ability to implicitly convert void allocators in allocators that allocate other types.
Here we have an example that builds a map in shared memory. Key is a string and the mapped type is a class that stores several containers:
#include <boost/interprocess/managed_shared_memory.hpp> #include <boost/interprocess/allocators/allocator.hpp> #include <boost/container/map.hpp> #include <boost/container/vector.hpp> #include <boost/container/string.hpp> using namespace boost::interprocess; namespace bc = boost::container; //Typedefs of allocators and containers typedef managed_shared_memory::segment_manager seg_mngr_t; typedef allocator<void, seg_mngr_t> void_alloc_t; typedef bc::vector<int, allocator<int, seg_mngr_t> > int_vec_t; typedef bc::vector<int_vec_t, allocator<int_vec_t, seg_mngr_t> > int_vec_vec_t; typedef bc::basic_string <char, std::char_traits<char>, allocator<char, seg_mngr_t> > string_t; class complex_data { string_t string_; int_vec_vec_t int_vec_vec_; public: complex_data(const char *name, const void_alloc_t &valloc) //void_alloc_t is convertible to allocator<T> : string_(name, valloc), int_vec_vec_(valloc) {} }; //Definition of a shared memory map<string_t,complex_data...> typedef std::pair<const string_t, complex_data> map_value_type; typedef allocator<map_value_type, seg_mngr_t> map_value_type_allocator; typedef bc::map< string_t, complex_data, std::less<string_t> , map_value_type_allocator> complex_map_type; int main () { managed_shared_memory segment(create_only,"MyName", 65536); //Create shared memory //An allocator convertible to any allocator<T, seg_mngr_t> type void_alloc_t alloc_inst (segment.get_segment_manager()); //Construct the map calling map(key_compare, allocator_type), the allocator argument is explicit complex_map_type *mymap = segment.construct<complex_map_type> ("MyMap")(std::less<string_t>(), alloc_inst); //Both key(string) and value(complex_data) need an allocator in their constructors string_t key_object(alloc_inst); complex_data mapped_object("default_name", alloc_inst); map_value_type value(key_object, mapped_object); //Modify values and insert them in the map mymap->insert(value); return 0; }
This example can be simplified if the user designs a type compatible with the uses-allocator construction. This simplifies code and allows more efficient operations like the use of transparent functions for lookups and insertions:
#include <boost/interprocess/managed_shared_memory.hpp> #include <boost/interprocess/allocators/allocator.hpp> #include <boost/container/map.hpp> #include <boost/container/vector.hpp> #include <boost/container/string.hpp> using namespace boost::interprocess; namespace bc = boost::container; //Typedefs of allocators and containers typedef managed_shared_memory::segment_manager seg_mngr_t; typedef allocator<void, seg_mngr_t> void_alloc_t; typedef bc::vector<int, allocator<int, seg_mngr_t> > int_vec_t; typedef bc::vector<int_vec_t, allocator<int_vec_t, seg_mngr_t> > int_vec_vec_t; typedef bc::basic_string <char, std::char_traits<char>, allocator<char, seg_mngr_t> > string_t; class complex_data { string_t string_; int_vec_vec_t int_vec_vec_; public: typedef void_alloc_t allocator_type; //Activates the uses-allocator protocol complex_data(const char *name, const void_alloc_t &valloc) //void_alloc_t is convertible to allocator<T> : string_(name, valloc), int_vec_vec_(valloc) {} }; //Definition of a shared memory map<string_t, complex_data...> with transparent comparison typedef std::pair<const string_t, complex_data> map_value_type; typedef allocator<map_value_type, seg_mngr_t> map_value_type_allocator; typedef bc::map< string_t, complex_data, std::less<> , map_value_type_allocator> complex_map_type; int main () { managed_shared_memory segment(create_only, "MyName", 65536); //Create shared memory //Construct the map calling map(key_compare, allocator_type), the allocator argument is implicit complex_map_type *mymap = segment.construct<complex_map_type>("MyMap")(); //Efficient transparent insertion, string_t and complex_data are constructed in-place with implicit allocators mymap->try_emplace("key_str", "default_name"); return 0; }