使用 boost 的 scoped_allocator_adaptor 作为共享内存容器
Using boost's scoped_allocator_adaptor for shared memory container
我正在编写一个 C++17 应用程序,我需要管理一个 STL 或
boost::collections 共享内存中的等效数据结构。
我不确定最简单的语法(避免遍历分配器
地点)来创建和更新共享数据结构。
我已经搜索了一段时间,但除了一个微不足道的 String->String
map,很难找到专注于自定义数据结构或 POD 结构的示例
经过。 (我怀疑与 POD 结构关联的分配器相当
很容易,因为这些可以从连续的内存中分配,因此可以使用
简单的字符分配器 - 相当于下面的 Shared::Alloc<char>
)。
据我了解,管理数据结构集合的关键在于
共享内存以 stateful allocators 的正确选择为中心
以及让该分配器与其嵌套子级共享的能力。
例如,假设我有一个 map<Shared::String, vector<Shared::String>>
在共享内存中,scoped_allocator_adaptor
的魔力会以某种方式起作用。
除了上面 map<SHMString, vector<String>>
的简单示例,我会
真的很想管理一个 map<SHMString, vector<UserStruct>>
,其中 UserStruct
可以
可以是 POD 结构或包含 String
或 List
字符串的结构。
我从以下内容开始作为另一个答案的有用起点
我在 SO 中找到:
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;
}
看起来 Shared:Scoped
分配器适配器是传播
从顶级容器到其子容器的分配器。我不确定这是不是
应用于增强容器与标准容器时不同。
一个例子,并解释如何以一种方式构造这些对象
将允许我将 scoped_allocator_adaptor
传播到我的 POD 或自定义
struct 正是我要找的。
向星星射击,我们是吗:)无痛分配器传播是圣杯。
It looks like the Shared:Scoped allocator adapter is key to propagating the allocator from a top level container to its children.
确实
I'm not sure if this is different when applied to the boost containers vs the standard containers.
以我的理解,现代 C++ 标准库应该支持相同的,但在实践中,我的经验表明它通常与 Boost Container 容器一起工作。 (YMMV 和标准库实现 may/will 赶上)
做什么
我想你会想了解 uses_allocator
协议:https://en.cppreference.com/w/cpp/memory/uses_allocator
我想这确实回答了您所有的问题。如果可以的话,我会尝试提供一个快速示例。
演示
到目前为止,我已经使用了以下两种方法:
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)
{ }
};
这允许:
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");
稍多 complicated/versatile (?) 的方法也有效:
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.
完整列表
#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;
}
调用三次:
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
更新:更复杂
为了回应评论,让我们通过两种方式让它变得更复杂:
- 结构构造函数将采用各种参数来初始化各种成员,其中一些将使用分配器。
- 我们想将其存储在 Map 中,涉及 map 的一些使用模式是 pesky 具有范围分配器支持(安置,
map[k]=v
更新分配具有默认构造要求)
std::initalizer_list<>
不会在通用转发包装器中推导:(
定义结构:
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)) {}
};
它解决了 "default construction"(在使用分配器机制下),以及采用多个参数的各种构造函数。并不是说 SFINAE 不再需要消除 uses-allocator copy-constructor 的歧义,因为参数的数量不同。
现在,使用它比上面更复杂。具体来说,由于要转发多个构造函数参数,我们需要另一位 "construction protocol":std::piece_wise_construct_t
.
内联评论谈论 QoL/QoI 关注点和陷阱:
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
std::cout << "\n=== After updates\n" << db << std::endl;
}
打印出 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, []}
完整列表
为了保护: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
std::cout << "\n=== After updates\n" << db << std::endl;
}
我正在编写一个 C++17 应用程序,我需要管理一个 STL 或 boost::collections 共享内存中的等效数据结构。
我不确定最简单的语法(避免遍历分配器 地点)来创建和更新共享数据结构。
我已经搜索了一段时间,但除了一个微不足道的 String->String
map,很难找到专注于自定义数据结构或 POD 结构的示例
经过。 (我怀疑与 POD 结构关联的分配器相当
很容易,因为这些可以从连续的内存中分配,因此可以使用
简单的字符分配器 - 相当于下面的 Shared::Alloc<char>
)。
据我了解,管理数据结构集合的关键在于 共享内存以 stateful allocators 的正确选择为中心 以及让该分配器与其嵌套子级共享的能力。
例如,假设我有一个 map<Shared::String, vector<Shared::String>>
在共享内存中,scoped_allocator_adaptor
的魔力会以某种方式起作用。
除了上面 map<SHMString, vector<String>>
的简单示例,我会
真的很想管理一个 map<SHMString, vector<UserStruct>>
,其中 UserStruct
可以
可以是 POD 结构或包含 String
或 List
字符串的结构。
我从以下内容开始作为另一个答案的有用起点 我在 SO 中找到:
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;
}
看起来 Shared:Scoped
分配器适配器是传播
从顶级容器到其子容器的分配器。我不确定这是不是
应用于增强容器与标准容器时不同。
一个例子,并解释如何以一种方式构造这些对象
将允许我将 scoped_allocator_adaptor
传播到我的 POD 或自定义
struct 正是我要找的。
向星星射击,我们是吗:)无痛分配器传播是圣杯。
It looks like the Shared:Scoped allocator adapter is key to propagating the allocator from a top level container to its children.
确实
I'm not sure if this is different when applied to the boost containers vs the standard containers.
以我的理解,现代 C++ 标准库应该支持相同的,但在实践中,我的经验表明它通常与 Boost Container 容器一起工作。 (YMMV 和标准库实现 may/will 赶上)
做什么
我想你会想了解 uses_allocator
协议:https://en.cppreference.com/w/cpp/memory/uses_allocator
我想这确实回答了您所有的问题。如果可以的话,我会尝试提供一个快速示例。
演示
到目前为止,我已经使用了以下两种方法:
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)
{ }
};
这允许:
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");
稍多 complicated/versatile (?) 的方法也有效:
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 thatMyStruct
supports allocator-construction, making the specialization ofuses_allocator<MyStruct, ...>
redundant.
完整列表
#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;
}
调用三次:
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
更新:更复杂
为了回应评论,让我们通过两种方式让它变得更复杂:
- 结构构造函数将采用各种参数来初始化各种成员,其中一些将使用分配器。
- 我们想将其存储在 Map 中,涉及 map 的一些使用模式是 pesky 具有范围分配器支持(安置,
map[k]=v
更新分配具有默认构造要求) std::initalizer_list<>
不会在通用转发包装器中推导:(
定义结构:
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)) {}
};
它解决了 "default construction"(在使用分配器机制下),以及采用多个参数的各种构造函数。并不是说 SFINAE 不再需要消除 uses-allocator copy-constructor 的歧义,因为参数的数量不同。
现在,使用它比上面更复杂。具体来说,由于要转发多个构造函数参数,我们需要另一位 "construction protocol":std::piece_wise_construct_t
.
内联评论谈论 QoL/QoI 关注点和陷阱:
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
std::cout << "\n=== After updates\n" << db << std::endl;
}
打印出 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, []}
完整列表
为了保护: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
std::cout << "\n=== After updates\n" << db << std::endl;
}