标准::allocator_traits<A>的使用

Use of std::allocator_traits<A>

本文关键字:gt lt allocator traits 标准      更新时间:2023-10-16

我想设计一个以分配器类型(如标准第17.6.3.5节中所定义)为模板参数的类模板。我看到std::allocator_traits<A>如何使用默认设置来帮助填充A的任何丢失成员。除此之外,标准库或boost中是否有任何东西可以帮助正确使用分配器?

特别是:

  1. 为了尊重像std::allocator_traits<A>::propagate_on_container_copy_assignment这样的typedef,我是否必须在每个具有类型A成员的类的特殊成员函数中检查这些东西?或者有没有一些包装器类型可以作为成员使用,来处理这些东西?

  2. 如果我想通过在用户可见对象旁边存储额外数据来过度分配以减少分配数量,那么像这样重新绑定分配器合适吗?

template<typename T, typename A>
class MyClass
{
private:
    //...
    struct storage {
        int m_special_data;
        T m_obj;
    };
    typedef typename std::allocator_traits<A>::template rebind_alloc<storage>
        storage_alloc;
    typedef typename std::allocator_traits<A>::template rebind_traits<storage>
        storage_traits;
    storage_alloc m_alloc;
    static T* alloc(T&& obj)
    {
        storage_traits::pointer sp = storage_traits::allocate(m_alloc, 1);
        sp->m_special_data = 69105;
        return ::new(&sp->m_obj) T(std::move(obj));
    }
    //...
};

我不知道有什么能让生活更轻松,allocator_traits通过提供所有样板代码,确实让编写分配器变得更简单,但它对使用分配器没有帮助。

为了在C++03和C++11代码中使用一个分配器API,我将<ext/alloc_traits.h>添加到GCC 4.7中,类模板__gnu_cxx::__alloc_traits提供了一个一致的API,它在C++11模式中使用allocator_traits,并在C++03模式中直接在分配器上调用相关的成员函数。

  1. 不,没有包装器或快捷方式,C++11分配器的要求使容器作者的工作变得更加复杂。根据容器管理内存的方式,每个容器的要求略有不同。对于类矢量类型,在复制分配运算符中,如果propagate_on_container_copy_assignment(POCCA)为false,并且现有容量大于源对象的大小,则可以重用现有内存(如果POCCA为true,并且新分配器不相等,则不能重用旧内存,因为在分配器被替换后,以后不可能取消分配),但这种优化对基于节点的内存没有多大帮助容器,如列表或映射。

  2. 这看起来几乎是正确的,尽管你可能想取代

    return ::new(&sp->m_obj) T(std::move(obj));
    

    带有

    A a(m_alloc);    
    std::allocator_traits<A>::construct(a, &sp->m_obj, std::move(obj));
    return &sp->m_obj;
    

如[container.requirements.general]/3中所述,使用分配器的容器使用allocator_traits<A>::construct来创建元素类型T本身,但分配的任何其他类型(如storage)都不得使用construct

如果storage本身被构造,则它将构造storage::m_obj,除非该成员是可以保持未初始化的类型,例如std::aligned_storage<sizeof(T)>,其稍后可以由allocator_traits<A>::construct显式初始化。或者,单独构建每个需要非平凡构建的成员,例如,如果storage也有string成员:

    storage_traits::pointer sp = storage_traits::allocate(m_alloc, 1);
    sp->m_special_data = 69105;
    ::new (&sp->m_str) std::string("foobar");
    A a(m_alloc);    
    std::allocator_traits<A>::construct(a, &sp->m_obj, std::move(obj));
    return &sp->m_obj;

m_special_data成员是一个平凡的类型,因此一旦为其分配了存储,其生存期就开始了。m_strm_obj成员需要非平凡的初始化,因此它们的生存期在构造函数完成时开始,这分别由placementnew和construct调用完成。

编辑:我最近了解到该标准有一个缺陷(我已经报告了),并且对construct的调用不需要使用反弹分配器,所以这些行:

    A a(m_alloc);    
    std::allocator_traits<A>::construct(a, &sp->m_obj, std::move(obj));

可以替换为:

    std::allocator_traits<storage_alloc>::construct(m_alloc, &sp->m_obj, std::move(obj));

这让生活稍微轻松一些。