2

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

似乎我需要为一个自定义容器实现“分配器扩展”构造函数,该容器本身不使用分配器,但将其传播到它的内部实现,它是一个变体类型,其允许的类型可能是一个容器,如 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创建容器类型的递归类型定义。(例如,当变体类使用 std::map 或 std::vector 时,变体类不需要使用“递归包装器”来实现 JSON 容器)。也就是说,这种类型机制创建了用于表示 JSON 类型的实际类型,例如一个for Array 其equals和一个for Object 其equals 。(是的,在生成类型时实际上是不完整的)。Policiesvaluestd::vectorvalue_typevaluestd::mapmapped_typevaluevalue

类模板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不是-allocator_type,value而是仅在类型机器中使用以生成正确的实现类型。也就是说,没有嵌入allocator_type-value但它会影响实现类型的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;
4

1 回答 1

2

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

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

如果你在scoped_allocator_adaptor内部使用,value那么你需要做的就是让作用域分配器工作是确保value支持使用分配器构造,这是由std::uses_allocator<value, Alloc>trait 指定的。value::allocator_type如果已定义且为真,则该特征将自动std::is_convertible<value::allocator_type, Alloc>为真。如果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>
    { };
}

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

为此,如您所愿:

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

custom_allocator模板必须支持 uses-allocator 构造,或者您必须将其包装好,也就是说,Value::array_type::allocator_typescoped_allocator_adaptor<custom_allocator<Value>>无法从您的问题中判断这是否属实。

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

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

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

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

于 2013-03-12T14:25:24.417 回答