如何为 Scoped Allocator 模型启用自定义容器

Posted

技术标签:

【中文标题】如何为 Scoped Allocator 模型启用自定义容器【英文标题】:How to Enable a Custom Container for the Scoped Allocator Model 【发布时间】:2013-03-12 11:29:17 【问题描述】:

这是一篇很长的帖子,所以我想在顶部写下唯一的问题:

似乎我需要为自定义容器实现“分配器扩展”构造函数,该容器本身不使用分配器,但将其传播到其内部实现,这是一个变体类型,其允许的类型可能是一个容器,如std::map,但也是一种不需要分配器的类型,比如布尔值。

一个人,我不知道如何做到这一点。

非常感谢您的帮助! ;)

“自定义容器”是一个类模板 value,它是 JSON 数据结构表示的实现。

类模板value 是一个围绕可区分联合的薄包装器:类模板variant(类似于boost 变体)。此变体允许的类型表示 JSON 类型 Object、Array、String、Number Boolean 和 Null。

类模板value 有一个可变参数模板模板参数包Policies,它基本上定义了JSON 类型的实现方式。默认情况下,JSON 类型使用 std::map(用于 Object)、std::vector(用于 Array)、std::string(用于 JSON 数据字符串)和一些代表剩余 JSON 类型的自定义类来实现。

value 中定义的类型机器用于根据给定的Policiesvalue 本身为容器类型创建递归类型定义。 (例如,当变体类使用 std::map 或 std::vector 时,它不需要使用“递归包装器”来实现 JSON 容器)。也就是说,这种类型机制会创建用于表示 JSON 类型的实际类型,例如std::vector 表示 value_type 等于 value 的数组,std::map 表示 mapped_type 等于 value 的对象。 (是的,value 在生成类型时实际上是不完整的)。

类模板value 基本上看起来是这样的(大大简化了):

template <template <typename, typename> class... Policies>
class value

    typedef json::Null                          null_type;
    typedef json::Boolean                       boolean_type;
    typedef typename <typegenerator>::type      float_number_type;
    typedef typename <typegenerator>::type      integral_number_type;
    typedef typename <typegenerator>::type      string_type;
    typedef typename <typegenerator>::type      object_type;
    typedef typename <typegenerator>::type      array_type;


    typedef variant<
        null_type
      , boolean_type
      , float_number_type
      , integral_number_type
      , string_type
      , object_type
      , array_type
    > variant_type;

public:

    ...

private:
    variant_type value_;
;

value 实现了通常的嫌疑犯,例如构造函数、赋值、访问器、比较器等。它还实现了转发构造函数,以便可以使用参数列表构造变体的某种实现类型。

typegenerator基本上会找到相关的实现策略并使用它,除非它没有找到,然后它使用一个默认的实现策略(这里没有详细展示,但请询问是否有些东西应该不清楚)。

例如 array_type 变为: std::vector&lt;value, std::allocator&lt;value&gt;&gt; 并且 object_type 变为std::map&lt;std::string, value, std::less&lt;std::string&gt;, std::allocator&lt;std::pair&lt;const std::string, value&gt;&gt;&gt;

到目前为止,这按预期工作。

现在,我们的想法是让用户指定一个自定义分配器,该分配器用于“容器”内的所有分配和所有构造,即value。例如,arena-allocator。

为此,我将value的模板参数扩展如下:

template <
    typename A = std::allocator<void>,
    template <typename, typename> class... Policies
>
class value ...

并且还调整了类型机制,以便在适当的时候使用 scoped_allocator_adaptor。

请注意,模板参数A 不是value allocator_type - 而只是在类型机器中使用以生成正确的实现类型。也就是说,value 中没有嵌入 allocator_type - 但它会影响实现类型的 allocator_type。

现在,当使用有状态的自定义分配器时,这只能工作到一半。更准确地说,它可以工作——除了作用域分配器的传播不会正确发生。例如:

假设,有一个有状态的自定义分配器,其属性为id,一个整数。不能默认构造。

    typedef test::custom_allocator<void> allocator_t;
    typedef json::value<allocator_t> Value;
    typedef typename Value::string_type String;
    typedef Value::array_type  Array;

    allocator_t a1(1);
    allocator_t a2(2);

    // Create an Array using allocator a1:
    Array array1(a1);
    EXPECT_EQ(a1, array1.get_allocator());

    // Create a value whose impl-type is a String which uses allocator a2:
    Value v1("abc",a2);

    // Insert via copy-ctor:
    array1.push_back(v1);

    // We expect, array1 used allocator a1 in order to construct internal copy of value v1 (containing a string):
    EXPECT_EQ(a1, array1.back().get<String>().get_allocator());
  --> FAILS !!

原因似乎是,array1 不会通过值 v1 的副本将其分配器成员(即 a1)传播到其当前的 imp 类型,即字符串的实际副本。

也许这可以通过值中的“分配器扩展”构造函数来实现,尽管它本身不使用分配器 - 但需要在需要时适当地“传播”它们。

但是我怎样才能做到这一点呢?

