Want to Efficiently Overcome Mismatch Between Key Types in a Map in Boost.Interprocess Shared Memory

want to efficiently overcome mismatch between key types in a map in Boost.Interprocess shared memory

You can use a custom comparator

   struct MyLess {
template <typename T, typename U>
bool operator()(const T&t, const U&u) const
{
return t<u;
}
};

In your code you can just typedef it as StringComparator

UPDATE To the comments


Multi Index To The Rescue

If you want to replace the std::map/boost::container::map with a Boost Multi Index container (which supports lookup by CompatibleKey), here's a demo of how to do it:

I've borrowed some of the idea's from the documentation section Emulating standard containers with multi_index_container.

Note that std::string as the lookup key still won't work, but you can easily use .c_strio() in that event.

Live On Coliru

#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/allocators/allocator.hpp>
#include <boost/interprocess/containers/map.hpp>
#include <boost/interprocess/containers/vector.hpp>
#include <boost/interprocess/containers/string.hpp>

#include <boost/multi_index_container.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/member.hpp>

namespace emulation {
template <typename T1,typename T2,typename Alloc>
struct mutable_pair
{
typedef T1 first_type;
typedef T2 second_type;

mutable_pair(Alloc alloc):first(T1(alloc)),second(T2(alloc)){}
mutable_pair(const T1& f,const T2& s):first(f),second(s){}
mutable_pair(const std::pair<T1,T2>& p):first(p.first),second(p.second){}

T1 first;
mutable T2 second;
};

using namespace boost::multi_index;

template <typename Key, typename T, typename Compare, typename Allocator, typename Element = mutable_pair<Key, T, Allocator> >
using map = multi_index_container<
Element,
indexed_by<
ordered_unique<member<Element,Key,&Element::first>,Compare>
>,
typename Allocator::template rebind<Element>::other
>;

template <typename Key, typename T, typename Compare, typename Allocator, typename Element = mutable_pair<Key, T, Allocator> >
using multimap = multi_index_container<
Element,
indexed_by<
ordered_non_unique<member<Element,Key,&Element::first>,Compare>
>,
typename Allocator::template rebind<Element>::other
>;

template <typename Key, typename T, typename Compare, typename Allocator>
struct wrap_map : map<Key, T, Compare, Allocator> {
typedef map<Key, T, Compare, Allocator> base_type;
typedef typename base_type::template nth_index<0>::type index_type;

wrap_map(Allocator alloc) : base_type({}, alloc)
{
}

wrap_map(Compare cmp, Allocator alloc) : base_type(
typename base_type::ctor_args_list{
typename index_type::ctor_args { typename index_type::key_from_value {}, cmp }
},
alloc)
{
}
};
}

// Typedefs of allocators and containers
namespace Shared {
typedef boost::interprocess::managed_shared_memory Segment;
typedef boost::interprocess::managed_shared_memory::segment_manager SegmentManager;
typedef boost::interprocess::allocator<void, SegmentManager> Allocator;
typedef boost::interprocess::allocator<char, SegmentManager> CharAllocator;
typedef boost::interprocess::basic_string<char, std::char_traits<char>, CharAllocator> String;

struct MyLess {
template <typename T, typename U> bool operator()(const T &t, const U &u) const { return t < u; }
};
typedef MyLess StringComparator;

typedef boost::interprocess::allocator<char, SegmentManager> StringAlloc;
typedef emulation::mutable_pair<const String, String, StringAlloc> MapItem;
typedef boost::interprocess::allocator<MapItem, SegmentManager> MapItemAllocator;
typedef emulation::wrap_map<String, String, StringComparator, MapItemAllocator> Map;
}

