If the address space is shared it is safe to use such an allocator for the complete structure
You're mixing "shared address space" and "address mapped at the same address". Because if there was no sharing of address space, there would also not be an issue storing raw pointers.
What is important is to use fancy pointers like boost::interprocess::offset_ptr<T>
. The interprocess allocator uses offset_ptr<T>
.
This is why you need to use container implementations that support fancy pointers.
You're very close. The canonical way to do what you sketch would look like this:
Live On Coliru
#include <boost/container/string.hpp>
#include <boost/container/vector.hpp>
#include <boost/interprocess/allocators/allocator.hpp>
#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/managed_mapped_file.hpp>
#include <string>
namespace bip = boost::interprocess;
namespace Shared {
#ifdef COLIRU
using Segment = bip::managed_mapped_file;
#else
using Segment = bip::managed_shared_memory;
#endif
using Mgr = Segment::segment_manager;
template <typename T> using Alloc = bip::allocator<T, Mgr>;
template <typename T> using Vector = std::vector<T, Alloc<T>>;
using String = boost::container::basic_string< //
char, std::char_traits<char>, Alloc<char>>;
struct Bar {
using allocator_type = Alloc<char>;
String first_name, last_name;
template <typename Alloc> Bar(std::string_view first_name, std::string_view last_name, Alloc alloc) :
first_name(first_name, alloc), last_name(last_name, alloc) {}
};
struct Snafu {
std::array<int, 5> no_problem;
};
struct Foo {
Vector<Bar> bars;
Vector<Snafu> snafus;
template <typename Alloc> //
Foo(Alloc alloc) : bars(alloc)
, snafus(alloc) {}
};
}
#include <iostream>
static inline std::ostream& operator<<(std::ostream& os, Shared::Foo const& foo) {
os << "Foo\n================\nBars:\n";
for (auto& [f, l] : foo.bars)
os << " - " << f << ", " << l << "\n";
os << "nafus:";
for (auto& s : foo.snafus) {
os << "\n - ";
for (auto el : s.no_problem)
os << " " << el;
}
return os << "\n";
}
int main() { Shared::Segment msm(bip::open_or_create, "my_shared_mem", 10ull << 10);
Shared::Foo& foo = *msm.find_or_construct<Shared::Foo>("the_foo") //
(msm.get_segment_manager()); // constructor arguments
foo.bars.emplace_back("John", "Doe", msm.get_segment_manager());
foo.bars.emplace_back("Jane", "Deer", msm.get_segment_manager());
foo.bars.emplace_back("Igor", "Stravinsky", msm.get_segment_manager());
foo.bars.emplace_back("Rimsky", "Korsakov", msm.get_segment_manager());
foo.snafus.push_back({1, 2, 3});
foo.snafus.push_back({2, 3, 4});
foo.snafus.push_back({3, 4, 5, 6, 7});
std::cout << foo << "\n";
}
Output first run:
Foo
================
Bars:
- John, Doe
- Jane, Deer
- Igor, Stravinsky
- Rimsky, Korsakov
nafus:
- 1 2 3 0 0
- 2 3 4 0 0
- 3 4 5 6 7
Output second run:
Foo
================
Bars:
- John, Doe
- Jane, Deer
- Igor, Stravinsky
- Rimsky, Korsakov
- John, Doe
- Jane, Deer
- Igor, Stravinsky
- Rimsky, Korsakov
nafus:
- 1 2 3 0 0
- 2 3 4 0 0
- 3 4 5 6 7
- 1 2 3 0 0
- 2 3 4 0 0
- 3 4 5 6 7
BONUS
As always, I cannot be remiss about scoped_allocator_adaptor
, the best thing since sliced semiconductors:
template <typename T>
using Alloc = boost::container::scoped_allocator_adaptor< //
bip::allocator<T, Mgr>>;
Now, anywhere uses_allocator
protocol is used, you get magic allocator propagation:
Live On Coliru
#include <boost/container/scoped_allocator.hpp>
#include <boost/container/string.hpp>
#include <boost/container/vector.hpp>
#include <boost/interprocess/allocators/allocator.hpp>
#include <boost/interprocess/managed_mapped_file.hpp>
#include <boost/interprocess/managed_shared_memory.hpp>
#include <string>
namespace bip = boost::interprocess;
namespace Shared {
#ifdef COLIRU
using Segment = bip::managed_mapped_file;
#else
using Segment = bip::managed_shared_memory;
#endif
using Mgr = Segment::segment_manager;
template <typename T>
using Alloc = boost::container::scoped_allocator_adaptor< //
bip::allocator<T, Mgr>>;
template <typename T> using Vector = std::vector<T, Alloc<T>>;
using String = boost::container::basic_string< //
char, std::char_traits<char>, Alloc<char>>;
struct Bar {
using allocator_type = Alloc<char>;
String first_name, last_name;
template <typename Alloc>
Bar(std::allocator_arg_t, Alloc alloc) : first_name(alloc)
, last_name(alloc) {}
template <typename Alloc>
Bar(Alloc&& alloc) : first_name(alloc)
, last_name(alloc) {}
Bar(Bar const&) = default;
template <typename Alloc>
Bar(Bar const& rhs, Alloc alloc) : first_name(rhs.first_name, alloc), last_name(rhs.last_name, alloc) {}
Bar& operator=(Bar const&) = default;
template <typename Alloc> Bar(std::string_view first_name, std::string_view last_name, Alloc alloc) :
first_name(first_name, alloc), last_name(last_name, alloc) {}
};
struct Snafu {
std::array<int, 5> no_problem;
};
struct Foo {
Vector<Bar> bars;
Vector<Snafu> snafus;
template <typename Alloc> //
Foo(Alloc alloc) : bars(alloc)
, snafus(alloc) {}
};
}
#include <iostream>
static inline std::ostream& operator<<(std::ostream& os, Shared::Foo const& foo) {
os << "Foo\n================\nBars:\n";
for (auto& [f, l] : foo.bars)
os << " - " << f << ", " << l << "\n";
os << "Snafus:";
for (auto& s : foo.snafus) {
os << "\n - ";
for (auto el : s.no_problem)
os << " " << el;
}
return os << "\n";
}
int main() { Shared::Segment msm(bip::open_or_create, "my_shared_mem", 10ull << 10);
Shared::Foo& foo = *msm.find_or_construct<Shared::Foo>("the_foo") //
(msm.get_segment_manager()); // constructor arguments
foo.bars.emplace_back("John", "Doe");
foo.bars.emplace_back("Jane", "Deer");
foo.bars.emplace_back("Igor", "Stravinsky");
foo.bars.emplace_back("Rimsky", "Korsakov");
foo.snafus.push_back({1, 2, 3});
foo.snafus.push_back({2, 3, 4});
foo.snafus.push_back({3, 4, 5, 6, 7});
std::cout << foo << "\n";
}
With identical output