Boost C++ 库

...世界上最受推崇和专业设计的 C++ 库项目之一。 Herb SutterAndrei Alexandrescu,《C++ 编码标准

PrevUpHomeNext

托管内存段中的容器

Boost.Interprocess 分配器的容器要求
托管内存段中的可移植容器
这分配在哪里?
容器的容器

Boost.Interprocess 兼容 STL 的分配器提供了一个兼容 STL 的分配器接口,并且如果它们将其内部 pointer typedef 定义为相对指针,则可以用于将 STL 容器放置在共享内存、内存映射文件或用户定义的内存段中。

最初,在 C++98 和 C++03 中,这个 pointer typedef 是无用的。正如 Scott Meyers 在他的《Effective STL》一书的第 10 项中提到的,“注意分配器约定和限制”

  • “标准明确允许库实现者假设每个分配器的 pointer typedef 都是 T* 的同义词”
  • “标准规定,STL 的实现允许假设所有相同类型的分配器对象都是等价的,并且始终比较相等”

显然,如果任何 STL 实现忽略 pointer typedef,则不能使用智能指针作为 allocator::pointer。如果 STL 实现假设所有相同类型的分配器对象都比较相等,它将假设两个分配器(每个分配器从不同的内存池分配)是相等的,这将是一场彻底的灾难。

我们想要使用 Boost.Interprocess 放置在共享内存或内存映射文件中的 STL 容器不能做出任何这些假设,因此

  • STL 容器可能不能假设用一个分配器分配的内存可以用相同类型的其他分配器释放。只有当用一个对象分配的内存可以用另一个对象释放时,所有分配器对象才必须比较相等,这只能在运行时通过 operator==() 进行测试。
  • 容器的内部指针应该是 allocator::pointer 类型,并且容器可能不能假设 allocator::pointer 是原始指针。

理论上,这在 C++11 中得到了修复,当时引入了 std::pointer_traits 实用程序,并且标准库容器添加了对它们的支持。然而,由于 ABI 问题和用户需求不高,一些实现仍然不完全支持像 boost::interprocess:offset_ptr 这样的花式指针类型。这些实现仍然在某些容器中使用原始指针用于内部数据或迭代器实现。

这种不可移植的情况在 “P0773R0:迈向有意义的花式指针” 中进行了描述

我们可以使用偏移花式指针类型来安全地共享内存区域吗?

是的;但是对供应商的要求尚不明确,并且程序员可能会遇到陷阱。

情景 (A),偏移指针,只有一个站得住脚的解决方案,“继续部分支持。”

[Important] 重要提示

由于 Boost.Container 是在 2011 年从 Boost.Interprocess 创建的,因此该库为了向后兼容性,维护了几个 <boost/interprocess/containers/.hpp> 头文件。这些头文件现在已被弃用,并将在未来的 Boost 版本中删除。用户应直接使用 Boost.Container 头文件

由于标准库实现对花式指针的部分支持,Boost.Interprocess 强烈建议使用 Boost.Container 容器,这些容器保证可以与 Boost.Interprocess 一起使用

以下 Boost.Container 容器与 Boost.Interprocess 兼容

  • deque
  • devector
  • flat_map/multimap
  • flat_set/multiset
  • list
  • map/multimap
  • set/multiset
  • slist
  • small_vector
  • stable_vector
  • static_vector
  • string
  • vector

要将这些容器中的任何一个放置在托管内存段中,我们必须使用 Boost.Interprocess 分配器定义分配器模板参数,以便容器在托管内存段中分配值。要将容器本身放置在共享内存中,我们只需像使用 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;
}

这些容器还展示了创建/修改现有容器以使其可以放置在共享内存中是多么容易。

Boost.Interprocess 容器通过两种机制同时放置在共享内存/内存映射文件等中

  • Boost.Interprocess construct<>, find_or_construct<>... 函数。这些函数将 C++ 对象放置在共享内存/内存映射文件中。但这仅放置对象本身,而不放置该对象可能动态分配的内存。
  • 共享内存分配器。这些分配器允许分配共享内存/内存映射文件部分,以便容器可以动态分配内存片段以存储新插入的元素。

这意味着要将任何 Boost.Interprocess 容器(包括 Boost.Interprocess 字符串)放置在共享内存或内存映射文件中,容器必须

  • 将其模板分配器参数定义为 Boost.Interprocess 分配器。
  • 每个容器构造函数都必须将 Boost.Interprocess 分配器作为参数。
  • 您必须使用 construct<>/find_or_construct<>... 函数将容器放置在托管内存中。

如果您执行了前两点,但没有使用 construct<>/find_or_construct<>,那么您创建的容器仅放置在您的进程中,但会从共享内存/内存映射文件为包含的类型分配内存。

让我们看一个例子

#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;
}

在创建容器的容器时,每个容器都需要一个分配器。为了避免使用具有复杂类型定义的多个分配器,我们可以利用 void 分配器提供的类型擦除以及将 void 分配器隐式转换为分配其他类型的分配器的能力。

这里我们有一个在共享内存中构建 map 的示例。键是一个字符串,映射的类型是一个存储多个容器的类

#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;

//Typedefs of allocators and containers
typedef managed_shared_memory::segment_manager                       segment_manager_t;
typedef allocator<void, segment_manager_t>                           void_allocator;
typedef allocator<int, segment_manager_t>                            int_allocator;
typedef boost::container::vector<int, int_allocator>                 int_vector;
typedef allocator<int_vector, segment_manager_t>                     int_vector_allocator;
typedef boost::container::vector<int_vector, int_vector_allocator>   int_vector_vector;
typedef allocator<char, segment_manager_t>                           char_allocator;
typedef boost::container::basic_string<char, std::char_traits<char>, char_allocator>   char_string;

class complex_data
{
   int               id_;
   char_string       char_string_;
   int_vector_vector int_vector_vector_;

   public:
   //Since void_allocator is convertible to any other allocator<T>, we can simplify
   //the initialization taking just one allocator for all inner containers.
   complex_data(int id, const char *name, const void_allocator &void_alloc)
      : id_(id), char_string_(name, void_alloc), int_vector_vector_(void_alloc)
   {}
   //Other members...
};

//Definition of the map holding a string as key and complex_data as mapped type
typedef std::pair<const char_string, complex_data>                      map_value_type;
typedef std::pair<char_string, complex_data>                            movable_to_map_value_type;
typedef allocator<map_value_type, segment_manager_t>                    map_value_type_allocator;
typedef boost::container::map< char_string, complex_data
           , std::less<char_string>, map_value_type_allocator>          complex_map_type;

int main ()
{
   //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;

   //Create shared memory
   managed_shared_memory segment(create_only,"MyName", 65536);

   //An allocator convertible to any allocator<T, segment_manager_t> type
   void_allocator alloc_inst (segment.get_segment_manager());

   //Construct the shared memory map and fill it
   complex_map_type *mymap = segment.construct<complex_map_type>
      //(object name), (first ctor parameter, second ctor parameter)
         ("MyMap")(std::less<char_string>(), alloc_inst);

   for(int i = 0; i < 100; ++i){
      //Both key(string) and value(complex_data) need an allocator in their constructors
      char_string  key_object(alloc_inst);
      complex_data mapped_object(i, "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;
}

PrevUpHomeNext