C ++ 17:多态分配器,调试资源和自定义类型。

C ++ 17:多态分配器,调试资源和自定义类型。

时间:2020-9-29 作者:admin

在本文中,请看一看多态分配器,并了解如何调试源和自定义类型。

在我先前关于多态分配器的文章中,我们讨论了一些基本思想。例如,您已经看到一个使用单调资源pmr::vector保存的pmr::string。在这样的容器中使用自定义类型怎么样?如何启用?让我们来看看。

目标

上一篇文章中,有类似的代码:

爪哇

1个
字符 缓冲区[ 256 ] = {}; //堆栈上的一个小缓冲区
2
STD :: fill_nSTD ::开始缓冲器),的std ::尺寸缓冲区-  1'_');
3

4
std :: pmr :: monotonic_buffer_resource { std :: databuffer),
5
                                         std :: sizebuffer)};
6
std :: pmr ::向量< std :: pmr ::字符串>  vec { pool };
7
// ...

查看完整示例@Coliru

在这种情况下,当您在向量中插入新字符串时,新对象还将使用向量中指定的内存资源。

“使用”是指字符串对象必须分配一些内存的情况,这意味着长字符串不适合“短字符串优化”缓冲区。如果该对象不需要获取任何额外的内存块,则它只是父向量的连续内存博客的一部分。

由于pmr::string可以使用向量的内存资源,因此这意味着它以某种方式“知道”分配器。

如何编写自定义类型:

爪哇


1个
结构 产品{
2
    std ::字符串 名称
3
    字符 费用{ 0 }; //为简单起见
4
};

如果我将此插入向量:

爪哇

1个
std :: pmr :: vector <产品> 产品{};

然后,向量将使用提供的内存资源,但不会将其传播到中Product。这样,如果Product必须为其分配内存,name则将使用默认分配器。

我们必须“启用”我们的类型并使它知道分配器,以便它可以利用父容器中的分配器。

参考文献

在开始之前,如果您想自己尝试使用分配器,我想提到一些很好的参考。这个主题不是很流行,因此查找教程或好的说明并不像我发现的那么容易。

  • CppCon 2017:Pablo Halpern“分配器:好的部分”-YouTube-分配器和PMR新内容的深入说明。即使使用一些基于节点的容器的测试实现。
  • CppCon 2015:YouTube的Andrei Alexandrescu“ std :: allocator…”-YouTube-从介绍中可以学到std::allocator解决远近问题并使之保持一致的意图,但是现在我们希望从该系统中获得更多。
  • 在C ++ 0x中allocator_traits的用途是什么?- 堆栈溢出
  • Jean Guegant的博客–从零开始制作与STL兼容的哈希图-第3部分-迭代器和分配器的奇妙世界-这是一篇有关如何更多地使用分配器的超级详细的博客文章,更不用说好的轶事和笑话了:)
  • 感谢您的内存(分配器) -Sticky Bits-对分配器,它们的故事以及PMR新模型如何适用的有价值的介绍。您还可以了解如何编写跟踪pmr分配器以及如何*_pool_resource工作。
  • CppCon 2018:Arthur O’Dwyer,“分配者是堆的把手” – Arthur的精彩演讲,他分享了理解分配器所需的全部知识。
  • C ++ 17- Nicolai Josuttis撰写的《完整指南》 -书中有一长篇关于PMR分配器的章节。

调试内存资源

为了有效地使用分配器,拥有一个允许我们跟踪容器中的内存分配的工具将很方便。

请参阅我列出的如何执行操作的资源,但是以基本形式,我们必须执行以下操作:

  • 从获得 std::pmr::memory_resource
  • 实行:
    • do_allocate() -用于以给定对齐方式分配N个字节的函数。
    • do_deallocate() -当对象想要释放内存时调用的函数。
    • do_is_equal() -它用于比较两个对象是否具有相同的分配器,在大多数情况下,您可以比较地址,但是如果使用某些分配器适配器,则可能需要查看一些高级教程。
  • 将您的自定义内存资源设置为对对象和容器有效。

这是基于Sticky BitsPablo Halpern的演讲的代码

爪哇


1个
class  debug_resourcepublic  std :: pmr :: memory_resource {
2
公开
3
    明确的 debug_resourcestd ::字符串 名称
4
       std :: pmr :: memory_resource *  up  =  std :: pmr :: get_default_resource())
5
        :_name { std :: movename)},_upstream { up }
6
    {}
7

8
    void *  do_allocatesize_t 字节size_t 对齐覆盖{
9
        std :: cout  <<  _name  <<  “ do_allocate():”  << 字节 <<  '\ n' ;
10
        无效*  ret  =  _upstream- >分配字节对齐);