int main(void) {
struct shm_remove {
shm_remove() { boost::interprocess::shared_memory_object::remove("MySharedMemory"); }
~shm_remove() { boost::interprocess::shared_memory_object::remove("MySharedMemory"); }
} remover;

// Create shared memory
Shared::Segment seg(boost::interprocess::create_only, "MySharedMemory", 65536);
Shared::Allocator alloc(seg.get_segment_manager());

// An instance of the string comparator, to construct the map
Shared::StringComparator cmp;

// Construct the shared memory map
Shared::Map *myMapPtr = seg.construct<Shared::Map>("myMap")(cmp, alloc);

myMapPtr->emplace(Shared::String("foo", alloc), Shared::String("bar", alloc));
myMapPtr->emplace(Shared::String("goo", alloc), Shared::String("car", alloc));
myMapPtr->emplace(Shared::String("hoo", alloc), Shared::String("dar", alloc));

Shared::String key("foo", alloc);

// This is the point of the exercise:
auto it = myMapPtr->find(key);

if (it!=myMapPtr->end())
std::cout << "Found: '" << it->first << "' -> '" << it->second << "'\n";

// this is now okay too
char szkey[] = "foo";
it = myMapPtr->find(szkey);
if (it!=myMapPtr->end())
std::cout << "Found: '" << it->first << "' -> '" << it->second << "'\n";

// this is now okay too
std::string skey("foo");
it = myMapPtr->find(skey.c_str());
if (it!=myMapPtr->end())
std::cout << "Found: '" << it->first << "' -> '" << it->second << "'\n";

return 0;
}

Prints:

Found: 'foo' -> 'bar'
Found: 'foo' -> 'bar'
Found: 'foo' -> 'bar'

Scoped Allocators For Extra Awesomesauce?

Now, interestingly, Boost Container supports Scoped Allocators, so you could do away with the repeated passing of the allocators, however, Boost Multi Index sadly doesn't support it fully. Here's a halfway approach that's about as far as I could get it (still somewhat user friendlier):

Live On Coliru

#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/allocators/allocator.hpp>
#include <boost/interprocess/containers/map.hpp>
#include <boost/interprocess/containers/vector.hpp>
#include <boost/interprocess/containers/string.hpp>

#include <boost/container/scoped_allocator.hpp>

#include <boost/multi_index_container.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/member.hpp>

namespace emulation {
template <typename T1,typename T2,typename Alloc>
struct mutable_pair
{
typedef Alloc allocator_type;
typedef T1 first_type;
typedef T2 second_type;

mutable_pair(Alloc alloc):first(T1(alloc)),second(T2(alloc)){}
mutable_pair(const T1& f,const T2& s):first(f),second(s){}
mutable_pair(const std::pair<T1,T2>& p):first(p.first),second(p.second){}

template <typename U, typename V, typename Alloc2>
mutable_pair(const U& f,const V& s, Alloc2 alloc):first(f, alloc),second(s, alloc){}

T1 first;
mutable T2 second;
};

using namespace boost::multi_index;

template <typename Key, typename T, typename Compare, typename Allocator, typename Element = mutable_pair<Key, T, Allocator> >
using map = multi_index_container<
Element,
indexed_by<
ordered_unique<member<Element,Key,&Element::first>,Compare>
>,
typename Allocator::template rebind<Element>::other
>;

template <typename Key, typename T, typename Compare, typename Allocator, typename Element = mutable_pair<Key, T, Allocator> >
using multimap = multi_index_container<
Element,
indexed_by<
ordered_non_unique<member<Element,Key,&Element::first>,Compare>
>,
typename Allocator::template rebind<Element>::other
>;

template <typename Key, typename T, typename Compare, typename Allocator>
struct wrap_map : map<Key, T, Compare, Allocator> {
typedef map<Key, T, Compare, Allocator> base_type;
typedef typename base_type::template nth_index<0>::type index_type;

wrap_map(Allocator alloc) : base_type({}, alloc)
{
}

wrap_map(Compare cmp, Allocator alloc) : base_type(
typename base_type::ctor_args_list{
typename index_type::ctor_args { typename index_type::key_from_value {}, cmp }
},
alloc)
{
}
};
}

// Typedefs of allocators and containers
namespace Shared {
typedef boost::interprocess::managed_shared_memory Segment;
typedef Segment::segment_manager SegmentManager;
typedef boost::container::scoped_allocator_adaptor<boost::interprocess::allocator<void, SegmentManager> > Allocator;

typedef Allocator::rebind<char>::other CharAllocator;
typedef boost::interprocess::basic_string<char, std::char_traits<char>, CharAllocator> String;

struct MyLess {
template <typename T, typename U> bool operator()(const T &t, const U &u) const { return t < u; }
};
typedef MyLess StringComparator;

typedef emulation::mutable_pair<String, String, CharAllocator> MapItem;
typedef Allocator::rebind<MapItem>::other MapItemAllocator;
typedef emulation::wrap_map<String, String, StringComparator, MapItemAllocator> Map;
}

