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

How to Enable a Custom Container for the Scoped Allocator Model

本文关键字:启用 自定义 模型 分配器 作用域      更新时间:2023-10-16

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

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

独自一人,我不知道如何完成这件事。

非常感谢帮助!;)

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

类模板value是区分联合的薄包装:类模板variant(类似于boost变体)。此变体允许的类型表示JSON类型:对象、数组、字符串、数字、布尔值和Null。

类模板value有一个可变模板模板参数包Policies,它基本上定义了JSON类型是如何实现的。默认情况下,JSON类型是用std::map(用于对象),std::vector(用于数组),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<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,而只是在类型机制中使用,以便生成适当的实现类型。也就是说,在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类型,即string的实际拷贝。

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

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

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

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

例如,对于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;

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

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

如果你在value内部使用scoped_allocator_adaptor,那么你需要做的就是确保value支持use -allocator构造,这是由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>
    { };
}

这意味着当一个value被一个支持use -allocator构造的类型构造时,它会尝试将分配器传递给value构造函数,所以你还需要添加分配器扩展的构造函数,这样它就可以被传递了。

让它按你想要的方式工作:

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

custom_allocator模板必须支持use -allocator结构,或者您必须将其包装以便Value::array_type::allocator_typescoped_allocator_adaptor<custom_allocator<Value>>,我无法从您的问题中判断这是否正确。

当然,标准库实现必须支持作用域分配器,你使用什么编译器?我只熟悉GCC在这个领域的状态,GCC 4.7只支持std::vector。对于GCC 4.8,我也添加了对forward_list的支持。我希望剩余的容器都将在GCC 4.9中完成。

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

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

在实例化标准模板组件时使用不完整类型作为模板参数是未定义的行为,除非另有指定,参见17.6.4.8 [res.on.functions]。