C++17の新機能 アロケータ編 / new features of C++17 - allocator

C++17の新機能 アロケータ編 / new features of C++17 - allocator

C++17 で追加された多相アロケータの紹介(アロケータのおさらい付き)です。
アロケータ編と言いながら C++17 で追加された is_always_equal に触れられていないのは、単なる時間切れです…

4cd53d17fd7e26f611822b508963f613?s=128

Miutsuru kariya

October 04, 2018
Tweet

Transcript

  1. 4.

    多相アロケータ 4 N1850 Towards a Better Allocator Model http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1850.pdf N3525

    Polymorphic Allocators http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3525.pdf N3726 Polymorphic Memory Resources http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3726.pdf N3816 Polymorphic Memory Resources - r1 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3816.pdf N3916 Polymorphic Memory Resources - r2 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3916.pdf 2005 年からってお前導入に10年以上かかってんのかよ…
  2. 7.

    多相アロケータ 導入の背景 7 // このコンテナの要素を… std::vector<int> v1 = { 1,

    2, 3 }; // このコンテナにコピーしたい… std::vector<int, my_allocator<int>> v2 = v1;
  3. 8.

    多相アロケータ 導入の背景 8 // このコンテナの要素を… std::vector<int> v1 = { 1,

    2, 3 }; // このコンテナにコピーしたい…けど出来ない… std::vector<int, my_allocator<int>> v2 = v1;
  4. 9.

    多相アロケータ 導入の背景 9 // このコンテナの要素を… std::vector<int> v1 = { 1,

    2, 3 }; // コピーするのめんどくさっ! std::vector<int, my_allocator<int>> v2{v1.begin(), v1.end()};
  5. 10.

    多相アロケータ 導入の背景 10 // このコンテナのデータを… std::vector<int, my_allocator<int>> v; // vector

    の引数を取る関数に… int calc(const std::vector<int>&); // 渡したい… auto dt = calc(v);
  6. 11.

    多相アロケータ 導入の背景 11 // このコンテナのデータを… std::vector<int, my_allocator<int>> v; // vector

    の引数を取る関数に… int calc(const std::vector<int>&); // 渡したい…けど出来ない… auto dt = calc(v);
  7. 12.

    多相アロケータ 導入の背景 12 // このコンテナのデータを… std::vector<int, my_allocator<int>> v; // vector

    の引数を取る関数に… int calc(const std::vector<int>&); // コピーしてから渡すのマジかよ… std::vector<int> v2{v.begin(), v.end()}; auto dt = calc(v2);
  8. 13.

    多相アロケータ 導入の背景 13 // このコンテナのデータを… std::vector<int, my_allocator<int>> v; // 関数が弄れるならテンプレートにすれば…

    template <template <typename> typename A> int calc(const std::vector<int, A<int>>&); // 渡せるけど、わざわざテンプレート書くのうぜぇ… auto dt = calc(v);
  9. 14.

    多相アロケータ 導入の背景 14 // この文字列と… std::string s; // この文字列を… std::basic_string<char,

    std::char_traits<char>, my_allocator<char>> t; // 比較したい… auto result = s == t;
  10. 15.

    多相アロケータ 導入の背景 15 // この文字列と… std::string s; // この文字列を… std::basic_string<char,

    std::char_traits<char>, my_allocator<char>> t; // 比較したい…けどできない… auto result = s == t;
  11. 16.

    多相アロケータ 導入の背景 16 // この文字列と… std::string s; // この文字列を… std::basic_string<char,

    std::char_traits<char>, my_allocator<char>> t; // 比較したい…まぁ許容範囲かな… auto result = s == std::string_view(t);
  12. 17.

    多相アロケータ 導入の背景 17 // この文字列と… std::string s; // この文字列を… てかクラス名なげぇよ…

    std::basic_string<char, std::char_traits<char>, my_allocator<char>> t; // 比較したい…まぁ許容範囲かな… auto result = s == std::string_view(t);
  13. 18.

    多相アロケータ 導入の背景 18 // この文字列と… std::string s; // この文字列を… てかクラス名なげぇよ…

    std::basic_string<char, std::char_traits<char>, my_allocator<char>> t; // 比較したい…てか比較にアロケータ関係無いの にこれ出来ないのどう考えても規格のバグやろ… auto result = s == t;
  14. 24.

    多相アロケータ 対応 24 多相アロケータ template <typename T> std::pmr::polymorphic_allocator pmr ⇒

    Polymorphic Memory Resource polymorphic ⇒ 多相 allocator ⇒アロケータ
  15. 30.

    多相アロケータ 対応 30 定義はこんな感じ namespace std::pmr { template <typename Key,

    typename T, typename C = less<Key>> using map = std::map<Key, T, C, polymorphic_allocator<pair<const Key, T>>> } 要は、アロケータの型だけ固定化している。 テンプレートのデフォルトを変更しただけじゃないので注意。 (std::pmr::vector<int, std::alloc<int>> とはできない) アロケータ用の テンプレート 引数は無い
  16. 36.

    多相アロケータ アロケータおさらい 36 new式 ⇓ // メモリを割り当てて… void* addr =

    ::operator new(sizeof(T)); // ホントは T::operator new もあるかもだけど… // コンストラクタを呼び出す T* p = ::new(addr) T(args...);
  17. 37.

    多相アロケータ アロケータおさらい 37 A 型のアロケータ a ⇓ // メモリを割り当てて… A::pointer

    p = a.allocate(1); // ホントは std::allocator_traits<A>::pointer std::allocator_traits<A>::allocate(a, 1) // コンストラクタを呼び出す a.construct(std::addressof(*p), args...); // ホントは std::allocator_traits<A>::construct(a, std::addressof(*p), args...)
  18. 39.

    多相アロケータ アロケータおさらい 39 A 型のアロケータ a ⇓ // デストラクタを呼び出して… a.destroy(std::addressof(*p));

    // ホントは std::allocator_traits<A>::destroy(a, std::addressof(*p)) // メモリを解放する a.deallocate(p, 1); // ホントは std::allocator_traits<A>::deallocate(a, p, 1)
  19. 41.

    多相アロケータ アロケータおさらい 41 std::vector<T>の場合 T T T T T T

    T T T T T型配列が割り当てられる。 こんなコンテナはむしろ稀。 std::vector<T> ポインタ サイズ 容量 アロケータ
  20. 45.

    多相アロケータ アロケータおさらい 45 アロケータの rebind ⇓ 要素型が T 型のアロケータ型 A

    から、 要素型が U 型のアロケータ型 B を生成 (std::allocator<T> ⇒ std::allocator<U> みたいな感じ) using B = typename A::rebind<U>::other; ホントは using B = std::allocator_traits<A>::rebind_alloc<U>
  21. 48.

    多相アロケータ アロケータおさらい 48 C++11 から、アロケータは オブジェクト毎に状態を 持てるようになった。 ※ Allocators@C++11 by

    Cryolite 先生 参照 しかし、rebind したアロケータは 状態を共有しなければならない 状態を共有しないとメモリ解放無理でしょ…
  22. 54.

    多相アロケータ アロケータおさらい 54 多相アロケータは strategy pattern になっているので、 strategy オブジェクト(memory_resource)側に メモリ割当に関する状態が持たれている。

    多相アロケータ本体は、memory_resource へのポインタを持っているだけ。 さすがにちゃんと考えられている… てかそのために strategy pattern にしたのかな、教えてエロい人…
  23. 60.

    多相アロケータ アロケータおさらい 60 string の vector の場合のイメージ(伝搬バージョン) 絵心が無さ過ぎてcpprefjp から拝借… stringはアロケータオ

    ブジェクトを持ってい るものの、コンテナの アロケータオブジェク トと状態を共有してい る。
  24. 63.

    多相アロケータ アロケータおさらい 63 手動で指定する例 // カスタムアロケータ MyAlloc のオブジェクト a MyAlloc<char>

    a; // カスタムアロケータ MyAlloc を使った文字列型 MyStr using MyStr = std::basic_string<char, std::char_traits<char>, MyAlloc<char>>; // MyStr を格納する、アロケータオブジェクト a を使ったvector std::vector<MyStr, MyAlloc<MyStr>> v(a); // アロケータオブジェクト a を使った要素格納 v.emplace_back("first element", a); v.emplace_back("second element", a);
  25. 64.

    多相アロケータ アロケータおさらい 64 自動だった場合の例 // カスタムアロケータ MyAlloc のオブジェクト a MyAlloc<char>

    a; // カスタムアロケータ MyAlloc を使った文字列型 MyStr using MyStr = std::basic_string<char, std::char_traits<char>, MyAlloc<char>>; // MyStr を格納する、アロケータオブジェクト a を使ったvector std::vector<MyStr, MyAlloc<MyStr>> v(a); // アロケータオブジェクト a を使った要素格納 v.emplace_back("first element"); v.emplace_back("second element");
  26. 68.

    多相アロケータ アロケータおさらい 68 要素に伝搬させたいアロケータの場合 a.construct(addr, args); ⇓ ::new((void*)addr) T(std::allocator_arg, a,

    std::forward<Args>(args)...); または ::new((void*)addr) T(std::forward<Args>(args)..., a); コンストラクタ引数の最初、あるいは最後に自分自身を紛れ込ませる (利用者はアロケータは明示的には指定しない) ちなみに、std::allocator_arg は引数の先頭がアロケータであることを示す タグ std::allocator_arg_t 型のダミーオブジェクト
  27. 69.

    多相アロケータ アロケータおさらい 69 ただし、アロケータが自分を要素に伝搬させたい場合でも ① そもそも要素がアロケータを使う必要が無い(int とか) ② アロケータは使用するけど型が違っている ③

    コンストラクタでアロケータを指定できない などの場合があるので、その場合は普通に構築したい。 そこで、std::uses_allocator と言う型トレイツで 伝搬させるかどうかを制御できるようにする。
  28. 70.

    多相アロケータ アロケータおさらい 70 std::uses_allocator<T, Alloc> T 型のオブジェクトが Alloc 型のアロケータを使用した構築を サポートしているかどうかを判別する型トレイツ。

    デフォルトでは T::allocator_type と言うメンバ型が定義されて いて、かつ std::is_convertible_v<Alloc, T::allocator_type> が true であれば、std::true_type から派生、さもなければ std::false_type から派生。 T::allocator_type と言うメンバ型が定義されていなくても T が Alloc 型のアロケータを使用した構築をサポートしているので あれば std::true_type から派生するように特殊化しておく。
  29. 71.

    多相アロケータ アロケータおさらい 71 uses-allocator 構築 obj を構築対象の T 型のオブジェクト、v1, v2,

    …, vN をそれぞれ V1, V2, …, VN 型 のコンストラクタ引数、alloc を Alloc 型のアロケータとすると… • std::uses_allocator_v<T, Alloc> が false で、かつ std::is_constructible_v<T, V1, V2, ..., VN> が true なら、obj(v1, v2, ..., vN) として構築 • そうでなくて、std::uses_allocator_v<T, Alloc> が true で、かつ std::is_constructible_v<T, std::allocator_arg_t, Alloc, V1, V2, ..., VN> が true な ら、obj(std::allocator_arg, alloc, v1, v2, ..., vN) として構築 • そうでなくて、std::uses_allocator_v<T, Alloc> が true で、かつ std::is_constructible_v<T, V1, V2, ..., VN, Alloc> が true なら、obj(v1, v2, ..., vN, alloc) として構築 • 上記のいずれでもない場合、ill-formed
  30. 72.

    多相アロケータ アロケータおさらい 72 uses-allocator 構築をサポートするアロケータ • std::polymorphic_allocator • std::scoped_allocator_adaptor ただし、scoped_allocator_adaptor

    自体は本物のアロケータではないので (名前の通り、アダプタ)、実際の処理は uses-allocator 構築とはちょっと違う。 あと、scoped_allocator_adaptor と polymorphic_allocator は組み合わせては使えないと思う(多分…)
  31. 73.

    多相アロケータ アロケータおさらい 73 uses-allocator 構築をサポートしている型 • std::array 以外の標準コンテナ • 標準コンテナアダプタ(std::stackとか)

    • std::basic_string • std::tuple(自分はアロケータ使わないけど※1) • std::match_results(正規表現の検索結果、一部※2) • std::promise(スレッド系、一部※2) ※1 全ての要素型に対して、uses-allocator 構築を行う ※2 引数がアロケータだけのコンストラクタのみ存在
  32. 76.

    多相アロケータ アロケータおさらい 76 tuple があるのに何で pair が無いの? ⇓ コンストラクタが多すぎると言う理由で、pair の

    コンストラクタからアロケータを使用するヤツが 削除されてしまった。 ⇓ pair の各要素を uses-allocator 構築するために は、各アロケータで特別対応する必要がある polymorphic_allocator と scoped_allocator_adaptor は特別対応している
  33. 77.

    多相アロケータ アロケータおさらい 77 勉強会での光成さん(@herumi)からの質問 自作アロケータで uses-allocator 構築する場合、 pair に特別対応しなかったらコンテナの要素に pair

    使うとエラーになる? ⇓ ならないです。ただし、コンテナのアロケータは pair の各要素には伝搬しなくなります。 勉強会での 話を基に追加
  34. 78.

    多相アロケータ アロケータおさらい 78 残念なお知らせ C++17 で追加された超有能三兄弟、 std::optional、std::variant、std::any は uses-allocator 構築をサポートしていない。

    したがって、コンテナの要素がこの兄弟の場合、 コンテナのアロケータはこの兄弟の要素には 伝搬しない。 わりと悲しい… 当日スライドに入れ 忘れてて勉強会で 口頭補足したやつ
  35. 79.

    多相アロケータ アロケータおさらい 79 余談 • std::shared_ptr はアロケータを使用するが、uses- allocator 構築はサポートしない。 •

    std::function、std::packaged_task は、C++14 までは uses-allocator 構築をサポートしていることになって いたが、いろいろ問題があったらしく、C++17 からそ もそもアロケータ自体をサポートしなくなった。
  36. 87.

    多相アロケータ アロケータおさらい 87 ちなみに、多相アロケータは select_on_container_copy_construction は 自分のコピーじゃなく新たなデフォルト構築された アロケータを返し、propagate_on_container_* は 全部

    false_type です。 つまり、コピー構築、コピー・ムーブ代入、スワップ ではアロケータ絶対伝搬させないマンです。 このあたりは、提案者の哲学(アロケータは構築時に 一旦設定したら変更されるべきではない)が 反映されているのではないかと思います。 勉強会での 話を基に追加
  37. 91.

    多相アロケータ アロケータおさらい 91 ファンシーポインタの要件 ① NullablePointer 要件を満たすこと ② コンストラクタ、比較演算子、コピー、ムーブ、スワップの処理は例 外を投げないこと

    ③ ランダムアクセスイテレータの要件を満たすこと ④ 連続イテレータの要件を満たすこと ①、➂、④は更にそれぞれ厳しい要件がある。 標準にはこの要件を満たすファンシーポインタは存在しない。 (shared_ptr とか unique_ptr は要件を満たさない)
  38. 94.

    多相アロケータ アロケータおさらい 94 共有メモリだとプロセス毎にアドレスが異なる可能性があるので、 普通のポインタ使うとデータが壊れる。 そこで、ポインタオブジェクト自身のアドレスとポインタの指す対象オブ ジェクトのアドレスの差分を保持するオフセットポインタが有効。 オフセットポインタ by Thomas

    Köppe また、光成さん(@herumi)もおっしゃっていたように、 アドレス範囲を制限してすることによってポインタサイズを 小さくするようなファンシーポインタも考えられる。 しかし、std::list などは通常番兵ノードを置くが、番兵ノードはアロケータ でアロケートせずにオブジェクト本体内に配置することが多い。 アロケータでアロケートしていないオブジェクトへのポインタをこの手の ポインタで指すと、問題が発生する。 D0773R1 Towards meaningful fancy pointers 勉強会での 話を基に追加
  39. 95.

    多相アロケータ アロケータおさらい 95 なお、単にポインタをクラス内にラップしただけのクラスで試 したところ、Clang(libc++) も GCC(libstdc++) も少なくとも以 下のような問題で正しくファンシーポインタをサポートしきれ ていないようでした。

    Clang:ファンシーポインタのデフォルト構築がヌルポインタ になることに依存している個所がある(そのような要件は無 い) GCC:本物のポインタを引数にするコンストラクタに依存して いる個所がある(そのような要件は無い) 勉強会での 話を基に追加
  40. 99.

    多相アロケータ 詳細 99 class memory_resource { public: virtual ~memory_resource(); //

    公開インタフェース(いわゆるNVI)、polymorphic_allocator で使われる。 void* allocate(size_t bytes, size_t alignment = alignof(max_align_t)); void deallocate(void* p, size_t bytes, size_t alignment = alignof(max_align_t)); bool is_equal(const memory_resource& other) const noexcept; private: // 仮想メンバ関数(ここでは純粋仮想)、派生クラスで実装する。 virtual void* do_allocate(size_t bytes, size_t alignment) = 0; virtual void do_deallocate(void* p, size_t bytes, size_t alignment) = 0; virtual bool do_is_equal(const memory_resource& other) const noexcept = 0; };
  41. 100.

    多相アロケータ 詳細 100 void* allocate(size_t bytes, size_t alignment = alignof(max_align_t));

    単に do_allocate を呼ぶだけ。 NVIだからね… virtual void* do_allocate(size_t bytes, size_t alignment) = 0; サイズ bytes、アラインメント alignment の メモリを割り当てる。 各具象派生クラスで実装する。
  42. 101.

    多相アロケータ 詳細 101 void deallocate(void* p, size_t bytes, size_t alignment

    = alignof(max_align_t)); 単に do_deallocate を呼ぶだけ。 NVIだ(ry virtual void do_deallocate(void* p, size_t bytes, size_t alignment) = 0; do_allocate で割り当てられたメモリ p を解放する。 サイズ bytes、アラインメント alignment は 割り当て時の値(正しく使われれば)。 各具象派生クラスで実装する。
  43. 102.

    多相アロケータ 詳細 102 bool is_equal(const memory_resource& other) const noexcept; 単に

    do_is_equal を呼ぶだけ。 N(ry virtual bool do_is_equal(const memory_resource& other) const noexcept = 0; *this で割り当てられたメモリを other で解放したり、 other で割り当てられたメモリを *this で解放したり できる場合のみ true を返す。 各具象派生クラスで実装する。 ふつうは this == &other で良いはず。
  44. 103.

    多相アロケータ 詳細 103 ポインタが void* であることに注意! (アロケータはメンバ型 pointer) ⇓ ファンシーポインタのサポートは

    諦めた、とのこと…※ クラス継承構造だからね、仕方ないね… (派生クラスでメンバ関数の戻り値型変更は無理) ※ CppCon 2017: Alisdair Meredith “An allocator model for std2” 32:37
  45. 105.

    多相アロケータ 詳細 105 コンストラクタ // デフォルトコンストラクタ。memory_resource はシステムデフォルトを使用(後述) polymorphic_allocator() noexcept; //

    memory_resource へのポインタからの暗黙変換コンストラクタ。 // アロケータが必要な個所で memory_resource が直接使えるようになるので便利。 polymorphic_allocator(memory_resource*); // コピーコンストラクタ。other の memory_resource をコピーして使用。 polymorphic_allocator(const polymorphic_allocator& other) = default; template<class U> polymorphic_allocator(const polymorphic_allocator<U>& other) noexcept; 見ての通り、ムーブコンストラクタは無いよ! (memory_resource のハンドラだからムーブされると困る…)
  46. 106.

    多相アロケータ 詳細 106 代入演算子など // 代入演算子は無い!(コピー代入もムーブ代入も出来ない) polymorphic_allocator& operator=(const polymorphic_allocator& rhs)

    = delete; // コンテナをコピーする際のアロケータ取得時に呼ばれるメンバ関数。 // デフォルト構築された新たな polymorphic_allocator オブジェクトを返す。 // ちなみに、自分自身を返すのが、アロケータのデフォルトの挙動。 polymorphic_allocator select_on_container_copy_construction() const;
  47. 107.

    多相アロケータ 詳細 107 メモリ割当・解放など // メモリ割当。 memory_resource の allocate 呼び出してキャストして返すだけ。

    // サイズは n * sizeof(Tp)、アラインメントは alignof(Tp)。 Tp* allocate(size_t n); // メモリ解放。 memory_resource の deallocate 呼び出すだけ。 // サイズは n * sizeof(Tp)、アラインメントは alignof(Tp)。 void deallocate(Tp* p, size_t n); // memory_resource の取得。 // 単にコンストラクタで設定した memory_resource を返すだけ。 memory_resource* resource() const;
  48. 108.

    多相アロケータ 詳細 108 オブジェクト構築 // コンテナ内でのオブジェクト構築。(emplace 的なヤツ) // 引数 p

    で指定された場所に、T 型のオブジェクトを、 // *this と std::forward<Args>(args)… で uses-allocator 構築する。 // T が std::pair の特殊化の場合は使用されない template<class T, class... Args> void construct(T* p, Args&&... args);
  49. 109.

    多相アロケータ 詳細 109 オブジェクト構築(続き) // 要素型が pair の場合の特別なオブジェクト構築 // pair

    の first と second をそれぞれうまい具合に uses-allocator 構築する template<class T1, class T2, class... Args1, class... Args2> void construct(pair<T1, T2>* p, piecewise_construct_t, tuple<Args1...> x, tuple<Args2...> y); template<class T1, class T2> void construct(pair<T1, T2>* p); template<class T1, class T2, class U, class V> void construct(pair<T1, T2>* p, U&& x, V&& y); template<class T1, class T2, class U, class V> void construct(pair<T1, T2>* p, const pair<U, V>& pr); template<class T1, class T2, class U, class V> void construct(pair<T1, T2>* p, pair<U, V>&& pr);
  50. 112.

    多相アロケータ 詳細 112 その他(非メンバ関数) // アロケータの比較。*a.resource() == *b.resource() の結果を返すだけ。 template<class

    T1, class T2> bool operator==(const polymorphic_allocator<T1>& a, const polymorphic_allocator<T2>& b) noexcept; // アロケータの比較。*a.resource() != *b.resource() の結果を返すだけ。 template<class T1, class T2> bool operator!=(const polymorphic_allocator<T1>& a, const polymorphic_allocator<T2>& b) noexcept;
  51. 114.

    多相アロケータ memory_resource の具象クラス 114 // グローバルオブジェクト(クラス名は未規定) memory_resource* new_delete_resource() noexcept; memory_resource*

    null_memory_resource() noexcept; // 特殊用途用クラス class synchronized_pool_resource; class unsynchronized_pool_resource; class monotonic_buffer_resource;
  52. 115.

    多相アロケータ memory_resource の具象クラス 115 標準が提供している memory_resource の具象クラス① memory_resource* new_delete_resource() noexcept;

    普通に ::operator new、::operator delete でメモリ割 当、解放するヤツ。つまり極めて普通。 この関数を呼ぶと、常に同じオブジェクトへのポイ ンタが返る。(自分でインスタンス生成とかしない)
  53. 116.

    多相アロケータ memory_resource の具象クラス 116 標準が提供している memory_resource の具象クラス② memory_resource* null_resource() noexcept;

    メモリ割当しようとすると常に bad_alloc 投げるヤツ。 つまり極めて変。他のと組み合わせて使うと有用。 この関数を呼ぶと、常に同じオブジェクトへのポイ ンタが返る。(自分でインスタ(ry
  54. 118.

    多相アロケータ memory_resource の具象クラス 118 synchronized_pool_resource イメージ サイズ ~16 17~32 33~64

    65~128 プールはブロックサイ ズ毎に分かれている。 (ただし、上限あり) メモリ割当は指定さ れたサイズ以上で最 小のブロックサイズ のプールから。 各プールのメモリは、同一サイズのブロックを複数まとめたチャ ンクに分割されている。プール内のメモリを使い果たすと、新た なチャンクを上位メモリリソースから確保する。その際、チャンク のサイズは等比級数的に増大していく。(ただし、上限あり) 割当済 未割当 次に上位から確保
  55. 120.

    多相アロケータ memory_resource の具象クラス 120 synchronized_pool_resource コンストラクタ① synchronized_pool_resource( const pool_options& opts,

    memory_resource* upstream); 引数のいずれか、あるいは両方を省いたバージョンもある。(引数1つの バージョンは explicit コンストラクタ) opts:挙動調整用のオプション。後述。 upstream:上位メモリリソースへのポインタ。このメモリリソースから割り 当てられるメモリは全て upstream から取得される。指定しないとデフォ ルトメモリリソース(後述)を指定したことになる。
  56. 121.

    多相アロケータ memory_resource の具象クラス 121 struct pool_options { size_t max_blocks_per_chunk =

    0; size_t largest_required_pool_block = 0; }; max_blocks_per_chunk:チャンクの最大ブロック数。チャンクサイズは、各プール のメモリが枯渇して上位メモリリソースに確保に行くたびに等比級数的に増加し ていくが、増加はここで指定したブロック数を上限とする。 largest_required_pool_block:プールするメモリサイズの上限。この値を超えるメ モリ割り当て要求は上位メモリリソースから直接割当し、プールでの管理は行わ ない。 いずれも、0(デフォルト値)を指定すると実装依存の上限値となる。また、指定 値がデカすぎる場合も適当に調整される。
  57. 124.

    多相アロケータ memory_resource の具象クラス 124 synchronized_pool_resource メンバ関数② pool_options options() const; プールのオプションを返す。ただし、デフォルト値

    0 が実際の実装依存の値になったり、指定された値 が実装によって調整されたりする可能性があるの で、コンストラクタで指定された値とは違うかもしれ ない。
  58. 126.

    多相アロケータ memory_resource の具象クラス 126 synchronized_pool_resource 仮想関数① void* do_allocate( size_t bytes,

    size_t alignment) override; 指定されたサイズ、指定されたアラインメントのメモ リをプールから、あるいは上位メモリリソースから直 接割り当てる。極めて普通。
  59. 127.

    多相アロケータ memory_resource の具象クラス 127 synchronized_pool_resource 仮想関数② void do_deallocate(void* p, size_t

    bytes, size_t alignment) override; 割り当てられたメモリをプールに返す。 なお、上位メモリリソースの deallocate が呼び出さ れるか否かは未規定。
  60. 128.

    多相アロケータ memory_resource の具象クラス 128 synchronized_pool_resource 仮想関数➂ bool do_is_equal(const memory_resource& other)

    const noexcept override; 指定されたメモリリソースが自分と互換性のあるメ モリリソースかどうかを返す。 ゆうても、単に this == &other の結果を返すだけ。
  61. 129.

    多相アロケータ memory_resource の具象クラス 129 synchronized_pool_resource デストラクタ ~synchromized_pool_resource(); release() メンバ関数を呼ぶ。つまり、全メモリが解 放される。まぁ当たり前っちゃあ当たり前。

    このメモリリソースから割り当てられたメモリを使用 しているコンテナ等よりも、このメモリリソースの生 存期間が短くならないように注意が必要。
  62. 130.

    多相アロケータ memory_resource の具象クラス 130 標準が提供している memory_resource の具象クラス④ class unsynchronized_pool_resource; synchronized_pool_resource

    のシングルスレッド版。 外部同期無しで複数スレッドから同時使用できない こと以外は synchronized_pool_resource と一緒。 なので説明(ry
  63. 131.

    多相アロケータ memory_resource の具象クラス 131 標準が提供している memory_resource の具象クラス➄ class monotonic_buffer_resource; メモリ割当するだけして、絶対解放しないヤツ。

    つまりメモリ使用量が単調(monotonic)増加。 同期とかガチ無視なので、まぁ単一スレッド用。 使用用途が限られるがちょっぱや。
  64. 133.

    多相アロケータ memory_resource の具象クラス 133 monotonic_buffer_resource コンストラクタ① monotonic_buffer_resource( size_t initial_size, memory_resource*

    upstream); 引数のいずれか、あるいは両方を省いたバージョンもある。(引数1つの バージョンは explicit コンストラクタ) initial_size:最初に上位メモリリソースから確保するメモリのサイズ。実 装によって調整が入るかもしれない。指定が無い場合は実装依存。 upstream:上位メモリリソースへのポインタ。このメモリリソースから割り 当てられるメモリは全て upstream から取得される。指定しないとデフォ ルトメモリリソース(後述)を指定したことになる。
  65. 134.

    多相アロケータ memory_resource の具象クラス 134 monotonic_buffer_resource コンストラクタ② monotonic_buffer_resource(void* buffer, size_t buffer_size,

    memory_resource* upstream); upstream を省いたバージョンもある。 buffer:初期の割当用メモリ領域。 buffer_size:buffer の容量。また、次に上位メモリリソースから確保するメ モリのサイズは、この値を等比級数的に増加させた値になる。 upstream:上位メモリリソースへのポインタ。buffer を使い果たしたら、 upstream から取得される。指定しないとデフォルトメモリリソース(後述) を指定したことになる。
  66. 138.

    多相アロケータ memory_resource の具象クラス 138 monotonic_buffer_resource 仮想関数① void* do_allocate( size_t bytes,

    size_t alignment) override; 指定されたサイズ、指定されたアラインメントのメモ リを割り当てる。極めて普通。
  67. 139.

    多相アロケータ memory_resource の具象クラス 139 monotonic_buffer_resource 仮想関数② void do_deallocate(void* p, size_t

    bytes, size_t alignment) override; メモリを解放?いや、そんなことはしない。 とにかく何もしない。
  68. 140.

    多相アロケータ memory_resource の具象クラス 140 monotonic_buffer_resource 仮想関数➂ bool do_is_equal(const memory_resource& other)

    const noexcept override; 指定されたメモリリソースが自分と互換性のあるメ モリリソースかどうかを返す。 ゆうても、単に this == &other の(ry
  69. 143.

    多相アロケータ その他 143 グローバルなユーティリティ① memory_resource* set_default_resource(memory_resource* r) noexcept; デフォルトのメモリリソースを引数 r

    に設定して、今 まで設定されてたメモリリソースを返す。 r が nullptr なら new_delete_resource を設定する。 ちなみに、システムの初期状態は new_delete_resource になっている。 普通っぽい…
  70. 145.

    多相アロケータ その他 145 グローバルなユーティリティ➂ bool operator==(const memory_resource& a, const memory_resource&

    b) noexcept; メモリリソースを比較する。 単に &a == &b || a.is_equal(b) を返すだけ。 こいつの引数はポインタじゃないので注意。
  71. 146.

    多相アロケータ その他 146 グローバルなユーティリティ④ bool operator!=(const memory_resource& a, const memory_resource&

    b) noexcept; メモリリソースを比較する。 みんなの予想通りの挙動。(たぶん) こいつの引数もポインタじゃない。
  72. 148.

    多相アロケータ デメリット 148 • アロケータオブジェクト毎に memory_resource へのポインタを持ってい るため、標準アロケータを使った時より各 オブジェクトがポインタ 1

    つ分大きくなる。 • メモリ割当、解放が仮想関数になっている ので、標準アロケータを使った時より関数 呼び出しオーバーヘッドが大きくなる。
  73. 150.

    多相アロケータ 悲報 150 GCC も Clang もまだ多相アロ ケータの実装が終わってない… MSVC2017 はぱっと見ありそうでした…

    (ただし、どれもちゃんと調査できてない…) 勉強会時コメント by いなむ先生(@いなむのみたま) MSVC2017はちゃんと動くよ!
  74. 151.

    多相アロケータ 悲報 151 GCC HEAD synchronized_pool_resource と unsynchronized_pool_resource が無い。 その他はありそう…

    Clang HEAD メモリリソースはグローバルなヤツしかないし、 そもそもまだ experimental。 自分でメモリリソース作るなら一応どっちも行けそう…
  75. 152.

    多相アロケータ 悲報 152 しばらくは Boost.Container 使う しかないね… でも Boost.Container も

    pool_options がちょっと変だった… なぜかデフォルトコンストラクタだけがあるから、 synchronized_pool_resource のコンストラクタに初期化リストが直接書けない…
  76. 153.

    多相アロケータ 参考資料 153 Allocators@C++11 by Cryolite 先生 C++11 でのアロケータ機能拡充に関する神スライド https://www.slideshare.net/Cryolite/allocator11final

    A visitor’s guide to C++ allocators by Thomas Köppe アロケータの詳細がまとめられた非常に良い記事 カスタムアロケータやオフセットポインターのサンプル実装へのリンクもある https://rawgit.com/google/cxx-std-draft/allocator-paper/allocator_user_guide.html D0773R1 Towards meaningful fancy pointers ファンシーポインタとその適用限界について考察した非常に良い記事 https://quuxplusone.github.io/draft/fancy-pointers.html 勉強会後に 追記
  77. 154.

    多相アロケータ 参考資料 154 CppCon 2017 でのアロケータに関する講演 Pablo Halpern “Allocators: The

    Good Parts” https://www.youtube.com/watch?v=v3dz-AKOVL8 Alisdair Meredith “An allocator model for std2” https://www.youtube.com/watch?v=oCi_QZ6K_qk John Lakos “Local ('Arena') Memory Allocators (part 1 of 2)” https://www.youtube.com/watch?v=nZNd5FjSquk John Lakos “Local ('Arena') Memory Allocators (part 2 of 2)” https://www.youtube.com/watch?v=CFzuFNSpycI Bob Steagall “How to Write a Custom Allocator” https://www.youtube.com/watch?v=kSWfushlvB8 勉強会後に 追記