容器如何使用分配器

std::allocator#

std::allocator是标准库提供的默认分配器模板,如果容器中没有显式指明分配器的话,就会使用这个分配器模板的实例为容器申请和释放内存。
std::allocator的使用方式也很简单:

allocator<int> alloc;
int *numptr = alloc.allocate(2);
cout << *numptr << endl;
cout << *(numptr + 1) << endl;
*numptr = 42;
*(numptr + 1) = 13;
cout << *numptr << endl;
cout << *(numptr + 1) << endl;
alloc.deallocate(numptr, 2);

这里实例化了一个int类型的分配器alloc,该实例对象只有两个成员函数allocatedeallocate,分别对应申请内存和释放内存(在C++17之前,std::allocator还有两个成员函数constructdestory,分别用于在申请到的内存上构造和析构对应类型的对象,C++17后弃用,并在C++20后彻底删除,并将这部分功能转移到了allocator_traits中)。
在调用了allocate方法后,分配器申请到了两个int大小的内存,并以指针的形式传递出去,此时这块内存是未进行初始化的,然后通过传递出来的指针,可以对申请到的内存进行操作,比如为其赋值。
然后在使用完成后,调用deallocate方法释放掉分配器分配的内存。deallocate需要传入指向内存的指针和对应的size,在默认分配器中,由于是直接使用的delete,size并不影响内存释放,但如果使用的是自定义分配器,这样可以获得更大的灵活性。
std::allocator中定义了一些类型特性,如果使用自定义的分配器,为了兼容标准库的接口,应该定义相同的类型特性,或者直接继承std::allocator模板类,这些类型特性包括:

allocator<int>::difference_type;
allocator<int>::size_type;
allocator<int>::value_type;
allocator<int>::propagate_on_container_move_assignment;
allocator<int>::is_always_equal;

std::allocator_traits#

allocator_traits是分配器的萃取类型,其为所有符合标准的分配器提供了一致的接口,并为指定分配器中未实现的特性提供默认的实现。

allocator<double> alloc2;
allocator_traits<allocator<double>> alloc_t;
double* dbptr = alloc_t.allocate(alloc2, 2);
alloc_t.construct(alloc2, dbptr, 10);
alloc_t.construct(alloc2, dbptr + 1, 20);
cout << *dbptr << endl;
cout << *(dbptr + 1) << endl;
alloc_t.destroy(alloc2, dbptr + 1);
alloc_t.destroy(alloc2, dbptr);
alloc_t.deallocate(alloc2, dbptr, 2);

allocator_traits实例化时指定包装的分配器类型,然后在所有的静态方法的第一个入参指定对应分配器的实例对象。allocator_traits在分配器的基础上提供了constructdestory方法,用于构造和析构对应的元素和类型。
allocator_traits同样可以用于自定义类型的分配器:

class CustomC {
public:
    CustomC() { cout << "default construct custom object\n"; }
    CustomC(int num) : num(num) { cout << "construct custom object\n"; }
    ~CustomC() { cout << "destory custom object\n"; }

    int num{-1};
};

allocator<CustomC> alloc3;
allocator_traits<allocator<CustomC>> alloc_t2;
CustomC *custom_ptr = alloc_t2.allocate(alloc3, 2); 
alloc_t2.construct(alloc3, custom_ptr);             // default construct custom object
alloc_t2.construct(alloc3, custom_ptr + 1, 100);    // construct custom object
cout << custom_ptr->num << endl;                    // -1
cout << (custom_ptr + 1)->num << endl;              // 100
alloc_t2.destroy(alloc3, custom_ptr);               // destory custom object
alloc_t2.destroy(alloc3, custom_ptr + 1);           // destory custom object
alloc_t2.deallocate(alloc3, custom_ptr, 2);

allocator_traits对于标准库默认的分配器有一个特化实现,但我们也完全可以使用自定义的分配器:

template<typename T>
class MyAlloc : public std::allocator<T> { };

