Boost C++ 库

……这是世界上评价最高、设计最精良的 C++ 库项目之一。 Herb SutterAndrei Alexandrescu, C++ Coding Standards

托管内存段中的容器 - Boost C++ 函数库
PrevUpHomeNext

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

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

  • “标准明确允许库实现者假定每个分配器的指针类型别名都是 T* 的同义词”
  • “标准规定,STL 的实现可以假定同一类型的每个分配器对象都是等效的,并且始终相等比较”

显然,如果任何 STL 实现忽略指针类型别名,则任何智能指针都不能用作 allocator::pointer。如果 STL 实现假定同一类型的所有分配器对象都相等,它将假定每个从不同内存池分配的两个分配器是相等的,这会带来完全的灾难。

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

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

理论上,这在 C++11 中得到了修复,当时引入了 std::pointer_traits 工具,并且标准库容器添加了对它们的支持。然而,由于 ABI 方面的考虑和用户需求的低下,一些实现仍然不支持 花式指针 类型,例如 boost::interprocess:offset_ptr。这些实现仍然在某些容器中使用原始指针来实现内部数据或迭代器。

这种不可移植的情况在 “P0773R0: Towards meaningful fancy pointers” 中有描述。

我们能否使用偏移花式指针类型来启用内存区域的安全共享?

可以;但对供应商的要求不明确,并且对程序员存在陷阱。

方案 (A),偏移指针,只有一个可行的解决方案,“继续部分支持”。

[Important] 重要提示

自 2011 年 Boost.ContainerBoost.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
  • 字符串
  • 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 分配器隐式转换为分配其他类型的分配器的能力。

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

#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