如何在现代C++中使用分配器(Allocators)

18

根据我在http://en.cppreference.com/w/cpp/memory/allocator上所读到的,大多数分配器功能现在将被弃用。问题是,新代码应该如何使用分配器?现在什么是“正确”的方法?

根据文档,我推断construct是分配器特性的一部分,而不是分配器本身。

我正在构建一个自定义容器,这里是一个非常简单版本的构造函数,这是新设计的良好使用吗?

container::container(std::size_t size, T const& value, Allocator const& allocator) : allocator_(allocator){
    data_ = std::allocator_traits<Alloc>::allocate(allocator_, size);
    for(auto ptr = data_; ptr != data_ + size; ++ptr){
        std::allocator_traits<Allocator>::construct(allocator_, ptr, value)
    }
}

我尝试在循环中使用算法(例如std::for_each),但是我没有成功地使用一个而不需要取地址(operator&)。

我在哪里可以找到一个现代分配器的完整示例?


经过一些调整,我找到了一种使用算法而不是原始循环的方法(可以传递执行策略给它)。我不是很确定,但可能是这个:

    data_ = std::allocator_traits<Allocator>::allocate(allocator_, size);
    std::for_each([policy? deduced from allocator?,] 
        boost::make_counting_iterator(data_),
        boost::make_counting_iterator(data_ + size), 
        [&](auto ptr){std::allocator_traits<Allocator>::construct(allocator_, ptr, value);}
    );

1
自 C++11 开始,这一直是正确的做法(取模 for 循环条件有点问题)。C++17 除了弃用一堆本不应调用的函数之外,没有任何变化。另外,没有 construct_n 函数。 - T.C.
根据我在http://en.cppreference.com/w/cpp/memory/allocator中阅读的内容,大多数分配器的功能现在将被削减和弃用。更正:这不是“deprecated”一词的意思。 Deprecated并不意味着“被削减”,而是表示“有望在以后的版本中删除”。它还没有消失。 - Nicol Bolas
@T.C.,我已经纠正了代码(循环)。有什么正确的方法可以编写循环(例如,可以提供执行策略std::for_each?(哪种方式) - alfC
@NicolBolas,谢谢。是的,我想知道现在正确的做法是什么。 - alfC
1个回答

9

是的,目前的方法是通过std::allocator_traits来实现。这样就可以支持“最小分配器接口”。

http://en.cppreference.com/w/cpp/concept/Allocator

一些要求是可选的:模板std::allocator_traits为所有可选要求提供默认实现,所有标准库容器和其他支持分配器的类都通过std::allocator_traits访问分配器,而不是直接访问。
如果您观察std::allocator_traits成员函数和类型定义,您会发现它们正在检测适当的函数/类型是否存在,并在可能的情况下通过它们进行调度。
如果您已经在使用std::allocator_traits,那么废弃和潜在的将来删除对您不会有任何影响,因为它仅适用于std::allocator及其成员函数/类型定义。
现在,如果你问我,for循环没有问题,使用std::for_each也没有任何好处。有几个uninitialized_*函数,但它们直接使用placement new。如果您真的关心,可以将此代码提取到单独的construct_range函数中。
还存在异常安全问题-如果其中一个构造函数引发异常,则需要销毁早期元素以满足强异常保证并释放内存(析构函数不会在构造函数引发异常的情况下被调用)。

我想要替换原始循环,以便将来可以传递执行策略。您会如何通过 unitialiazed_* 来替换原始循环(哪一个)? - alfC
正如答案中提到的那样,uninitialized_*(在这里我们基本上正在做uninitialized_fill_n所做的事情)函数直接使用放置new,而不是std::allocator_traits<Alloc>::construct,因此它们不会分派到自定义构造函数。现在,如果您计划使用执行策略,那可能是一个好事,因为您无法确定这些分配器的成员函数是否线程安全,以及它们是否会抛出异常。 - milleniumbug
好的,所以在这里真的不应该使用unitialized_*,因为它们对分配器一无所知。这迫使我使用std::for_each,但这让我感到困惑,因为我会这样做:std::for_each([policy,] data_, data_ + size, [&](auto&& e){ std::allocator_traits<Allocator>::construct( allocator_, &e, value) } ),听起来有点可疑。 - alfC
你提到的执行策略非常有趣,因为它意味着执行策略应该取决于分配器,实际上这是一种特性。最终,我认为应该由“allocator_trait”提供一个“construct_n”函数。 - alfC

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接