11
        返回 ret ;
12
    }
13
    void  do_deallocatevoid *  ptrsize_t 字节size_t 对齐覆盖{
14
        std :: cout  <<  _name  <<  “ do_deallocate():”  << 字节 <<  '\ n' ;
15
        _upstream- >释放ptrbytesalignment);
16
    }
17
    bool  do_is_equalconst  std :: pmr :: memory_resource  otherconst  noexcept 覆盖{
18
        返回 this  ==  other ;
19
    }
20

21
私人的
22
    std ::字符串 _name ;
23
    std :: pmr :: memory_resource *  _upstream ;
24
};

调试资源只是实际内存资源的包装。如您在分配/解除分配函数中看到的,我们只记录数字,然后将实际作业推迟到上游资源。

用例示例:

爪哇


1个
constexpr  size_t  BUF_SIZE  =  128 ;
2
字符 缓冲区[ BUF_SIZE ] = {}; //堆栈上的一个小缓冲区
3
STD :: fill_nSTD ::开始缓冲器),的std ::尺寸缓冲区-  1'_');
4

5
debug_resource  default_dbg { “ default” };
6
std :: pmr :: monotonic_buffer_resource { std :: databuffer),std :: sizebuffer),default_dbg };
7
debug_resource  dbg { “ pool”pool };
8
std :: pmr ::向量< std ::字符串> 字符串{ dbg };
9

10
emplace_back“ Hello Short String”);
11
emplace_back“ Hello Short String 2”);

输出:

爪哇


1个
 do_allocate():32
2
 do_allocate():64
3
 do_deallocate():32
4
 do_deallocate():64

上面我们两次使用了调试资源,第一个"pool"用于记录请求到的每个分配monotonic_buffer_resource。在输出中,您可以看到我们有两个分配和两个释放。

还有另一个调试资源"default"。它被配置为单调缓冲区的父级。这意味着如果pool需要分配。,那么它必须通过我们的"default"对象来请求内存。

如果您添加三个字符串,例如:

爪哇


1个
emplace_back“ Hello Short String”);
2
emplace_back“ Hello Short String 2”);
3
emplace_back“你好,字符串更长”);

然后输出是不同的:

爪哇


1个
 do_allocate():32
2
 do_allocate():64
3
 do_deallocate():32
4
 do_allocate():128
5
默认 do_allocate():256
6
 do_deallocate():64
7
 do_deallocate():128
8
默认的 do_deallocate():256

这次您可以注意到,对于第三个字符串,我们预定义的小缓冲区内没有空间,这就是为什么单调资源不得不要求“默认”另外256个字节的原因。

在此处查看完整代码@Coliru

自定义类型

配备了调试资源以及一些“缓冲区打印技术”,我们现在可以检查我们的自定义类型是否可与分配器一起使用。让我们来看看:

爪哇


1个
struct  SimpleProduct {
2
    std ::字符串 _name ;
3
    char  _price { 0 };
4
};
5

6
int  main(){
7
    constexpr  size_t  BUF_SIZE  =  256 ;
8
    字符 缓冲区[ BUF_SIZE ] = {}; //堆栈上的一个小缓冲区
9
    STD :: fill_nSTD ::开始缓冲器),的std ::尺寸缓冲区-  1'_');
10

11
    常量 自动 BufferPrinter  = [](STD :: string_view  BUF的std :: string_view 标题){
12
        std :: cout  << 标题 <<  “:\ n” ;
13
        为size_t  =  0 ; <  BUF大小(); ++){
14
            std :: cout  <<buf [ i ] > =  ''   buf [ i ]:'#');
15
            如果((i + 164  ==  0std :: cout  <<  '\ n' ;
16
        }
17
        std :: cout  <<  '\ n' ;
18
    };
19

20
    BufferPrinterbuffer“ initial buffer”);
21

22
    debug_resource  default_dbg { “ default” };
23
    std :: pmr :: monotonic_buffer_resource { std :: databuffer),std :: sizebuffer),default_dbg };
24
    debug_resource  dbg { “ buffer”pool };
25
    std :: pmr :: vector < SimpleProduct > 产品{ dbg };
26
    产品储备金4);
27

28
    产品emplace_backSimpleProduct { “ car”'7' });
29
    产品emplace_backSimpleProduct { “ TV”'9' });
30
    产品emplace_backSimpleProduct { “产品名称再延长一点'4' });
31

32
    BufferPrinterSTD :: string_view {缓冲器BUF_SIZE },“之后插入”);
33
}

可能的输出:

爪哇


1个
________________________________________________________________
2
________________________________________________________________
3
________________________________________________________________
4
________________________________________________________________
5
缓冲区 do_allocate():160
6
 插入