int main(void) {
struct shm_remove {
shm_remove() { boost::interprocess::shared_memory_object::remove("MySharedMemory"); }
~shm_remove() { boost::interprocess::shared_memory_object::remove("MySharedMemory"); }
} remover;

// Create shared memory
Shared::Segment seg(boost::interprocess::create_only, "MySharedMemory", 65536);
Shared::Allocator alloc(seg.get_segment_manager());

// An instance of the string comparator, to construct the map
Shared::StringComparator cmp;

// Construct the shared memory map
Shared::Map *myMapPtr = seg.construct<Shared::Map>("myMap")(cmp, alloc);

myMapPtr->emplace("foo", "bar", alloc);
myMapPtr->emplace("goo", "car", alloc);
myMapPtr->emplace("hoo", "dar", alloc);

// This the only version I can get to work. But it forces you to create a
// copy of the key you are searching for, in the managed segment.
Shared::String key("foo", alloc);

// This is the point of the exercise:
auto it = myMapPtr->find(key);

if (it!=myMapPtr->end())
std::cout << "Found: '" << it->first << "' -> '" << it->second << "'\n";

// this is now okay too
char szkey[] = "foo";
it = myMapPtr->find(szkey);
if (it!=myMapPtr->end())
std::cout << "Found: '" << it->first << "' -> '" << it->second << "'\n";

// this is now okay too
std::string skey("foo");
it = myMapPtr->find(skey.c_str());
if (it!=myMapPtr->end())
std::cout << "Found: '" << it->first << "' -> '" << it->second << "'\n";

return 0;
}

Also printing

Found: 'foo' -> 'bar'
Found: 'foo' -> 'bar'
Found: 'foo' -> 'bar'

boost unordered map in shared memory using std::string key

Yes.

You can see an example here:

  • Boost interprocess unordered_map compilation

You will want to watch the overhead when doing lookups. Using non-standard key equality/hash functions can solve this:

  • want to efficiently overcome mismatch between key types in a map in Boost.Interprocess shared memory

    Note that that answer concerns itself with ordered containers

Using boost's scoped_allocator_adaptor for shared memory container

Shooting for the stars, are we :) Painless allocator propagation is the holy grail.

It looks like the Shared:Scoped allocator adapter is key to propagating the allocator from a top level container to its children.

Indeed

I'm not sure if this is different when applied to the boost containers vs the standard containers.

In my understanding, modern C++ standard libraries should support the same, but in practice my experience has shown that it often worked with Boost Container containers. (YMMV and standard library implementations may/will catch up)

What To Do

I think you will want to understand the uses_allocator protocol: https://en.cppreference.com/w/cpp/memory/uses_allocator

Sample Image

This really answers all of your questions, I suppose. I'll try to come up with a quick sample if I can.

Demo

So far I have got the following two approaches working:

struct MyStruct {
String data;

using allocator_type = Alloc<char>;

MyStruct(MyStruct const& rhs, allocator_type = {}) : data(rhs.data) {}
template <typename I, typename = std::enable_if_t<not std::is_same_v<MyStruct, I>, void> >
MyStruct(I&& init, allocator_type a)
: data(std::forward<I>(init), a)
{ }
};

This allows:

Shared::Segment mf(bip::open_or_create, "test.bin", 10<<20);

auto& db = *mf.find_or_construct<Shared::Database>("db")(mf.get_segment_manager());

db.emplace_back("one");
db.emplace_back("two");
db.emplace_back("three");

The slightly more complicated/versatile (?) approach also works:

    MyStruct(std::allocator_arg_t, allocator_type, MyStruct const& rhs) : data(rhs.data) {}

template <
typename I,
typename A = Alloc<char>,
typename = std::enable_if_t<not std::is_same_v<MyStruct, I>, void> >
MyStruct(std::allocator_arg_t, A alloc, I&& init)
: data(std::forward<I>(init), alloc.get_segment_manager())
{ }

It appears that for the current use-case, the inner typedef allocator_type is enough to signal that MyStruct supports allocator-construction, making the specialization of uses_allocator<MyStruct, ...> redundant.

Full Listing

Live On Coliru

#include <boost/interprocess/containers/vector.hpp>
#include <boost/interprocess/containers/string.hpp>
#include <boost/interprocess/managed_mapped_file.hpp>
#include <boost/interprocess/allocators/allocator.hpp>
#include <boost/container/scoped_allocator.hpp>
#include <iostream>

namespace bip = boost::interprocess;

