Inside a boost::interprocess::managed_shared_memory
, I am trying to create boost::unordered_map
inside another boost::unordered_map
as value, having key as std::string
for both maps. This Map in Map inside a shared memory segment gets accessed by two different processes fetch values from both outer & inner maps.
Below is my implementation & want to know if this is possible/right way or any other better way possible?
boost::interprocess::managed_shared_memory segment(boost::interprocess::open_or_create, "BOOST_SHM", 65536);
typedef std::string KeyType;
typedef std::string ValueType;
typedef std::pair<const KeyType, ValueType> MapType;
typedef boost::interprocess::allocator<MapType, boost::interprocess::managed_shared_memory::segment_manager> ShmemAllocator;
typedef boost::unordered_map<KeyType, ValueType, boost::hash<KeyType>, std::equal_to<KeyType>, ShmemAllocator> InMap;
ShmemAllocator alloc_inst(segment.get_segment_manager());
InMap *inside_map = segment.construct<InMap>("SHM_IN_MAP")(3, boost::hash<KeyType>(), std::equal_to<KeyType>(), alloc_inst);
typedef std::pair<const KeyType, MapType> MIMType;
typedef boost::interprocess::allocator<MIMType, boost::interprocess::managed_shared_memory::segment_manager> MIMShmemAllocator;
typedef boost::unordered_map<KeyType, MapType, boost::hash<KeyType>, std::equal_to<KeyType>, MIMShmemAllocator> OutMap;
//MIMShmemAllocator alloc_inst(segment.get_segment_manager()); /*Commented due to Error*/
OutMap *outside_map = segment.construct<OutMap>("SHM_OUT_MAP")(3, boost::hash<KeyType>(), std::equal_to<KeyType>(), alloc_inst);
Other details:
gcc version 4.8.3 20140911 (Red Hat 4.8.3-9) (GCC) on CentOS 7, BOOST_LIB_VERSION "1_58"
Ok.
So there were a few basic errors, and possibly some confusion.
Next, there are some power tricks that make using nested containers with custom (stateful) allocators much more convenient.
Here's the roll-up of all three hints in a working sample that hopefully helps!
Your strings must use shared memory allocators too
Otherwise the data would be illegal to use in another process. Using the strings would result in Undefined Behaviour.
At the very least, make your strings use the shared memory allocator:
namespace Shared {
using Segment = bip::managed_shared_memory;
template <typename T>
using Alloc = bip::allocator<T, Segment::segment_manager>;
using String = boost::container::basic_string<char, std::char_traits<char>, Alloc<char> >;
using KeyType = String;
using ValueType = String;
}
The map allocators were overspecified. The actual node-types wrapping the pair<K const, v>
elements in a map are implementation defined anyways. So how do maps know how to allocate these nodes?
They rebind allocators: see rebind
in the docs here
So, you can just pass Alloc<void>
. Or the same allocator as for the Shared::String
. The map will figure it out:
typedef boost::unordered_map<KeyType, ValueType, boost::hash<KeyType>, std::equal_to<KeyType>, Alloc<void> > InMap;
typedef boost::unordered_map<KeyType, InMap, boost::hash<KeyType>, std::equal_to<KeyType>, Alloc<void> > OutMap;
Now for the power tips.
Passing stateful allocators all the freaking time is annoying. It makes code a mess. Luckily, c++11 (and Boost Containers for c++03) has you covered:
scoped_allocator_adaptor<T...>
allocator_type
uses_allocator<T>
traitThese helpers can make your life a lot easier. They do this by passing the allocator down to element type constructors when applicable. Automatically. Again, implicit conversions from rebound allocator types make things work.
So, you can actually just construct one outer map with the correct allocator (make it Scoped
) and one key, and from there you don't even have to keep specifying allocators.
Here's a full demo:
#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/containers/string.hpp>
#include <boost/container/scoped_allocator.hpp>
#include <boost/unordered_map.hpp>
#include <iostream>
namespace bip = boost::interprocess;
namespace Shared {
using Segment = bip::managed_shared_memory;
template <typename T>
using Alloc = bip::allocator<T, Segment::segment_manager>;
using Scoped = boost::container::scoped_allocator_adaptor<Alloc<char> >;
using String = boost::container::basic_string<char, std::char_traits<char>, Scoped>;
using KeyType = String;
typedef boost::unordered_map<KeyType, String, boost::hash<KeyType>, std::equal_to<KeyType>, Scoped> InMap;
typedef boost::unordered_map<KeyType, InMap, boost::hash<KeyType>, std::equal_to<KeyType>, Scoped> OutMap;
}
int main() {
srand(time(NULL));
Shared::Segment segment(bip::open_or_create, "BOOST_SHM", 65536);
auto* mgr = segment.get_segment_manager();
Shared::OutMap *p_outside_map = segment.find_or_construct<Shared::OutMap> ("SHM_OUT_MAP") (mgr);
auto& outside_map = *p_outside_map;
Shared::String sskey(mgr); // reduce shared allocations as they are costly (in terms of fragmentation/overhead)
char outer_keys[3], inner_keys[3];
std::generate_n(outer_keys, 3, [] { return rand()%26+'a'; });
std::generate_n(inner_keys, 3, [] { return rand()%26+'a'; });
for (auto key : outer_keys) {
sskey = key;
auto& inner = outside_map[sskey];
for (auto more : inner_keys) {
inner[sskey + "_" + more] += "value";
}
}
for (auto const& oe : outside_map) {
for (auto const& ie : oe.second) {
std::cout << "outside_map[" << oe.first << "][" << ie.first << "] == " << ie.second << "\n";
}
}
}
Actually, to make it work on Coliru, we need to use a mapped file instead:
Run it a few times:
outside_map[s][s_t] == value
outside_map[s][s_r] == value
outside_map[s][s_c] == value
outside_map[f][f_t] == value
outside_map[f][f_r] == value
outside_map[f][f_c] == value
outside_map[o][o_t] == value
outside_map[o][o_r] == value
outside_map[o][o_c] == value
Second run:
outside_map[a][a_d] == value
outside_map[a][a_c] == value
outside_map[a][a_g] == value
outside_map[r][r_d] == value
outside_map[r][r_c] == value
outside_map[r][r_g] == value
outside_map[g][g_d] == value
outside_map[g][g_c] == value
outside_map[g][g_g] == value
outside_map[s][s_t] == value
outside_map[s][s_r] == value
outside_map[s][s_c] == value
outside_map[f][f_t] == value
outside_map[f][f_r] == value
outside_map[f][f_c] == value
outside_map[o][o_t] == value
outside_map[o][o_r] == value
outside_map[o][o_c] == value
Note how each run successfully appends value
to 9 keys in 3 inner maps.