在自定义容器类中使用分配器

时间:2014-01-19 19:28:53

标签: c++ memory-management c++11

我正在开发一个类似容器的类,我想使用标准分配器基础结构,就像标准容器一样。在网上我发现了很多关于如何单独使用std::allocator类,或者如何为标准容器定义自定义分配器的材料,但是关于如何一般地使用标准符合分配器的材料是非常罕见的特别是在C ++ 11的上下文中,从编写自定义分配器的人的角度来看,事情似乎要容易得多,但从容器的角度来看更复杂。

所以我的问题是如何以最通用的方式正确使用标准的符合标准的分配器,具体来说:

  • 首先,我应该何时以这种方式设计自定义容器?使用默认分配器而不是普通的新/删除是否有明显的性能开销(包括缺少优化机会)?
  • 我是否必须显式调用包含对象的析构函数?
  • 如何区分有状态和无状态分配器?
  • 如何处理有状态分配器?
    • 当(如果有的话)两个实例可以互换时(我什么时候可以用一个实例销毁用另一个实例分配的内存)?
    • 复制容器时必须复制它们吗?
    • 移动容器时可以/必须移动它们吗?
    • 在容器的移动构造函数和移动赋值运算符中,何时可以将指针移动到已分配的内存,何时我必须分配不同的内存并移动元素呢?
  • 在这种情况下是否存在关于异常安全的问题?

我对C ++ 11世界的答案特别感兴趣(它是否改变了C ++ 14中的任何内容?)

1 个答案:

答案 0 :(得分:20)

在下面的所有答案中,我假设您要遵循C ++ 11标准定义容器的规则。该标准不要求您以这种方式编写自定义容器。

  
      
  • 首先,我应该何时以这种方式设计自定义容器?是否存在合理的性能开销(包括缺失   优化机会)使用默认分配器而不是   普通的新/删除?
  •   

自定义分配器最常见和最有效的用途之一是出于性能原因将其分配到堆栈。如果您的自定义容器无法接受这样的分配器,那么您的客户端将无法执行此类优化。

  
      
  • 我是否必须明确调用包含的对象'析构函数?
  •   

您必须明确调用allocator_traits<allocator_type>::destroy(alloc, ptr),然后直接调用value_type的析构函数,或者调用destroy allocator_type成员}。

  
      
  • 如何区分有状态和无状态分配器?
  •   

我不会打扰。假设分配器是有状态的。

  
      
  • 如何处理有状态分配器?
  •   

非常仔细地遵循C ++ 11中规定的规则。特别是那些在[container.requirements.general]中指定的分配器感知容器。规则太多,无法在此列出。但是,我很乐意回答有关这些规则的具体问题。但是第一步是获得标准的副本,并阅读它,至少是容器需求部分。为此,我建议the latest C++14 working draft

  
      
  • 当(如果有的话)两个实例可以互换时(我什么时候可以用一个实例销毁用另一个实例分配的内存)?
  •   

如果两个分配器比较相等,则可以解除分配从另一个分配器的指针。副本(通过复制构造或复制分配)需要比较相等。

  
      
  • 复制容器时必须复制它们吗?
  •   

搜索propagate_onselect_on_container_copy_construction的标准,了解详细信息。简而言之,答案是&#34;它取决于。&#34;

  
      
  • 移动容器时可以/必须移动它们吗?
  •   

必须为移动建设。移动分配取决于propagate_on_container_move_assignment

  
      
  • 在容器的移动构造函数和移动赋值运算符中,何时可以将指针移动到已分配的内存,何时需要分配不同的内存   并改为移动元素?
  •   

新移动的构造容器应该通过移动构造rhs的分配器来获得它的分配器。这两个分配器需要比较相等。因此,您可以为所有已分配的内存传输内存所有权,对于该指针,该指针的有效状态为rhs中的nullptr

移动赋值运算符可以说是最复杂的:行为取决于propagate_on_container_move_assignment以及两个分配器是否相等。我的&#34;分配器备忘单中有一个更完整的描述。&#34;

  
      
  • 在这种情况下是否存在关于异常安全的问题?
  •   

是的,吨。 [allocator.requirements]列出了容器可以依赖的分配器要求。这包括哪些操作可以和不可以抛出。

您还需要处理分配器pointer实际上不是value_type*的可能性。 [allocator.requirements]也是查找这些细节的地方。

祝你好运。这不是一个初学者项目。如果您有更具体的问题,请将其发布到SO。要开始,请直接进入标准。我不知道有关该主题的任何其他权威来源。

这是我为自己制作的备忘单,描述了分配器的行为,以及容器的特殊成员。它用英文写成,而不是标准版。如果您发现我的备忘单和C ++ 14工作草案之间存在任何差异,请相信工作草案。一个已知的差异是我已经以标准没有的方式添加了noexcept规范。


  

分配器行为:

C() noexcept(is_nothrow_default_constructible<allocator_type>::value);

C(const C& c);
     

alloc_traits::select_on_container_copy_construction(c)获取分配器。

C(const C& c, const allocator_type& a);
     

a获取分配器。

C(C&& c)
  noexcept(is_nothrow_move_constructible<allocator_type>::value && ...);
     

move(c.get_allocator())获取分配器,传输资源。

C(C&& c, const allocator_type& a);
     

a获取分配器。如果a == c.get_allocator()转移资源。   如果c[i],则移动每个a != c.get_allocator()的构造。

C& operator=(const C& c);
     

如果alloc_traits::propagate_on_container_copy_assignment::valuetrue,   copy指定分配器。在这种情况下,如果分配器先前不相等   转让,从*this转储所有资源。

C& operator=(C&& c)
  noexcept(
    allocator_type::propagate_on_container_move_assignment::value &&
    is_nothrow_move_assignable<allocator_type>::value);
     

如果alloc_traits::propagate_on_container_move_assignment::valuetrue,   转储资源,移动分配分配器,并从c转移资源。

     

如果alloc_traits::propagate_on_container_move_assignment::valuefalse   和get_allocator() == c.get_allocator(),转储资源和转移   来自c的资源。

     

如果alloc_traits::propagate_on_container_move_assignment::valuefalse   和get_allocator() != c.get_allocator(),移动分配每个c[i]

void swap(C& c)
  noexcept(!allocator_type::propagate_on_container_swap::value ||
           __is_nothrow_swappable<allocator_type>::value);
     

如果alloc_traits::propagate_on_container_swap::valuetrue,则互换   分配器。无论如何,交换资源。如果是未定义的行为   分配器不相等,propagate_on_container_swap::valuefalse