namespace Shared {
using Segment = bip::managed_mapped_file;
using SMgr = Segment::segment_manager;

template <typename T> using Alloc = boost::container::scoped_allocator_adaptor<
bip::allocator<T, SMgr>
>;

template <typename T> using Vec = boost::container::vector<T, Alloc<T> >;

using String = bip::basic_string<char, std::char_traits<char>, Alloc<char> >;

struct MyStruct {
String data;

using allocator_type = Alloc<char>;

#if 1 // one approach
MyStruct(std::allocator_arg_t, allocator_type, MyStruct const& rhs) : data(rhs.data) {}

template <
typename I,
typename A = Alloc<char>,
typename = std::enable_if_t<not std::is_same_v<MyStruct, I>, void> >
MyStruct(std::allocator_arg_t, A alloc, I&& init)
: data(std::forward<I>(init), alloc.get_segment_manager())
{ }
#else // the simpler(?) approach
MyStruct(MyStruct const& rhs, allocator_type = {}) : data(rhs.data) {}
template <typename I, typename = std::enable_if_t<not std::is_same_v<MyStruct, I>, void> >
MyStruct(I&& init, allocator_type a)
: data(std::forward<I>(init), a)
{ }
#endif
};

using Database = Vec<MyStruct>;
}

namespace std {
// this appears optional for the current use case
template <typename T> struct uses_allocator<Shared::MyStruct, T> : std::true_type {};
}

int main() {
Shared::Segment mf(bip::open_or_create, "test.bin", 10<<20);

auto& db = *mf.find_or_construct<Shared::Database>("db")(mf.get_segment_manager());

db.emplace_back("one");
db.emplace_back("two");
db.emplace_back("three");

std::cout << "db has " << db.size() << " elements:";

for (auto& el : db) {
std::cout << " " << el.data;
}

std::cout << std::endl;
}

Invoking it three times:

db has 3 elements: one two three
db has 6 elements: one two three one two three
db has 9 elements: one two three one two three one two three

Update: More Complicated

