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

时间:2013-03-12 11:29:17

标签: c++ c++11

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

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

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

非常感谢帮助! ;)

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

类模板value是一个有区别的联合的瘦包装:类模板variant(类似于boost变体)。此变体的允许类型表示JSON类型Object,Array,String,Number Boolean和Null。

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

value中定义的类型机制用于根据给定Policiesvalue为容器类型创建递归类型定义本身。 (当使用std :: map或std :: vector时,变体类不需要使用“递归包装器”来实现JSON容器)。也就是说,这种类型的机制创建用于表示JSON类型的实际类型,例如std::vector等于value_type的{​​{1}}数组valuestd::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<value, std::allocator<value>> 和object_type成为
std::map<std::string, value, std::less<std::string>, std::allocator<std::pair<const std::string, value>>>

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

现在,我们的想法是让用户指定一个自定义分配器,它用于“容器”中的所有分配和所有构造,即value。例如,竞技场分配器。

为此,我扩展了value的模板参数,如下所示:

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

并且还适应了类型机制,以便在适当时使用scoped_allocator_adaptor。

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

现在,当使用状态自定义分配器时,这只能工作一半。更确切地说,它可以工作 - 除了范围分配器的传播不会正确发生。 E.g:

假设有一个状态有限的自定义分配器,其属性为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类型,即字符串的实际副本。

也许这可以通过“allocator-extended”构造函数来实现,虽然它本身不使用分配器 - 但需要在需要时适当地“传播”它们。

但我怎么能做到这一点?

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

“策略”是模板模板参数,其第一个参数是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 个答案:

答案 0 :(得分:2)

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

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

如果您在scoped_allocator_adaptor内部使用scoped_allocator_adaptor,那么您需要做的就是让范围分配器工作,确保value支持uses-allocator构造,该构造由value特征。如果定义std::uses_allocator<value, Alloc>value::allocator_type为真,则该特征将自动为真。如果std::is_convertible<value::allocator_type, Alloc>不存在,您可以将特征专门化为真(这是value::allocator_typestd::promise所做的事情):

std::packaged_task

这意味着当namespace std { template<typename A, typename... P, typename A2> struct uses_allocator<value<A, P...>, A2> : is_convertible<A, A2> { }; } 由支持uses-allocator构造的类型构造时,它将尝试将分配器传递给value构造函数,因此您还需要添加allocator-扩展构造函数,以便它可以传递。

为此,您可以按照自己的意愿工作:

value

// Insert via copy-ctor: array1.push_back(v1); 模板必须支持使用分配器构造,或者您必须将其包装起来custom_allocatorValue::array_type::allocator_type,我无法从您的问题中判断出这是否属实

当然,为了实现这一点,标准库实现必须支持作用域分配器,您使用的是什么编译器?我只熟悉GCC在这个领域的地位,GCC 4.7仅支持scoped_allocator_adaptor<custom_allocator<Value>>。对于GCC 4.8,我也增加了对std::vector的支持。我希望剩下的容器都将用于GCC 4.9。

N.B。您的类型也应该使用forward_list进行所有与分配器相关的操作,而不是直接在分配器类型上调用成员函数。

  

是的,当生成类型时,值实际上是不完整的

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