前の項目で軽い紹介をしましたが、アロケーターをスマートポインターに対応させるためにデリーターを実装したいと思います。
では実装したコードを見てみましょう。
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