In response to the comments, let's make it more complicated in two ways:

  • The struct constructor will take various arguments initializing various members, some of which will use an allocator.
  • We want to store it in a Map, and some of the use-patterns involving map are pesky with scoped allocator support (emplacement, map[k]=v update-assignment with default-construction requirements)
  • std::initalizer_list<> will not be deduced in generic forwarding wrappers :(

Defining the struct:

struct MyPodStruct {
using allocator_type = ScopedAlloc<char>;

int a = 0; // simplify default constructor using NSMI
int b = 0;
Vec<uint8_t> data;

explicit MyPodStruct(allocator_type alloc) : data(alloc) {}
//MyPodStruct(MyPodStruct const&) = default;
//MyPodStruct(MyPodStruct&&) = default;
//MyPodStruct& operator=(MyPodStruct const&) = default;
//MyPodStruct& operator=(MyPodStruct&&) = default;

MyPodStruct(std::allocator_arg_t, allocator_type, MyPodStruct&& rhs) : MyPodStruct(std::move(rhs)) {}
MyPodStruct(std::allocator_arg_t, allocator_type, MyPodStruct const& rhs) : MyPodStruct(rhs) {}

template <typename I, typename A = Alloc<char>>
MyPodStruct(std::allocator_arg_t, A alloc, int a, int b, I&& init)
: MyPodStruct(a, b, Vec<uint8_t>(std::forward<I>(init), alloc)) { }

private:
explicit MyPodStruct(int a, int b, Vec<uint8_t> data) : a(a), b(b), data(std::move(data)) {}
};

It addresses "default construction" (under uses-allocator regime), and the various constructors that take multiple arguments. Not that SFINAE is no longer required to disambiguate the uses-allocator copy-constructor, because the number of arguments differs.

Now, using it is more involved than above. Specifically, since there are multiple constructor arguments to be forwarded, we need another bit of "construction protocol": std::piece_wise_construct_t.

The inline comments talk about QoL/QoI concerns and pitfalls:

int main() {
using Shared::MyPodStruct;
Shared::Segment mf(bip::open_or_create, "test.bin", 10<<10); // smaller for Coliru
auto mgr = mf.get_segment_manager();

auto& db = *mf.find_or_construct<Shared::Database>("complex")(mgr);

// Issues with brace-enclosed initializer list
using Bytes = std::initializer_list<uint8_t>;

// More magic: piecewise construction protocol :)
static constexpr std::piecewise_construct_t pw{};
using std::forward_as_tuple;
db.emplace(pw, forward_as_tuple("one"), forward_as_tuple(1,2, Bytes {1,2}));
db.emplace(pw, forward_as_tuple("two"), forward_as_tuple(2,3, Bytes {4}));
db.emplace(pw, forward_as_tuple("three"), forward_as_tuple(3,4, Bytes {5,8}));

std::cout << "\n=== Before updates\n" << db << std::endl;

// Clumsy:
db[Shared::String("one", mgr)] = MyPodStruct{std::allocator_arg, mgr, 1,20, Bytes {7,8,9}};

// As efficient or better, and less clumsy:
auto insert_or_update = [&db](auto&& key, auto&&... initializers) -> MyPodStruct& {
// Be careful not to move twice: https://en.cppreference.com/w/cpp/container/map/emplace
// > The element may be constructed even if there already is an element
// > with the key in the container, in which case the newly constructed
// > element will be destroyed immediately.
if (auto insertion = db.emplace(pw, forward_as_tuple(key), std::tie(initializers...)); insertion.second) {
return insertion.first->second;
} else {
return insertion.first->second = MyPodStruct(
std::allocator_arg,
db.get_allocator(),
std::forward<decltype(initializers)>(initializers)...); // forwarding ok here
}
};

insert_or_update("two", 2,30, Bytes{});
insert_or_update("nine", 9,100, Bytes{5,6});

// partial updates:
db.at(Shared::String("nine", mgr)).data.push_back(42);

// For more efficient key lookups in the case of unlikely insertion, use
// heterogeneous comparer, see https://stackoverflow.com/a/27330042/85371

std::cout << "\n=== After updates\n" << db << std::endl;
}

Which prints Live On Coliru

=== Before updates
db has 3 elements: {one: 1,2, [1,2,]} {three: 3,4, [5,8,]} {two: 2,3, [4,]}

=== After updates
db has 4 elements: {nine: 9,100, [5,6,42,]} {one: 1,20, [7,8,9,]} {three: 3,4, [5,8,]} {two: 2,30, []}

Full Listing

For conservation: Live On Coliru

#include <boost/interprocess/containers/map.hpp>
#include <boost/interprocess/containers/string.hpp>
#include <boost/interprocess/containers/vector.hpp>
#include <boost/interprocess/managed_mapped_file.hpp>
#include <boost/interprocess/allocators/allocator.hpp>
#include <boost/container/scoped_allocator.hpp>
#include <iostream>

namespace bip = boost::interprocess;

namespace Shared {
using Segment = bip::managed_mapped_file;
using SMgr = Segment::segment_manager;

template <typename T> using Alloc = bip::allocator<T, SMgr>;
template <typename T> using ScopedAlloc = boost::container::scoped_allocator_adaptor<Alloc<T> >;

using String = bip::basic_string<char, std::char_traits<char>, Alloc<char> >;

using boost::interprocess::map;

template <typename T> using Vec =
boost::container::vector<T, ScopedAlloc<T>>;

template <typename K, typename T> using Map =
map<K, T, std::less<K>, ScopedAlloc<typename map<K, T>::value_type>>;

struct MyPodStruct {
using allocator_type = ScopedAlloc<char>;

int a = 0; // simplify default constructor using NSMI
int b = 0;
Vec<uint8_t> data;

explicit MyPodStruct(allocator_type alloc) : data(alloc) {}
//MyPodStruct(MyPodStruct const&) = default;
//MyPodStruct(MyPodStruct&&) = default;
//MyPodStruct& operator=(MyPodStruct const&) = default;
//MyPodStruct& operator=(MyPodStruct&&) = default;

MyPodStruct(std::allocator_arg_t, allocator_type, MyPodStruct&& rhs) : MyPodStruct(std::move(rhs)) {}
MyPodStruct(std::allocator_arg_t, allocator_type, MyPodStruct const& rhs) : MyPodStruct(rhs) {}

template <typename I, typename A = Alloc<char>>
MyPodStruct(std::allocator_arg_t, A alloc, int a, int b, I&& init)
: MyPodStruct(a, b, Vec<uint8_t>(std::forward<I>(init), alloc)) { }

private:
explicit MyPodStruct(int a, int b, Vec<uint8_t> data) : a(a), b(b), data(std::move(data)) {}
};

using Database = Map<String, MyPodStruct>;

static inline std::ostream& operator<<(std::ostream& os, Database const& db) {
os << "db has " << db.size() << " elements:";

for (auto& [k,v] : db) {
os << " {" << k << ": " << v.a << "," << v.b << ", [";
for (unsigned i : v.data)
os << i << ",";
os << "]}";
}

return os;
}
}

int main() {
using Shared::MyPodStruct;
Shared::Segment mf(bip::open_or_create, "test.bin", 10<<10); // smaller for Coliru
auto mgr = mf.get_segment_manager();

auto& db = *mf.find_or_construct<Shared::Database>("complex")(mgr);

// Issues with brace-enclosed initializer list
using Bytes = std::initializer_list<uint8_t>;

// More magic: piecewise construction protocol :)
static constexpr std::piecewise_construct_t pw{};
using std::forward_as_tuple;
db.emplace(pw, forward_as_tuple("one"), forward_as_tuple(1,2, Bytes {1,2}));
db.emplace(pw, forward_as_tuple("two"), forward_as_tuple(2,3, Bytes {4}));
db.emplace(pw, forward_as_tuple("three"), forward_as_tuple(3,4, Bytes {5,8}));

std::cout << "\n=== Before updates\n" << db << std::endl;

// Clumsy:
db[Shared::String("one", mgr)] = MyPodStruct{std::allocator_arg, mgr, 1,20, Bytes {7,8,9}};

// As efficient or better, and less clumsy:
auto insert_or_update = [&db](auto&& key, auto&&... initializers) -> MyPodStruct& {
// Be careful not to move twice: https://en.cppreference.com/w/cpp/container/map/emplace
// > The element may be constructed even if there already is an element
// > with the key in the container, in which case the newly constructed
// > element will be destroyed immediately.
if (auto insertion = db.emplace(pw, forward_as_tuple(key), std::tie(initializers...)); insertion.second) {
return insertion.first->second;
} else {
return insertion.first->second = MyPodStruct(
std::allocator_arg,
db.get_allocator(),
std::forward<decltype(initializers)>(initializers)...); // forwarding ok here
}
};

insert_or_update("two", 2,30, Bytes{});
insert_or_update("nine", 9,100, Bytes{5,6});

// partial updates:
db.at(Shared::String("nine", mgr)).data.push_back(42);

// For more efficient key lookups in the case of unlikely insertion, use
// heterogeneous comparer, see https://stackoverflow.com/a/27330042/85371

std::cout << "\n=== After updates\n" << db << std::endl;
}

Can we use boost::multi_index::multi_index_container as a multiindex map?

Of course.

  • http://www.boost.org/doc/libs/1_57_0/libs/multi_index/doc/tutorial/techniques.html#emulate_std_containers

However, first look at Boost Bimap, as it appears to already do what you describe:

  • The One Minute tutorial: http://www.boost.org/doc/libs/1_57_0/libs/bimap/doc/html/boost_bimap/one_minute_tutorial.html

I've given an example of how to use Boost Multi Index to emulate a map here:

  • want to efficiently overcome mismatch between key types in a map in Boost.Interprocess shared memory

The relevant type machinery looks like this:

namespace emulation {
template <typename T1,typename T2,typename Alloc>
struct mutable_pair
{
typedef T1 first_type;
typedef T2 second_type;

mutable_pair(Alloc alloc):first(T1(alloc)),second(T2(alloc)){}
mutable_pair(const T1& f,const T2& s):first(f),second(s){}
mutable_pair(const std::pair<T1,T2>& p):first(p.first),second(p.second){}

T1 first;
mutable T2 second;
};

using namespace boost::multi_index;

template <typename Key, typename T, typename Compare, typename Allocator, typename Element = mutable_pair<Key, T, Allocator> >
using map = multi_index_container<
Element,
indexed_by<
ordered_unique<member<Element,Key,&Element::first>,Compare>
>,
typename Allocator::template rebind<Element>::other
>;

template <typename Key, typename T, typename Compare, typename Allocator, typename Element = mutable_pair<Key, T, Allocator> >
using multimap = multi_index_container<
Element,
indexed_by<
ordered_non_unique<member<Element,Key,&Element::first>,Compare>
>,
typename Allocator::template rebind<Element>::other
>;
}

In case you want that answer contains a full demo, although it contains some unrelated complexity in order to use shared memory allocators.

Is it Possible to Place Vectors Containing Pointers into Shared Managed Memory?

You can.

However you have to make sure the pointers are also allocated from the shared memory.

If you show a SSCCE I'll show you how I'd do that.



Related Topics



Leave a reply



Submit