7
p “ ---  ..-....... car.er ..-〜---  ..7 _______-” ---  .. - ....... TV .. er ..
8
- ---  .. 9_______0 - Ĵ - ...... - ...... - ...... ________4_______________
9
________________________________________________________________
10
_______________________________________________________________
11
缓冲区 do_deallocate():160

图例:在输出中,点.表示缓冲区的元素为0。不是零但小于空格32的值显示为-

让我们解密代码和输出:

向量包含的SimpleProduct对象只是一个字符串和一个数字。我们保留了四个元素,您会注意到我们的调试资源记录了160个字节的分配。插入三个元素之后,我们可以发现car和数字7(这就是为什么我将其char用作价格类型)。然后TV9。我们也可以注意到4第三个元素的价格,但是那里没有名字。这意味着它被分配到其他地方。

实时代码@Coliru

分配器感知类型

知道自定义类型分配器并不困难,但是我们必须记住以下几点:

  • pmr::*尽可能使用类型,以便您可以将它们传递给分配器。
  • 声明,allocator_type以便分配器特征可以“识别”您的类型使用分配器。您还可以为分配器特征声明其他属性,但是在大多数情况下,默认设置就可以了。
  • 声明采用分配器的构造函数,并将其进一步传递给您的成员。
  • 声明复制并移动构造器,该构造器还负责分配器。
  • 与分配和移动操作相同。

这意味着我们相对简单的自定义类型声明必须增加:

爪哇


1个
结构 产品{
2
    使用 allocator_type  =  std :: pmr :: polymorphic_allocator < char > ;
3

4
    显式 产品allocator_type  alloc  = {})
5
    :_name { alloc } {}
6

7
    产品std :: pmr ::字符串 名称字符 价格
8
            const  allocator_type alloc  = {})
9
    :_name { std :: movename),alloc },_price { price } {}
10

11
    产品const  Product  otherconst  allocator_type  alloc
12
    :_name {其他_namealloc },_price { other_price } {}
13

14
    产品产品&& 其他const  allocator_type alloc
15
    :_name {性病::移动其他_name),ALLOC },_price {其它_price } {}
16

17
    产品 运营商=常量 产品 其他= 默认;
18
    产品 运营商=产品&& 其他= 默认值;
19

20
    std :: pmr ::字符串 _name ;
21
    char  _price { '0' };
22
};

这是一个示例测试代码:

爪哇


1个
debug_resource  default_dbg { “ default” };
2
std :: pmr :: monotonic_buffer_resource { std :: databuffer),
3
                       std :: sizebuffer),default_dbg };
4
debug_resource  dbg { “ buffer”pool };
5
std :: pmr :: vector <产品> 产品{ dbg };
6
产品储备金3);
7

8
产品emplace_back产品{ “汽车”'7' , DBG });
9
产品emplace_back产品{ “TV” , '9' DBG });
10
产品emplace_back产品{ “多一点的时间商品名”'4' , DBG });

输出:

爪哇


1个
缓冲区 do_allocate():144
2
缓冲区 do_allocate():26
3
 插入
4
-----  .. -----  .. - .......汽车 .. - ....... 7_______ -----  .. -----  ..
5
- .......电视..  .. - ....... 9_______ -----  .. @ ----  .. - ....... - 。......
6
________4_______ 产品名称更长一点 ______________________
7
_______________________________________________________________
8
缓冲区 do_deallocate():26
9
缓冲区 do_deallocate():144

示例代码@Coliru

在输出中,第一个内存分配144是给的vector.reserve(3),然后我们为另一个较长的字符串(第三个元素)分配了另一个。还打印了完整的缓冲区(Coliru链接中可用的代码),该缓冲区显示了字符串所在的位置。

“全”自定义容器

我们的自定义对象由其他pmr::容器组成,因此更加简单!而且我想在大多数情况下,您可以利用现有类型。但是,如果您需要访问分配器并执行自定义内存分配,那么您应该看到Pablo的演讲,他在其中指导了一个自定义列表容器的示例。

CppCon 2017:Pablo Halpern“分配者:好的部分”-YouTube

概要

在此博客文章中,我们在标准库的更深层次内进行了另一次旅程。尽管分配器令人恐惧,但使用多态分配器似乎使事情变得更加舒适。如果您坚持使用pmr::命名空间中公开的许多标准容器,则会发生这种情况。

让我知道您对分配器和pmr::东西有什么经验。也许您以不同的方式实现您的类型?(我尝试编写正确的代码,但仍然有些细微之处是棘手的。让我们一起学习一些知识吧:)   福州APP开发

版权所有:https://www.eraycloud.com 转载请注明出处