第109章 std::pmr::polymorphic_allocator の動作チェック

目次

109.1. ポリモーフィックアロケーターの実装

 では std::pmr::polymorphic_allocator と std::experimental::pmr::vector の最低限の情報は得られたので、使って確認して見ましょう。

 std::pmr::polymorphic_allocator で説明したいのはポリモーフィズムなので、それに特化したソースコードとするには、そのまんまのポリモーフィズムの例を書くのが早いです。

 つまりですね std::pmr::polymorphic_allocator を切り替えられれば十分です。

 まずはソースコードです。

main.cpp. 

  1 #include <experimental/memory_resource>
  2 #include <experimental/vector>
  3 #include <iostream>
  4 #include <vector>
  5 #include <cstdlib>
  6 #include <cstdio>
  7 #include <cassert>
  8
  9 #define MAX_OBJECTS 32
 10
 11 template<typename T, std::size_t N>
 12 class aligned_allocator : public std::experimental::pmr::memory_resource
 13 {
 14   typedef std::size_t size_type;
 15   typedef T value_type;
 16   typedef value_type* pointer;
 17   typedef const value_type* const_pointer;
 18   typedef value_type& reference;
 19   typedef const value_type& const_reference;
 20
 21 public:
 22   explicit aligned_allocator() noexcept = default;
 23   explicit inline aligned_allocator(const aligned_allocator&)  {}
 24
 25   template<typename U>
 26   inline aligned_allocator(const aligned_allocator<U,N>&) {}
 27
 28 protected:
 29   void *do_allocate(std::size_t n, std::size_t) override
 30   {
 31     std::printf("do_allocate() @ aligned_allocator\n");
 32     return static_cast<pointer>(std::aligned_alloc(16,n * sizeof(T)));
 33   }
 34
 35   void do_deallocate(void *p, size_t, std::size_t) override
 36   {
 37     std::printf("do_deallocate() %p  @ aligned_allocator\n",p);
 38     std::free(p);
 39     p = nullptr;
 40   }
 41
 42   bool do_is_equal(std::experimental::pmr::memory_resource const & other) const noexcept override
 43   {
 44     std::printf("do_is_equal() @ aligned_allocator\n");
 45     return this == &other;
 46   }
 47 };
 48
 49 template<typename T, std::size_t N>
 50 class heap_allocator : public std::experimental::pmr::memory_resource
 51 {
 52   typedef std::size_t size_type;
 53   typedef T value_type;
 54   typedef value_type* pointer;
 55   typedef const value_type* const_pointer;
 56   typedef value_type& reference;
 57   typedef const value_type& const_reference;
 58
 59 public:
 60   explicit heap_allocator() noexcept = default;
 61   explicit inline heap_allocator(const heap_allocator&) {}
 62
 63   template<typename U>
 64   inline heap_allocator(const heap_allocator<U,N>&) {}
 65
 66 protected:
 67   void *do_allocate(std::size_t n, std::size_t) override
 68   {
 69     std::printf("do_allocate() @ heap_allocator\n");
 70     return static_cast<pointer>(std::malloc(n * sizeof(T)));
 71   }
 72
 73   void do_deallocate(void *p, size_t, std::size_t) override
 74   {
 75     std::printf("do_deallocate() %p @ heap_allocator\n",p);
 76     std::free(p);
 77     p = nullptr;
 78   }
 79
 80   bool do_is_equal(std::experimental::pmr::memory_resource const& other) const noexcept override
 81   {
 82     std::printf("do_is_equal() @ heap_allocator\n");
 83     return this == &other;
 84   }
 85 };
 86
 87 int main()
 88 {
 89   aligned_allocator<int,1024> ma;
 90   heap_allocator<char,1024> ha;
 91   std::experimental::pmr::polymorphic_allocator<int> pa(&ma);
 92   std::experimental::pmr::polymorphic_allocator<int> pa2(pa);
 93   std::experimental::pmr::polymorphic_allocator<int> pa3(&ha);
 94
 95   std::experimental::pmr::vector<int> v1(pa);
 96   v1.push_back(1);
 97   v1.push_back(2);
 98   v1.push_back(3);
 99   std::experimental::pmr::vector<int> v2(pa2);
100   v2 = v1;
101
102   std::cout << "v2 size: " << v2.size() << '\n';
103
104   for(auto const& x : v2){
105     std::cout << x << " ";
106   }
107   std::cout << '\n';
108
109   assert(v1.get_allocator() == v2.get_allocator());
110
111   std::experimental::pmr::vector<int> v3(pa3);
112   v3 = v2;
113
114   v3.push_back(100);
115   v3.push_back(1000);
116   v3.pop_back();
117
118   std::cout << "v3 size: " << v3.size() << '\n';
119
120   for(auto const& x : v3){
121     std::cout << x << " ";
122   }
123   std::cout << '\n';
124
125   v1 = v3;
126   v1.push_back(10000);
127
128   return 0;
129 }

