Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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. C++17 の新機能 アロケータ編 2018/10/4 鳥頭かりやマン 1

  2. アロケータおいしい!!! 2 注:個人の感想です

  3. 多相アロケータ 3

  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年以上かかってんのかよ…
  5. 多相アロケータ 5 導入の背景

  6. 多相アロケータ 導入の背景 6 コンテナの型にアロケータの 型が現れるのは控えめに 言って頭おかしい ※ Allocators@C++11 by Cryolite

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

    2, 3 }; // このコンテナにコピーしたい… std::vector<int, my_allocator<int>> v2 = v1;
  8. 多相アロケータ 導入の背景 8 // このコンテナの要素を… std::vector<int> v1 = { 1,

    2, 3 }; // このコンテナにコピーしたい…けど出来ない… std::vector<int, my_allocator<int>> v2 = v1;
  9. 多相アロケータ 導入の背景 9 // このコンテナの要素を… std::vector<int> v1 = { 1,

    2, 3 }; // コピーするのめんどくさっ! std::vector<int, my_allocator<int>> v2{v1.begin(), v1.end()};
  10. 多相アロケータ 導入の背景 10 // このコンテナのデータを… std::vector<int, my_allocator<int>> v; // vector

    の引数を取る関数に… int calc(const std::vector<int>&); // 渡したい… auto dt = calc(v);
  11. 多相アロケータ 導入の背景 11 // このコンテナのデータを… std::vector<int, my_allocator<int>> v; // vector

    の引数を取る関数に… int calc(const std::vector<int>&); // 渡したい…けど出来ない… auto dt = calc(v);
  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);
  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);
  14. 多相アロケータ 導入の背景 14 // この文字列と… std::string s; // この文字列を… std::basic_string<char,

    std::char_traits<char>, my_allocator<char>> t; // 比較したい… auto result = s == t;
  15. 多相アロケータ 導入の背景 15 // この文字列と… std::string s; // この文字列を… std::basic_string<char,

    std::char_traits<char>, my_allocator<char>> t; // 比較したい…けどできない… auto result = s == t;
  16. 多相アロケータ 導入の背景 16 // この文字列と… std::string s; // この文字列を… std::basic_string<char,

    std::char_traits<char>, my_allocator<char>> t; // 比較したい…まぁ許容範囲かな… auto result = s == std::string_view(t);
  17. 多相アロケータ 導入の背景 17 // この文字列と… std::string s; // この文字列を… てかクラス名なげぇよ…

    std::basic_string<char, std::char_traits<char>, my_allocator<char>> t; // 比較したい…まぁ許容範囲かな… auto result = s == std::string_view(t);
  18. 多相アロケータ 導入の背景 18 // この文字列と… std::string s; // この文字列を… てかクラス名なげぇよ…

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

  20. 多相アロケータ 対応 20 標準アロケータ (std::allocator)魔改造 してメモリ割当戦略の 違いが型に出ないよう にしよう!!!

  21. 多相アロケータ 対応 21 標準アロケータ (std::allocator)魔改造 してメモリ割当戦略の 違いが型に出ないよう にしよう!!!

  22. 多相アロケータ 対応 22 チッ メモリ割当戦略の違い が型に出ない新しいア ロケータ作ろう!!!

  23. 多相アロケータ 対応 23 どうやって?

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

    Polymorphic Memory Resource polymorphic ⇒ 多相 allocator ⇒アロケータ
  25. 多相アロケータ 対応 25 多相?

  26. 多相アロケータ 対応 26 polymorphic_allocator 自身ではメモリ割当、 解放処理を実装せず、 std::pmr::memory_resource (の具象派生クラス)にメモリ 割当、解放処理を委譲する。

  27. 多相アロケータ 対応 27 古き良き Strategy Pattern !!! つまり動的多相 GoF万歳!!!1!

  28. 多相アロケータ 対応 28 あ、あと、標準アロケータを多相にする 夢が破れて、標準コンテナを多相アロ ケータで使うのが面倒になっちゃったん で、標準コンテナに多相アロケータを使 うエイリアステンプレートを追加しよう!

  29. 多相アロケータ 対応 29 こんなやつ std::pmr::vector std::pmr::map … 全部あるよ (もちろん std::array

    以外)
  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>> とはできない) アロケータ用の テンプレート 引数は無い
  31. 多相アロケータ 31 polymorphic_allocator や memory_resource の詳細に行く 前に、アロケータのおさらい

  32. 多相アロケータ アロケータおさらい 32 そもそもアロケータとは?

  33. 多相アロケータ アロケータおさらい 33 そもそもアロケータとは? ⇓ メモリ割当をカスタマイズ するためのもの

  34. 多相アロケータ アロケータおさらい 34 そもそもアロケータとは? ⇓ メモリ割当をカスタマイズ するためのもの だけじゃない!!! ※ Allocators@C++11

    by Cryolite 先生 参照
  35. 多相アロケータ アロケータおさらい 35 アロケータが無い場合の 動的メモリ使用方法 ⇓ new 式 delete 式

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

    ::operator new(sizeof(T)); // ホントは T::operator new もあるかもだけど… // コンストラクタを呼び出す T* p = ::new(addr) T(args...);
  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...)
  38. 多相アロケータ アロケータおさらい 38 delete式 ⇓ // デストラクタを呼び出して… p->~T(); // メモリを解放する

    ::operator delete(addr); // ホントは T::operator delete もあるかもだけど…
  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)
  40. 多相アロケータ アロケータおさらい 40 アロケータのポイント① メモリ割当・解放とコンストラクタ・デスト ラクタ呼び出しが完全に分離している コンテナの要素型とアロケーションした いオブジェクトの型が必ずしも一致しな いので(むしろ一致する方が稀)

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

    T T T T T型配列が割り当てられる。 こんなコンテナはむしろ稀。 std::vector<T> ポインタ サイズ 容量 アロケータ
  42. 多相アロケータ アロケータおさらい 42 std::forward_list<T>の場合 ポインタ T ノード型(node<T>的な)のメモリが割り当てられる。 ほとんどのコンテナがこれ。 forward_listにしたのはポインタ書くのが面倒だったから… std::forward_list<T>

    ポインタ サイズ アロケータ ポインタ T ポインタ T
  43. 多相アロケータ アロケータおさらい 43 でも、指定されたアロケータは T型配列しかアロケーション できない…

  44. 多相アロケータ アロケータおさらい 44 でも、指定されたアロケータは T型配列しかアロケーション できない… ⇓ rebind

  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>
  46. 多相アロケータ アロケータおさらい 46 rebind を使えば、渡されたアロケータから 任意の型用のアロケータを生成可能 // 型T 用のアロケータから型U用のアロケータを生成 B

    b(a); ただし、常に以下が成り立つ必要がある assert(a == A(b));
  47. 多相アロケータ アロケータおさらい 47 アロケータの operator== は、一方で割り当てた メモリがもう一方で解放できる場合にのみ true を返さなきゃならない +

    a == A(b) じゃなきゃならない ⇓ a で割り当てられたメモリは A(b) でも解放できなきゃならない
  48. 多相アロケータ アロケータおさらい 48 C++11 から、アロケータは オブジェクト毎に状態を 持てるようになった。 ※ Allocators@C++11 by

    Cryolite 先生 参照 しかし、rebind したアロケータは 状態を共有しなければならない 状態を共有しないとメモリ解放無理でしょ…
  49. 多相アロケータ アロケータおさらい 49 つまりこんな感じ これ割と重要な要件です… でもこれに触れてる日本語の記事を見かけない… T 型用 アロケータ a

    U 型用 アロケータ b a と b と A(b)に 共有の状態 rebind rebind T 型用 アロケータ A(b)
  50. 多相アロケータ アロケータおさらい 50 ちなみに、実際は rebind だけじゃ なくて、普通のコピーやムーブでも 状態を共有しなくちゃならない。 アロケータって引数としてコピーされたりするから当たり前っちゃあ当たり前なんだけど… しかも、ムーブされた元も状態を

    保持し続けなきゃならない。 アロケータにはムーブなんてなかったんや…
  51. 多相アロケータ アロケータおさらい 51 多相アロケータは型に メモリ割当戦略が現れない

  52. 多相アロケータ アロケータおさらい 52 多相アロケータは型に メモリ割当戦略が現れない ⇓ 多相アロケータはオブジェクト毎に メモリ割当戦略が異なる

  53. 多相アロケータ アロケータおさらい 53 多相アロケータは型に メモリ割当戦略が現れない ⇓ 多相アロケータはオブジェクト毎に メモリ割当戦略が異なる ⇓ 多相アロケータはオブジェクト毎に

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

    多相アロケータ本体は、memory_resource へのポインタを持っているだけ。 さすがにちゃんと考えられている… てかそのために strategy pattern にしたのかな、教えてエロい人…
  55. 多相アロケータ アロケータおさらい 55 アロケータのポイント② メモリ割当・解放だけじゃなくて、 コンストラクタ、デストラクタ呼び出しも カスタマイズ可能 a.construct(std::addressof(*p), args...); a.destroy(std::addressof(*p));

  56. 多相アロケータ アロケータおさらい 56 素朴な疑問 コンストラクタ、デストラクタ呼び出しを カスタマイズできるのは分かったけど、 そもそも、コンストラクタ、デストラクタ 呼び出しなんてカスタマイズしたい?

  57. 多相アロケータ アロケータおさらい 57 答え アロケータをコンテナの要素に 伝搬したい時に、コンストラクタ 呼び出しのカスタマイズが必要 それ以外のケースやデストラクタ呼び出しのカスタマイズって必要な時ありますかね? 教えてエロい人!

  58. 多相アロケータ アロケータおさらい 58 答え アロケータをコンテナの要素に 伝搬したい時に、コンストラクタ 呼び出しのカスタマイズが必要 それ以外のケースやデストラクタ呼び出しのカスタマイズって必要な時ありますかね? 教えてエロい人! 勉強会後追記

    デバッグログとかはありますね…
  59. 多相アロケータ アロケータおさらい 59 string の vector の場合のイメージ(普通バージョン) 絵心が無さ過ぎてcpprefjp から拝借… stringは、コンテナの

    アロケータオブジェク トとは異なる、自分独 自のアロケータオブ ジェクトを持っている。
  60. 多相アロケータ アロケータおさらい 60 string の vector の場合のイメージ(伝搬バージョン) 絵心が無さ過ぎてcpprefjp から拝借… stringはアロケータオ

    ブジェクトを持ってい るものの、コンテナの アロケータオブジェク トと状態を共有してい る。
  61. 多相アロケータ アロケータおさらい 61 素朴な疑問その2 そんなのコンテナに要素格納する時に、 コンテナのアロケータオブジェクトを 取得して、コンストラクタに明示的に 指定してやれば済む話しじゃないの?

  62. 多相アロケータ アロケータおさらい 62 答えその2 それはそうなんだけど、 毎回必ず手動でやるのは面倒だし 間違いも起きやすい。

  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);
  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");
  65. 多相アロケータ アロケータおさらい 65 ほんのちょっとの違いに見えるけど、 コードが離れていたり、ネストが深かっ たり(string の vector の map

    みたいな) すると、かなり厳しい…
  66. 多相アロケータ アロケータおさらい 66 そこで、アロケータの construct メンバ関数に 小細工する

  67. 多相アロケータ アロケータおさらい 67 普通の場合 a.construct(addr, args); ⇓ ::new((void*)addr) T(std::forward<Args>(args)...); 至って普通の配置new…

  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 型のダミーオブジェクト
  69. 多相アロケータ アロケータおさらい 69 ただし、アロケータが自分を要素に伝搬させたい場合でも ① そもそも要素がアロケータを使う必要が無い(int とか) ② アロケータは使用するけど型が違っている ③

    コンストラクタでアロケータを指定できない などの場合があるので、その場合は普通に構築したい。 そこで、std::uses_allocator と言う型トレイツで 伝搬させるかどうかを制御できるようにする。
  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 から派生するように特殊化しておく。
  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
  72. 多相アロケータ アロケータおさらい 72 uses-allocator 構築をサポートするアロケータ • std::polymorphic_allocator • std::scoped_allocator_adaptor ただし、scoped_allocator_adaptor

    自体は本物のアロケータではないので (名前の通り、アダプタ)、実際の処理は uses-allocator 構築とはちょっと違う。 あと、scoped_allocator_adaptor と polymorphic_allocator は組み合わせては使えないと思う(多分…)
  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 引数がアロケータだけのコンストラクタのみ存在
  74. 多相アロケータ アロケータおさらい 74 tuple があるのに何で pair が無いの?

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

    コンストラクタからアロケータを使用するヤツが 削除されてしまった。
  76. 多相アロケータ アロケータおさらい 76 tuple があるのに何で pair が無いの? ⇓ コンストラクタが多すぎると言う理由で、pair の

    コンストラクタからアロケータを使用するヤツが 削除されてしまった。 ⇓ pair の各要素を uses-allocator 構築するために は、各アロケータで特別対応する必要がある polymorphic_allocator と scoped_allocator_adaptor は特別対応している
  77. 多相アロケータ アロケータおさらい 77 勉強会での光成さん(@herumi)からの質問 自作アロケータで uses-allocator 構築する場合、 pair に特別対応しなかったらコンテナの要素に pair

    使うとエラーになる? ⇓ ならないです。ただし、コンテナのアロケータは pair の各要素には伝搬しなくなります。 勉強会での 話を基に追加
  78. 多相アロケータ アロケータおさらい 78 残念なお知らせ C++17 で追加された超有能三兄弟、 std::optional、std::variant、std::any は uses-allocator 構築をサポートしていない。

    したがって、コンテナの要素がこの兄弟の場合、 コンテナのアロケータはこの兄弟の要素には 伝搬しない。 わりと悲しい… 当日スライドに入れ 忘れてて勉強会で 口頭補足したやつ
  79. 多相アロケータ アロケータおさらい 79 余談 • std::shared_ptr はアロケータを使用するが、uses- allocator 構築はサポートしない。 •

    std::function、std::packaged_task は、C++14 までは uses-allocator 構築をサポートしていることになって いたが、いろいろ問題があったらしく、C++17 からそ もそもアロケータ自体をサポートしなくなった。
  80. 多相アロケータ アロケータおさらい 80 アロケータのポイント➂ コピー構築、コピー代入、ムーブ代入、 スワップ時のアロケータ伝搬を 制御可能

  81. 多相アロケータ アロケータおさらい 81 uses-allocator 構築では、construct メンバ関数で コンテナの要素に対するアロケータ伝搬を制御 したが、コピー構築、コピー代入、ムーブ代入、 スワップ時のアロケータ伝搬もメンバの定義に よって制御できるようになっている。

    ちなみに、ムーブ構築の場合は問答無用で伝搬する
  82. 多相アロケータ アロケータおさらい 82 コピー構築時の制御 コピー構築時には、コピー元アロケータの以 下のメンバ関数を呼び出してアロケータオブ ジェクトを取得する。 現在 API 名最長選手権

    C++ 標準ライブラリ 同率 6 位※ Alloc select_on_container_copy_construction() const; ※ API名最長選手権 in C++ by yohhoy 先生 参照
  83. 多相アロケータ アロケータおさらい 83 コピー代入時の制御 コピー代入時には、以下のメンバ型が true_type だった場合にはコピー元アロケータ を、false_type(デフォルト)だった場合にはデ フォルト構築されたアロケータを使用する。 現在

    API 名最長選手権 C++ 標準ライブラリ 同率 3 位※ propagate_on_container_copy_assignment ※ API名最長選手権 in C++ by yohhoy 先生 参照
  84. 多相アロケータ アロケータおさらい 84 ムーブ代入時の制御 ムーブ代入時には、以下のメンバ型が true_type だった場合にはムーブ元アロケー タを、false_type(デフォルト)だった場合には デフォルト構築されたアロケータを使用する。 現在

    API 名最長選手権 C++ 標準ライブラリ 同率 3 位※ propagate_on_container_move_assignment ※ API名最長選手権 in C++ by yohhoy 先生 参照
  85. 多相アロケータ アロケータおさらい 85 スワップ時の制御 スワップ時には、以下のメンバ型が true_type だった場合にはアロケータもスワッ プし、false_type(デフォルト)だった場合には アロケータはスワップしない。 propagate_on_container_swap

  86. 多相アロケータ アロケータおさらい 86 勉強会での光成さん(@herumi)からの質問 スワップ時にアロケータをスワップしない 選択肢ってあるの? ⇓ 無いです。もしあるとすると、もともと同じアロケータ だった場合。アロケータが違うのにスワップしない 場合は、みんな大好き未定義動作になります。

    つまり、その場合コンテナのスワップはやっちゃダメ、 ということです。 勉強会での 話を基に追加
  87. 多相アロケータ アロケータおさらい 87 ちなみに、多相アロケータは select_on_container_copy_construction は 自分のコピーじゃなく新たなデフォルト構築された アロケータを返し、propagate_on_container_* は 全部

    false_type です。 つまり、コピー構築、コピー・ムーブ代入、スワップ ではアロケータ絶対伝搬させないマンです。 このあたりは、提案者の哲学(アロケータは構築時に 一旦設定したら変更されるべきではない)が 反映されているのではないかと思います。 勉強会での 話を基に追加
  88. 多相アロケータ アロケータおさらい 88 アロケータのポイント④ メモリ割当・解放の際の「ポインタ」は T* じゃなくてもよい。 A::pointer p =

    a.allocate(1); a.deallocate(p, 1);
  89. 多相アロケータ アロケータおさらい 89 アロケータのポイント④ ただし、コンストラクタ、デストラクタ 呼び出しの際は普通のポインタ a.construct(std::addressof(*p), args...); a.destroy(std::addressof(*p));

  90. 多相アロケータ アロケータおさらい 90 T* じゃない「ポインタ」は 「ファンシーポインタ」と呼ばれる。 ただし、ファンシーポインタへの 道のりは厳しい…

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

    ③ ランダムアクセスイテレータの要件を満たすこと ④ 連続イテレータの要件を満たすこと ①、➂、④は更にそれぞれ厳しい要件がある。 標準にはこの要件を満たすファンシーポインタは存在しない。 (shared_ptr とか unique_ptr は要件を満たさない)
  92. 多相アロケータ アロケータおさらい 92 ファンシーポインタっていつ使うの?

  93. 多相アロケータ アロケータおさらい 93 ファンシーポインタっていつ使うの? ⇓ 共有メモリとかのように、特殊なメモリを 使用する時に使いたくなるらしい…

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

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

    Clang:ファンシーポインタのデフォルト構築がヌルポインタ になることに依存している個所がある(そのような要件は無 い) GCC:本物のポインタを引数にするコンストラクタに依存して いる個所がある(そのような要件は無い) 勉強会での 話を基に追加
  96. 多相アロケータ アロケータおさらい 96 多相アロケータでは、 ファンシーポインタのサポートは していない。(後述)

  97. 多相アロケータ 97 詳細

  98. 多相アロケータ 詳細 98 std::pmr::memory_resource 実際のメモリ割当・解放処理 を実装する、全てのクラスの 抽象基底クラス

  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; };
  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 の メモリを割り当てる。 各具象派生クラスで実装する。
  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 は 割り当て時の値(正しく使われれば)。 各具象派生クラスで実装する。
  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 で良いはず。
  103. 多相アロケータ 詳細 103 ポインタが void* であることに注意! (アロケータはメンバ型 pointer) ⇓ ファンシーポインタのサポートは

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

  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 のハンドラだからムーブされると困る…)
  106. 多相アロケータ 詳細 106 代入演算子など // 代入演算子は無い!(コピー代入もムーブ代入も出来ない) polymorphic_allocator& operator=(const polymorphic_allocator& rhs)

    = delete; // コンテナをコピーする際のアロケータ取得時に呼ばれるメンバ関数。 // デフォルト構築された新たな polymorphic_allocator オブジェクトを返す。 // ちなみに、自分自身を返すのが、アロケータのデフォルトの挙動。 polymorphic_allocator select_on_container_copy_construction() const;
  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;
  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);
  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);
  110. 多相アロケータ 詳細 110 オブジェクト破棄 // オブジェクト破棄。単にデストラクタ呼ぶだけ。 template<class T> void destroy(T*

    p);
  111. 多相アロケータ 詳細 111 その他 // 使用しているメモリリソースの取得。 memory_resource* resource() const;

  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;
  113. 多相アロケータ 113 標準ライブラリが提供している memory_resource の具象クラス

  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;
  115. 多相アロケータ memory_resource の具象クラス 115 標準が提供している memory_resource の具象クラス① memory_resource* new_delete_resource() noexcept;

    普通に ::operator new、::operator delete でメモリ割 当、解放するヤツ。つまり極めて普通。 この関数を呼ぶと、常に同じオブジェクトへのポイ ンタが返る。(自分でインスタンス生成とかしない)
  116. 多相アロケータ memory_resource の具象クラス 116 標準が提供している memory_resource の具象クラス② memory_resource* null_resource() noexcept;

    メモリ割当しようとすると常に bad_alloc 投げるヤツ。 つまり極めて変。他のと組み合わせて使うと有用。 この関数を呼ぶと、常に同じオブジェクトへのポイ ンタが返る。(自分でインスタ(ry
  117. 多相アロケータ memory_resource の具象クラス 117 標準が提供している memory_resource の具象クラス➂ class synchronized_pool_resource; メモリプールを使用するヤツ。実際に使用するメモ

    リは上位メモリリソースから割り当てられる。 外部同期無しで複数スレッドから同時使用可能。
  118. 多相アロケータ memory_resource の具象クラス 118 synchronized_pool_resource イメージ サイズ ~16 17~32 33~64

    65~128 プールはブロックサイ ズ毎に分かれている。 (ただし、上限あり) メモリ割当は指定さ れたサイズ以上で最 小のブロックサイズ のプールから。 各プールのメモリは、同一サイズのブロックを複数まとめたチャ ンクに分割されている。プール内のメモリを使い果たすと、新た なチャンクを上位メモリリソースから確保する。その際、チャンク のサイズは等比級数的に増大していく。(ただし、上限あり) 割当済 未割当 次に上位から確保
  119. 多相アロケータ memory_resource の具象クラス 119 なお、synchronized_pool_resource では、スレッド間の同期コストを削 減するために、スレッド毎にメモリ プールがあるかもしれない… 無いかもしれない… でもスレッド毎にプール持ったら、違うスレッドから解放された場合どうするんだろう…

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

    memory_resource* upstream); 引数のいずれか、あるいは両方を省いたバージョンもある。(引数1つの バージョンは explicit コンストラクタ) opts:挙動調整用のオプション。後述。 upstream:上位メモリリソースへのポインタ。このメモリリソースから割り 当てられるメモリは全て upstream から取得される。指定しないとデフォ ルトメモリリソース(後述)を指定したことになる。
  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(デフォルト値)を指定すると実装依存の上限値となる。また、指定 値がデカすぎる場合も適当に調整される。
  122. 多相アロケータ memory_resource の具象クラス 122 synchronized_pool_resource コンストラクタ② コピーコンストラクタ、ムーブコンストラクタは無い。 ちなみに、コピー代入演算子、ムーブ代入演算子も 無い。 つまり、コピー、ムーブは一切できない。

  123. 多相アロケータ memory_resource の具象クラス 123 synchronized_pool_resource メンバ関数① void release(); 管理している全てのメモリを、たとえ deallocate

    が 呼び出されていなくても、上位メモリリソースに返す。
  124. 多相アロケータ memory_resource の具象クラス 124 synchronized_pool_resource メンバ関数② pool_options options() const; プールのオプションを返す。ただし、デフォルト値

    0 が実際の実装依存の値になったり、指定された値 が実装によって調整されたりする可能性があるの で、コンストラクタで指定された値とは違うかもしれ ない。
  125. 多相アロケータ memory_resource の具象クラス 125 synchronized_pool_resource メンバ関数➂ memory_resource* upstream_resource() const; 上位メモリリソースへのポインタを返す。極めて普

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

    size_t alignment) override; 指定されたサイズ、指定されたアラインメントのメモ リをプールから、あるいは上位メモリリソースから直 接割り当てる。極めて普通。
  127. 多相アロケータ memory_resource の具象クラス 127 synchronized_pool_resource 仮想関数② void do_deallocate(void* p, size_t

    bytes, size_t alignment) override; 割り当てられたメモリをプールに返す。 なお、上位メモリリソースの deallocate が呼び出さ れるか否かは未規定。
  128. 多相アロケータ memory_resource の具象クラス 128 synchronized_pool_resource 仮想関数➂ bool do_is_equal(const memory_resource& other)

    const noexcept override; 指定されたメモリリソースが自分と互換性のあるメ モリリソースかどうかを返す。 ゆうても、単に this == &other の結果を返すだけ。
  129. 多相アロケータ memory_resource の具象クラス 129 synchronized_pool_resource デストラクタ ~synchromized_pool_resource(); release() メンバ関数を呼ぶ。つまり、全メモリが解 放される。まぁ当たり前っちゃあ当たり前。

    このメモリリソースから割り当てられたメモリを使用 しているコンテナ等よりも、このメモリリソースの生 存期間が短くならないように注意が必要。
  130. 多相アロケータ memory_resource の具象クラス 130 標準が提供している memory_resource の具象クラス④ class unsynchronized_pool_resource; synchronized_pool_resource

    のシングルスレッド版。 外部同期無しで複数スレッドから同時使用できない こと以外は synchronized_pool_resource と一緒。 なので説明(ry
  131. 多相アロケータ memory_resource の具象クラス 131 標準が提供している memory_resource の具象クラス➄ class monotonic_buffer_resource; メモリ割当するだけして、絶対解放しないヤツ。

    つまりメモリ使用量が単調(monotonic)増加。 同期とかガチ無視なので、まぁ単一スレッド用。 使用用途が限られるがちょっぱや。
  132. 多相アロケータ memory_resource の具象クラス 132 monotonic_buffer_resource イメージ メモリは端から順に割り当て ていくだけ。解放は無視。 足りなくなったら上位メモリリ ソースから新たなメモリを確

    保するが、その際のサイズ は等比級数的に増加する。 割当済 未割当 次に上位から確保 未割当ポインタ
  133. 多相アロケータ memory_resource の具象クラス 133 monotonic_buffer_resource コンストラクタ① monotonic_buffer_resource( size_t initial_size, memory_resource*

    upstream); 引数のいずれか、あるいは両方を省いたバージョンもある。(引数1つの バージョンは explicit コンストラクタ) initial_size:最初に上位メモリリソースから確保するメモリのサイズ。実 装によって調整が入るかもしれない。指定が無い場合は実装依存。 upstream:上位メモリリソースへのポインタ。このメモリリソースから割り 当てられるメモリは全て upstream から取得される。指定しないとデフォ ルトメモリリソース(後述)を指定したことになる。
  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 から取得される。指定しないとデフォルトメモリリソース(後述) を指定したことになる。
  135. 多相アロケータ memory_resource の具象クラス 135 monotonic_buffer_resource コンストラクタ➂ コピーコンストラクタ、ムーブコンストラクタは無い。 ちなみに、コピー代入演算子、ムーブ代入演算子も 無い。 つまり、コピー、ムーブは一切できない。

  136. 多相アロケータ memory_resource の具象クラス 136 monotonic_buffer_resource メンバ関数① void release(); 管理している全てのメモリを、たとえ deallocate

    が 呼び出されていなくても、上位メモリリソースに返す。
  137. 多相アロケータ memory_resource の具象クラス 137 monotonic_buffer_resource メンバ関数② memory_resource* upstream_resource() const; 上位メモリリソースへのポインタを返す。極めて普

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

    size_t alignment) override; 指定されたサイズ、指定されたアラインメントのメモ リを割り当てる。極めて普通。
  139. 多相アロケータ memory_resource の具象クラス 139 monotonic_buffer_resource 仮想関数② void do_deallocate(void* p, size_t

    bytes, size_t alignment) override; メモリを解放?いや、そんなことはしない。 とにかく何もしない。
  140. 多相アロケータ memory_resource の具象クラス 140 monotonic_buffer_resource 仮想関数➂ bool do_is_equal(const memory_resource& other)

    const noexcept override; 指定されたメモリリソースが自分と互換性のあるメ モリリソースかどうかを返す。 ゆうても、単に this == &other の(ry
  141. 多相アロケータ memory_resource の具象クラス 141 monotonic_buffer_resource デストラクタ ~monotonic_buffer_resource(); release() メンバ関数を呼ぶ。つまり、全メモリが解 放される。まぁ当たり前っちゃあ当たり前。

    このメモリリソースから割り当て(ry
  142. 多相アロケータ 142 その他のユーティリティ

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

    に設定して、今 まで設定されてたメモリリソースを返す。 r が nullptr なら new_delete_resource を設定する。 ちなみに、システムの初期状態は new_delete_resource になっている。 普通っぽい…
  144. 多相アロケータ その他 144 グローバルなユーティリティ② memory_resource* get_default_resource() noexcept; 現在のデフォルトのメモリリソースを取得する。

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

    b) noexcept; メモリリソースを比較する。 単に &a == &b || a.is_equal(b) を返すだけ。 こいつの引数はポインタじゃないので注意。
  146. 多相アロケータ その他 146 グローバルなユーティリティ④ bool operator!=(const memory_resource& a, const memory_resource&

    b) noexcept; メモリリソースを比較する。 みんなの予想通りの挙動。(たぶん) こいつの引数もポインタじゃない。
  147. 多相アロケータ 147 デメリット

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

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

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

    (ただし、どれもちゃんと調査できてない…) 勉強会時コメント by いなむ先生(@いなむのみたま) MSVC2017はちゃんと動くよ!
  151. 多相アロケータ 悲報 151 GCC HEAD synchronized_pool_resource と unsynchronized_pool_resource が無い。 その他はありそう…

    Clang HEAD メモリリソースはグローバルなヤツしかないし、 そもそもまだ experimental。 自分でメモリリソース作るなら一応どっちも行けそう…
  152. 多相アロケータ 悲報 152 しばらくは Boost.Container 使う しかないね… でも Boost.Container も

    pool_options がちょっと変だった… なぜかデフォルトコンストラクタだけがあるから、 synchronized_pool_resource のコンストラクタに初期化リストが直接書けない…
  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 勉強会後に 追記
  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 勉強会後に 追記
  155. 多相アロケータ 155 完