前の項目で軽い紹介をしましたが、アロケーターをスマートポインターに対応させるためにデリーターを実装したいと思います。
では実装したコードを見てみましょう。
main.cpp.
1 #include <memory>
2 #include <iostream>
3
4 template<typename T>
5 class my_allocator
6 {
7 public:
8 typedef T value_type;
9 typedef T* pointer;
10 typedef const T* const_pointer;
11 typedef std::size_t size_type;
12
13 template<typename U>
14 struct rebind {
15 typedef my_allocator<U> other;
16 };
17
18 inline explicit my_allocator(){}
19
20 inline ~my_allocator(){}
21
22 inline explicit my_allocator(my_allocator const&){}
23
24 template<typename U>
25 inline explicit my_allocator(my_allocator<U> const&){}
26
27 inline pointer allocate(size_type cnt,
28 typename std::allocator<void>::const_pointer = 0)
29 {
30 return reinterpret_cast<pointer>(::operator new(cnt*sizeof(T)));
31 }
32
33 inline void deallocate(pointer p, size_type)
34 {
35 ::operator delete(p);
36 std::cout << "deallocated" << '\n';
37 }
38
39 inline size_type max_size() const
40 {
41 return std::numeric_limits<size_type>::max();
42 }
43 };
44
45 template<typename T>
46 using rebind_t = typename my_allocator<T>::template rebind<T>::type;
47
48 template<typename T, typename T2>
49 inline bool operator==(my_allocator<T> const&,
50 my_allocator<T2> const&)
51 {
52 return true;
53 }
54
55 template<typename T, typename OtherAllocator>
56 inline bool operator==(my_allocator<T> const&,
57 OtherAllocator const&)
58 {
59 return false;
60 }
61
62 template<typename Allocator, typename U>
63 struct my_rebind {
64 using type = typename Allocator::template rebind<U>::other;
65 };
66
67 template<typename Allocator, typename U>
68 using my_rebind_t = typename my_rebind<Allocator,U>::type;
69
70 template<typename Allocator, typename T>
71 class my_delete
72 {
73 public:
74 constexpr my_delete(Allocator& alloc) noexcept
75 : M_allocator_(alloc)
76 {}
77
78 void operator()(T* p) const
79 {
80 p->~T();
81 M_allocator_.deallocate(p,1);
82 }
83 private:
84 mutable my_rebind_t<Allocator,T> M_allocator_;
85 };
86
87 template<typename T,typename Allocator>
88 using my_smart_ptr = std::unique_ptr<T,my_delete<Allocator,T>>;
89
90 int main()
91 {
92 my_allocator<int> alloc;
93 my_rebind_t<my_allocator<long>,long> rebound_alloc(alloc);
94 long* p_raw = rebound_alloc.allocate(1);
95 long* result = new (p_raw) long(1000);
96 {
97 auto p = my_smart_ptr<long,my_allocator<long>>(result,my_delete<my_allocator<long>,long>(rebound_alloc));
98 std::cout << *p << '\n';
99 }
100 return 0;
101 }
ビルドと実行結果.
$ g++ main.cpp -std=c++17 $ ./a.out 1000 deallocated
このソースコードではアロケーターではなく、スマートポインターが主役です。
アロケーターをスマートポインターにするのではなく、アロケーターで割り当てたメモリー領域をスマートポインターとして自動で解放するのがコードの目的だからです。
まずアロケーターをリバインドするための型を作っておきます。
62 template<typename Allocator, typename U>
63 struct my_rebind {
64 using type = typename Allocator::template rebind<U>::other;
65 };
66
67 template<typename Allocator, typename U>
68 using my_rebind_t = typename my_rebind<Allocator,U>::type;my_rebind_t がアロケーターの型エイリアス(別名)となります。
次にデリーターのクラステンプレート my_delete を用意します。
70 template<typename Allocator, typename T>
71 class my_delete
72 {
73 public:
74 constexpr my_delete(Allocator& alloc) noexcept
75 : M_allocator_(alloc)
76 {}
77
78 void operator()(T* p) const
79 {
80 p->~T();
81 M_allocator_.deallocate(p,1);
82 }
83 private:
84 mutable my_rebind_t<Allocator,T> M_allocator_;
85 };この中でデリーターの解放箇所を実装するのは void operator(T* p) となりますが、スマートポインターが削除時にコールします。
後は my_rebind_t 型のインスタンスを my_delete クラス内に保持させます。
84 mutable my_rebind_t<Allocator,T> M_allocator_;
後は std::unique_ptr のテンプレート引数に放り込むだけです。
87 template<typename T,typename Allocator> 88 using my_smart_ptr = std::unique_ptr<T,my_delete<Allocator,T>>;
やっと std::unique_ptr のご登場となりますが my_delete はテンプレート引数に指定されてますね。
my_smart_ptr としてエイリアスにし、識別子を短縮すれば後は、使うだけです。
92 my_allocator<int> alloc;
93 my_rebind_t<my_allocator<long>,long> rebound_alloc(alloc);
94 long* p_raw = rebound_alloc.allocate(1);
95 long* result = new (p_raw) long(1000);
96 {
97 auto p = my_smart_ptr<long,my_allocator<long>>(result,my_delete<my_allocator<long>,long>(rebound_alloc));
98 std::cout << *p << '\n';
99 }これちょっと遠回しに見えるかもしれませんので、少し詳しく説明してきます。
最初にアロケーターのインスタンスを作りますが、型を変えるのであれば、それをリバインド型としてコピーします。
92 my_allocator<int> alloc; 93 my_rebind_t<my_allocator<long>,long> rebound_alloc(alloc);
アロケーターを int 型から long 型に変更(リバインド)しているってことです。
次にリバインドしたアロケーターからメモリー割り当てを行ってアドレスを取得します。
94 long* p_raw = rebound_alloc.allocate(1);
そのアドレスに対してデータの初期化をします。
95 long* result = new (p_raw) long(1000);
この行は construct() を実装していれば、 construct() をコールしてもOKです。
最後に割り当て済みのデータを my_smart_ptr のインスタンス生成のパラメーターとして使います。
96 {
97 auto p = my_smart_ptr<long,my_allocator<long>>(result,my_delete<my_allocator<long>,long>(rebound_alloc));
98 std::cout << *p << '\n';
99 }まあ、こんな感じで、スマートポインターとして使えるようになりましたとさ(昔話感)。
Copyright 2018-2019, by Masaki Komatsu