ビルドと実行結果. 

$ g++ main.cpp -std=c++17
$ ./a.out
do_allocate() @ aligned_allocator
do_allocate() @ aligned_allocator
do_deallocate() 0x55814a6ac280  @ aligned_allocator
do_allocate() @ aligned_allocator
do_deallocate() 0x55814a6ac2a0  @ aligned_allocator
do_allocate() @ aligned_allocator
v2 size: 3
1 2 3
do_allocate() @ heap_allocator
do_allocate() @ heap_allocator
do_deallocate() 0x55814a6ac280 @ heap_allocator
v3 size: 4
1 2 3 100
do_allocate() @ aligned_allocator
do_deallocate() 0x55814a6ac2d0  @ aligned_allocator
do_deallocate() 0x55814a6ac360 @ heap_allocator
do_deallocate() 0x55814a6ac320  @ aligned_allocator
do_deallocate() 0x55814a6ac380  @ aligned_allocator

 最初に言ったとおり、このソースコードでは memory_resource のインターフェース(抽象クラス)を実装した 2 つのクラスを使い分けるポリモーフィズムを検証します。

 alinged_allocator はアラインメントを指定できるヒープアロケーターです。

 heap_allocator はアラインメントはデフォルトを使うので、アラインメントをデフォルト以外に変えることはできません。

 aligned_allocator は以下のようになってますね。

 11 template<typename T, std::size_t N>
 12 class aligned_allocator : public std::experimental::pmr::memory_resource
 13 {
 14   typedef std::size_t size_type;
 15   typedef T value_type;
 16   typedef value_type* pointer;
 17   typedef const value_type* const_pointer;
 18   typedef value_type& reference;
 19   typedef const value_type& const_reference;
 20
 21 public:
 22   explicit aligned_allocator() noexcept = default;
 23   explicit inline aligned_allocator(const aligned_allocator&)  {}
 24
 25   template<typename U>
 26   inline aligned_allocator(const aligned_allocator<U,N>&) {}
 27
 28 protected:
 29   void *do_allocate(std::size_t n, std::size_t) override
 30   {
 31     std::printf("do_allocate() @ aligned_allocator\n");
 32     return static_cast<pointer>(std::aligned_alloc(16,n * sizeof(T)));
 33   }
 34
 35   void do_deallocate(void *p, size_t, std::size_t) override
 36   {
 37     std::printf("do_deallocate() %p  @ aligned_allocator\n",p);
 38     std::free(p);
 39     p = nullptr;
 40   }
 41
 42   bool do_is_equal(std::experimental::pmr::memory_resource const & other) const noexcept override
 43   {
 44     std::printf("do_is_equal() @ aligned_allocator\n");
 45     return this == &other;
 46   }
 47 };

 std::experimental::pmr::memory_resource の実装をやってるだけのクラスです。

 それとヒープアロケーターも、まあ中身はほぼ同じです。

 49 template<typename T, std::size_t N>
 50 class heap_allocator : public std::experimental::pmr::memory_resource
 51 {
 52   typedef std::size_t size_type;
 53   typedef T value_type;
 54   typedef value_type* pointer;
 55   typedef const value_type* const_pointer;
 56   typedef value_type& reference;
 57   typedef const value_type& const_reference;
 58
 59 public:
 60   explicit heap_allocator() noexcept = default;
 61   explicit inline heap_allocator(const heap_allocator&) {}
 62
 63   template<typename U>
 64   inline heap_allocator(const heap_allocator<U,N>&) {}
 65
 66 protected:
 67   void *do_allocate(std::size_t n, std::size_t) override
 68   {
 69     std::printf("do_allocate() @ heap_allocator\n");
 70     return static_cast<pointer>(std::malloc(n * sizeof(T)));
 71   }
 72
 73   void do_deallocate(void *p, size_t, std::size_t) override
 74   {
 75     std::printf("do_deallocate() %p @ heap_allocator\n",p);
 76     std::free(p);
 77     p = nullptr;
 78   }
 79
 80   bool do_is_equal(std::experimental::pmr::memory_resource const& other) const noexcept override
 81   {
 82     std::printf("do_is_equal() @ heap_allocator\n");
 83     return this == &other;
 84   }
 85 };

 中身は malloc() をやってるだけですね。

 それでアロケーターの検証については少し考えてみました。

 89   aligned_allocator<int,1024> ma;
 90   heap_allocator<char,1024> ha;

  2 つのクラステンプレートのインスタンスを ma と ha とします。@

 次に polymorphic_allocator のコンストラクターの引数に memory_resource を継承した ma と ha を渡します。

 91   std::experimental::pmr::polymorphic_allocator<int> pa(&ma);
 92   std::experimental::pmr::polymorphic_allocator<int> pa2(pa);
 93   std::experimental::pmr::polymorphic_allocator<int> pa3(&ha);

 あとは polymorphic_allocator のインスタンスもコンストラクターの引数に使えますが、ここら辺は polymorphic_allocator の宣言を思いだしてくださいね。

