では 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