第87章 スマートポインターで使えるようにするためのデリーター実装

前の項目で軽い紹介をしましたが、アロケーターをスマートポインターに対応させるためにデリーターを実装したいと思います。

では実装したコードを見てみましょう。

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