编辑:揭示部分类型生成:

“策略”是一个模板模板参数,其第一个参数是 value_type(在本例中为 value),第二个参数是分配器类型。 “策略”定义了如何根据值类型和分配器类型来实现 JSON 类型(例如数组)。

例如,对于 JSON 数组:

template <typename Value, typename Allocator>
struct default_array_policy : array_tag

private:
    typedef Value value_type;
    typedef typename Allocator::template rebind<value_type>::other value_type_allocator;
    typedef GetScopedAllocator<value_type_allocator> allocator_type;
public:
    typedef std::vector<value_type, allocator_type> type;
;

其中GetScopedAllocator 定义为:

template <typename Allocator>
using GetScopedAllocator = typename std::conditional<
    std::is_empty<Allocator>::value,
    Allocator,
    std::scoped_allocator_adaptor<Allocator>
>::type;

【问题讨论】:

【参考方案1】:

决定是否将分配器传递给子元素的逻辑在标准中称为 uses-allocator 构造,参见 20.6.7 [allocator.uses]。

有两个标准组件使用 uses-allocator 协议:std::tuplestd::scoped_allocator_adaptor,您也可以编写用户定义的分配器(但通常使用 scoped_allocator_adaptor 来添加支持更容易)用于现有分配器的协议。)

如果您在value 内部使用scoped_allocator_adaptor,那么您需要做的就是确保value 支持由std::uses_allocator&lt;value, Alloc&gt; 特征指定的uses-allocator 构造。如果定义了value::allocator_type 并且std::is_convertible&lt;value::allocator_type, Alloc&gt; 为真,则该特征将自动为真。如果value::allocator_type 不存在,您可以将特征特化为真实(这就是std::promisestd::packaged_task 所做的):

namespace std

  template<typename A, typename... P, typename A2>
    struct uses_allocator<value<A, P...>, A2>
    : is_convertible<A, A2>
     ;

这意味着当 value 由支持使用分配器构造的类型构造时,它将尝试将分配器传递给 value 构造器,因此您还需要添加分配器扩展的构造器以便它可以通过。

为了让它按你的意愿工作:

// Insert via copy-ctor:
array1.push_back(v1);

custom_allocator 模板必须支持 uses-allocator 构造,或者您必须将其包装为 Value::array_type::allocator_typescoped_allocator_adaptor&lt;custom_allocator&lt;Value&gt;&gt;,我无法从您的问题中判断这是否属实。

当然,标准库实现必须支持范围分配器才能正常工作,您使用的是什么编译器?我只熟悉 GCC 在该领域的地位,其中 GCC 4.7 仅支持 std::vector。对于 GCC 4.8,我也添加了对 forward_list 的支持。我希望剩下的容器都为 GCC 4.9 完成。

注意您的类型还应该使用std::allocator_traits 进行所有与分配器相关的操作,而不是直接在分配器类型上调用成员函数。

是的,在生成类型的那一刻,值实际上是不完整的

除非另有说明,否则在实例化标准模板组件时使用不完整类型作为模板参数是未定义的行为,请参阅 17.6.4.8 [res.on.functions]。它可能适用于您的实现,但不是必需的。

【讨论】:

首先,感谢乔纳森的回复!我正在使用 clang 和 clang 的标准库。我在我的帖子中添加了类型生成的摘录,它应该显示 scoped_allocator_adapter 如何用于容器。关于不完整类型:是的,我知道使用不完整类型作为模板参数可能会很吓人。但是,如果它变得无效/非法,我们不应该得到编译器错误吗? 一般来说,你不能依赖于获取未定义行为的诊断,但是在实例化具有不完整类型的模板的情况下,如果模板需要完整类型,那么你会得到一个错误,所以如果它编译然后你的实现可能允许不完整的类型作为扩展。我没有检查过,但 libc++ 可能确实支持完成这项工作所需的一切。 查看您的编辑,我认为应该可以确保嵌套类型可以使用范围分配器。分配器重新绑定应该使用std::allocator_traits&lt;Allocator&gt;::rebind_alloc&lt;value_type&gt; 来支持未定义rebind 的分配器,因为它在C++11 中不是必需的 关于不完整的类型,我希望这些——正如我在这里使用的——不会导致不希望出现的问题。为了缓解可能出现的问题,我定义了这些“策略”——我可以在其中轻松切换到使用其他容器(例如 boost——被证明能够处理不完整的类型)。 不过,定义扩展分配器的努力似乎仍然存在。由于value 只是我的variant 类的包装,看来我必须另外实现一个“作用域分配器模型”感知变体。我可能需要一些时间来弄清楚如何实施。

以上是关于如何为 Scoped Allocator 模型启用自定义容器的主要内容,如果未能解决你的问题,请参考以下文章

如何为Usb网络上的81ry52芯片启用Linux内核驱动程序

C++——代码风格

如何为 PHP CLI 启用颜色?

如何为特定目录或文件启用特定 gcc 警告? [复制]

如何为 Django 启用 WSGIPassAuthorization?

如何为 Spring Security 启用日志记录?