MyAlloc<CustomC> myalloc;
allocator_traits<MyAlloc<CustomC>> alloc_t3;
CustomC* mycustom = alloc_t3.allocate(myalloc, 2);
alloc_t3.construct(myalloc, mycustom);
alloc_t3.construct(myalloc, mycustom + 1, 33);
alloc_t3.destroy(myalloc, mycustom + 1);
alloc_t3.destroy(myalloc, mycustom);
alloc_t3.deallocate(myalloc, mycustom, 2);

这与上述默认的分配器执行结果相同,但会匹配allocator_traits更一般的模板,并且由于我们没有实现constructdestory函数,执行时会转而使用allocator_traits默认的方法。这正体现了allocator_traits优势——只需要实现关心的部分,其他无关的部分标准库会帮你实现。

在容器中使用分配器#

为了让容器可以更灵活地使用分配器,分配器萃取类allocator_traits提供了rebind_traitsrebind_alloc类型特征(代替并简化了了C++11中分配器的rebind操作),通过这两个类型特征,可以生成一个与模板参数类型不同,但分配策略相同的分配器和萃取类。
从int类型的分配器中获取一个double类型的分配器:

MyAlloc<int> myialloc;
allocator_traits<MyAlloc<int>> myialloc_trait;
using my_rebind_alloc_type = allocator_traits<MyAlloc<int>>::rebind_alloc<double>;
using my_rebind_alloc_trait = allocator_traits<MyAlloc<int>>::rebind_traits<double>;
my_rebind_alloc_type mydballoc;
my_rebind_alloc_trait my_dballoc_trait;
double* dbptr = my_dballoc_trait.allocate(mydballoc, 2);
my_dballoc_trait.construct(mydballoc, dbptr, 1.3);
my_dballoc_trait.construct(mydballoc, dbptr + 1, 4.2);
cout << *dbptr << endl;
cout << *(dbptr + 1) << endl;
my_dballoc_trait.destroy(mydballoc, dbptr);
my_dballoc_trait.destroy(mydballoc, dbptr + 1);
my_dballoc_trait.deallocate(mydballoc, dbptr, 2);

用途在于容器为了方便管理,往往会对元素类型再次封装,而模板参数传入的分配器是元素类型的,通过重新绑定到容器内部封装的类型,保证分配策略的一致性。
最后演示一个自定义的链表容器和插入操作实现,链表容器内部通过Node类型管理插入的元素:

template <typename T, typename Alloc = std::allocator<T>>
class List {
public:
    using value_type = T;
    using alloc_trait = typename std::allocator_traits<Alloc>;
    using node_alloc_type = typename alloc_trait::template rebind_alloc<Node>;
    using rebind_trait = typename alloc_trait::template rebind_traits<Node>;

    struct Node {
        value_type data{};
        Node* next{nullptr};
    };

    List() = default;

    void insert(T elem) {
        rebind_trait rebind_t;
        Node* node = rebind_t.allocate(m_alloc, 1);
        rebind_t.construct(m_alloc, node);
        node->data = elem;
        return insert(m_root, node);
    }

private:
    void insert(Node* root, Node* node) {
        if(m_root == nullptr) {
            m_root = node;
            return;
        }
        if(m_root->next == nullptr) {
            m_root->next = node;
            return;
        }
        return insert(m_root->next, node);
    }

public:
    Node* m_root{nullptr};
    node_alloc_type m_alloc{};
};

在列表容器内部,保存了容器元素类型的分配器,但在容器内部元素是通过Node结构来封装的。所以每次插入元素时,我们都将分配器重新绑定一个模板参数为Node的分配器,其使用的分配策略和指定传给容器的分配器相同,只是分配的类型不同。

List<int> mylist;
mylist.insert(10);
mylist.insert(20);
cout << mylist.m_root->data << endl;        // 10
cout << mylist.m_root->next->data << endl;  // 20

作者:cwtxx

出处:https://www.cnblogs.com/cwtxx/p/18718206

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   cwtxx  阅读(18)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· Apache Tomcat RCE漏洞复现(CVE-2025-24813)
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示