template< class U >
polymorphic_allocator( const polymorphic_allocator<U>& other ) noexcept;
polymorphic_allocator( memory_resource* r);

 これによって pa と pa2 は同じ memory_resource を指すポインターを保持する polymorphic_allocator のインスタンスとなります。

 pa3 は heap_allocator をベースにするので、アロケーターが他の 2 つと異なります。

 ではテストと行きましょう。

 95   std::experimental::pmr::vector<int> v1(pa);
 96   v1.push_back(1);
 97   v1.push_back(2);
 98   v1.push_back(3);
 99   std::experimental::pmr::vector<int> v2(pa2);
100   v2 = v1;
101
102   std::cout << "v2 size: " << v2.size() << '\n';
103
104   for(auto const& x : v2){
105     std::cout << x << " ";
106   }
107   std::cout << '\n';
108
109   assert(v1.get_allocator() == v2.get_allocator());

 std::experimental::pmr::vector は std::vector と同様でアロケーターを引数に指定することができます。

 これによりアロケーターが pa や pa2 の vector インスタンスが生成できます。

 それでポリモーフィズムのチェックは以下の行です。

100   v2 = v1;

 これはコピーができるかの確認をしています。

do_allocate() @ aligned_allocator
do_allocate() @ aligned_allocator
do_deallocate() 0x55814a6ac280  @ aligned_allocator
do_allocate() @ aligned_allocator
do_deallocate() 0x55814a6ac2a0  @ aligned_allocator
do_allocate() @ aligned_allocator
v2 size: 3
1 2 3

 これを見ると v2 のサイズは 3 になってますね。

 つまり v1 の中身がそのまま v2 にコピーされてます。

 ただこの代入ではアロケーターは同じなので、ある意味当然と言えば当然の挙動かと思います。

109   assert(v1.get_allocator() == v2.get_allocator());

 ちなみに std::move による代入は例外を投げる可能性があり、 swap 関数については未定義動作になってしまうので代入はコピーに限ります。

 swap() 関数については実際試してみましたが、何か変な感じになってので、本当にアカンやつなんだなと思いました。

 てなことでコピー代入以外は使わない方が良いと思います。

 次は異なるアロケーターへのコピーができるか試して見ています。

111   std::experimental::pmr::vector<int> v3(pa3);
112   v3 = v2;
113
114   v3.push_back(100);
115   v3.push_back(1000);
116   v3.pop_back();
117
118   std::cout << "v3 size: " << v3.size() << '\n';
119
120   for(auto const& x : v3){
121     std::cout << x << " ";
122   }
123   std::cout << '\n';
124
125   v1 = v3;
126   v1.push_back(10000);

 コピー代入をしている行は以下です。

112   v3 = v2;

 これは heap_allocator を指すポインターを持つコンテナに対して aligned_allocator を指すポインターを持つコンテナをコピー代入しています。

 では出力をチェックです。

do_allocate() @ heap_allocator
do_allocate() @ heap_allocator
do_deallocate() 0x55814a6ac280 @ heap_allocator
v3 size: 4
1 2 3 100
do_allocate() @ aligned_allocator
do_deallocate() 0x55814a6ac2d0  @ aligned_allocator
do_deallocate() 0x55814a6ac360 @ heap_allocator
do_deallocate() 0x55814a6ac320  @ aligned_allocator
do_deallocate() 0x55814a6ac380  @ aligned_allocator

 100 と 1000 を追加後に 1 回ポップしているので、サイズは v2 の分を加えて 4 個になります。

 さらにコンテナ内の要素は 1,2,3,100 となっていますので aligned_allocator で割り当てたデータは残っています。

 後は最後に v3 を v1 にコピー代入します。

125   v1 = v3;
126   v1.push_back(10000);

 この最後の push_back() 関数は heap_allocator でなく aligned_allocator を使います。

 こんな感じで複数のアロケーターを動的に変更できるって仕組みです。

Copyright 2018-2019, by Masaki Komatsu