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